diff --git a/src/modules/products/dto/responses/create-product-response.dto.ts b/src/modules/products/dto/responses/create-product-response.dto.ts new file mode 100644 index 00000000..dd4cda98 --- /dev/null +++ b/src/modules/products/dto/responses/create-product-response.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; +import { ProductDetailsDto } from './product-details.dto'; + +export class SuccessfulCreateResponseDto { + @ApiProperty({ + description: 'The status of the request', + example: '200', + }) + status: number; + + @ApiProperty({ + description: 'The message of the response', + example: 'Product created successfully', + }) + @IsString() + message: string; + + @ApiProperty({ + description: 'The created product details', + }) + data: ProductDetailsDto; +} diff --git a/src/modules/products/dto/responses/delete-product.dto.ts b/src/modules/products/dto/responses/delete-product.dto.ts new file mode 100644 index 00000000..c5f845c7 --- /dev/null +++ b/src/modules/products/dto/responses/delete-product.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class DeleteProductDto { + @ApiProperty({ + description: 'The response message', + example: 'Product successfully deleted', + }) + @IsString() + message: string; + + @ApiProperty({ + description: 'The response data', + }) + @IsString() + data: {}; +} diff --git a/src/modules/products/dto/responses/error-response.dto.ts b/src/modules/products/dto/responses/error-response.dto.ts new file mode 100644 index 00000000..9544286e --- /dev/null +++ b/src/modules/products/dto/responses/error-response.dto.ts @@ -0,0 +1,110 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class BadRequestResponseDto { + @ApiProperty({ + description: 'The status of the request', + example: 'Unprocessable entity exception', + }) + @IsString() + status: string; + + @ApiProperty({ + description: 'The response code', + example: '422', + }) + @IsString() + status_code: string; + + @ApiProperty({ + description: 'The message of the response', + example: 'Invalid organisation credentials', + }) + @IsString() + message: string; +} + +export class NotFoundResponseDto { + @ApiProperty({ + description: 'The response code', + example: '404', + }) + @IsString() + status_code: string; + + @ApiProperty({ + description: 'The message of the response', + example: 'Product not found', + }) + @IsString() + message: string; +} + +export class NoResultsResponseDto { + @ApiProperty({ + description: 'The status of the request', + example: 'No Content', + }) + @IsString() + status: string; + + @ApiProperty({ + description: 'The response code', + example: '204', + }) + @IsString() + status_code: string; + + @ApiProperty({ + description: 'The message of the response', + example: 'No products found', + }) + @IsString() + message: string; +} + +export class ForbiddenErrorResponseDto { + @ApiProperty({ + description: 'The status of the request', + example: 'fail', + }) + @IsString() + status: string; + + @ApiProperty({ + description: 'The response code', + example: '403', + }) + @IsString() + status_code: string; + + @ApiProperty({ + description: 'The message of the response', + example: 'Not allowed to perform this action', + }) + @IsString() + message: string; +} + +export class ServerErrorResponseDto { + @ApiProperty({ + description: 'The status of the request', + example: 'Internal server error', + }) + @IsString() + status: string; + + @ApiProperty({ + description: 'The response code', + example: '500', + }) + @IsString() + status_code: string; + + @ApiProperty({ + description: 'The message of the response', + example: 'An unexpected error occurred. Please try again later.', + }) + @IsString() + message: string; +} diff --git a/src/modules/products/dto/responses/get-products.dto.ts b/src/modules/products/dto/responses/get-products.dto.ts new file mode 100644 index 00000000..7945af20 --- /dev/null +++ b/src/modules/products/dto/responses/get-products.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsBoolean } from 'class-validator'; +import { ProductDetailsDto } from './product-details.dto'; + +export class ProductResponseDto { + @ApiProperty({ + description: 'The message of the response', + example: 'Product retrieved successfully', + }) + @IsString() + message: string; + + @ApiProperty({ + description: 'The product details', + }) + data: ProductDetailsDto; +} diff --git a/src/modules/products/dto/responses/product-comment.dto.ts b/src/modules/products/dto/responses/product-comment.dto.ts new file mode 100644 index 00000000..be6a0411 --- /dev/null +++ b/src/modules/products/dto/responses/product-comment.dto.ts @@ -0,0 +1,52 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsBoolean } from 'class-validator'; + +class CommentDto { + @ApiProperty({ + description: 'Comment id', + example: 'comment_1', + }) + @IsString() + id: string; + + @ApiProperty({ + description: 'Product id', + example: 'product_1', + }) + @IsString() + product_id: string; + + @ApiProperty({ + description: 'User id', + example: 'user_1', + }) + @IsString() + user_id: string; + + @ApiProperty({ + description: 'Created comment', + example: '15', + }) + @IsString() + comment: string; + + @ApiProperty({ + description: 'Date when the stock was last updated', + example: new Date(), + }) + created_at: Date; +} + +export class CommentResponseDto { + @ApiProperty({ + description: 'The message of the response', + example: 'Comment added successfully', + }) + @IsString() + message: string; + + @ApiProperty({ + description: 'The product details', + }) + data: CommentDto; +} diff --git a/src/modules/products/dto/responses/product-details.dto.ts b/src/modules/products/dto/responses/product-details.dto.ts new file mode 100644 index 00000000..17aba9cd --- /dev/null +++ b/src/modules/products/dto/responses/product-details.dto.ts @@ -0,0 +1,63 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsBoolean } from 'class-validator'; + +export class ProductDetailsDto { + @ApiProperty({ + description: 'Product id', + example: 'product_1', + }) + @IsString() + id: string; + + @ApiProperty({ + description: 'Product name', + example: 'Product 1', + }) + @IsString() + name: string; + + @ApiProperty({ + description: 'Product description', + example: 'Product description 1', + }) + @IsString() + description: string; + + @ApiProperty({ + description: 'Product price', + example: '500', + }) + price: number; + + @ApiProperty({ + description: 'Product status', + example: 'Product status', + }) + @IsString() + status: string; + + @ApiProperty({ + description: 'Product deletion status', + example: 'false', + }) + @IsBoolean() + is_deleted: boolean; + + @ApiProperty({ + description: 'Product quantity', + example: 'Product quantity', + }) + quantity: number; + + @ApiProperty({ + description: 'Date when the product was created', + example: new Date(), + }) + created_at: Date; + + @ApiProperty({ + description: 'Date when the product was last updated', + example: new Date(), + }) + updated_at: Date; +} diff --git a/src/modules/products/dto/responses/search-products.dto.ts b/src/modules/products/dto/responses/search-products.dto.ts new file mode 100644 index 00000000..f4fd0674 --- /dev/null +++ b/src/modules/products/dto/responses/search-products.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsBoolean } from 'class-validator'; +import { ProductDetailsDto } from './product-details.dto'; + +export class SearchResponseDto { + @ApiProperty({ + description: 'The status of the request', + example: 'true', + }) + @IsBoolean() + success: boolean; + + @ApiProperty({ + description: 'The status of the request', + example: '200', + }) + statusCode: number; + + @ApiProperty({ + description: 'The search results', + }) + data: ProductDetailsDto[]; +} diff --git a/src/modules/products/dto/responses/stock-response.dto.ts b/src/modules/products/dto/responses/stock-response.dto.ts new file mode 100644 index 00000000..2332538d --- /dev/null +++ b/src/modules/products/dto/responses/stock-response.dto.ts @@ -0,0 +1,37 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +class StockDto { + @ApiProperty({ + description: 'Product id', + example: 'product_1', + }) + @IsString() + product_id: string; + + @ApiProperty({ + description: 'Product stock', + example: '15', + }) + current_stock: number; + + @ApiProperty({ + description: 'Date when the stock was last updated', + example: new Date(), + }) + last_updated: Date; +} + +export class StockResponseDto { + @ApiProperty({ + description: 'The response message', + example: 'Product stock retrieved successfully', + }) + @IsString() + message: string; + + @ApiProperty({ + description: 'The response data', + }) + data: StockDto; +} diff --git a/src/modules/products/products.controller.ts b/src/modules/products/products.controller.ts index 6ee65f6c..9c11fde5 100644 --- a/src/modules/products/products.controller.ts +++ b/src/modules/products/products.controller.ts @@ -7,6 +7,19 @@ import { UpdateProductDTO } from './dto/update-product.dto'; import { AddCommentDto } from '../comments/dto/add-comment.dto'; import { GetTotalProductsResponseDto } from './dto/get-total-products.dto'; import { SuperAdminGuard } from '../../guards/super-admin.guard'; +import { SuccessfulCreateResponseDto } from './dto/responses/create-product-response.dto'; +import { + BadRequestResponseDto, + ServerErrorResponseDto, + NotFoundResponseDto, + NoResultsResponseDto, + ForbiddenErrorResponseDto, +} from './dto/responses/error-response.dto'; +import { StockResponseDto } from './dto/responses/stock-response.dto'; +import { DeleteProductDto } from './dto/responses/delete-product.dto'; +import { SearchResponseDto } from './dto/responses/search-products.dto'; +import { ProductResponseDto } from './dto/responses/get-products.dto'; +import { CommentResponseDto } from './dto/responses/product-comment.dto'; @ApiBearerAuth() @ApiTags('Products') @@ -27,20 +40,20 @@ export class ProductsController { @ApiOperation({ summary: 'Creates a new product' }) @ApiParam({ name: 'id', description: 'organisation ID', example: '12345' }) @ApiBody({ type: CreateProductRequestDto, description: 'Details of the product to be created' }) - @ApiResponse({ status: 201, description: 'Product created successfully' }) - @ApiResponse({ status: 400, description: 'Bad request' }) - @ApiResponse({ status: 500, description: 'Internal server error' }) - async createProduct(@Param('orgId') orgId: string, @Body() createProductDto: CreateProductRequestDto) { - return this.productsService.createProduct(orgId, createProductDto); + @ApiResponse({ status: 201, description: 'Product created successfully', type: SuccessfulCreateResponseDto }) + @ApiResponse({ status: 422, description: 'Invalid Organisation', type: BadRequestResponseDto }) + @ApiResponse({ status: 500, description: 'Internal server error', type: ServerErrorResponseDto }) + async createProduct(@Param('id') id: string, @Body() createProductDto: CreateProductRequestDto) { + return this.productsService.createProduct(id, createProductDto); } @Get('/:orgId/products/search') @ApiOperation({ summary: 'Search for products' }) - @ApiParam({ name: 'orgId', description: 'organisation ID', example: '12345' }) - @ApiResponse({ status: 200, description: 'Products found successfully' }) - @ApiResponse({ status: 204, description: 'No products found' }) - @ApiResponse({ status: 400, description: 'Bad request' }) - @ApiResponse({ status: 500, description: 'Internal server error' }) + @ApiParam({ name: 'id', description: 'organisation ID', example: '12345' }) + @ApiResponse({ status: 200, description: 'Products found successfully', type: SearchResponseDto }) + @ApiResponse({ status: 204, description: 'No products found', type: NoResultsResponseDto }) + @ApiResponse({ status: 422, description: 'Invalid organisation', type: BadRequestResponseDto }) + @ApiResponse({ status: 500, description: 'Internal server error', type: ServerErrorResponseDto }) async searchProducts( @Param('orgId') orgId: string, @Query('name') name?: string, @@ -55,10 +68,10 @@ export class ProductsController { @Get('/:orgId/products/:productId') @ApiOperation({ summary: 'Gets a product by id' }) @ApiParam({ name: 'orgId', description: 'Organization ID', example: '12345' }) - @ApiResponse({ status: 200, description: 'Product created successfully' }) - @ApiResponse({ status: 400, description: 'Bad request' }) - @ApiResponse({ status: 404, description: 'Product not found' }) - @ApiResponse({ status: 500, description: 'Internal server error' }) + @ApiResponse({ status: 200, description: 'Product created successfully', type: SuccessfulCreateResponseDto }) + @ApiResponse({ status: 400, description: 'Bad request', type: BadRequestResponseDto }) + @ApiResponse({ status: 404, description: 'Product not found', type: NotFoundResponseDto }) + @ApiResponse({ status: 500, description: 'Internal server error', type: ServerErrorResponseDto }) async getById(@Param('orgId') id: string, @Param('productId') productId: string) { return this.productsService.getProductById(productId); } @@ -68,10 +81,11 @@ export class ProductsController { @HttpCode(200) @ApiOperation({ summary: 'Update product' }) @ApiParam({ name: 'productId', type: String, description: 'Product ID' }) - @ApiResponse({ status: 200, description: 'Product updated successfully' }) - @ApiResponse({ status: 400, description: 'Bad request' }) - @ApiResponse({ status: 404, description: 'Product not found' }) - @ApiResponse({ status: 500, description: 'Internal server error' }) + @ApiResponse({ status: 200, description: 'Product updated successfully', type: SuccessfulCreateResponseDto }) + @ApiResponse({ status: 422, description: 'Invalid organisation', type: BadRequestResponseDto }) + @ApiResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorResponseDto }) + @ApiResponse({ status: 404, description: 'Product not found', type: NotFoundResponseDto }) + @ApiResponse({ status: 500, description: 'Internal server error', type: ServerErrorResponseDto }) async updateProduct( @Param('orgId') orgId: string, @Param('productId') productId: string, @@ -84,12 +98,12 @@ export class ProductsController { @Delete('/:orgId/products/:productId') @ApiOperation({ summary: 'Delete a product' }) @ApiParam({ name: 'productId', description: 'Product ID' }) - @ApiResponse({ status: 200, description: 'Product deleted successfully' }) - @ApiResponse({ status: 400, description: 'Bad request' }) - @ApiResponse({ status: 404, description: 'Product not found' }) - @ApiResponse({ status: 500, description: 'Internal server error' }) - async deleteProduct(@Param('orgId') orgId: string, @Param('productId') productId: string) { - return this.productsService.deleteProduct(orgId, productId); + @ApiResponse({ status: 200, description: 'Product deleted successfully', type: DeleteProductDto }) + @ApiResponse({ status: 422, description: 'Bad request', type: BadRequestResponseDto }) + @ApiResponse({ status: 404, description: 'Product not found', type: NotFoundResponseDto }) + @ApiResponse({ status: 500, description: 'Internal server error', type: ServerErrorResponseDto }) + async deleteProduct(@Param('id') id: string, @Param('productId') productId: string) { + return this.productsService.deleteProduct(id, productId); } @UseGuards(OwnershipGuard) @@ -99,10 +113,10 @@ export class ProductsController { @ApiParam({ name: 'id', description: 'organisation ID', example: '870ccb14-d6b0-4a50-b459-9895af803i89' }) @ApiParam({ name: 'productId', description: 'product ID', example: '126ccb14-d6b0-4a50-b459-9895af803h6y' }) @ApiBody({ type: AddCommentDto, description: 'Comment to be added' }) - @ApiResponse({ status: 201, description: 'Comment added successfully' }) - @ApiResponse({ status: 400, description: 'Bad request' }) - @ApiResponse({ status: 404, description: 'Not found' }) - @ApiResponse({ status: 500, description: 'Internal server error' }) + @ApiResponse({ status: 201, description: 'Comment added successfully', type: CommentResponseDto }) + @ApiResponse({ status: 400, description: 'Bad request', type: BadRequestResponseDto }) + @ApiResponse({ status: 404, description: 'Not found', type: NotFoundResponseDto }) + @ApiResponse({ status: 500, description: 'Internal server error', type: ServerErrorResponseDto }) async addCommentToProduct(@Param('productId') productId: string, @Body() commentDto: AddCommentDto, @Req() req: any) { const user = req.user; return this.productsService.addCommentToProduct(productId, commentDto, user.sub); @@ -112,9 +126,9 @@ export class ProductsController { @ApiOperation({ summary: 'Gets a product stock details by id' }) @ApiParam({ name: 'id', description: 'Organization ID', example: '12345' }) @ApiParam({ name: 'productId', description: 'Product ID' }) - @ApiResponse({ status: 200, description: 'Product stock retrieved successfully' }) - @ApiResponse({ status: 404, description: 'Product not found' }) - @ApiResponse({ status: 500, description: 'Internal server error' }) + @ApiResponse({ status: 200, description: 'Product stock retrieved successfully', type: StockResponseDto }) + @ApiResponse({ status: 404, description: 'Product not found', type: NotFoundResponseDto }) + @ApiResponse({ status: 500, description: 'Internal server error', type: ServerErrorResponseDto }) async getProductStock(@Param('productId') productId: string) { return this.productsService.getProductStock(productId); }