From 76a387c52a226b55b081ca82b0e1847da665b45c Mon Sep 17 00:00:00 2001 From: Andres Alvarez Date: Thu, 15 May 2025 22:47:13 -0400 Subject: [PATCH 1/6] Create endpoint for bulk update user role and is validated column --- src/user/dto/user.dto.ts | 21 +++++++++++++++++++++ src/user/user.controller.ts | 14 ++++++++++++++ src/user/user.service.ts | 25 +++++++++++++++++++++++-- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/user/dto/user.dto.ts b/src/user/dto/user.dto.ts index fbf5c83..f7cf93a 100644 --- a/src/user/dto/user.dto.ts +++ b/src/user/dto/user.dto.ts @@ -18,6 +18,7 @@ import { IsString, IsUUID, IsBoolean, + ArrayNotEmpty, } from 'class-validator'; import { UserGender } from '../entities/profile.entity'; import { IsOlderThan } from 'src/utils/is-older-than-validator'; @@ -102,6 +103,26 @@ export class BaseUserDTO { export class UserDTO extends IntersectionType(BaseUserDTO, PasswordDTO) {} +export class UserBulkUpdateDTO { + @IsOptional() + @ApiProperty({ description: 'If the user has validated the email' }) + isValidated: boolean; + + @IsOptional() + @IsEnum(UserRole) + @ApiProperty({ description: 'Role of the user', enum: UserRole }) + role: UserRole; +} + +export class UserListUpdateDTO { + @ArrayNotEmpty() + @IsUUID(undefined, { each: true }) + users: string[]; + + @Type(() => UserBulkUpdateDTO) + data: UserBulkUpdateDTO; +} + export class UserAdminDTO extends BaseUserDTO { @ApiProperty({ description: 'the role of the user' }) @IsNotEmpty() diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 8d7ee8b..10f67f4 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -36,6 +36,7 @@ import { UserAdminDTO, UpdateUserDTO, UserMotoDTO, + UserListUpdateDTO, } from './dto/user.dto'; import { PaginationDTO, UserQueryDTO } from 'src/utils/dto/pagination.dto'; import { plainToInstance } from 'class-transformer'; @@ -75,6 +76,19 @@ export class UserController { await this.userService.validateEmail(userOtp); } + @UseGuards(AuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @Post('bulk') + @ApiBearerAuth() + @ApiOperation({ summary: 'Bulk update users' }) + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Users updated successfully', + }) + async bulkUpdate(@Body() updateUserDto: UserListUpdateDTO): Promise { + await this.userService.bulkUpdate(updateUserDto.users, updateUserDto.data); + } + @HttpCode(HttpStatus.OK) @Get(':userId') @UseGuards(AuthGuard, UserOrAdminGuard) diff --git a/src/user/user.service.ts b/src/user/user.service.ts index a2849ed..fc2b362 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -5,8 +5,13 @@ import { BadRequestException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { UserAdminDTO, UserDTO, UpdateUserDTO } from './dto/user.dto'; +import { In, Repository } from 'typeorm'; +import { + UserAdminDTO, + UserDTO, + UpdateUserDTO, + UserBulkUpdateDTO, +} from './dto/user.dto'; import { User, UserRole } from './entities/user.entity'; import { UserOTP } from './entities/user-otp.entity'; import { Profile } from './entities/profile.entity'; @@ -412,4 +417,20 @@ export class UserService { } await this.userRepository.update(user.id, { wsId: '' }); } + + async bulkUpdate( + userIds: string[], + userDto: UserBulkUpdateDTO, + ): Promise { + const users = await this.userRepository.findBy({ id: In(userIds) }); + if (!users.length) { + throw new NotFoundException('No users found'); + } + + const updatedUsers = users.map((user) => { + return { ...user, ...userDto }; + }); + + return await this.userRepository.save(updatedUsers); + } } From ce3bc37f020ab9ae244ce78c94409b5186aa0e0e Mon Sep 17 00:00:00 2001 From: Andres Alvarez Date: Thu, 15 May 2025 22:58:10 -0400 Subject: [PATCH 2/6] Create bulk update for order status --- src/order/controllers/order.controller.ts | 17 +++++++++++++++++ src/order/dto/order.ts | 11 +++++++++++ src/order/order.service.ts | 18 +++++++++++++++++- src/user/user.controller.ts | 2 +- 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/order/controllers/order.controller.ts b/src/order/controllers/order.controller.ts index e6094b8..3aefbd3 100644 --- a/src/order/controllers/order.controller.ts +++ b/src/order/controllers/order.controller.ts @@ -17,6 +17,7 @@ import { import { OrderService } from '../order.service'; import { CreateOrderDTO, + OrderListUpdateDTO, OrderQueryDTO, ResponseOrderDetailedDTO, ResponseOrderDTO, @@ -162,6 +163,22 @@ export class OrderController { }; } + @UseGuards(AuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @Patch('bulk') + @ApiBearerAuth() + @ApiOperation({ summary: 'Bulk update orders' }) + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Orders updated successfully', + }) + async bulkUpdate(@Body() updateOrderDto: OrderListUpdateDTO): Promise { + await this.orderService.bulkUpdate( + updateOrderDto.orders, + updateOrderDto.status, + ); + } + @Get(':id') @UseGuards(AuthGuard) @ApiBearerAuth() diff --git a/src/order/dto/order.ts b/src/order/dto/order.ts index 1096e04..eb5eb5e 100644 --- a/src/order/dto/order.ts +++ b/src/order/dto/order.ts @@ -3,6 +3,7 @@ import { IsArray, IsEnum, IsInt, + IsNotEmpty, IsOptional, IsPositive, IsString, @@ -213,6 +214,16 @@ export class UpdateOrderStatusWsDTO { status: OrderStatus; } +export class OrderListUpdateDTO { + @ArrayNotEmpty() + @IsUUID(undefined, { each: true }) + orders: string[]; + + @IsNotEmpty() + @IsEnum(OrderStatus) + status: OrderStatus; +} + export class SalesReportDTO { @Expose() @ApiProperty({ description: 'ID of the order' }) diff --git a/src/order/order.service.ts b/src/order/order.service.ts index f15e652..bc41728 100644 --- a/src/order/order.service.ts +++ b/src/order/order.service.ts @@ -7,7 +7,7 @@ import { CreateOrderDTO, SalesReportDTO } from './dto/order'; import { User, UserRole } from 'src/user/entities/user.entity'; import { ProductPresentationService } from 'src/products/services/product-presentation.service'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { In, Repository } from 'typeorm'; import { Order, OrderDetail, @@ -276,6 +276,22 @@ export class OrderService { return await this.orderRepository.save(order); } + async bulkUpdate(ordersIds: string[], status: OrderStatus) { + const orders = await this.orderRepository.findBy({ + id: In(ordersIds), + }); + if (orders.length === 0) { + throw new NotFoundException('No orders found'); + } + const updatedOrders = orders.map((order) => { + if (order.status !== OrderStatus.COMPLETED) { + order.status = status; + return order; + } else return order; + }); + return await this.orderRepository.save(updatedOrders); + } + async findAllOD( user: User, page: number, diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 10f67f4..6c1ac9f 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -78,7 +78,7 @@ export class UserController { @UseGuards(AuthGuard, RolesGuard) @Roles(UserRole.ADMIN) - @Post('bulk') + @Patch('bulk') @ApiBearerAuth() @ApiOperation({ summary: 'Bulk update users' }) @ApiResponse({ From ce705c9759d43fc093cb72e2eeda1ea788c870c3 Mon Sep 17 00:00:00 2001 From: Andres Alvarez Date: Thu, 15 May 2025 23:05:52 -0400 Subject: [PATCH 3/6] Create bulk update for product presentation visibility --- .../product-presentation.controller.ts | 23 ++++++++++++++++++- src/products/dto/product-presentation.dto.ts | 13 +++++++++++ .../services/product-presentation.service.ts | 16 +++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/products/controllers/product-presentation.controller.ts b/src/products/controllers/product-presentation.controller.ts index e41d393..1c3470a 100644 --- a/src/products/controllers/product-presentation.controller.ts +++ b/src/products/controllers/product-presentation.controller.ts @@ -13,7 +13,10 @@ import { UseGuards, } from '@nestjs/common'; import { ProductPresentationService } from '../services/product-presentation.service'; -import { CreateProductPresentationDTO } from '../dto/product-presentation.dto'; +import { + CreateProductPresentationDTO, + ProductPresentationListUpdateDTO, +} from '../dto/product-presentation.dto'; import { PresentationService } from '../services/presentation.service'; import { AuthGuard } from 'src/auth/auth.guard'; import { RolesGuard } from 'src/auth/roles.guard'; @@ -74,6 +77,24 @@ export class ProductPresentationController { ); } + @UseGuards(AuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @Patch('bulk') + @ApiBearerAuth() + @ApiOperation({ summary: 'Bulk update orders' }) + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Orders updated successfully', + }) + async bulkUpdate( + @Body() updateProductPresentationDto: ProductPresentationListUpdateDTO, + ): Promise { + await this.productPresentationService.bulkUpdate( + updateProductPresentationDto.ids, + updateProductPresentationDto.isVisible, + ); + } + @Get(':presentationId') @ApiOperation({ summary: 'Get product presentation by product and presentation ID', diff --git a/src/products/dto/product-presentation.dto.ts b/src/products/dto/product-presentation.dto.ts index 43b01fb..0d54193 100644 --- a/src/products/dto/product-presentation.dto.ts +++ b/src/products/dto/product-presentation.dto.ts @@ -93,3 +93,16 @@ export class ResponseOrderProductPresentationDetailDTO extends IntersectionType( @ApiProperty({ type: ResponsePromoDTO }) promo: ResponsePromoDTO; } + +export class ProductPresentationListUpdateDTO { + @ApiProperty({ description: 'The IDs of the product presentation' }) + @IsUUID(undefined, { each: true }) + ids: string[]; + + @ApiProperty({ + description: 'Indicates if the product presentation is visible', + default: true, + }) + @IsBoolean() + isVisible: boolean; +} diff --git a/src/products/services/product-presentation.service.ts b/src/products/services/product-presentation.service.ts index 1921bee..4af0ba7 100644 --- a/src/products/services/product-presentation.service.ts +++ b/src/products/services/product-presentation.service.ts @@ -115,6 +115,22 @@ export class ProductPresentationService { return await this.repository.save(updatedProductPresentation); } + async bulkUpdate(productPresentationIds: string[], isVisible: boolean) { + const productPresentations = await this.repository.findBy({ + id: In(productPresentationIds), + }); + if (productPresentations.length === 0) { + throw new NotFoundException('No product presentations found'); + } + const updatedProductPresentations = productPresentations.map( + (productPresentation) => { + productPresentation.isVisible = isVisible; + return productPresentation; + }, + ); + return await this.repository.save(updatedProductPresentations); + } + async remove(productId: string, presentationId: string): Promise { const productPresentation = await this.findOneProductPresentation( productId, From f41ce434b3c5a387caef74a702df4cd79c97acfc Mon Sep 17 00:00:00 2001 From: Andres Alvarez Date: Fri, 16 May 2025 09:05:45 -0400 Subject: [PATCH 4/6] Create bulk delete and bulk update for promos --- src/discount/controllers/promo.controller.ts | 31 ++++++++++++++++++++ src/discount/dto/promo.dto.ts | 23 +++++++++++++++ src/discount/services/promo.service.ts | 27 ++++++++++++++++- 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/discount/controllers/promo.controller.ts b/src/discount/controllers/promo.controller.ts index cd45416..6d7627d 100644 --- a/src/discount/controllers/promo.controller.ts +++ b/src/discount/controllers/promo.controller.ts @@ -29,6 +29,8 @@ import { UpdatePromoDTO, ResponsePromoDTO, PromoQueryDTO, + PromoListDeleteDTO, + PromoListUpdateDTO, } from '../dto/promo.dto'; import { PromoService } from '../services/promo.service'; import { UserRole } from 'src/user/entities/user.entity'; @@ -115,6 +117,35 @@ export class PromoController { return { data, total }; } + @HttpCode(HttpStatus.NO_CONTENT) + @Delete('bulk') + @ApiOperation({ summary: 'Bulk delete promos' }) + @ApiResponse({ + description: 'Successful bulk deletion of promos', + status: HttpStatus.NO_CONTENT, + }) + async bulkDelete( + @Body() promoListDeleteDTO: PromoListDeleteDTO, + ): Promise { + await this.promoService.bulkDelete(promoListDeleteDTO.ids); + } + + @HttpCode(HttpStatus.NO_CONTENT) + @Patch('bulk') + @ApiOperation({ summary: 'Bulk delete promos' }) + @ApiResponse({ + description: 'Successful bulk deletion of promos', + status: HttpStatus.NO_CONTENT, + }) + async bulkUpdate( + @Body() promoListUpdateDTO: PromoListUpdateDTO, + ): Promise { + await this.promoService.bulkUpdate( + promoListUpdateDTO.ids, + promoListUpdateDTO.expiredAt, + ); + } + @Get(':id') @ApiOperation({ summary: 'Get promo by ID' }) @ApiResponse({ diff --git a/src/discount/dto/promo.dto.ts b/src/discount/dto/promo.dto.ts index f32c012..6e1bdbc 100644 --- a/src/discount/dto/promo.dto.ts +++ b/src/discount/dto/promo.dto.ts @@ -6,6 +6,7 @@ import { IsString, IsDateString, IsOptional, + IsUUID, } from 'class-validator'; import { BaseDTO } from 'src/utils/dto/base.dto'; import { PaginationQueryDTO } from 'src/utils/dto/pagination.dto'; @@ -58,3 +59,25 @@ export class PromoQueryDTO extends PaginationQueryDTO { this.expirationBetween = expirationBetween ? expirationBetween : []; } } + +export class PromoListDeleteDTO { + @IsUUID(undefined, { each: true }) + @ApiProperty({ + description: 'List of promo ids to be deleted', + type: [String], + }) + ids: string[]; +} + +export class PromoListUpdateDTO { + @IsUUID(undefined, { each: true }) + @ApiProperty({ + description: 'List of promo ids to be updated', + type: [String], + }) + ids: string[]; + + @IsNotEmpty() + @ApiProperty({ description: 'The new expiration date of the promos' }) + expiredAt: Date; +} diff --git a/src/discount/services/promo.service.ts b/src/discount/services/promo.service.ts index edbea56..b07b655 100644 --- a/src/discount/services/promo.service.ts +++ b/src/discount/services/promo.service.ts @@ -1,6 +1,6 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { IsNull, Repository } from 'typeorm'; +import { In, IsNull, Repository } from 'typeorm'; import { PromoDTO, UpdatePromoDTO } from '../dto/promo.dto'; import { Promo } from '../entities/promo.entity'; @@ -69,4 +69,29 @@ export class PromoService { }); return result.affected === 1; } + + async bulkDelete(ids: string[]) { + const promos = await this.promoRepository.findBy({ + id: In(ids), + deletedAt: IsNull(), + }); + if (promos.length === 0) { + throw new NotFoundException(`No promos found with the given IDs`); + } + await this.promoRepository.softDelete({ id: In(ids) }); + } + + async bulkUpdate(ids: string[], expiredAt: Date) { + const promos = await this.promoRepository.findBy({ + id: In(ids), + deletedAt: IsNull(), + }); + if (promos.length === 0) { + throw new NotFoundException(`No promos found with the given IDs`); + } + await this.promoRepository.update( + { id: In(ids) }, + { expiredAt, updatedAt: new Date() }, + ); + } } From 3f99b0b0e6f6a01c0903691ab57aae3f7a76500c Mon Sep 17 00:00:00 2001 From: Andres Alvarez Date: Fri, 16 May 2025 09:38:16 -0400 Subject: [PATCH 5/6] Create bulk delete and bulk update for coupons --- src/discount/controllers/coupon.controller.ts | 31 ++++++++++++++++ src/discount/controllers/promo.controller.ts | 4 +-- src/discount/dto/coupon.dto.ts | 36 ++++++++++++++++++- src/discount/services/coupon.service.ts | 33 +++++++++++++++-- 4 files changed, 99 insertions(+), 5 deletions(-) diff --git a/src/discount/controllers/coupon.controller.ts b/src/discount/controllers/coupon.controller.ts index 039929e..a90051c 100644 --- a/src/discount/controllers/coupon.controller.ts +++ b/src/discount/controllers/coupon.controller.ts @@ -18,6 +18,8 @@ import { UpdateCouponDTO, ResponseCouponDTO, CouponQueryDTO, + CouponListDeleteDTO, + CouponListUpdateDTO, } from '../dto/coupon.dto'; import { AuthGuard } from 'src/auth/auth.guard'; import { RolesGuard } from 'src/auth/roles.guard'; @@ -115,6 +117,35 @@ export class CouponController { return { data, total }; } + @HttpCode(HttpStatus.NO_CONTENT) + @Delete('bulk') + @ApiOperation({ summary: 'Bulk delete coupons' }) + @ApiResponse({ + description: 'Successful bulk deletion of coupons', + status: HttpStatus.NO_CONTENT, + }) + async bulkDelete( + @Body() couponListDeleteDTO: CouponListDeleteDTO, + ): Promise { + await this.couponService.bulkDelete(couponListDeleteDTO.ids); + } + + @HttpCode(HttpStatus.NO_CONTENT) + @Patch('bulk') + @ApiOperation({ summary: 'Bulk update coupons' }) + @ApiResponse({ + description: 'Successful bulk update of coupons', + status: HttpStatus.NO_CONTENT, + }) + async bulkUpdate( + @Body() couponListUpdateDTO: CouponListUpdateDTO, + ): Promise { + await this.couponService.bulkUpdate( + couponListUpdateDTO.ids, + couponListUpdateDTO.data, + ); + } + @Get(':code') @ApiOperation({ summary: 'Get coupon by code' }) @ApiResponse({ diff --git a/src/discount/controllers/promo.controller.ts b/src/discount/controllers/promo.controller.ts index 6d7627d..9cde9a1 100644 --- a/src/discount/controllers/promo.controller.ts +++ b/src/discount/controllers/promo.controller.ts @@ -132,9 +132,9 @@ export class PromoController { @HttpCode(HttpStatus.NO_CONTENT) @Patch('bulk') - @ApiOperation({ summary: 'Bulk delete promos' }) + @ApiOperation({ summary: 'Bulk update promos' }) @ApiResponse({ - description: 'Successful bulk deletion of promos', + description: 'Successful bulk update of promos', status: HttpStatus.NO_CONTENT, }) async bulkUpdate( diff --git a/src/discount/dto/coupon.dto.ts b/src/discount/dto/coupon.dto.ts index 0454d38..e01bd24 100644 --- a/src/discount/dto/coupon.dto.ts +++ b/src/discount/dto/coupon.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty, PartialType, IntersectionType } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; +import { Transform, Type } from 'class-transformer'; import { IsNotEmpty, IsString, @@ -8,6 +8,7 @@ import { Min, IsDateString, IsOptional, + IsUUID, } from 'class-validator'; import { BaseDTO } from 'src/utils/dto/base.dto'; import { PaginationQueryDTO } from 'src/utils/dto/pagination.dto'; @@ -73,3 +74,36 @@ export class CouponQueryDTO extends PaginationQueryDTO { this.expirationBetween = expirationBetween ? expirationBetween : []; } } + +export class CouponListDeleteDTO { + @IsUUID(undefined, { each: true }) + @ApiProperty({ + description: 'List of coupon ids to be deleted', + type: [String], + }) + ids: string[]; +} + +export class CouponBulkUpdateDTO { + @IsOptional() + @ApiProperty({ description: 'The new expiration date of the coupons' }) + expirationDate: Date; + + @ApiProperty({ description: 'Maximum number of coupon uses' }) + @IsOptional() + @IsInt() + @Min(0) + maxUses: number; +} + +export class CouponListUpdateDTO { + @IsUUID(undefined, { each: true }) + @ApiProperty({ + description: 'List of coupon ids to be updated', + type: [String], + }) + ids: string[]; + + @Type(() => CouponBulkUpdateDTO) + data: CouponBulkUpdateDTO; +} diff --git a/src/discount/services/coupon.service.ts b/src/discount/services/coupon.service.ts index 1b6536b..d75c3be 100644 --- a/src/discount/services/coupon.service.ts +++ b/src/discount/services/coupon.service.ts @@ -1,8 +1,12 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { In, IsNull, Repository } from 'typeorm'; import { Coupon } from '../entities/coupon.entity'; -import { CouponDTO, UpdateCouponDTO } from '../dto/coupon.dto'; +import { + CouponBulkUpdateDTO, + CouponDTO, + UpdateCouponDTO, +} from '../dto/coupon.dto'; @Injectable() export class CouponService { @@ -76,4 +80,29 @@ export class CouponService { } return true; } + + async bulkDelete(ids: string[]) { + const coupons = await this.couponRepository.findBy({ + id: In(ids), + deletedAt: IsNull(), + }); + if (coupons.length === 0) { + throw new NotFoundException(`No coupons found with the given IDs`); + } + await this.couponRepository.softDelete({ id: In(ids) }); + } + + async bulkUpdate(ids: string[], updateDto: CouponBulkUpdateDTO) { + const coupons = await this.couponRepository.findBy({ + id: In(ids), + deletedAt: IsNull(), + }); + if (coupons.length === 0) { + throw new NotFoundException(`No coupons found with the given IDs`); + } + const couponsToUpdate = coupons.map((coupon) => { + return { ...coupon, ...updateDto }; + }); + await this.couponRepository.save(couponsToUpdate); + } } From 4944b7b8a08ad5a38f6a7741fdc73dddd1c9f664 Mon Sep 17 00:00:00 2001 From: Andres Alvarez Date: Fri, 16 May 2025 21:46:46 -0400 Subject: [PATCH 6/6] Fix inner dto for plain properties --- src/discount/controllers/coupon.controller.ts | 3 +- src/discount/dto/coupon.dto.ts | 25 +++++++--------- src/discount/services/coupon.service.ts | 10 ++----- .../product-presentation.controller.ts | 23 +-------------- src/products/products.controller.ts | 29 +++++++++++++++++++ src/user/dto/user.dto.ts | 21 +++++++------- src/user/user.controller.ts | 6 +++- src/user/user.service.ts | 12 +++----- 8 files changed, 66 insertions(+), 63 deletions(-) diff --git a/src/discount/controllers/coupon.controller.ts b/src/discount/controllers/coupon.controller.ts index a90051c..b09b0bd 100644 --- a/src/discount/controllers/coupon.controller.ts +++ b/src/discount/controllers/coupon.controller.ts @@ -142,7 +142,8 @@ export class CouponController { ): Promise { await this.couponService.bulkUpdate( couponListUpdateDTO.ids, - couponListUpdateDTO.data, + couponListUpdateDTO.maxUses, + couponListUpdateDTO.expirationDate, ); } diff --git a/src/discount/dto/coupon.dto.ts b/src/discount/dto/coupon.dto.ts index e01bd24..5f0048a 100644 --- a/src/discount/dto/coupon.dto.ts +++ b/src/discount/dto/coupon.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty, PartialType, IntersectionType } from '@nestjs/swagger'; -import { Transform, Type } from 'class-transformer'; +import { Transform, Expose } from 'class-transformer'; import { IsNotEmpty, IsString, @@ -84,26 +84,23 @@ export class CouponListDeleteDTO { ids: string[]; } -export class CouponBulkUpdateDTO { +export class CouponListUpdateDTO { + @IsUUID(undefined, { each: true }) + @ApiProperty({ + description: 'List of coupon ids to be updated', + type: [String], + }) + ids: string[]; + + @Expose() @IsOptional() @ApiProperty({ description: 'The new expiration date of the coupons' }) expirationDate: Date; + @Expose() @ApiProperty({ description: 'Maximum number of coupon uses' }) @IsOptional() @IsInt() @Min(0) maxUses: number; } - -export class CouponListUpdateDTO { - @IsUUID(undefined, { each: true }) - @ApiProperty({ - description: 'List of coupon ids to be updated', - type: [String], - }) - ids: string[]; - - @Type(() => CouponBulkUpdateDTO) - data: CouponBulkUpdateDTO; -} diff --git a/src/discount/services/coupon.service.ts b/src/discount/services/coupon.service.ts index d75c3be..79cdf70 100644 --- a/src/discount/services/coupon.service.ts +++ b/src/discount/services/coupon.service.ts @@ -2,11 +2,7 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { In, IsNull, Repository } from 'typeorm'; import { Coupon } from '../entities/coupon.entity'; -import { - CouponBulkUpdateDTO, - CouponDTO, - UpdateCouponDTO, -} from '../dto/coupon.dto'; +import { CouponDTO, UpdateCouponDTO } from '../dto/coupon.dto'; @Injectable() export class CouponService { @@ -92,7 +88,7 @@ export class CouponService { await this.couponRepository.softDelete({ id: In(ids) }); } - async bulkUpdate(ids: string[], updateDto: CouponBulkUpdateDTO) { + async bulkUpdate(ids: string[], maxUses?: number, expirationDate?: Date) { const coupons = await this.couponRepository.findBy({ id: In(ids), deletedAt: IsNull(), @@ -101,7 +97,7 @@ export class CouponService { throw new NotFoundException(`No coupons found with the given IDs`); } const couponsToUpdate = coupons.map((coupon) => { - return { ...coupon, ...updateDto }; + return { ...coupon, maxUses, expirationDate }; }); await this.couponRepository.save(couponsToUpdate); } diff --git a/src/products/controllers/product-presentation.controller.ts b/src/products/controllers/product-presentation.controller.ts index 1c3470a..e41d393 100644 --- a/src/products/controllers/product-presentation.controller.ts +++ b/src/products/controllers/product-presentation.controller.ts @@ -13,10 +13,7 @@ import { UseGuards, } from '@nestjs/common'; import { ProductPresentationService } from '../services/product-presentation.service'; -import { - CreateProductPresentationDTO, - ProductPresentationListUpdateDTO, -} from '../dto/product-presentation.dto'; +import { CreateProductPresentationDTO } from '../dto/product-presentation.dto'; import { PresentationService } from '../services/presentation.service'; import { AuthGuard } from 'src/auth/auth.guard'; import { RolesGuard } from 'src/auth/roles.guard'; @@ -77,24 +74,6 @@ export class ProductPresentationController { ); } - @UseGuards(AuthGuard, RolesGuard) - @Roles(UserRole.ADMIN) - @Patch('bulk') - @ApiBearerAuth() - @ApiOperation({ summary: 'Bulk update orders' }) - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Orders updated successfully', - }) - async bulkUpdate( - @Body() updateProductPresentationDto: ProductPresentationListUpdateDTO, - ): Promise { - await this.productPresentationService.bulkUpdate( - updateProductPresentationDto.ids, - updateProductPresentationDto.isVisible, - ); - } - @Get(':presentationId') @ApiOperation({ summary: 'Get product presentation by product and presentation ID', diff --git a/src/products/products.controller.ts b/src/products/products.controller.ts index 819a971..a3978ac 100644 --- a/src/products/products.controller.ts +++ b/src/products/products.controller.ts @@ -1,6 +1,9 @@ import { + Body, Controller, Get, + HttpStatus, + Patch, Query, Req, UseGuards, @@ -8,10 +11,12 @@ import { } from '@nestjs/common'; import { ProductsService } from './products.service'; import { + ApiBearerAuth, ApiExtraModels, ApiOkResponse, ApiOperation, ApiQuery, + ApiResponse, getSchemaPath, } from '@nestjs/swagger'; import { ProductPresentationDTO, ProductQueryDTO } from './dto/product.dto'; @@ -20,6 +25,11 @@ import { PaginationInterceptor } from 'src/utils/pagination.interceptor'; import { plainToInstance } from 'class-transformer'; import { AuthGuard, CustomRequest } from 'src/auth/auth.guard'; import { RecommendationService } from 'src/recommendation/recommendation.service'; +import { RolesGuard } from 'src/auth/roles.guard'; +import { Roles } from 'src/auth/roles.decorador'; +import { UserRole } from 'src/user/entities/user.entity'; +import { ProductPresentationListUpdateDTO } from './dto/product-presentation.dto'; +import { ProductPresentationService } from './services/product-presentation.service'; @Controller('product') @ApiExtraModels(PaginationDTO, ProductPresentationDTO) @@ -27,6 +37,7 @@ export class ProductsController { constructor( private productsServices: ProductsService, private recommendationService: RecommendationService, + private productPresentationService: ProductPresentationService, ) {} @Get() @UseInterceptors(PaginationInterceptor) @@ -187,4 +198,22 @@ export class ProductsController { total: products.total, }; } + + @UseGuards(AuthGuard, RolesGuard) + @Roles(UserRole.ADMIN) + @Patch('presentation/bulk') + @ApiBearerAuth() + @ApiOperation({ summary: 'Bulk update orders' }) + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Orders updated successfully', + }) + async bulkUpdate( + @Body() updateProductPresentationDto: ProductPresentationListUpdateDTO, + ): Promise { + await this.productPresentationService.bulkUpdate( + updateProductPresentationDto.ids, + updateProductPresentationDto.isVisible, + ); + } } diff --git a/src/user/dto/user.dto.ts b/src/user/dto/user.dto.ts index f7cf93a..d24ca4f 100644 --- a/src/user/dto/user.dto.ts +++ b/src/user/dto/user.dto.ts @@ -103,26 +103,27 @@ export class BaseUserDTO { export class UserDTO extends IntersectionType(BaseUserDTO, PasswordDTO) {} -export class UserBulkUpdateDTO { +export class UserListUpdateDTO { + @ApiProperty({ + description: 'List of user IDs to be updated', + type: [String], + }) + @ArrayNotEmpty() + @IsUUID(undefined, { each: true }) + users: string[]; + + @Expose() @IsOptional() @ApiProperty({ description: 'If the user has validated the email' }) isValidated: boolean; + @Expose() @IsOptional() @IsEnum(UserRole) @ApiProperty({ description: 'Role of the user', enum: UserRole }) role: UserRole; } -export class UserListUpdateDTO { - @ArrayNotEmpty() - @IsUUID(undefined, { each: true }) - users: string[]; - - @Type(() => UserBulkUpdateDTO) - data: UserBulkUpdateDTO; -} - export class UserAdminDTO extends BaseUserDTO { @ApiProperty({ description: 'the role of the user' }) @IsNotEmpty() diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 6c1ac9f..ea9e56f 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -86,7 +86,11 @@ export class UserController { description: 'Users updated successfully', }) async bulkUpdate(@Body() updateUserDto: UserListUpdateDTO): Promise { - await this.userService.bulkUpdate(updateUserDto.users, updateUserDto.data); + await this.userService.bulkUpdate( + updateUserDto.users, + updateUserDto.isValidated, + updateUserDto.role, + ); } @HttpCode(HttpStatus.OK) diff --git a/src/user/user.service.ts b/src/user/user.service.ts index fc2b362..b24d5c2 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -6,12 +6,7 @@ import { } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { In, Repository } from 'typeorm'; -import { - UserAdminDTO, - UserDTO, - UpdateUserDTO, - UserBulkUpdateDTO, -} from './dto/user.dto'; +import { UserAdminDTO, UserDTO, UpdateUserDTO } from './dto/user.dto'; import { User, UserRole } from './entities/user.entity'; import { UserOTP } from './entities/user-otp.entity'; import { Profile } from './entities/profile.entity'; @@ -420,7 +415,8 @@ export class UserService { async bulkUpdate( userIds: string[], - userDto: UserBulkUpdateDTO, + isValidated?: boolean, + UserRole?: UserRole, ): Promise { const users = await this.userRepository.findBy({ id: In(userIds) }); if (!users.length) { @@ -428,7 +424,7 @@ export class UserService { } const updatedUsers = users.map((user) => { - return { ...user, ...userDto }; + return { ...user, isValidated, role: UserRole }; }); return await this.userRepository.save(updatedUsers);