From 1a02871d1befbc58b0cdbe4214d5cefd8b7025fd Mon Sep 17 00:00:00 2001 From: Ajay Maurya Date: Wed, 20 Sep 2023 19:40:07 +0530 Subject: [PATCH 01/26] Request APIs created --- package-lock.json | 35 +++++ package.json | 2 + .../20230919071959_init/migration.sql | 16 -- prisma/schema.prisma | 40 ++++- src/app.module.ts | 2 + src/prisma/prisma.service.ts | 2 +- src/request/dto/create-request.dto.ts | 88 +++++++++++ src/request/dto/response-request.dto.ts | 15 ++ src/request/dto/update-request.dto.ts | 4 + src/request/entities/request.entity.ts | 1 + src/request/enum/request.enum.ts | 13 ++ src/request/request.controller.ts | 147 ++++++++++++++++++ src/request/request.module.ts | 13 ++ src/request/request.service.ts | 83 ++++++++++ 14 files changed, 439 insertions(+), 22 deletions(-) delete mode 100644 prisma/migrations/20230919071959_init/migration.sql create mode 100644 src/request/dto/create-request.dto.ts create mode 100644 src/request/dto/response-request.dto.ts create mode 100644 src/request/dto/update-request.dto.ts create mode 100644 src/request/entities/request.entity.ts create mode 100644 src/request/enum/request.enum.ts create mode 100644 src/request/request.controller.ts create mode 100644 src/request/request.module.ts create mode 100644 src/request/request.service.ts diff --git a/package-lock.json b/package-lock.json index e103b20..f04d866 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,8 @@ "@nestjs/platform-express": "^9.0.0", "@nestjs/swagger": "^7.1.11", "@prisma/client": "^5.3.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0" @@ -2141,6 +2143,11 @@ "@types/superagent": "*" } }, + "node_modules/@types/validator": { + "version": "13.11.1", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.1.tgz", + "integrity": "sha512-d/MUkJYdOeKycmm75Arql4M5+UuXmf4cHdHKsyw1GcvnNgL6s77UkgSgJ8TE/rI5PYsnwYq5jkcWBLuN/MpQ1A==" + }, "node_modules/@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -3145,6 +3152,21 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "node_modules/class-validator": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.0.tgz", + "integrity": "sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==", + "dependencies": { + "@types/validator": "^13.7.10", + "libphonenumber-js": "^1.10.14", + "validator": "^13.7.0" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -5660,6 +5682,11 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.10.44", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.44.tgz", + "integrity": "sha512-svlRdNBI5WgBjRC20GrCfbFiclbF0Cx+sCcQob/C1r57nsoq0xg8r65QbTyVyweQIlB33P+Uahyho6EMYgcOyQ==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -7874,6 +7901,14 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 2318514..c1bbacc 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "@nestjs/platform-express": "^9.0.0", "@nestjs/swagger": "^7.1.11", "@prisma/client": "^5.3.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0" diff --git a/prisma/migrations/20230919071959_init/migration.sql b/prisma/migrations/20230919071959_init/migration.sql deleted file mode 100644 index e8c5afa..0000000 --- a/prisma/migrations/20230919071959_init/migration.sql +++ /dev/null @@ -1,16 +0,0 @@ --- CreateTable -CREATE TABLE "User" ( - "id" SERIAL NOT NULL, - "email" TEXT NOT NULL, - "userCategory" TEXT NOT NULL, - "username" TEXT NOT NULL, - "password" TEXT NOT NULL, - "profilePicture" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "User_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0564330..0703dab 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -12,12 +12,42 @@ datasource db { // Dummy user model model User { - id Int @id @default(autoincrement()) - email String @unique - userCategory String + id Int @id @default(autoincrement()) + email String @unique + role String username String password String profilePicture String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + requests Request[] +} + +model Request { + requestId Int @id @default(autoincrement()) + userId Int + title String + type RequestTypeEnum + description String + status RequestStatusEnum + requestContent Json? + responseContent Json? + remark String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id]) // Specify fields and references here +} + +enum RequestTypeEnum { + REFUND + CREDIT + INVOICE_REQUEST + SETTLEMENT +} + +enum RequestStatusEnum { + PENDING + IN_PROGRESS + APPROVED + REJECTED } diff --git a/src/app.module.ts b/src/app.module.ts index 8468bfa..f892894 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,6 +3,7 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { PrismaModule } from './prisma/prisma.module'; import { ConfigModule } from '@nestjs/config'; +import { RequestModule } from './request/request.module'; @Module({ imports: [ @@ -11,6 +12,7 @@ import { ConfigModule } from '@nestjs/config'; isGlobal: true, // Make the configuration available globally }), PrismaModule, + RequestModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/prisma/prisma.service.ts b/src/prisma/prisma.service.ts index 072abe7..2c93095 100644 --- a/src/prisma/prisma.service.ts +++ b/src/prisma/prisma.service.ts @@ -1,4 +1,4 @@ -import { INestApplication, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() diff --git a/src/request/dto/create-request.dto.ts b/src/request/dto/create-request.dto.ts new file mode 100644 index 0000000..01d09a9 --- /dev/null +++ b/src/request/dto/create-request.dto.ts @@ -0,0 +1,88 @@ +import { RequestStatusEnum, RequestTypeEnum } from '@prisma/client'; +import { Transform, TransformFnParams } from 'class-transformer'; +import { + IsDate, + IsEnum, + IsInt, + IsNotEmpty, + IsOptional, + IsString, +} from 'class-validator'; + +export class CreateRequestDto { + @IsNotEmpty() + @IsInt() + userId: number; + + @IsNotEmpty() + @IsString() + title: string; + + @IsNotEmpty() + @IsEnum(RequestStatusEnum) + status: RequestStatusEnum; + + @IsNotEmpty() + @IsString() + description: string; + + @IsNotEmpty() + @IsEnum(RequestTypeEnum) + type: RequestTypeEnum; + + @IsOptional() + @Transform(({ value }: TransformFnParams) => { + try { + return JSON.parse(value); + } catch (error) { + return value; + } + }) + requestContent?: object; + + @IsOptional() + @Transform(({ value }: TransformFnParams) => { + try { + return JSON.parse(value); + } catch (error) { + return value; + } + }) + responseContent?: object; + + @IsOptional() + @IsString() + remark?: string; + + @IsDate() + @IsOptional() + createdAt?: Date; + + @IsDate() + @IsOptional() + updatedAt?: Date; +} + +export class RequestStatusDto { + @IsNotEmpty() + @IsEnum(RequestStatusEnum) + status: RequestStatusEnum; +} + +export class RequestFilterDto { + @IsOptional() + @IsEnum(RequestStatusEnum, { each: true }) // Validate that status is a valid enum value + status?: RequestStatusEnum; + + @IsOptional() + @IsInt() // Validate that limit is an integer + limit?: number; + + @IsOptional() + @IsInt() // Validate that offset is an integer + offset?: number; + + @IsOptional() + @IsString() // Validate that orderBy is a string + orderBy?: string; +} diff --git a/src/request/dto/response-request.dto.ts b/src/request/dto/response-request.dto.ts new file mode 100644 index 0000000..2c98010 --- /dev/null +++ b/src/request/dto/response-request.dto.ts @@ -0,0 +1,15 @@ +import { RequestStatusEnum, RequestTypeEnum } from '../enum/request.enum'; + +export class ResponseRequestDto { + requestId: number; + userId: number; + title: string; + type: RequestTypeEnum; + description: string; + status: RequestStatusEnum; + requestContent: object; + responseContent: object; + remark: string; + createdAt: Date; + updatedAt: Date; +} diff --git a/src/request/dto/update-request.dto.ts b/src/request/dto/update-request.dto.ts new file mode 100644 index 0000000..bac9f9f --- /dev/null +++ b/src/request/dto/update-request.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateRequestDto } from './create-request.dto'; + +export class UpdateRequestDto extends PartialType(CreateRequestDto) {} diff --git a/src/request/entities/request.entity.ts b/src/request/entities/request.entity.ts new file mode 100644 index 0000000..d5e892a --- /dev/null +++ b/src/request/entities/request.entity.ts @@ -0,0 +1 @@ +export class Request {} diff --git a/src/request/enum/request.enum.ts b/src/request/enum/request.enum.ts new file mode 100644 index 0000000..884355b --- /dev/null +++ b/src/request/enum/request.enum.ts @@ -0,0 +1,13 @@ +export enum RequestTypeEnum { + REFUND, + CREDIT, + INVOICE_REQUEST, + SETTLEMENT, +} + +export enum RequestStatusEnum { + PENDING, + IN_PROGRESS, + APPROVED, + REJECTED, +} diff --git a/src/request/request.controller.ts b/src/request/request.controller.ts new file mode 100644 index 0000000..cb3f548 --- /dev/null +++ b/src/request/request.controller.ts @@ -0,0 +1,147 @@ +import { + Controller, + Post, + Get, + Body, + Param, + Patch, + Query, + HttpStatus, + HttpException, +} from '@nestjs/common'; +import { RequestService } from './request.service'; +import { + CreateRequestDto, + RequestFilterDto, + RequestStatusDto, +} from './dto/create-request.dto'; +import { UpdateRequestDto } from './dto/update-request.dto'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ResponseRequestDto } from './dto/response-request.dto'; + +@Controller('requests') +@ApiTags('requests') +export class RequestController { + constructor(private readonly requestService: RequestService) {} + + @Post() + @ApiOperation({ summary: 'Create a request' }) + @ApiResponse({ status: HttpStatus.CREATED, type: ResponseRequestDto }) + async createRequest(@Body() createRequestDto: CreateRequestDto) { + try { + const request = await this.requestService.createRequest(createRequestDto); + console.log('request:', request); + return { message: 'Request created successfully', data: request }; + } catch (error) { + console.log('error:', error); + throw new HttpException( + 'Failed to create the request', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @Get('') + @ApiOperation({ summary: 'Get all requests' }) + @ApiResponse({ + status: HttpStatus.OK, + type: ResponseRequestDto, + isArray: true, + }) + async getAllRequestsForAdmin(@Query() filter: RequestFilterDto) { + try { + const requests = await this.requestService.getAllRequestsForAdmin(filter); + return { message: 'Requests fetched successfully', data: requests }; + } catch (error) { + throw new HttpException( + 'Failed to fetch requests', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @Get('user/:userId') + @ApiOperation({ summary: 'Get all requests of a user' }) + @ApiResponse({ + status: HttpStatus.OK, + type: ResponseRequestDto, + isArray: true, + }) + async getAllRequestsForUser( + @Param('userId') userId: number, + @Query() filter: RequestFilterDto, + ) { + try { + const requests = await this.requestService.getAllRequestsForUser( + userId, + filter, + ); + return { message: 'User requests fetched successfully', data: requests }; + } catch (error) { + throw new HttpException( + 'Failed to fetch user requests', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @Get(':requestId') + @ApiOperation({ summary: 'Get a request' }) + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) + async getRequestById(@Param('requestId') requestId: number) { + try { + const request = await this.requestService.getRequestById(requestId); + return { message: 'Request fetched successfully', data: request }; + } catch (error) { + throw new HttpException( + 'Failed to fetch the request', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @Patch('update/:requestId') + @ApiOperation({ summary: 'Update a request' }) + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) + async updateRequestByRequestId( + @Param('requestId') requestId: number, + @Body() updateRequestDto: UpdateRequestDto, + ) { + try { + const updatedRequest = await this.requestService.updateRequestByRequestId( + requestId, + updateRequestDto, + ); + return { message: 'Request updated successfully', data: updatedRequest }; + } catch (error) { + throw new HttpException( + 'Failed to update the request', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @Patch('update/status/:requestId') + @ApiOperation({ summary: 'Update a request status' }) + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) + async updateRequestStatus( + @Param('requestId') requestId: number, + @Body() updateRequestDtoStatus: RequestStatusDto, + ) { + try { + const updatedRequest = await this.requestService.updateRequestStatus( + requestId, + updateRequestDtoStatus, + ); + return { + message: 'Request status updated successfully', + data: updatedRequest, + }; + } catch (error) { + throw new HttpException( + 'Failed to update the request status', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/request/request.module.ts b/src/request/request.module.ts new file mode 100644 index 0000000..83a1df1 --- /dev/null +++ b/src/request/request.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { RequestController } from './request.controller'; +import { RequestService } from './request.service'; +import { PrismaService } from 'src/prisma/prisma.service'; +import { PrismaModule } from 'src/prisma/prisma.module'; + +@Module({ + imports: [PrismaModule], + controllers: [RequestController], + providers: [RequestService, PrismaService], + exports: [RequestService], +}) +export class RequestModule {} diff --git a/src/request/request.service.ts b/src/request/request.service.ts new file mode 100644 index 0000000..cdb389f --- /dev/null +++ b/src/request/request.service.ts @@ -0,0 +1,83 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; // Import your Prisma service +import { + CreateRequestDto, + RequestFilterDto, + RequestStatusDto, +} from './dto/create-request.dto'; +import { UpdateRequestDto } from './dto/update-request.dto'; + +@Injectable() +export class RequestService { + constructor( + @Inject(forwardRef(() => PrismaService)) + private prisma: PrismaService, + ) {} + + async createRequest(createRequestDto: CreateRequestDto) { + return this.prisma.request.create({ + data: createRequestDto, + }); + } + + async getAllRequestsForAdmin(filter: RequestFilterDto) { + const { status, limit, offset, orderBy } = filter; + return this.prisma.request.findMany({ + where: { + status: status || undefined, // Optional status filter + }, + orderBy: { + [orderBy || 'createdAt']: 'asc', // Default sorting by createdAt + }, + skip: offset, + take: limit, + }); + } + + async getAllRequestsForUser(userId: number, filter: any) { + const { status, limit, offset, orderBy } = filter; + return this.prisma.request.findMany({ + where: { + userId: userId, + status: status || undefined, // Optional status filter + }, + orderBy: { + [orderBy || 'createdAt']: 'asc', // Default sorting by createdAt + }, + skip: offset, + take: limit, + }); + } + + async getRequestById(requestId: number) { + return this.prisma.request.findUnique({ + where: { + requestId: requestId, + }, + }); + } + + async updateRequestByRequestId( + requestId: number, + updateRequestDto: UpdateRequestDto, + ) { + return this.prisma.request.update({ + where: { + requestId: requestId, + }, + data: updateRequestDto, + }); + } + + async updateRequestStatus( + requestId: number, + updateRequestStatusDto: RequestStatusDto, + ) { + return this.prisma.request.update({ + where: { + requestId: requestId, + }, + data: updateRequestStatusDto, + }); + } +} From 085aa3a36303cc8a28aa408fbb574fa9260ae203 Mon Sep 17 00:00:00 2001 From: Ajay Maurya Date: Thu, 21 Sep 2023 11:36:01 +0530 Subject: [PATCH 02/26] Response return and logger messages implemented --- prisma/migrations/migration_lock.toml | 3 - src/app.controller.ts | 5 +- src/request/dto/create-request.dto.ts | 4 + src/request/request.controller.ts | 175 ++++++++++++++++++++------ src/request/request.service.ts | 16 +-- 5 files changed, 150 insertions(+), 53 deletions(-) delete mode 100644 prisma/migrations/migration_lock.toml diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml deleted file mode 100644 index fbffa92..0000000 --- a/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "postgresql" \ No newline at end of file diff --git a/src/app.controller.ts b/src/app.controller.ts index 9df4fd8..8ef5a2d 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -1,13 +1,14 @@ import { Controller, Get, HttpStatus } from '@nestjs/common'; import { AppService } from './app.service'; -import { ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; @Controller() +@ApiTags('get service health') export class AppController { constructor(private readonly appService: AppService) {} @Get() - @ApiOperation({ summary: 'default route for health check' }) // Describes the operation for Swagger. + @ApiOperation({ summary: 'default route for service health check' }) // Describes the operation for Swagger. @ApiResponse({ status: HttpStatus.OK, type: String }) // Describes the response for Swagger. getHealth(): string { return this.appService.getHealth(); diff --git a/src/request/dto/create-request.dto.ts b/src/request/dto/create-request.dto.ts index 01d09a9..2204cf2 100644 --- a/src/request/dto/create-request.dto.ts +++ b/src/request/dto/create-request.dto.ts @@ -85,4 +85,8 @@ export class RequestFilterDto { @IsOptional() @IsString() // Validate that orderBy is a string orderBy?: string; + + @IsOptional() + @IsEnum(RequestTypeEnum, { each: true }) // Validate that request type is a valid enum value + type?: RequestTypeEnum; } diff --git a/src/request/request.controller.ts b/src/request/request.controller.ts index cb3f548..8d9e099 100644 --- a/src/request/request.controller.ts +++ b/src/request/request.controller.ts @@ -7,8 +7,10 @@ import { Patch, Query, HttpStatus, - HttpException, + Logger, + Res, } from '@nestjs/common'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { RequestService } from './request.service'; import { CreateRequestDto, @@ -16,28 +18,41 @@ import { RequestStatusDto, } from './dto/create-request.dto'; import { UpdateRequestDto } from './dto/update-request.dto'; -import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ResponseRequestDto } from './dto/response-request.dto'; @Controller('requests') @ApiTags('requests') export class RequestController { + private readonly logger = new Logger(RequestController.name); + constructor(private readonly requestService: RequestService) {} @Post() @ApiOperation({ summary: 'Create a request' }) @ApiResponse({ status: HttpStatus.CREATED, type: ResponseRequestDto }) - async createRequest(@Body() createRequestDto: CreateRequestDto) { + async createRequest(@Res() res, @Body() createRequestDto: CreateRequestDto) { try { + this.logger.log( + `Creating a new request for user id #${createRequestDto.userId}`, + ); const request = await this.requestService.createRequest(createRequestDto); - console.log('request:', request); - return { message: 'Request created successfully', data: request }; + + this.logger.log( + `Successfully created a new request for user id #${request.userId}, request id ${request.requestId}`, + ); + + return res + .status(HttpStatus.CREATED) + .json({ message: 'Request created successfully', data: request }); } catch (error) { - console.log('error:', error); - throw new HttpException( - 'Failed to create the request', - HttpStatus.INTERNAL_SERVER_ERROR, + this.logger.error( + `Failed to create new request for user id #${createRequestDto.userId}`, + error, ); + + return res.status(HttpStatus.CREATED).json({ + message: `Failed to create the request for user id #${createRequestDto.userId}`, + }); } } @@ -48,15 +63,22 @@ export class RequestController { type: ResponseRequestDto, isArray: true, }) - async getAllRequestsForAdmin(@Query() filter: RequestFilterDto) { + async getAllRequests(@Res() res, @Query() filter: RequestFilterDto) { try { - const requests = await this.requestService.getAllRequestsForAdmin(filter); - return { message: 'Requests fetched successfully', data: requests }; + this.logger.log(`Initiated fetching requests`); + + const requests = await this.requestService.getAllRequests(filter); + + this.logger.log(`Successfully fetched requests`); + + return res + .status(HttpStatus.OK) + .json({ message: 'Successfully fetched requests', data: requests }); } catch (error) { - throw new HttpException( - 'Failed to fetch requests', - HttpStatus.INTERNAL_SERVER_ERROR, - ); + this.logger.error(`Failed to fetch requests`, error); + return res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ message: 'Failed to fetch requests' }); } } @@ -68,35 +90,67 @@ export class RequestController { isArray: true, }) async getAllRequestsForUser( + @Res() res, @Param('userId') userId: number, @Query() filter: RequestFilterDto, ) { try { + this.logger.log( + `Initiated fetching user requests for user id #${userId}`, + ); + const requests = await this.requestService.getAllRequestsForUser( - userId, + +userId, filter, ); - return { message: 'User requests fetched successfully', data: requests }; + + this.logger.log( + `Successfully fetched user requests for user id #${userId}`, + ); + + return res.status(HttpStatus.OK).json({ + message: `Successfully fetched user requests for user id #${userId}`, + data: requests, + }); } catch (error) { - throw new HttpException( - 'Failed to fetch user requests', - HttpStatus.INTERNAL_SERVER_ERROR, + this.logger.error( + `Failed to fetch user requests for user id #${userId}`, + error, ); + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: `Failed to fetch user requests for user id #${userId}`, + }); } } @Get(':requestId') @ApiOperation({ summary: 'Get a request' }) @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) - async getRequestById(@Param('requestId') requestId: number) { + async getRequestById(@Res() res, @Param('requestId') requestId: number) { try { - const request = await this.requestService.getRequestById(requestId); - return { message: 'Request fetched successfully', data: request }; + this.logger.log( + `Initiated fetching request for request id #${requestId}`, + ); + + const request = await this.requestService.getRequestById(+requestId); + + this.logger.log( + `Successfully fetched request for request id #${requestId}`, + ); + + return res.status(HttpStatus.OK).json({ + message: `Successfully fetched request for request id #${requestId}`, + data: request, + }); } catch (error) { - throw new HttpException( - 'Failed to fetch the request', - HttpStatus.INTERNAL_SERVER_ERROR, + this.logger.error( + `Failed to fetch request for request id #${requestId}`, + error, ); + + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: `Failed to fetch request for request id #${requestId}`, + }); } } @@ -104,20 +158,37 @@ export class RequestController { @ApiOperation({ summary: 'Update a request' }) @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) async updateRequestByRequestId( + @Res() res, @Param('requestId') requestId: number, @Body() updateRequestDto: UpdateRequestDto, ) { try { + this.logger.log( + `Initiated updating request for request id #${requestId}`, + ); + const updatedRequest = await this.requestService.updateRequestByRequestId( - requestId, + +requestId, updateRequestDto, ); - return { message: 'Request updated successfully', data: updatedRequest }; + + this.logger.log( + `Successfully updated request for request id #${requestId}`, + ); + + return res.status(HttpStatus.OK).json({ + message: `Successfully updated request for request id #${requestId}`, + data: updatedRequest, + }); } catch (error) { - throw new HttpException( - 'Failed to update the request', - HttpStatus.INTERNAL_SERVER_ERROR, + this.logger.error( + `Failed to update request for request id #${requestId}`, + error, ); + + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: `Failed to updated request for request id #${requestId}`, + }); } } @@ -125,23 +196,45 @@ export class RequestController { @ApiOperation({ summary: 'Update a request status' }) @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) async updateRequestStatus( + @Res() res, @Param('requestId') requestId: number, - @Body() updateRequestDtoStatus: RequestStatusDto, + @Body() updateRequestStatusDto: RequestStatusDto, ) { try { + this.logger.log( + `Initiated updating request status to ${JSON.stringify( + updateRequestStatusDto, + )} for request id #${requestId}`, + ); + const updatedRequest = await this.requestService.updateRequestStatus( - requestId, - updateRequestDtoStatus, + +requestId, + updateRequestStatusDto, ); - return { - message: 'Request status updated successfully', + + this.logger.log( + `Successfully updated request status to ${JSON.stringify( + updateRequestStatusDto, + )} for request id #${requestId}`, + ); + + return res.status(HttpStatus.OK).json({ + message: `Successfully updated request status for request id #${requestId}`, data: updatedRequest, - }; + }); } catch (error) { - throw new HttpException( - 'Failed to update the request status', - HttpStatus.INTERNAL_SERVER_ERROR, + this.logger.error( + `Failed to update request status to ${JSON.stringify( + updateRequestStatusDto, + )} for request id #${requestId}`, + error, ); + + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: `Failed to updated request status to ${JSON.stringify( + updateRequestStatusDto, + )} for request id #${requestId}`, + }); } } } diff --git a/src/request/request.service.ts b/src/request/request.service.ts index cdb389f..6e19fba 100644 --- a/src/request/request.service.ts +++ b/src/request/request.service.ts @@ -20,26 +20,28 @@ export class RequestService { }); } - async getAllRequestsForAdmin(filter: RequestFilterDto) { - const { status, limit, offset, orderBy } = filter; + async getAllRequests(filter: RequestFilterDto) { + const { status, type, limit = 10, offset = 0, orderBy } = filter; return this.prisma.request.findMany({ where: { - status: status || undefined, // Optional status filter + status: status ?? undefined, // Optional status filter + type: type ?? undefined, // Optional type filter }, orderBy: { [orderBy || 'createdAt']: 'asc', // Default sorting by createdAt }, - skip: offset, - take: limit, + skip: +offset, + take: +limit, }); } async getAllRequestsForUser(userId: number, filter: any) { - const { status, limit, offset, orderBy } = filter; + const { status, type, limit = 10, offset = 0, orderBy } = filter; return this.prisma.request.findMany({ where: { userId: userId, - status: status || undefined, // Optional status filter + status: status ?? undefined, // Optional status filter + type: type ?? undefined, // Optional type filter }, orderBy: { [orderBy || 'createdAt']: 'asc', // Default sorting by createdAt From 3f91ed6cbadc09c0dd337937f98b280c3297171e Mon Sep 17 00:00:00 2001 From: Ajay Maurya Date: Thu, 21 Sep 2023 12:36:30 +0530 Subject: [PATCH 03/26] Added comments and minor updates --- src/app.controller.spec.ts | 6 +++-- src/main.ts | 4 +-- src/request/dto/create-request.dto.ts | 35 +++++++++++++++++++++---- src/request/dto/response-request.dto.ts | 22 ++++++++-------- src/request/request.controller.ts | 33 ++++++++++++++--------- src/request/request.module.ts | 7 +++++ src/request/request.service.ts | 13 +++++---- src/utils/constants.ts | 4 ++- 8 files changed, 86 insertions(+), 38 deletions(-) diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts index d22f389..2b9d42f 100644 --- a/src/app.controller.spec.ts +++ b/src/app.controller.spec.ts @@ -15,8 +15,10 @@ describe('AppController', () => { }); describe('root', () => { - it('should return "Hello World!"', () => { - expect(appController.getHello()).toBe('Hello World!'); + it('should return "marketplace query service running successfully"', () => { + expect(appController.getHealth()).toBe( + 'marketplace query service running successfully', + ); }); }); }); diff --git a/src/main.ts b/src/main.ts index b50f522..c403bfd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,7 +21,7 @@ async function bootstrap() { .addApiKey( { type: 'apiKey', - name: 'x-thats-my-college-api-config-key', + name: 'x-marketplace-request-service-api-config-key', in: 'header', }, SWAGGER_CONSTANTS.SWAGGER_AUTH_SECURITY_SCHEMA_API_KEY, // API key security scheme name @@ -38,7 +38,7 @@ async function bootstrap() { .setTitle(SWAGGER_CONSTANTS.TITLE) .setDescription(SWAGGER_CONSTANTS.DESCRIPTION) .setVersion(SWAGGER_CONSTANTS.VERSION) - // .addTag(SWAGGER_TAGS. ) // Add a tag for API grouping + .addTag(SWAGGER_TAGS.REQUEST) // Add a tag for API grouping .build(); diff --git a/src/request/dto/create-request.dto.ts b/src/request/dto/create-request.dto.ts index 2204cf2..5c44f08 100644 --- a/src/request/dto/create-request.dto.ts +++ b/src/request/dto/create-request.dto.ts @@ -9,84 +9,109 @@ import { IsString, } from 'class-validator'; +// CreateRequestDto is used for creating a new request. export class CreateRequestDto { + // User ID associated with the request. @IsNotEmpty() @IsInt() userId: number; + // Title of the request. @IsNotEmpty() @IsString() title: string; + // Status of the request, should be one of the valid RequestStatusEnum values. @IsNotEmpty() @IsEnum(RequestStatusEnum) status: RequestStatusEnum; + // Description of the request. @IsNotEmpty() @IsString() description: string; + // Type of the request, should be one of the valid RequestTypeEnum values. @IsNotEmpty() @IsEnum(RequestTypeEnum) type: RequestTypeEnum; + // Optional content of the request in JSON format. @IsOptional() + // Use the @Transform decorator to apply a custom transformation to a property. @Transform(({ value }: TransformFnParams) => { try { + // Try to parse the input value as JSON to convert it into a JavaScript object. return JSON.parse(value); } catch (error) { + // If parsing fails (e.g., if value is not a valid JSON string), return the original value. return value; } }) requestContent?: object; + // Optional content of the response in JSON format. @IsOptional() + // Use the @Transform decorator to apply a custom transformation to a property. @Transform(({ value }: TransformFnParams) => { try { + // Try to parse the input value as JSON to convert it into a JavaScript object. return JSON.parse(value); } catch (error) { + // If parsing fails (e.g., if value is not a valid JSON string), return the original value. return value; } }) responseContent?: object; + // Optional remark or additional information about the request. @IsOptional() @IsString() remark?: string; + // Optional creation date of the request. @IsDate() @IsOptional() createdAt?: Date; + // Optional update date of the request. @IsDate() @IsOptional() updatedAt?: Date; } +// RequestStatusDto is used for updating the status of a request. export class RequestStatusDto { + // New status for the request, should be one of the valid RequestStatusEnum values. @IsNotEmpty() @IsEnum(RequestStatusEnum) status: RequestStatusEnum; } +// RequestFilterDto is used for filtering and paginating requests. export class RequestFilterDto { + // Optional status filter, validate that it's a valid enum value. @IsOptional() - @IsEnum(RequestStatusEnum, { each: true }) // Validate that status is a valid enum value + @IsEnum(RequestStatusEnum, { each: true }) status?: RequestStatusEnum; + // Optional limit for pagination, validate that it's an integer. @IsOptional() - @IsInt() // Validate that limit is an integer + @IsInt() limit?: number; + // Optional offset for pagination, validate that it's an integer. @IsOptional() - @IsInt() // Validate that offset is an integer + @IsInt() offset?: number; + // Optional field to specify the order of results, validate that it's a string. @IsOptional() - @IsString() // Validate that orderBy is a string + @IsString() orderBy?: string; + // Optional request type filter, validate that it's a valid enum value. @IsOptional() - @IsEnum(RequestTypeEnum, { each: true }) // Validate that request type is a valid enum value + @IsEnum(RequestTypeEnum, { each: true }) type?: RequestTypeEnum; } diff --git a/src/request/dto/response-request.dto.ts b/src/request/dto/response-request.dto.ts index 2c98010..f614f35 100644 --- a/src/request/dto/response-request.dto.ts +++ b/src/request/dto/response-request.dto.ts @@ -1,15 +1,15 @@ import { RequestStatusEnum, RequestTypeEnum } from '../enum/request.enum'; export class ResponseRequestDto { - requestId: number; - userId: number; - title: string; - type: RequestTypeEnum; - description: string; - status: RequestStatusEnum; - requestContent: object; - responseContent: object; - remark: string; - createdAt: Date; - updatedAt: Date; + readonly requestId: number; + readonly userId: number; + readonly title: string; + readonly type: RequestTypeEnum; + readonly description: string; + readonly status: RequestStatusEnum; + readonly requestContent: object; + readonly responseContent: object; + readonly remark: string; + readonly createdAt: Date; + readonly updatedAt: Date; } diff --git a/src/request/request.controller.ts b/src/request/request.controller.ts index 8d9e099..54949db 100644 --- a/src/request/request.controller.ts +++ b/src/request/request.controller.ts @@ -23,33 +23,41 @@ import { ResponseRequestDto } from './dto/response-request.dto'; @Controller('requests') @ApiTags('requests') export class RequestController { + // Create a logger instance for this controller to log events and errors. private readonly logger = new Logger(RequestController.name); + // Constructor for the RequestController class, injecting the RequestService. constructor(private readonly requestService: RequestService) {} @Post() - @ApiOperation({ summary: 'Create a request' }) - @ApiResponse({ status: HttpStatus.CREATED, type: ResponseRequestDto }) + @ApiOperation({ summary: 'Create a request' }) // Describes the api operation for Swagger. + @ApiResponse({ status: HttpStatus.CREATED, type: ResponseRequestDto }) // Describes the response for Swagger. async createRequest(@Res() res, @Body() createRequestDto: CreateRequestDto) { try { + // Log the initiation of request creation this.logger.log( `Creating a new request for user id #${createRequestDto.userId}`, ); + const request = await this.requestService.createRequest(createRequestDto); + // Log the successful creation of the request this.logger.log( `Successfully created a new request for user id #${request.userId}, request id ${request.requestId}`, ); + // Return a success response with the created request data return res .status(HttpStatus.CREATED) .json({ message: 'Request created successfully', data: request }); } catch (error) { + // Log the error if request creation fails this.logger.error( `Failed to create new request for user id #${createRequestDto.userId}`, error, ); + // Return an error response return res.status(HttpStatus.CREATED).json({ message: `Failed to create the request for user id #${createRequestDto.userId}`, }); @@ -57,12 +65,12 @@ export class RequestController { } @Get('') - @ApiOperation({ summary: 'Get all requests' }) + @ApiOperation({ summary: 'Get all requests' }) // Describes the api operation for Swagger. @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto, isArray: true, - }) + }) // Describes the response for Swagger. async getAllRequests(@Res() res, @Query() filter: RequestFilterDto) { try { this.logger.log(`Initiated fetching requests`); @@ -83,12 +91,12 @@ export class RequestController { } @Get('user/:userId') - @ApiOperation({ summary: 'Get all requests of a user' }) + @ApiOperation({ summary: 'Get all requests of a user' }) // Describes the api operation for Swagger. @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto, isArray: true, - }) + }) // Describes the response for Swagger. async getAllRequestsForUser( @Res() res, @Param('userId') userId: number, @@ -117,6 +125,7 @@ export class RequestController { `Failed to fetch user requests for user id #${userId}`, error, ); + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ message: `Failed to fetch user requests for user id #${userId}`, }); @@ -124,8 +133,8 @@ export class RequestController { } @Get(':requestId') - @ApiOperation({ summary: 'Get a request' }) - @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) + @ApiOperation({ summary: 'Get a request' }) // Describes the api operation for Swagger. + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) // Describes the response for Swagger. async getRequestById(@Res() res, @Param('requestId') requestId: number) { try { this.logger.log( @@ -155,8 +164,8 @@ export class RequestController { } @Patch('update/:requestId') - @ApiOperation({ summary: 'Update a request' }) - @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) + @ApiOperation({ summary: 'Update a request' }) // Describes the api operation for Swagger. + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) // Describes the response for Swagger. async updateRequestByRequestId( @Res() res, @Param('requestId') requestId: number, @@ -193,8 +202,8 @@ export class RequestController { } @Patch('update/status/:requestId') - @ApiOperation({ summary: 'Update a request status' }) - @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) + @ApiOperation({ summary: 'Update a request status' }) // Describes the api operation for Swagger. + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) // Describes the response for Swagger. async updateRequestStatus( @Res() res, @Param('requestId') requestId: number, diff --git a/src/request/request.module.ts b/src/request/request.module.ts index 83a1df1..91c477b 100644 --- a/src/request/request.module.ts +++ b/src/request/request.module.ts @@ -5,9 +5,16 @@ import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaModule } from 'src/prisma/prisma.module'; @Module({ + // Import the PrismaModule to make Prisma service available within this module imports: [PrismaModule], + + // Declare the RequestController as a controller for this module controllers: [RequestController], + + // Declare the RequestService and PrismaService as providers for this module providers: [RequestService, PrismaService], + + // Export the RequestService to make it available for other modules that import this module exports: [RequestService], }) export class RequestModule {} diff --git a/src/request/request.service.ts b/src/request/request.service.ts index 6e19fba..8646ce7 100644 --- a/src/request/request.service.ts +++ b/src/request/request.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; // Import your Prisma service import { CreateRequestDto, @@ -9,17 +9,16 @@ import { UpdateRequestDto } from './dto/update-request.dto'; @Injectable() export class RequestService { - constructor( - @Inject(forwardRef(() => PrismaService)) - private prisma: PrismaService, - ) {} + constructor(private prisma: PrismaService) {} + // Create a new request async createRequest(createRequestDto: CreateRequestDto) { return this.prisma.request.create({ data: createRequestDto, }); } + // Get all requests with optional filters and pagination async getAllRequests(filter: RequestFilterDto) { const { status, type, limit = 10, offset = 0, orderBy } = filter; return this.prisma.request.findMany({ @@ -35,6 +34,7 @@ export class RequestService { }); } + // Get all requests for a specific user with optional filters and pagination async getAllRequestsForUser(userId: number, filter: any) { const { status, type, limit = 10, offset = 0, orderBy } = filter; return this.prisma.request.findMany({ @@ -51,6 +51,7 @@ export class RequestService { }); } + // Get a request by its ID async getRequestById(requestId: number) { return this.prisma.request.findUnique({ where: { @@ -59,6 +60,7 @@ export class RequestService { }); } + // Update a request by its ID async updateRequestByRequestId( requestId: number, updateRequestDto: UpdateRequestDto, @@ -71,6 +73,7 @@ export class RequestService { }); } + // Update the status of a request by its ID async updateRequestStatus( requestId: number, updateRequestStatusDto: RequestStatusDto, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 2881a49..ad17a45 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -11,4 +11,6 @@ export const SWAGGER_CONSTANTS = { SWAGGER_AUTH_SECURITY_SCHEMA_JWT: 'jwt', }; -export enum SWAGGER_TAGS {} +export enum SWAGGER_TAGS { + REQUEST = 'request', +} From 8465b0e4489c7686027f38f49d350f26efc22320 Mon Sep 17 00:00:00 2001 From: Prashant Kumar Date: Thu, 21 Sep 2023 23:34:29 +0530 Subject: [PATCH 04/26] Added the Model for Settlement in Prisma Module --- .../20230921144709_settlement/migration.sql | 42 +++++++++++++++ prisma/migrations/migration_lock.toml | 3 ++ prisma/schema.prisma | 51 +++++++++++++++++-- src/app.module.ts | 2 + src/settlement/dto/create-settlement.dto.ts | 8 +++ src/settlement/dto/update-settlement.dto.ts | 4 ++ src/settlement/entities/settlement.entity.ts | 1 + src/settlement/settlement.controller.spec.ts | 20 ++++++++ src/settlement/settlement.controller.ts | 34 +++++++++++++ src/settlement/settlement.module.ts | 9 ++++ src/settlement/settlement.service.spec.ts | 18 +++++++ src/settlement/settlement.service.ts | 26 ++++++++++ 12 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 prisma/migrations/20230921144709_settlement/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 src/settlement/dto/create-settlement.dto.ts create mode 100644 src/settlement/dto/update-settlement.dto.ts create mode 100644 src/settlement/entities/settlement.entity.ts create mode 100644 src/settlement/settlement.controller.spec.ts create mode 100644 src/settlement/settlement.controller.ts create mode 100644 src/settlement/settlement.module.ts create mode 100644 src/settlement/settlement.service.spec.ts create mode 100644 src/settlement/settlement.service.ts diff --git a/prisma/migrations/20230921144709_settlement/migration.sql b/prisma/migrations/20230921144709_settlement/migration.sql new file mode 100644 index 0000000..ccf1e5e --- /dev/null +++ b/prisma/migrations/20230921144709_settlement/migration.sql @@ -0,0 +1,42 @@ +-- CreateEnum +CREATE TYPE "RequestTypeEnum" AS ENUM ('REFUND', 'CREDIT', 'INVOICE_REQUEST', 'SETTLEMENT'); + +-- CreateEnum +CREATE TYPE "RequestStatusEnum" AS ENUM ('PENDING', 'IN_PROGRESS', 'APPROVED', 'REJECTED'); + +-- CreateTable +CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + "email" TEXT NOT NULL, + "role" TEXT NOT NULL, + "username" TEXT NOT NULL, + "password" TEXT NOT NULL, + "profilePicture" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Request" ( + "requestId" SERIAL NOT NULL, + "userId" INTEGER NOT NULL, + "title" TEXT NOT NULL, + "type" "RequestTypeEnum" NOT NULL, + "description" TEXT NOT NULL, + "status" "RequestStatusEnum" NOT NULL, + "requestContent" JSONB, + "responseContent" JSONB, + "remark" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Request_pkey" PRIMARY KEY ("requestId") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- AddForeignKey +ALTER TABLE "Request" ADD CONSTRAINT "Request_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..fbffa92 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0703dab..1da3444 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -12,15 +12,30 @@ datasource db { // Dummy user model model User { - id Int @id @default(autoincrement()) - email String @unique + id Int @id @default(autoincrement()) + email String @unique role String username String password String profilePicture String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt requests Request[] + Settlement Settlement[] +} + +// Dummy Admin Model +model Admin { + id Int @id @default(autoincrement()) + email String @unique + role String + username String + password String + profilePicture String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + requests Request[] + Settlement Settlement[] } model Request { @@ -36,6 +51,24 @@ model Request { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) // Specify fields and references here + Admin Admin? @relation(fields: [adminId], references: [id]) + adminId Int? + Settlement Settlement[] +} + +model Settlement { + settlementId Int @id @default(autoincrement()) + requestId Int + userId Int + adminId Int + requestStatus SettlementStatusEnum + userResponseStatus UserResponseStatusEnum + transactionId Int? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id]) // Specify fields and references here + Admin Admin @relation(fields: [adminId], references: [id]) // Specify Admin field reference here + request Request @relation(fields: [requestId], references: [requestId]) } enum RequestTypeEnum { @@ -51,3 +84,13 @@ enum RequestStatusEnum { APPROVED REJECTED } + +enum SettlementStatusEnum { + APPROVED + REJECTED +} + +enum UserResponseStatusEnum { + PENDING + RECIEVED +} diff --git a/src/app.module.ts b/src/app.module.ts index f892894..fc9791c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,6 +4,7 @@ import { AppService } from './app.service'; import { PrismaModule } from './prisma/prisma.module'; import { ConfigModule } from '@nestjs/config'; import { RequestModule } from './request/request.module'; +import { SettlementModule } from './settlement/settlement.module'; @Module({ imports: [ @@ -13,6 +14,7 @@ import { RequestModule } from './request/request.module'; }), PrismaModule, RequestModule, + SettlementModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/settlement/dto/create-settlement.dto.ts b/src/settlement/dto/create-settlement.dto.ts new file mode 100644 index 0000000..bd7c032 --- /dev/null +++ b/src/settlement/dto/create-settlement.dto.ts @@ -0,0 +1,8 @@ + + +// createSettlementDto is used for creating the new settlement request +export class CreateSettlementDto { + +} + + diff --git a/src/settlement/dto/update-settlement.dto.ts b/src/settlement/dto/update-settlement.dto.ts new file mode 100644 index 0000000..64bdde7 --- /dev/null +++ b/src/settlement/dto/update-settlement.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateSettlementDto } from './create-settlement.dto'; + +export class UpdateSettlementDto extends PartialType(CreateSettlementDto) {} diff --git a/src/settlement/entities/settlement.entity.ts b/src/settlement/entities/settlement.entity.ts new file mode 100644 index 0000000..8091db6 --- /dev/null +++ b/src/settlement/entities/settlement.entity.ts @@ -0,0 +1 @@ +export class Settlement {} diff --git a/src/settlement/settlement.controller.spec.ts b/src/settlement/settlement.controller.spec.ts new file mode 100644 index 0000000..93dc158 --- /dev/null +++ b/src/settlement/settlement.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SettlementController } from './settlement.controller'; +import { SettlementService } from './settlement.service'; + +describe('SettlementController', () => { + let controller: SettlementController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [SettlementController], + providers: [SettlementService], + }).compile(); + + controller = module.get(SettlementController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/settlement/settlement.controller.ts b/src/settlement/settlement.controller.ts new file mode 100644 index 0000000..2db5477 --- /dev/null +++ b/src/settlement/settlement.controller.ts @@ -0,0 +1,34 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { SettlementService } from './settlement.service'; +import { CreateSettlementDto } from './dto/create-settlement.dto'; +import { UpdateSettlementDto } from './dto/update-settlement.dto'; + +@Controller('settlement') +export class SettlementController { + constructor(private readonly settlementService: SettlementService) {} + + @Post() + create(@Body() createSettlementDto: CreateSettlementDto) { + return this.settlementService.create(createSettlementDto); + } + + @Get() + findAll() { + return this.settlementService.findAll(); + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.settlementService.findOne(+id); + } + + @Patch(':id') + update(@Param('id') id: string, @Body() updateSettlementDto: UpdateSettlementDto) { + return this.settlementService.update(+id, updateSettlementDto); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.settlementService.remove(+id); + } +} diff --git a/src/settlement/settlement.module.ts b/src/settlement/settlement.module.ts new file mode 100644 index 0000000..46d502c --- /dev/null +++ b/src/settlement/settlement.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { SettlementService } from './settlement.service'; +import { SettlementController } from './settlement.controller'; + +@Module({ + controllers: [SettlementController], + providers: [SettlementService] +}) +export class SettlementModule {} diff --git a/src/settlement/settlement.service.spec.ts b/src/settlement/settlement.service.spec.ts new file mode 100644 index 0000000..ed6e549 --- /dev/null +++ b/src/settlement/settlement.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SettlementService } from './settlement.service'; + +describe('SettlementService', () => { + let service: SettlementService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [SettlementService], + }).compile(); + + service = module.get(SettlementService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/settlement/settlement.service.ts b/src/settlement/settlement.service.ts new file mode 100644 index 0000000..61f6eba --- /dev/null +++ b/src/settlement/settlement.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { CreateSettlementDto } from './dto/create-settlement.dto'; +import { UpdateSettlementDto } from './dto/update-settlement.dto'; + +@Injectable() +export class SettlementService { + create(createSettlementDto: CreateSettlementDto) { + return 'This action adds a new settlement'; + } + + findAll() { + return `This action returns all settlement`; + } + + findOne(id: number) { + return `This action returns a #${id} settlement`; + } + + update(id: number, updateSettlementDto: UpdateSettlementDto) { + return `This action updates a #${id} settlement`; + } + + remove(id: number) { + return `This action removes a #${id} settlement`; + } +} From 70cc0bdfd84830ce1c05acc57dd2e33d2cae3f4a Mon Sep 17 00:00:00 2001 From: Prashant Kumar Date: Fri, 22 Sep 2023 19:50:22 +0530 Subject: [PATCH 05/26] created the API to create new settlement request, get all the settlement, get settlement by request ID, get settlement by the userId --- .../migration.sql | 30 +++ prisma/schema.prisma | 43 ++--- src/settlement/dto/create-settlement.dto.ts | 101 +++++++++- src/settlement/dto/response-settlement.dto.ts | 15 ++ src/settlement/enum/settlement.enum.ts | 10 + src/settlement/settlement.controller.ts | 180 ++++++++++++++++-- src/settlement/settlement.module.ts | 13 +- src/settlement/settlement.service.ts | 85 +++++++-- 8 files changed, 416 insertions(+), 61 deletions(-) rename prisma/migrations/{20230921144709_settlement => 20230922134527_y}/migration.sql (53%) create mode 100644 src/settlement/dto/response-settlement.dto.ts create mode 100644 src/settlement/enum/settlement.enum.ts diff --git a/prisma/migrations/20230921144709_settlement/migration.sql b/prisma/migrations/20230922134527_y/migration.sql similarity index 53% rename from prisma/migrations/20230921144709_settlement/migration.sql rename to prisma/migrations/20230922134527_y/migration.sql index ccf1e5e..faefe8a 100644 --- a/prisma/migrations/20230921144709_settlement/migration.sql +++ b/prisma/migrations/20230922134527_y/migration.sql @@ -4,6 +4,12 @@ CREATE TYPE "RequestTypeEnum" AS ENUM ('REFUND', 'CREDIT', 'INVOICE_REQUEST', 'S -- CreateEnum CREATE TYPE "RequestStatusEnum" AS ENUM ('PENDING', 'IN_PROGRESS', 'APPROVED', 'REJECTED'); +-- CreateEnum +CREATE TYPE "SettlementStatusEnum" AS ENUM ('APPROVED', 'REJECTED'); + +-- CreateEnum +CREATE TYPE "thirdPartyResponseStatusEnum" AS ENUM ('PENDING', 'VERIFIED'); + -- CreateTable CREATE TABLE "User" ( "id" SERIAL NOT NULL, @@ -35,8 +41,32 @@ CREATE TABLE "Request" ( CONSTRAINT "Request_pkey" PRIMARY KEY ("requestId") ); +-- CreateTable +CREATE TABLE "Settlement" ( + "settlementId" SERIAL NOT NULL, + "requestId" INTEGER NOT NULL, + "userId" INTEGER NOT NULL, + "requestStatus" "SettlementStatusEnum" NOT NULL, + "thirdPartyResponseStatus" "thirdPartyResponseStatusEnum" NOT NULL, + "transactionId" INTEGER, + "content" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Settlement_pkey" PRIMARY KEY ("settlementId") +); + -- CreateIndex CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); +-- CreateIndex +CREATE UNIQUE INDEX "Settlement_requestId_key" ON "Settlement"("requestId"); + -- AddForeignKey ALTER TABLE "Request" ADD CONSTRAINT "Request_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Settlement" ADD CONSTRAINT "Settlement_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Settlement" ADD CONSTRAINT "Settlement_requestId_fkey" FOREIGN KEY ("requestId") REFERENCES "Request"("requestId") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1da3444..29c65c3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -24,20 +24,6 @@ model User { Settlement Settlement[] } -// Dummy Admin Model -model Admin { - id Int @id @default(autoincrement()) - email String @unique - role String - username String - password String - profilePicture String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - requests Request[] - Settlement Settlement[] -} - model Request { requestId Int @id @default(autoincrement()) userId Int @@ -51,24 +37,21 @@ model Request { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) // Specify fields and references here - Admin Admin? @relation(fields: [adminId], references: [id]) - adminId Int? Settlement Settlement[] } model Settlement { - settlementId Int @id @default(autoincrement()) - requestId Int - userId Int - adminId Int - requestStatus SettlementStatusEnum - userResponseStatus UserResponseStatusEnum - transactionId Int? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - user User @relation(fields: [userId], references: [id]) // Specify fields and references here - Admin Admin @relation(fields: [adminId], references: [id]) // Specify Admin field reference here - request Request @relation(fields: [requestId], references: [requestId]) + settlementId Int @id @default(autoincrement()) + requestId Int @unique + userId Int + requestStatus SettlementStatusEnum + thirdPartyResponseStatus thirdPartyResponseStatusEnum + transactionId Int? + content Json? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id]) // Specify fields and references here + request Request @relation(fields: [requestId], references: [requestId]) } enum RequestTypeEnum { @@ -90,7 +73,7 @@ enum SettlementStatusEnum { REJECTED } -enum UserResponseStatusEnum { +enum thirdPartyResponseStatusEnum { PENDING - RECIEVED + VERIFIED } diff --git a/src/settlement/dto/create-settlement.dto.ts b/src/settlement/dto/create-settlement.dto.ts index bd7c032..307fee4 100644 --- a/src/settlement/dto/create-settlement.dto.ts +++ b/src/settlement/dto/create-settlement.dto.ts @@ -1,8 +1,107 @@ - +import { SettlementStatusEnum, thirdPartyResponseStatusEnum } from '@prisma/client'; +import { Transform, TransformFnParams } from 'class-transformer'; +import { + IsDate, + IsEnum, + IsInt, + IsNotEmpty, + IsNumber, + IsOptional, + IsString, +} from 'class-validator'; // createSettlementDto is used for creating the new settlement request export class CreateSettlementDto { + // Request ID associated with the request + @IsNotEmpty() + @IsInt() + requestId: number; + + // User ID associated with the request. + @IsNotEmpty() + @IsInt() + userId: number; + + // Status of the request, should be one of the valid SettlementStatusEnum values. + @IsNotEmpty() + @IsEnum(SettlementStatusEnum) + requestStatus: SettlementStatusEnum; + + // Status of the request, should be one of the valid UserResponseStatusEnum values. + @IsNotEmpty() + @IsEnum(thirdPartyResponseStatusEnum) + thirdPartyResponseStatus: thirdPartyResponseStatusEnum; + // Optional content of the request for the transaction ID. + @IsOptional() + @IsNumber() + transactionID: number; + + // Optional content of the request in JSON format. + @IsOptional() + // Use the @Transform decorator to apply a custom transformation to a property. + @Transform(({ value }: TransformFnParams) => { + try { + // Try to parse the input value as JSON to convert it into a JavaScript object. + return JSON.parse(value); + } catch (error) { + // If parsing fails (e.g., if value is not a valid JSON string), return the original value. + return value; + } + }) + content?: object; + + // Optional creation date of the request. + @IsDate() + @IsOptional() + createdAt?: Date; + + // Optional update date of the request. + @IsDate() + @IsOptional() + updatedAt?: Date; } +// SettlementStatusDto is used for updating the status of a request. +export class SettlementStatusDto { + // New status for the request, should be one of the valid SettlementStatusEnum values. + @IsNotEmpty() + @IsEnum(SettlementStatusEnum) + requestStatus: SettlementStatusEnum; +} +// SettlementStatusDto is used for updating the status of a request. +export class thirdPartyResponseStatusEnumDto { + // New status for the request, should be one of the valid UserResponseStatusEnum values. + @IsNotEmpty() + @IsEnum(thirdPartyResponseStatusEnum) + thirdPartyResponseStatus: thirdPartyResponseStatusEnum; +} + +// SettlementFilterDto is used for filtering and paginating requests. +export class SettlementFilterDto { + // Optional settlement status filter, validated that it's valid SettlementStatusEnum value + @IsOptional() + @IsEnum(SettlementStatusEnum, { each: true }) + requestStatus?: SettlementStatusEnum; + + // Optional third party status filter, validate that it's valid thirdPartyResponseStatusEnum value + @IsOptional() + @IsEnum(thirdPartyResponseStatusEnum, { each: true }) + thirdPartyResponseStatus?: thirdPartyResponseStatusEnum; + + // Optional limit for pagination, validate that it's an integer. + @IsOptional() + @IsInt() + limit?: number; + + // Optional offset for pagination, validate that it's an integer. + @IsOptional() + @IsInt() + offset?: number; + + // Optional field to specify the order of results, validate that it's a string. + @IsOptional() + @IsString() + orderBy?: string; +} diff --git a/src/settlement/dto/response-settlement.dto.ts b/src/settlement/dto/response-settlement.dto.ts new file mode 100644 index 0000000..109c80a --- /dev/null +++ b/src/settlement/dto/response-settlement.dto.ts @@ -0,0 +1,15 @@ +import { + SettlementStatusEnum, + thirdPartyResponseStatusEnum, +} from '../enum/settlement.enum'; + +export class ResponseSettlementDto { + readonly SettlementId: number; + readonly requestId: number; + readonly userId: number; + readonly type: thirdPartyResponseStatusEnum; + readonly transactionId: number; + readonly status: SettlementStatusEnum; + readonly createdAt: Date; + readonly updatedAt: Date; +} diff --git a/src/settlement/enum/settlement.enum.ts b/src/settlement/enum/settlement.enum.ts new file mode 100644 index 0000000..753a831 --- /dev/null +++ b/src/settlement/enum/settlement.enum.ts @@ -0,0 +1,10 @@ + +export enum SettlementStatusEnum { + APPROVED, + REJECTED +} + +export enum thirdPartyResponseStatusEnum { + PENDING, + VERIFIED +} \ No newline at end of file diff --git a/src/settlement/settlement.controller.ts b/src/settlement/settlement.controller.ts index 2db5477..d0aa811 100644 --- a/src/settlement/settlement.controller.ts +++ b/src/settlement/settlement.controller.ts @@ -1,34 +1,178 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, + Query, + Logger, + HttpStatus, + Res, +} from '@nestjs/common'; import { SettlementService } from './settlement.service'; -import { CreateSettlementDto } from './dto/create-settlement.dto'; +import { + CreateSettlementDto, + SettlementFilterDto, +} from './dto/create-settlement.dto'; import { UpdateSettlementDto } from './dto/update-settlement.dto'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ResponseSettlementDto } from './dto/response-settlement.dto'; @Controller('settlement') +@ApiTags('settlement') export class SettlementController { - constructor(private readonly settlementService: SettlementService) {} + // Create a logger instance for this controller to log events and errors. + private readonly logger = new Logger(SettlementController.name); + // Constructor for the SettlementController class, injecting the settlement service + constructor(private settlementService: SettlementService) {} + + // API to create a new settlement request. @Post() - create(@Body() createSettlementDto: CreateSettlementDto) { - return this.settlementService.create(createSettlementDto); - } + @ApiOperation({ summary: 'Create a request' }) // Describes the api operation for Swagger. + @ApiResponse({ status: HttpStatus.CREATED, type: ResponseSettlementDto }) // Describes the response for Swagger. + async createSettlement( + @Res() res, + @Body() createSettlementDto: CreateSettlementDto, + ) { + try { + // Log the initiation of settlement creation + this.logger.log( + `Creating a new settlement for user id #${createSettlementDto.userId}`, + ); + + const settlement = await this.settlementService.createSettlement( + createSettlementDto, + ); + + // Log the successful creation of the settlement + this.logger.log( + `Successfully created a new settlement for user id #${settlement.userId}, request id ${settlement.requestId}`, + ); - @Get() - findAll() { - return this.settlementService.findAll(); + // Return a success response with the created request data + return res.status(HttpStatus.CREATED).json({ + message: 'Settlement request created successfully', + data: settlement, + }); + } catch (error) { + // Log the error if request creation fails + this.logger.error( + `Failed to create new request for user id #${createSettlementDto.userId}`, + error, + ); + + // Return an error response + return res.status(HttpStatus.CREATED).json({ + message: `Failed to create the request for user id #${createSettlementDto.userId}`, + }); + } } - @Get(':id') - findOne(@Param('id') id: string) { - return this.settlementService.findOne(+id); + // API to get all the settlements + @Get('') + @ApiOperation({ summary: 'Get all settlements' }) // Api oepration for swagger. + @ApiResponse({ + status: HttpStatus.OK, + type: ResponseSettlementDto, + isArray: true, + }) // Describe response for swagger + async getAllSettlements(@Res() res, @Query() filter: SettlementFilterDto) { + try { + this.logger.log(`Initiated fetching Settlements`); + + const settlements = await this.settlementService.getAllSettlements( + filter, + ); + + this.logger.log(`Successfully fetched settlements`); + + return res.status(HttpStatus.OK).json({ + message: 'Successfully fetched settlements', + data: settlements, + }); + } catch (error) { + this.logger.error(`Failed to fetch settlements`, error); + return res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ message: 'Failed to fetch settlements' }); + } } - @Patch(':id') - update(@Param('id') id: string, @Body() updateSettlementDto: UpdateSettlementDto) { - return this.settlementService.update(+id, updateSettlementDto); + // API to get all the settlements by the userId. + @Get('user/:userId') + @ApiOperation({ summary: 'Get all the settlement of a user.' }) + @ApiResponse({ + status: HttpStatus.OK, + type: ResponseSettlementDto, + isArray: true, + }) // Describes the response for Swagger. + async getUserSettlementsById( + @Param('userId') userId: number, + @Res() res, + @Query() filter: SettlementFilterDto, + ) { + try { + this.logger.log( + `Initiated fetching user settlements for user id #${userId}`, + ); + + const settlements = await this.settlementService.getAllSettlementForUser( + +userId, + filter, + ); + + this.logger.log( + `Successfully fetched user settlements for user id #${userId}`, + ); + + return res.status(HttpStatus.OK).json({ + message: `Successfully fetched user settlements for user id #${userId}`, + data: settlements, + }); + } catch (error) { + this.logger.error( + `Failed to fetch user settlements for user id #${userId}`, + error, + ); + + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: `Failed to fetch user settlements for user id #${userId}`, + }); + } } - @Delete(':id') - remove(@Param('id') id: string) { - return this.settlementService.remove(+id); + // Get settlement by request ID + @Get(':requestId') + @ApiOperation({ summary: 'Get a settlement by request ID' }) // Describes the api operation for Swagger. + @ApiResponse({ status: HttpStatus.OK, type: ResponseSettlementDto }) // Describes the response for Swagger. + async getSettlementById(@Res() res, @Param('requestId') requestId: number) { + try { + this.logger.log( + `Initiated fetching settlement for request id #${requestId}`, + ); + + const request = await this.settlementService.getsettlementById(+requestId); + + this.logger.log( + `Successfully fetched settlement for request id #${requestId}`, + ); + + return res.status(HttpStatus.OK).json({ + message: `Successfully fetched settlement for request id #${requestId}`, + data: request, + }); + } catch (error) { + this.logger.error( + `Failed to fetch settlement for request id #${requestId}`, + error, + ); + + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: `Failed to fetch settlement for request id #${requestId}`, + }); + } } } diff --git a/src/settlement/settlement.module.ts b/src/settlement/settlement.module.ts index 46d502c..c161d23 100644 --- a/src/settlement/settlement.module.ts +++ b/src/settlement/settlement.module.ts @@ -1,9 +1,20 @@ import { Module } from '@nestjs/common'; import { SettlementService } from './settlement.service'; import { SettlementController } from './settlement.controller'; +import { PrismaModule } from '../prisma/prisma.module'; +import { PrismaService } from '../prisma/prisma.service'; @Module({ + // Import the PrismaModule to make Prisma service available within this module + imports: [PrismaModule], + + // Declare the SettlemetController as a controller for this module controllers: [SettlementController], - providers: [SettlementService] + + // Declare the SettlementService and PrismaService as providers for this module + providers: [SettlementService, PrismaService], + + // Export the RequestService to make it available for other modules that import this module + exports: [SettlementService], }) export class SettlementModule {} diff --git a/src/settlement/settlement.service.ts b/src/settlement/settlement.service.ts index 61f6eba..273f932 100644 --- a/src/settlement/settlement.service.ts +++ b/src/settlement/settlement.service.ts @@ -1,26 +1,89 @@ import { Injectable } from '@nestjs/common'; -import { CreateSettlementDto } from './dto/create-settlement.dto'; +import { + CreateSettlementDto, + SettlementFilterDto, + SettlementStatusDto, + thirdPartyResponseStatusEnumDto, +} from './dto/create-settlement.dto'; +import { PrismaService } from '../prisma/prisma.service'; import { UpdateSettlementDto } from './dto/update-settlement.dto'; @Injectable() export class SettlementService { - create(createSettlementDto: CreateSettlementDto) { - return 'This action adds a new settlement'; + constructor(private prisma: PrismaService) {} + + // create a new request for the settlement + async createSettlement(createSettlementDto: CreateSettlementDto) { + return this.prisma.settlement.create({ + data: createSettlementDto, + }); } - findAll() { - return `This action returns all settlement`; + // get all settlements with optional filter and pagination + async getAllSettlements(filter: SettlementFilterDto) { + const { + thirdPartyResponseStatus, + requestStatus, + limit = 10, + offset = 0, + orderBy, + } = filter; + return this.prisma.settlement.findMany({ + where: { + requestStatus: requestStatus ?? undefined, // Optional status filter + thirdPartyResponseStatus: thirdPartyResponseStatus ?? undefined, // Optional response filter + }, + orderBy: { + [orderBy || 'createdAt']: 'asc', // Default sorting by createdAt + }, + skip: offset, + take: limit, + }); } - findOne(id: number) { - return `This action returns a #${id} settlement`; + // Get settlement for a specific user using the userId + async getAllSettlementForUser(userId: number, filter: any) { + const { + thirdPartyResponseStatus, + requestStatus, + limit = 10, + offset = 0, + orderBy, + } = filter; + return this.prisma.settlement.findMany({ + where: { + userId: userId, + requestStatus: requestStatus ?? undefined, // Optional status filter + thirdPartyResponseStatus: thirdPartyResponseStatus ?? undefined, // Optional thirdPartyResponseStatus filter + }, + orderBy: { + [orderBy || 'createdAt']: 'asc', // Default sorting by createdAt + }, + skip: offset, + take: limit, + }); } - update(id: number, updateSettlementDto: UpdateSettlementDto) { - return `This action updates a #${id} settlement`; + // Get a settlement by its requestID + async getsettlementById(requestId: number) { + return this.prisma.settlement.findMany({ + where: { + requestId: requestId, + }, + }); } - remove(id: number) { - return `This action removes a #${id} settlement`; + // Update a settlement by its requestId + async updateSettlementById( + requestId: number, + updateSettlementDto: UpdateSettlementDto, + ) { + return this.prisma.settlement.updateMany({ + where: { + requestId: requestId, + }, + data: updateSettlementDto, + }); } + } From 9a75b9827c291961433252ff540159d9f99b8b6f Mon Sep 17 00:00:00 2001 From: Prashant Kumar Date: Sat, 23 Sep 2023 01:07:00 +0530 Subject: [PATCH 06/26] Added the API to update the status of a settlement request --- src/settlement/dto/response-settlement.dto.ts | 2 +- src/settlement/settlement.controller.ts | 45 ++++++++++++++++++- src/settlement/settlement.service.ts | 11 +++-- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/settlement/dto/response-settlement.dto.ts b/src/settlement/dto/response-settlement.dto.ts index 109c80a..f4fd965 100644 --- a/src/settlement/dto/response-settlement.dto.ts +++ b/src/settlement/dto/response-settlement.dto.ts @@ -9,7 +9,7 @@ export class ResponseSettlementDto { readonly userId: number; readonly type: thirdPartyResponseStatusEnum; readonly transactionId: number; - readonly status: SettlementStatusEnum; + readonly requestStatus: SettlementStatusEnum; readonly createdAt: Date; readonly updatedAt: Date; } diff --git a/src/settlement/settlement.controller.ts b/src/settlement/settlement.controller.ts index d0aa811..03aee9e 100644 --- a/src/settlement/settlement.controller.ts +++ b/src/settlement/settlement.controller.ts @@ -154,7 +154,9 @@ export class SettlementController { `Initiated fetching settlement for request id #${requestId}`, ); - const request = await this.settlementService.getsettlementById(+requestId); + const settlement = await this.settlementService.getsettlementById( + +requestId, + ); this.logger.log( `Successfully fetched settlement for request id #${requestId}`, @@ -162,7 +164,7 @@ export class SettlementController { return res.status(HttpStatus.OK).json({ message: `Successfully fetched settlement for request id #${requestId}`, - data: request, + data: settlement, }); } catch (error) { this.logger.error( @@ -175,4 +177,43 @@ export class SettlementController { }); } } + + // update the settlement by the requestID + @Patch(':requestId') + @ApiOperation({ summary: 'Update a settlement request' }) // Describes the api operation for Swagger. + @ApiResponse({ status: HttpStatus.OK, type: ResponseSettlementDto }) // Describes the response for Swagger. + async updateSettlementByRequestId( + @Res() res, + @Param('requestId') requestId: number, + @Body() updateSettlementDto: UpdateSettlementDto, + ) { + try { + this.logger.log( + `Initiated updating settlement request for request id #${requestId}`, + ); + + const updatedSettlement = + await this.settlementService.updateSettlementByRequestId( + +requestId, + updateSettlementDto, + ); + + this.logger.log( + `Successfully updated settlement request for request id #${requestId}`, + ); + return res.status(HttpStatus.OK).json({ + message: `Successfully updated settlement request for request id #${requestId}`, + data: updatedSettlement, + }); + } catch (error) { + this.logger.error( + `Failed to update settlement request for request id #${requestId}`, + error, + ); + + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: `Failed to update settlement request for request id #${requestId}`, + }); + } + } } diff --git a/src/settlement/settlement.service.ts b/src/settlement/settlement.service.ts index 273f932..86331d6 100644 --- a/src/settlement/settlement.service.ts +++ b/src/settlement/settlement.service.ts @@ -64,7 +64,7 @@ export class SettlementService { }); } - // Get a settlement by its requestID + // Get a settlement by its request ID async getsettlementById(requestId: number) { return this.prisma.settlement.findMany({ where: { @@ -73,17 +73,16 @@ export class SettlementService { }); } - // Update a settlement by its requestId - async updateSettlementById( + // Update the requested settlement status by its ID + async updateSettlementByRequestId( requestId: number, - updateSettlementDto: UpdateSettlementDto, + updateSettlementStatusDto: UpdateSettlementDto, ) { return this.prisma.settlement.updateMany({ where: { requestId: requestId, }, - data: updateSettlementDto, + data: updateSettlementStatusDto, }); } - } From da17c3525ba1dce3311ad41835b93b281b9899f4 Mon Sep 17 00:00:00 2001 From: Prashant Kumar Date: Mon, 25 Sep 2023 11:16:20 +0530 Subject: [PATCH 07/26] Removing unnecessary modules and files. --- .../migrations/20230922134527_y/migration.sql | 72 ----- prisma/migrations/migration_lock.toml | 3 - src/app.module.ts | 2 - src/request/dto/create-request.dto.ts | 117 -------- src/request/dto/response-request.dto.ts | 15 -- src/request/dto/update-request.dto.ts | 4 - src/request/entities/request.entity.ts | 1 - src/request/enum/request.enum.ts | 13 - src/request/request.controller.ts | 249 ------------------ src/request/request.module.ts | 20 -- src/request/request.service.ts | 88 ------- 11 files changed, 584 deletions(-) delete mode 100644 prisma/migrations/20230922134527_y/migration.sql delete mode 100644 prisma/migrations/migration_lock.toml delete mode 100644 src/request/dto/create-request.dto.ts delete mode 100644 src/request/dto/response-request.dto.ts delete mode 100644 src/request/dto/update-request.dto.ts delete mode 100644 src/request/entities/request.entity.ts delete mode 100644 src/request/enum/request.enum.ts delete mode 100644 src/request/request.controller.ts delete mode 100644 src/request/request.module.ts delete mode 100644 src/request/request.service.ts diff --git a/prisma/migrations/20230922134527_y/migration.sql b/prisma/migrations/20230922134527_y/migration.sql deleted file mode 100644 index faefe8a..0000000 --- a/prisma/migrations/20230922134527_y/migration.sql +++ /dev/null @@ -1,72 +0,0 @@ --- CreateEnum -CREATE TYPE "RequestTypeEnum" AS ENUM ('REFUND', 'CREDIT', 'INVOICE_REQUEST', 'SETTLEMENT'); - --- CreateEnum -CREATE TYPE "RequestStatusEnum" AS ENUM ('PENDING', 'IN_PROGRESS', 'APPROVED', 'REJECTED'); - --- CreateEnum -CREATE TYPE "SettlementStatusEnum" AS ENUM ('APPROVED', 'REJECTED'); - --- CreateEnum -CREATE TYPE "thirdPartyResponseStatusEnum" AS ENUM ('PENDING', 'VERIFIED'); - --- CreateTable -CREATE TABLE "User" ( - "id" SERIAL NOT NULL, - "email" TEXT NOT NULL, - "role" TEXT NOT NULL, - "username" TEXT NOT NULL, - "password" TEXT NOT NULL, - "profilePicture" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "User_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Request" ( - "requestId" SERIAL NOT NULL, - "userId" INTEGER NOT NULL, - "title" TEXT NOT NULL, - "type" "RequestTypeEnum" NOT NULL, - "description" TEXT NOT NULL, - "status" "RequestStatusEnum" NOT NULL, - "requestContent" JSONB, - "responseContent" JSONB, - "remark" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Request_pkey" PRIMARY KEY ("requestId") -); - --- CreateTable -CREATE TABLE "Settlement" ( - "settlementId" SERIAL NOT NULL, - "requestId" INTEGER NOT NULL, - "userId" INTEGER NOT NULL, - "requestStatus" "SettlementStatusEnum" NOT NULL, - "thirdPartyResponseStatus" "thirdPartyResponseStatusEnum" NOT NULL, - "transactionId" INTEGER, - "content" JSONB, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Settlement_pkey" PRIMARY KEY ("settlementId") -); - --- CreateIndex -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); - --- CreateIndex -CREATE UNIQUE INDEX "Settlement_requestId_key" ON "Settlement"("requestId"); - --- AddForeignKey -ALTER TABLE "Request" ADD CONSTRAINT "Request_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Settlement" ADD CONSTRAINT "Settlement_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Settlement" ADD CONSTRAINT "Settlement_requestId_fkey" FOREIGN KEY ("requestId") REFERENCES "Request"("requestId") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml deleted file mode 100644 index fbffa92..0000000 --- a/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "postgresql" \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index fc9791c..9e0663c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,7 +3,6 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { PrismaModule } from './prisma/prisma.module'; import { ConfigModule } from '@nestjs/config'; -import { RequestModule } from './request/request.module'; import { SettlementModule } from './settlement/settlement.module'; @Module({ @@ -13,7 +12,6 @@ import { SettlementModule } from './settlement/settlement.module'; isGlobal: true, // Make the configuration available globally }), PrismaModule, - RequestModule, SettlementModule, ], controllers: [AppController], diff --git a/src/request/dto/create-request.dto.ts b/src/request/dto/create-request.dto.ts deleted file mode 100644 index 5c44f08..0000000 --- a/src/request/dto/create-request.dto.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { RequestStatusEnum, RequestTypeEnum } from '@prisma/client'; -import { Transform, TransformFnParams } from 'class-transformer'; -import { - IsDate, - IsEnum, - IsInt, - IsNotEmpty, - IsOptional, - IsString, -} from 'class-validator'; - -// CreateRequestDto is used for creating a new request. -export class CreateRequestDto { - // User ID associated with the request. - @IsNotEmpty() - @IsInt() - userId: number; - - // Title of the request. - @IsNotEmpty() - @IsString() - title: string; - - // Status of the request, should be one of the valid RequestStatusEnum values. - @IsNotEmpty() - @IsEnum(RequestStatusEnum) - status: RequestStatusEnum; - - // Description of the request. - @IsNotEmpty() - @IsString() - description: string; - - // Type of the request, should be one of the valid RequestTypeEnum values. - @IsNotEmpty() - @IsEnum(RequestTypeEnum) - type: RequestTypeEnum; - - // Optional content of the request in JSON format. - @IsOptional() - // Use the @Transform decorator to apply a custom transformation to a property. - @Transform(({ value }: TransformFnParams) => { - try { - // Try to parse the input value as JSON to convert it into a JavaScript object. - return JSON.parse(value); - } catch (error) { - // If parsing fails (e.g., if value is not a valid JSON string), return the original value. - return value; - } - }) - requestContent?: object; - - // Optional content of the response in JSON format. - @IsOptional() - // Use the @Transform decorator to apply a custom transformation to a property. - @Transform(({ value }: TransformFnParams) => { - try { - // Try to parse the input value as JSON to convert it into a JavaScript object. - return JSON.parse(value); - } catch (error) { - // If parsing fails (e.g., if value is not a valid JSON string), return the original value. - return value; - } - }) - responseContent?: object; - - // Optional remark or additional information about the request. - @IsOptional() - @IsString() - remark?: string; - - // Optional creation date of the request. - @IsDate() - @IsOptional() - createdAt?: Date; - - // Optional update date of the request. - @IsDate() - @IsOptional() - updatedAt?: Date; -} - -// RequestStatusDto is used for updating the status of a request. -export class RequestStatusDto { - // New status for the request, should be one of the valid RequestStatusEnum values. - @IsNotEmpty() - @IsEnum(RequestStatusEnum) - status: RequestStatusEnum; -} - -// RequestFilterDto is used for filtering and paginating requests. -export class RequestFilterDto { - // Optional status filter, validate that it's a valid enum value. - @IsOptional() - @IsEnum(RequestStatusEnum, { each: true }) - status?: RequestStatusEnum; - - // Optional limit for pagination, validate that it's an integer. - @IsOptional() - @IsInt() - limit?: number; - - // Optional offset for pagination, validate that it's an integer. - @IsOptional() - @IsInt() - offset?: number; - - // Optional field to specify the order of results, validate that it's a string. - @IsOptional() - @IsString() - orderBy?: string; - - // Optional request type filter, validate that it's a valid enum value. - @IsOptional() - @IsEnum(RequestTypeEnum, { each: true }) - type?: RequestTypeEnum; -} diff --git a/src/request/dto/response-request.dto.ts b/src/request/dto/response-request.dto.ts deleted file mode 100644 index f614f35..0000000 --- a/src/request/dto/response-request.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { RequestStatusEnum, RequestTypeEnum } from '../enum/request.enum'; - -export class ResponseRequestDto { - readonly requestId: number; - readonly userId: number; - readonly title: string; - readonly type: RequestTypeEnum; - readonly description: string; - readonly status: RequestStatusEnum; - readonly requestContent: object; - readonly responseContent: object; - readonly remark: string; - readonly createdAt: Date; - readonly updatedAt: Date; -} diff --git a/src/request/dto/update-request.dto.ts b/src/request/dto/update-request.dto.ts deleted file mode 100644 index bac9f9f..0000000 --- a/src/request/dto/update-request.dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { PartialType } from '@nestjs/mapped-types'; -import { CreateRequestDto } from './create-request.dto'; - -export class UpdateRequestDto extends PartialType(CreateRequestDto) {} diff --git a/src/request/entities/request.entity.ts b/src/request/entities/request.entity.ts deleted file mode 100644 index d5e892a..0000000 --- a/src/request/entities/request.entity.ts +++ /dev/null @@ -1 +0,0 @@ -export class Request {} diff --git a/src/request/enum/request.enum.ts b/src/request/enum/request.enum.ts deleted file mode 100644 index 884355b..0000000 --- a/src/request/enum/request.enum.ts +++ /dev/null @@ -1,13 +0,0 @@ -export enum RequestTypeEnum { - REFUND, - CREDIT, - INVOICE_REQUEST, - SETTLEMENT, -} - -export enum RequestStatusEnum { - PENDING, - IN_PROGRESS, - APPROVED, - REJECTED, -} diff --git a/src/request/request.controller.ts b/src/request/request.controller.ts deleted file mode 100644 index 54949db..0000000 --- a/src/request/request.controller.ts +++ /dev/null @@ -1,249 +0,0 @@ -import { - Controller, - Post, - Get, - Body, - Param, - Patch, - Query, - HttpStatus, - Logger, - Res, -} from '@nestjs/common'; -import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { RequestService } from './request.service'; -import { - CreateRequestDto, - RequestFilterDto, - RequestStatusDto, -} from './dto/create-request.dto'; -import { UpdateRequestDto } from './dto/update-request.dto'; -import { ResponseRequestDto } from './dto/response-request.dto'; - -@Controller('requests') -@ApiTags('requests') -export class RequestController { - // Create a logger instance for this controller to log events and errors. - private readonly logger = new Logger(RequestController.name); - - // Constructor for the RequestController class, injecting the RequestService. - constructor(private readonly requestService: RequestService) {} - - @Post() - @ApiOperation({ summary: 'Create a request' }) // Describes the api operation for Swagger. - @ApiResponse({ status: HttpStatus.CREATED, type: ResponseRequestDto }) // Describes the response for Swagger. - async createRequest(@Res() res, @Body() createRequestDto: CreateRequestDto) { - try { - // Log the initiation of request creation - this.logger.log( - `Creating a new request for user id #${createRequestDto.userId}`, - ); - - const request = await this.requestService.createRequest(createRequestDto); - - // Log the successful creation of the request - this.logger.log( - `Successfully created a new request for user id #${request.userId}, request id ${request.requestId}`, - ); - - // Return a success response with the created request data - return res - .status(HttpStatus.CREATED) - .json({ message: 'Request created successfully', data: request }); - } catch (error) { - // Log the error if request creation fails - this.logger.error( - `Failed to create new request for user id #${createRequestDto.userId}`, - error, - ); - - // Return an error response - return res.status(HttpStatus.CREATED).json({ - message: `Failed to create the request for user id #${createRequestDto.userId}`, - }); - } - } - - @Get('') - @ApiOperation({ summary: 'Get all requests' }) // Describes the api operation for Swagger. - @ApiResponse({ - status: HttpStatus.OK, - type: ResponseRequestDto, - isArray: true, - }) // Describes the response for Swagger. - async getAllRequests(@Res() res, @Query() filter: RequestFilterDto) { - try { - this.logger.log(`Initiated fetching requests`); - - const requests = await this.requestService.getAllRequests(filter); - - this.logger.log(`Successfully fetched requests`); - - return res - .status(HttpStatus.OK) - .json({ message: 'Successfully fetched requests', data: requests }); - } catch (error) { - this.logger.error(`Failed to fetch requests`, error); - return res - .status(HttpStatus.INTERNAL_SERVER_ERROR) - .json({ message: 'Failed to fetch requests' }); - } - } - - @Get('user/:userId') - @ApiOperation({ summary: 'Get all requests of a user' }) // Describes the api operation for Swagger. - @ApiResponse({ - status: HttpStatus.OK, - type: ResponseRequestDto, - isArray: true, - }) // Describes the response for Swagger. - async getAllRequestsForUser( - @Res() res, - @Param('userId') userId: number, - @Query() filter: RequestFilterDto, - ) { - try { - this.logger.log( - `Initiated fetching user requests for user id #${userId}`, - ); - - const requests = await this.requestService.getAllRequestsForUser( - +userId, - filter, - ); - - this.logger.log( - `Successfully fetched user requests for user id #${userId}`, - ); - - return res.status(HttpStatus.OK).json({ - message: `Successfully fetched user requests for user id #${userId}`, - data: requests, - }); - } catch (error) { - this.logger.error( - `Failed to fetch user requests for user id #${userId}`, - error, - ); - - return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ - message: `Failed to fetch user requests for user id #${userId}`, - }); - } - } - - @Get(':requestId') - @ApiOperation({ summary: 'Get a request' }) // Describes the api operation for Swagger. - @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) // Describes the response for Swagger. - async getRequestById(@Res() res, @Param('requestId') requestId: number) { - try { - this.logger.log( - `Initiated fetching request for request id #${requestId}`, - ); - - const request = await this.requestService.getRequestById(+requestId); - - this.logger.log( - `Successfully fetched request for request id #${requestId}`, - ); - - return res.status(HttpStatus.OK).json({ - message: `Successfully fetched request for request id #${requestId}`, - data: request, - }); - } catch (error) { - this.logger.error( - `Failed to fetch request for request id #${requestId}`, - error, - ); - - return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ - message: `Failed to fetch request for request id #${requestId}`, - }); - } - } - - @Patch('update/:requestId') - @ApiOperation({ summary: 'Update a request' }) // Describes the api operation for Swagger. - @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) // Describes the response for Swagger. - async updateRequestByRequestId( - @Res() res, - @Param('requestId') requestId: number, - @Body() updateRequestDto: UpdateRequestDto, - ) { - try { - this.logger.log( - `Initiated updating request for request id #${requestId}`, - ); - - const updatedRequest = await this.requestService.updateRequestByRequestId( - +requestId, - updateRequestDto, - ); - - this.logger.log( - `Successfully updated request for request id #${requestId}`, - ); - - return res.status(HttpStatus.OK).json({ - message: `Successfully updated request for request id #${requestId}`, - data: updatedRequest, - }); - } catch (error) { - this.logger.error( - `Failed to update request for request id #${requestId}`, - error, - ); - - return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ - message: `Failed to updated request for request id #${requestId}`, - }); - } - } - - @Patch('update/status/:requestId') - @ApiOperation({ summary: 'Update a request status' }) // Describes the api operation for Swagger. - @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) // Describes the response for Swagger. - async updateRequestStatus( - @Res() res, - @Param('requestId') requestId: number, - @Body() updateRequestStatusDto: RequestStatusDto, - ) { - try { - this.logger.log( - `Initiated updating request status to ${JSON.stringify( - updateRequestStatusDto, - )} for request id #${requestId}`, - ); - - const updatedRequest = await this.requestService.updateRequestStatus( - +requestId, - updateRequestStatusDto, - ); - - this.logger.log( - `Successfully updated request status to ${JSON.stringify( - updateRequestStatusDto, - )} for request id #${requestId}`, - ); - - return res.status(HttpStatus.OK).json({ - message: `Successfully updated request status for request id #${requestId}`, - data: updatedRequest, - }); - } catch (error) { - this.logger.error( - `Failed to update request status to ${JSON.stringify( - updateRequestStatusDto, - )} for request id #${requestId}`, - error, - ); - - return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ - message: `Failed to updated request status to ${JSON.stringify( - updateRequestStatusDto, - )} for request id #${requestId}`, - }); - } - } -} diff --git a/src/request/request.module.ts b/src/request/request.module.ts deleted file mode 100644 index 91c477b..0000000 --- a/src/request/request.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Module } from '@nestjs/common'; -import { RequestController } from './request.controller'; -import { RequestService } from './request.service'; -import { PrismaService } from 'src/prisma/prisma.service'; -import { PrismaModule } from 'src/prisma/prisma.module'; - -@Module({ - // Import the PrismaModule to make Prisma service available within this module - imports: [PrismaModule], - - // Declare the RequestController as a controller for this module - controllers: [RequestController], - - // Declare the RequestService and PrismaService as providers for this module - providers: [RequestService, PrismaService], - - // Export the RequestService to make it available for other modules that import this module - exports: [RequestService], -}) -export class RequestModule {} diff --git a/src/request/request.service.ts b/src/request/request.service.ts deleted file mode 100644 index 8646ce7..0000000 --- a/src/request/request.service.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '../prisma/prisma.service'; // Import your Prisma service -import { - CreateRequestDto, - RequestFilterDto, - RequestStatusDto, -} from './dto/create-request.dto'; -import { UpdateRequestDto } from './dto/update-request.dto'; - -@Injectable() -export class RequestService { - constructor(private prisma: PrismaService) {} - - // Create a new request - async createRequest(createRequestDto: CreateRequestDto) { - return this.prisma.request.create({ - data: createRequestDto, - }); - } - - // Get all requests with optional filters and pagination - async getAllRequests(filter: RequestFilterDto) { - const { status, type, limit = 10, offset = 0, orderBy } = filter; - return this.prisma.request.findMany({ - where: { - status: status ?? undefined, // Optional status filter - type: type ?? undefined, // Optional type filter - }, - orderBy: { - [orderBy || 'createdAt']: 'asc', // Default sorting by createdAt - }, - skip: +offset, - take: +limit, - }); - } - - // Get all requests for a specific user with optional filters and pagination - async getAllRequestsForUser(userId: number, filter: any) { - const { status, type, limit = 10, offset = 0, orderBy } = filter; - return this.prisma.request.findMany({ - where: { - userId: userId, - status: status ?? undefined, // Optional status filter - type: type ?? undefined, // Optional type filter - }, - orderBy: { - [orderBy || 'createdAt']: 'asc', // Default sorting by createdAt - }, - skip: offset, - take: limit, - }); - } - - // Get a request by its ID - async getRequestById(requestId: number) { - return this.prisma.request.findUnique({ - where: { - requestId: requestId, - }, - }); - } - - // Update a request by its ID - async updateRequestByRequestId( - requestId: number, - updateRequestDto: UpdateRequestDto, - ) { - return this.prisma.request.update({ - where: { - requestId: requestId, - }, - data: updateRequestDto, - }); - } - - // Update the status of a request by its ID - async updateRequestStatus( - requestId: number, - updateRequestStatusDto: RequestStatusDto, - ) { - return this.prisma.request.update({ - where: { - requestId: requestId, - }, - data: updateRequestStatusDto, - }); - } -} From 9d9d3cae31622745255a2807eaba85c853a86c4b Mon Sep 17 00:00:00 2001 From: Ajay Maurya Date: Tue, 26 Sep 2023 12:23:00 +0530 Subject: [PATCH 08/26] Dockerfile and it's config added --- .env.example | 3 +++ Dockerfile | 24 ++++++++++++++++++++++++ docker-compose.yml | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 Dockerfile diff --git a/.env.example b/.env.example index 6d963a7..452d934 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,5 @@ +POSTGRES_DB= +POSTGRES_USER= +POSTGRES_PASSWORD= DATABASE_URL= PORT = 4000 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2c07246 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +# Use the official Node.js image as the base image +FROM node:16 + +# Set the working directory inside the container +WORKDIR /app + +# Copy package.json and package-lock.json to the working directory +COPY package*.json ./ + +# Install project dependencies +RUN npm install + +# Copy the rest of the application code to the container +COPY . . + +# Expose the PORT environment variable (default to 4000 if not provided) +ARG PORT=4000 +ENV PORT=$PORT + +# Build your Nest.js application +RUN npm run build + +# Start the Nest.js application using the start:prod script +CMD ["npm", "run", "start:prod"] diff --git a/docker-compose.yml b/docker-compose.yml index e69de29..1d6ace5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +version: '3' +services: + app: + build: + context: . + dockerfile: Dockerfile + image: marketplace-request-service + container_name: marketplace + environment: + - PORT=${PORT:-4000} + ports: + - '${PORT:-4000}:4000' + depends_on: + - db + networks: + - nest-network + + db: + image: postgres:13 + container_name: postgres-container + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + ports: + - '5432:5432' + volumes: + - pg-data:/var/lib/postgresql/data + networks: + - nest-network + +volumes: + pg-data: + +networks: + nest-network: From a57d184fb56406c19b806df3f9e00d56d827cd0b Mon Sep 17 00:00:00 2001 From: Prashant Kumar Date: Wed, 27 Sep 2023 10:09:03 +0530 Subject: [PATCH 09/26] Settlement sevice revert --- src/app.module.ts | 2 + src/request/dto/create-request.dto.ts | 117 +++++++++ src/request/dto/response-request.dto.ts | 15 ++ src/request/dto/update-request.dto.ts | 4 + src/request/entities/request.entity.ts | 1 + src/request/enum/request.enum.ts | 13 + src/request/request.controller.ts | 249 +++++++++++++++++++ src/request/request.module.ts | 20 ++ src/request/request.service.ts | 88 +++++++ src/settlement/dto/create-settlement.dto.ts | 16 -- src/settlement/entities/settlement.entity.ts | 1 - src/settlement/settlement.service.ts | 26 +- 12 files changed, 528 insertions(+), 24 deletions(-) create mode 100644 src/request/dto/create-request.dto.ts create mode 100644 src/request/dto/response-request.dto.ts create mode 100644 src/request/dto/update-request.dto.ts create mode 100644 src/request/entities/request.entity.ts create mode 100644 src/request/enum/request.enum.ts create mode 100644 src/request/request.controller.ts create mode 100644 src/request/request.module.ts create mode 100644 src/request/request.service.ts delete mode 100644 src/settlement/entities/settlement.entity.ts diff --git a/src/app.module.ts b/src/app.module.ts index 9e0663c..fc9791c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,6 +3,7 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { PrismaModule } from './prisma/prisma.module'; import { ConfigModule } from '@nestjs/config'; +import { RequestModule } from './request/request.module'; import { SettlementModule } from './settlement/settlement.module'; @Module({ @@ -12,6 +13,7 @@ import { SettlementModule } from './settlement/settlement.module'; isGlobal: true, // Make the configuration available globally }), PrismaModule, + RequestModule, SettlementModule, ], controllers: [AppController], diff --git a/src/request/dto/create-request.dto.ts b/src/request/dto/create-request.dto.ts new file mode 100644 index 0000000..5c44f08 --- /dev/null +++ b/src/request/dto/create-request.dto.ts @@ -0,0 +1,117 @@ +import { RequestStatusEnum, RequestTypeEnum } from '@prisma/client'; +import { Transform, TransformFnParams } from 'class-transformer'; +import { + IsDate, + IsEnum, + IsInt, + IsNotEmpty, + IsOptional, + IsString, +} from 'class-validator'; + +// CreateRequestDto is used for creating a new request. +export class CreateRequestDto { + // User ID associated with the request. + @IsNotEmpty() + @IsInt() + userId: number; + + // Title of the request. + @IsNotEmpty() + @IsString() + title: string; + + // Status of the request, should be one of the valid RequestStatusEnum values. + @IsNotEmpty() + @IsEnum(RequestStatusEnum) + status: RequestStatusEnum; + + // Description of the request. + @IsNotEmpty() + @IsString() + description: string; + + // Type of the request, should be one of the valid RequestTypeEnum values. + @IsNotEmpty() + @IsEnum(RequestTypeEnum) + type: RequestTypeEnum; + + // Optional content of the request in JSON format. + @IsOptional() + // Use the @Transform decorator to apply a custom transformation to a property. + @Transform(({ value }: TransformFnParams) => { + try { + // Try to parse the input value as JSON to convert it into a JavaScript object. + return JSON.parse(value); + } catch (error) { + // If parsing fails (e.g., if value is not a valid JSON string), return the original value. + return value; + } + }) + requestContent?: object; + + // Optional content of the response in JSON format. + @IsOptional() + // Use the @Transform decorator to apply a custom transformation to a property. + @Transform(({ value }: TransformFnParams) => { + try { + // Try to parse the input value as JSON to convert it into a JavaScript object. + return JSON.parse(value); + } catch (error) { + // If parsing fails (e.g., if value is not a valid JSON string), return the original value. + return value; + } + }) + responseContent?: object; + + // Optional remark or additional information about the request. + @IsOptional() + @IsString() + remark?: string; + + // Optional creation date of the request. + @IsDate() + @IsOptional() + createdAt?: Date; + + // Optional update date of the request. + @IsDate() + @IsOptional() + updatedAt?: Date; +} + +// RequestStatusDto is used for updating the status of a request. +export class RequestStatusDto { + // New status for the request, should be one of the valid RequestStatusEnum values. + @IsNotEmpty() + @IsEnum(RequestStatusEnum) + status: RequestStatusEnum; +} + +// RequestFilterDto is used for filtering and paginating requests. +export class RequestFilterDto { + // Optional status filter, validate that it's a valid enum value. + @IsOptional() + @IsEnum(RequestStatusEnum, { each: true }) + status?: RequestStatusEnum; + + // Optional limit for pagination, validate that it's an integer. + @IsOptional() + @IsInt() + limit?: number; + + // Optional offset for pagination, validate that it's an integer. + @IsOptional() + @IsInt() + offset?: number; + + // Optional field to specify the order of results, validate that it's a string. + @IsOptional() + @IsString() + orderBy?: string; + + // Optional request type filter, validate that it's a valid enum value. + @IsOptional() + @IsEnum(RequestTypeEnum, { each: true }) + type?: RequestTypeEnum; +} diff --git a/src/request/dto/response-request.dto.ts b/src/request/dto/response-request.dto.ts new file mode 100644 index 0000000..f614f35 --- /dev/null +++ b/src/request/dto/response-request.dto.ts @@ -0,0 +1,15 @@ +import { RequestStatusEnum, RequestTypeEnum } from '../enum/request.enum'; + +export class ResponseRequestDto { + readonly requestId: number; + readonly userId: number; + readonly title: string; + readonly type: RequestTypeEnum; + readonly description: string; + readonly status: RequestStatusEnum; + readonly requestContent: object; + readonly responseContent: object; + readonly remark: string; + readonly createdAt: Date; + readonly updatedAt: Date; +} diff --git a/src/request/dto/update-request.dto.ts b/src/request/dto/update-request.dto.ts new file mode 100644 index 0000000..bac9f9f --- /dev/null +++ b/src/request/dto/update-request.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateRequestDto } from './create-request.dto'; + +export class UpdateRequestDto extends PartialType(CreateRequestDto) {} diff --git a/src/request/entities/request.entity.ts b/src/request/entities/request.entity.ts new file mode 100644 index 0000000..d5e892a --- /dev/null +++ b/src/request/entities/request.entity.ts @@ -0,0 +1 @@ +export class Request {} diff --git a/src/request/enum/request.enum.ts b/src/request/enum/request.enum.ts new file mode 100644 index 0000000..884355b --- /dev/null +++ b/src/request/enum/request.enum.ts @@ -0,0 +1,13 @@ +export enum RequestTypeEnum { + REFUND, + CREDIT, + INVOICE_REQUEST, + SETTLEMENT, +} + +export enum RequestStatusEnum { + PENDING, + IN_PROGRESS, + APPROVED, + REJECTED, +} diff --git a/src/request/request.controller.ts b/src/request/request.controller.ts new file mode 100644 index 0000000..54949db --- /dev/null +++ b/src/request/request.controller.ts @@ -0,0 +1,249 @@ +import { + Controller, + Post, + Get, + Body, + Param, + Patch, + Query, + HttpStatus, + Logger, + Res, +} from '@nestjs/common'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { RequestService } from './request.service'; +import { + CreateRequestDto, + RequestFilterDto, + RequestStatusDto, +} from './dto/create-request.dto'; +import { UpdateRequestDto } from './dto/update-request.dto'; +import { ResponseRequestDto } from './dto/response-request.dto'; + +@Controller('requests') +@ApiTags('requests') +export class RequestController { + // Create a logger instance for this controller to log events and errors. + private readonly logger = new Logger(RequestController.name); + + // Constructor for the RequestController class, injecting the RequestService. + constructor(private readonly requestService: RequestService) {} + + @Post() + @ApiOperation({ summary: 'Create a request' }) // Describes the api operation for Swagger. + @ApiResponse({ status: HttpStatus.CREATED, type: ResponseRequestDto }) // Describes the response for Swagger. + async createRequest(@Res() res, @Body() createRequestDto: CreateRequestDto) { + try { + // Log the initiation of request creation + this.logger.log( + `Creating a new request for user id #${createRequestDto.userId}`, + ); + + const request = await this.requestService.createRequest(createRequestDto); + + // Log the successful creation of the request + this.logger.log( + `Successfully created a new request for user id #${request.userId}, request id ${request.requestId}`, + ); + + // Return a success response with the created request data + return res + .status(HttpStatus.CREATED) + .json({ message: 'Request created successfully', data: request }); + } catch (error) { + // Log the error if request creation fails + this.logger.error( + `Failed to create new request for user id #${createRequestDto.userId}`, + error, + ); + + // Return an error response + return res.status(HttpStatus.CREATED).json({ + message: `Failed to create the request for user id #${createRequestDto.userId}`, + }); + } + } + + @Get('') + @ApiOperation({ summary: 'Get all requests' }) // Describes the api operation for Swagger. + @ApiResponse({ + status: HttpStatus.OK, + type: ResponseRequestDto, + isArray: true, + }) // Describes the response for Swagger. + async getAllRequests(@Res() res, @Query() filter: RequestFilterDto) { + try { + this.logger.log(`Initiated fetching requests`); + + const requests = await this.requestService.getAllRequests(filter); + + this.logger.log(`Successfully fetched requests`); + + return res + .status(HttpStatus.OK) + .json({ message: 'Successfully fetched requests', data: requests }); + } catch (error) { + this.logger.error(`Failed to fetch requests`, error); + return res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ message: 'Failed to fetch requests' }); + } + } + + @Get('user/:userId') + @ApiOperation({ summary: 'Get all requests of a user' }) // Describes the api operation for Swagger. + @ApiResponse({ + status: HttpStatus.OK, + type: ResponseRequestDto, + isArray: true, + }) // Describes the response for Swagger. + async getAllRequestsForUser( + @Res() res, + @Param('userId') userId: number, + @Query() filter: RequestFilterDto, + ) { + try { + this.logger.log( + `Initiated fetching user requests for user id #${userId}`, + ); + + const requests = await this.requestService.getAllRequestsForUser( + +userId, + filter, + ); + + this.logger.log( + `Successfully fetched user requests for user id #${userId}`, + ); + + return res.status(HttpStatus.OK).json({ + message: `Successfully fetched user requests for user id #${userId}`, + data: requests, + }); + } catch (error) { + this.logger.error( + `Failed to fetch user requests for user id #${userId}`, + error, + ); + + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: `Failed to fetch user requests for user id #${userId}`, + }); + } + } + + @Get(':requestId') + @ApiOperation({ summary: 'Get a request' }) // Describes the api operation for Swagger. + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) // Describes the response for Swagger. + async getRequestById(@Res() res, @Param('requestId') requestId: number) { + try { + this.logger.log( + `Initiated fetching request for request id #${requestId}`, + ); + + const request = await this.requestService.getRequestById(+requestId); + + this.logger.log( + `Successfully fetched request for request id #${requestId}`, + ); + + return res.status(HttpStatus.OK).json({ + message: `Successfully fetched request for request id #${requestId}`, + data: request, + }); + } catch (error) { + this.logger.error( + `Failed to fetch request for request id #${requestId}`, + error, + ); + + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: `Failed to fetch request for request id #${requestId}`, + }); + } + } + + @Patch('update/:requestId') + @ApiOperation({ summary: 'Update a request' }) // Describes the api operation for Swagger. + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) // Describes the response for Swagger. + async updateRequestByRequestId( + @Res() res, + @Param('requestId') requestId: number, + @Body() updateRequestDto: UpdateRequestDto, + ) { + try { + this.logger.log( + `Initiated updating request for request id #${requestId}`, + ); + + const updatedRequest = await this.requestService.updateRequestByRequestId( + +requestId, + updateRequestDto, + ); + + this.logger.log( + `Successfully updated request for request id #${requestId}`, + ); + + return res.status(HttpStatus.OK).json({ + message: `Successfully updated request for request id #${requestId}`, + data: updatedRequest, + }); + } catch (error) { + this.logger.error( + `Failed to update request for request id #${requestId}`, + error, + ); + + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: `Failed to updated request for request id #${requestId}`, + }); + } + } + + @Patch('update/status/:requestId') + @ApiOperation({ summary: 'Update a request status' }) // Describes the api operation for Swagger. + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) // Describes the response for Swagger. + async updateRequestStatus( + @Res() res, + @Param('requestId') requestId: number, + @Body() updateRequestStatusDto: RequestStatusDto, + ) { + try { + this.logger.log( + `Initiated updating request status to ${JSON.stringify( + updateRequestStatusDto, + )} for request id #${requestId}`, + ); + + const updatedRequest = await this.requestService.updateRequestStatus( + +requestId, + updateRequestStatusDto, + ); + + this.logger.log( + `Successfully updated request status to ${JSON.stringify( + updateRequestStatusDto, + )} for request id #${requestId}`, + ); + + return res.status(HttpStatus.OK).json({ + message: `Successfully updated request status for request id #${requestId}`, + data: updatedRequest, + }); + } catch (error) { + this.logger.error( + `Failed to update request status to ${JSON.stringify( + updateRequestStatusDto, + )} for request id #${requestId}`, + error, + ); + + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: `Failed to updated request status to ${JSON.stringify( + updateRequestStatusDto, + )} for request id #${requestId}`, + }); + } + } +} diff --git a/src/request/request.module.ts b/src/request/request.module.ts new file mode 100644 index 0000000..91c477b --- /dev/null +++ b/src/request/request.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { RequestController } from './request.controller'; +import { RequestService } from './request.service'; +import { PrismaService } from 'src/prisma/prisma.service'; +import { PrismaModule } from 'src/prisma/prisma.module'; + +@Module({ + // Import the PrismaModule to make Prisma service available within this module + imports: [PrismaModule], + + // Declare the RequestController as a controller for this module + controllers: [RequestController], + + // Declare the RequestService and PrismaService as providers for this module + providers: [RequestService, PrismaService], + + // Export the RequestService to make it available for other modules that import this module + exports: [RequestService], +}) +export class RequestModule {} diff --git a/src/request/request.service.ts b/src/request/request.service.ts new file mode 100644 index 0000000..8646ce7 --- /dev/null +++ b/src/request/request.service.ts @@ -0,0 +1,88 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; // Import your Prisma service +import { + CreateRequestDto, + RequestFilterDto, + RequestStatusDto, +} from './dto/create-request.dto'; +import { UpdateRequestDto } from './dto/update-request.dto'; + +@Injectable() +export class RequestService { + constructor(private prisma: PrismaService) {} + + // Create a new request + async createRequest(createRequestDto: CreateRequestDto) { + return this.prisma.request.create({ + data: createRequestDto, + }); + } + + // Get all requests with optional filters and pagination + async getAllRequests(filter: RequestFilterDto) { + const { status, type, limit = 10, offset = 0, orderBy } = filter; + return this.prisma.request.findMany({ + where: { + status: status ?? undefined, // Optional status filter + type: type ?? undefined, // Optional type filter + }, + orderBy: { + [orderBy || 'createdAt']: 'asc', // Default sorting by createdAt + }, + skip: +offset, + take: +limit, + }); + } + + // Get all requests for a specific user with optional filters and pagination + async getAllRequestsForUser(userId: number, filter: any) { + const { status, type, limit = 10, offset = 0, orderBy } = filter; + return this.prisma.request.findMany({ + where: { + userId: userId, + status: status ?? undefined, // Optional status filter + type: type ?? undefined, // Optional type filter + }, + orderBy: { + [orderBy || 'createdAt']: 'asc', // Default sorting by createdAt + }, + skip: offset, + take: limit, + }); + } + + // Get a request by its ID + async getRequestById(requestId: number) { + return this.prisma.request.findUnique({ + where: { + requestId: requestId, + }, + }); + } + + // Update a request by its ID + async updateRequestByRequestId( + requestId: number, + updateRequestDto: UpdateRequestDto, + ) { + return this.prisma.request.update({ + where: { + requestId: requestId, + }, + data: updateRequestDto, + }); + } + + // Update the status of a request by its ID + async updateRequestStatus( + requestId: number, + updateRequestStatusDto: RequestStatusDto, + ) { + return this.prisma.request.update({ + where: { + requestId: requestId, + }, + data: updateRequestStatusDto, + }); + } +} diff --git a/src/settlement/dto/create-settlement.dto.ts b/src/settlement/dto/create-settlement.dto.ts index 307fee4..1b43e09 100644 --- a/src/settlement/dto/create-settlement.dto.ts +++ b/src/settlement/dto/create-settlement.dto.ts @@ -62,22 +62,6 @@ export class CreateSettlementDto { updatedAt?: Date; } -// SettlementStatusDto is used for updating the status of a request. -export class SettlementStatusDto { - // New status for the request, should be one of the valid SettlementStatusEnum values. - @IsNotEmpty() - @IsEnum(SettlementStatusEnum) - requestStatus: SettlementStatusEnum; -} - -// SettlementStatusDto is used for updating the status of a request. -export class thirdPartyResponseStatusEnumDto { - // New status for the request, should be one of the valid UserResponseStatusEnum values. - @IsNotEmpty() - @IsEnum(thirdPartyResponseStatusEnum) - thirdPartyResponseStatus: thirdPartyResponseStatusEnum; -} - // SettlementFilterDto is used for filtering and paginating requests. export class SettlementFilterDto { // Optional settlement status filter, validated that it's valid SettlementStatusEnum value diff --git a/src/settlement/entities/settlement.entity.ts b/src/settlement/entities/settlement.entity.ts deleted file mode 100644 index 8091db6..0000000 --- a/src/settlement/entities/settlement.entity.ts +++ /dev/null @@ -1 +0,0 @@ -export class Settlement {} diff --git a/src/settlement/settlement.service.ts b/src/settlement/settlement.service.ts index 86331d6..61c39be 100644 --- a/src/settlement/settlement.service.ts +++ b/src/settlement/settlement.service.ts @@ -1,9 +1,7 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { CreateSettlementDto, - SettlementFilterDto, - SettlementStatusDto, - thirdPartyResponseStatusEnumDto, + SettlementFilterDto } from './dto/create-settlement.dto'; import { PrismaService } from '../prisma/prisma.service'; import { UpdateSettlementDto } from './dto/update-settlement.dto'; @@ -14,9 +12,23 @@ export class SettlementService { // create a new request for the settlement async createSettlement(createSettlementDto: CreateSettlementDto) { - return this.prisma.settlement.create({ - data: createSettlementDto, - }); + try { + // Attempt to create a new settlement + const newSettlement = await this.prisma.settlement.create({ + data: createSettlementDto, + }); + + return newSettlement; + } catch (error) { + // Check if the error is related to a duplicate entry (unique constraint violation) + if (error.code === 'P2002' && error.meta?.target?.includes('requestId')) { + // Handle the duplicate entry error here, e.g., by returning a custom error response + throw new NotFoundException('Duplicate requestId. Settlement already exists.'); + } + + // For other errors, rethrow the error or handle it as needed + throw error; + } } // get all settlements with optional filter and pagination From 71f56bf94dc8ab8cbdc08b1461e4801d35683ecc Mon Sep 17 00:00:00 2001 From: Prashant Kumar Date: Wed, 27 Sep 2023 11:35:11 +0530 Subject: [PATCH 10/26] Added the Dummy Admin model and relation in prisma schema and changes in required files --- prisma/schema.prisma | 38 ++++++++++++++----- src/settlement/dto/create-settlement.dto.ts | 5 +++ src/settlement/dto/response-settlement.dto.ts | 1 + 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 29c65c3..2e06f28 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -24,6 +24,20 @@ model User { Settlement Settlement[] } +// Dummy Admin Model +model Admin { + id Int @id @default(autoincrement()) + email String @unique + role String + username String + password String + profilePicture String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + requests Request[] + Settlement Settlement[] +} + model Request { requestId Int @id @default(autoincrement()) userId Int @@ -37,21 +51,25 @@ model Request { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) // Specify fields and references here + Admin Admin? @relation(fields: [adminId], references: [id]) + adminId Int? Settlement Settlement[] } model Settlement { - settlementId Int @id @default(autoincrement()) - requestId Int @unique - userId Int - requestStatus SettlementStatusEnum + settlementId Int @id @default(autoincrement()) + requestId Int + userId Int + adminId Int + requestStatus SettlementStatusEnum thirdPartyResponseStatus thirdPartyResponseStatusEnum - transactionId Int? - content Json? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - user User @relation(fields: [userId], references: [id]) // Specify fields and references here - request Request @relation(fields: [requestId], references: [requestId]) + transactionId Int? + content Json? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id]) // Specify fields and references here + Admin Admin @relation(fields: [adminId], references: [id]) // Specify Admin field reference here + request Request @relation(fields: [requestId], references: [requestId]) } enum RequestTypeEnum { diff --git a/src/settlement/dto/create-settlement.dto.ts b/src/settlement/dto/create-settlement.dto.ts index 1b43e09..a3d0a3f 100644 --- a/src/settlement/dto/create-settlement.dto.ts +++ b/src/settlement/dto/create-settlement.dto.ts @@ -21,6 +21,11 @@ export class CreateSettlementDto { @IsNotEmpty() @IsInt() userId: number; + + // Admin Id associated with the Admin + @IsNotEmpty() + @IsInt() + adminId: number; // Status of the request, should be one of the valid SettlementStatusEnum values. @IsNotEmpty() diff --git a/src/settlement/dto/response-settlement.dto.ts b/src/settlement/dto/response-settlement.dto.ts index f4fd965..bb46a40 100644 --- a/src/settlement/dto/response-settlement.dto.ts +++ b/src/settlement/dto/response-settlement.dto.ts @@ -7,6 +7,7 @@ export class ResponseSettlementDto { readonly SettlementId: number; readonly requestId: number; readonly userId: number; + readonly adminId: number; readonly type: thirdPartyResponseStatusEnum; readonly transactionId: number; readonly requestStatus: SettlementStatusEnum; From 43dd0436530a2b1e5b17da502a83d3e55ce16d63 Mon Sep 17 00:00:00 2001 From: Prashant Kumar Date: Thu, 28 Sep 2023 11:24:57 +0530 Subject: [PATCH 11/26] Added the error handler across the API --- prisma/schema.prisma | 2 +- src/settlement/settlement.service.ts | 89 +++++++++++++++++++++------- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2e06f28..c1db1ab 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -68,7 +68,7 @@ model Settlement { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) // Specify fields and references here - Admin Admin @relation(fields: [adminId], references: [id]) // Specify Admin field reference here + admin Admin @relation(fields: [adminId], references: [id]) // Specify Admin field reference here request Request @relation(fields: [requestId], references: [requestId]) } diff --git a/src/settlement/settlement.service.ts b/src/settlement/settlement.service.ts index 61c39be..8628770 100644 --- a/src/settlement/settlement.service.ts +++ b/src/settlement/settlement.service.ts @@ -1,7 +1,11 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; +import { + Injectable, + NotAcceptableException, + NotFoundException, +} from '@nestjs/common'; import { CreateSettlementDto, - SettlementFilterDto + SettlementFilterDto, } from './dto/create-settlement.dto'; import { PrismaService } from '../prisma/prisma.service'; import { UpdateSettlementDto } from './dto/update-settlement.dto'; @@ -9,26 +13,33 @@ import { UpdateSettlementDto } from './dto/update-settlement.dto'; @Injectable() export class SettlementService { constructor(private prisma: PrismaService) {} - - // create a new request for the settlement async createSettlement(createSettlementDto: CreateSettlementDto) { - try { - // Attempt to create a new settlement - const newSettlement = await this.prisma.settlement.create({ - data: createSettlementDto, - }); + // Check for duplicate requestId + const existingSettlement = await this.checkForDuplicateRequest( + createSettlementDto, + ); - return newSettlement; - } catch (error) { - // Check if the error is related to a duplicate entry (unique constraint violation) - if (error.code === 'P2002' && error.meta?.target?.includes('requestId')) { - // Handle the duplicate entry error here, e.g., by returning a custom error response - throw new NotFoundException('Duplicate requestId. Settlement already exists.'); - } - - // For other errors, rethrow the error or handle it as needed - throw error; + if (existingSettlement) { + throw new NotAcceptableException( + 'Duplicate requestId. Settlement already exists.', + ); } + + // If no duplicate, create the new settlement + const newSettlement = await this.prisma.settlement.create({ + data: createSettlementDto, + }); + return newSettlement; + } + + // to check the dublicate entry of the existing requestId + async checkForDuplicateRequest(createSettlementDto: CreateSettlementDto) { + const existingSettlement = await this.prisma.settlement.findFirst({ + where: { + requestId: createSettlementDto.requestId, + }, + }); + return existingSettlement; } // get all settlements with optional filter and pagination @@ -54,7 +65,19 @@ export class SettlementService { } // Get settlement for a specific user using the userId - async getAllSettlementForUser(userId: number, filter: any) { + async getAllSettlementForUser(userId: number, filter: SettlementFilterDto) { + // Check if the user exists + const user = await this.prisma.user.findUnique({ + where: { + id: userId, + }, + }); + + if (!user) { + throw new NotFoundException(`User with ID ${userId} not found.`); + } + + // get the settlement if the userId exist. const { thirdPartyResponseStatus, requestStatus, @@ -78,6 +101,19 @@ export class SettlementService { // Get a settlement by its request ID async getsettlementById(requestId: number) { + // Check the the settlement is available for the given requestId. + const findRequestId = await this.prisma.request.findUnique({ + where: { + requestId: requestId, + }, + }); + + if (!findRequestId) { + // Handle the case where the requestId does not exist in the database + throw new NotFoundException(`Given requestId ${requestId} not found.`); + } + + // Get settlement by the requestId return this.prisma.settlement.findMany({ where: { requestId: requestId, @@ -90,6 +126,19 @@ export class SettlementService { requestId: number, updateSettlementStatusDto: UpdateSettlementDto, ) { + // Check the the requestId is available for settlement + const findRequestId = await this.prisma.request.findUnique({ + where: { + requestId: requestId, + }, + }); + + if (!findRequestId) { + // Handle the case where the requestId does not exist in the database + throw new NotFoundException(`Given requestId ${requestId} not found.`); + } + + // Update the settlement by the request Id return this.prisma.settlement.updateMany({ where: { requestId: requestId, From 724bba8f18457b0a780184c38863883950aa3371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CVamshiBatta=E2=80=9D?= <“batta.v@esmagico.in”> Date: Tue, 3 Oct 2023 11:57:46 +0530 Subject: [PATCH 12/26] Fix: Removing "Refund" capability from the app. --- prisma/schema.prisma | 1 - src/request/enum/request.enum.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0703dab..523c3bc 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -39,7 +39,6 @@ model Request { } enum RequestTypeEnum { - REFUND CREDIT INVOICE_REQUEST SETTLEMENT diff --git a/src/request/enum/request.enum.ts b/src/request/enum/request.enum.ts index 884355b..25e5a40 100644 --- a/src/request/enum/request.enum.ts +++ b/src/request/enum/request.enum.ts @@ -1,5 +1,4 @@ export enum RequestTypeEnum { - REFUND, CREDIT, INVOICE_REQUEST, SETTLEMENT, From 771a3ac1bfcd0b91df7440b1cab8e49c85175f53 Mon Sep 17 00:00:00 2001 From: Ajay Maurya Date: Wed, 20 Sep 2023 19:40:07 +0530 Subject: [PATCH 13/26] Request APIs created --- package-lock.json | 35 +++++ package.json | 2 + .../20230919071959_init/migration.sql | 16 -- prisma/schema.prisma | 40 ++++- src/app.module.ts | 2 + src/prisma/prisma.service.ts | 2 +- src/request/dto/create-request.dto.ts | 88 +++++++++++ src/request/dto/response-request.dto.ts | 15 ++ src/request/dto/update-request.dto.ts | 4 + src/request/entities/request.entity.ts | 1 + src/request/enum/request.enum.ts | 13 ++ src/request/request.controller.ts | 147 ++++++++++++++++++ src/request/request.module.ts | 13 ++ src/request/request.service.ts | 83 ++++++++++ 14 files changed, 439 insertions(+), 22 deletions(-) delete mode 100644 prisma/migrations/20230919071959_init/migration.sql create mode 100644 src/request/dto/create-request.dto.ts create mode 100644 src/request/dto/response-request.dto.ts create mode 100644 src/request/dto/update-request.dto.ts create mode 100644 src/request/entities/request.entity.ts create mode 100644 src/request/enum/request.enum.ts create mode 100644 src/request/request.controller.ts create mode 100644 src/request/request.module.ts create mode 100644 src/request/request.service.ts diff --git a/package-lock.json b/package-lock.json index e103b20..f04d866 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,8 @@ "@nestjs/platform-express": "^9.0.0", "@nestjs/swagger": "^7.1.11", "@prisma/client": "^5.3.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0" @@ -2141,6 +2143,11 @@ "@types/superagent": "*" } }, + "node_modules/@types/validator": { + "version": "13.11.1", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.1.tgz", + "integrity": "sha512-d/MUkJYdOeKycmm75Arql4M5+UuXmf4cHdHKsyw1GcvnNgL6s77UkgSgJ8TE/rI5PYsnwYq5jkcWBLuN/MpQ1A==" + }, "node_modules/@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -3145,6 +3152,21 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "node_modules/class-validator": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.0.tgz", + "integrity": "sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==", + "dependencies": { + "@types/validator": "^13.7.10", + "libphonenumber-js": "^1.10.14", + "validator": "^13.7.0" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -5660,6 +5682,11 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.10.44", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.44.tgz", + "integrity": "sha512-svlRdNBI5WgBjRC20GrCfbFiclbF0Cx+sCcQob/C1r57nsoq0xg8r65QbTyVyweQIlB33P+Uahyho6EMYgcOyQ==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -7874,6 +7901,14 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 2318514..c1bbacc 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "@nestjs/platform-express": "^9.0.0", "@nestjs/swagger": "^7.1.11", "@prisma/client": "^5.3.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0" diff --git a/prisma/migrations/20230919071959_init/migration.sql b/prisma/migrations/20230919071959_init/migration.sql deleted file mode 100644 index e8c5afa..0000000 --- a/prisma/migrations/20230919071959_init/migration.sql +++ /dev/null @@ -1,16 +0,0 @@ --- CreateTable -CREATE TABLE "User" ( - "id" SERIAL NOT NULL, - "email" TEXT NOT NULL, - "userCategory" TEXT NOT NULL, - "username" TEXT NOT NULL, - "password" TEXT NOT NULL, - "profilePicture" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "User_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0564330..0703dab 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -12,12 +12,42 @@ datasource db { // Dummy user model model User { - id Int @id @default(autoincrement()) - email String @unique - userCategory String + id Int @id @default(autoincrement()) + email String @unique + role String username String password String profilePicture String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + requests Request[] +} + +model Request { + requestId Int @id @default(autoincrement()) + userId Int + title String + type RequestTypeEnum + description String + status RequestStatusEnum + requestContent Json? + responseContent Json? + remark String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id]) // Specify fields and references here +} + +enum RequestTypeEnum { + REFUND + CREDIT + INVOICE_REQUEST + SETTLEMENT +} + +enum RequestStatusEnum { + PENDING + IN_PROGRESS + APPROVED + REJECTED } diff --git a/src/app.module.ts b/src/app.module.ts index 8468bfa..f892894 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,6 +3,7 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { PrismaModule } from './prisma/prisma.module'; import { ConfigModule } from '@nestjs/config'; +import { RequestModule } from './request/request.module'; @Module({ imports: [ @@ -11,6 +12,7 @@ import { ConfigModule } from '@nestjs/config'; isGlobal: true, // Make the configuration available globally }), PrismaModule, + RequestModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/prisma/prisma.service.ts b/src/prisma/prisma.service.ts index 072abe7..2c93095 100644 --- a/src/prisma/prisma.service.ts +++ b/src/prisma/prisma.service.ts @@ -1,4 +1,4 @@ -import { INestApplication, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() diff --git a/src/request/dto/create-request.dto.ts b/src/request/dto/create-request.dto.ts new file mode 100644 index 0000000..01d09a9 --- /dev/null +++ b/src/request/dto/create-request.dto.ts @@ -0,0 +1,88 @@ +import { RequestStatusEnum, RequestTypeEnum } from '@prisma/client'; +import { Transform, TransformFnParams } from 'class-transformer'; +import { + IsDate, + IsEnum, + IsInt, + IsNotEmpty, + IsOptional, + IsString, +} from 'class-validator'; + +export class CreateRequestDto { + @IsNotEmpty() + @IsInt() + userId: number; + + @IsNotEmpty() + @IsString() + title: string; + + @IsNotEmpty() + @IsEnum(RequestStatusEnum) + status: RequestStatusEnum; + + @IsNotEmpty() + @IsString() + description: string; + + @IsNotEmpty() + @IsEnum(RequestTypeEnum) + type: RequestTypeEnum; + + @IsOptional() + @Transform(({ value }: TransformFnParams) => { + try { + return JSON.parse(value); + } catch (error) { + return value; + } + }) + requestContent?: object; + + @IsOptional() + @Transform(({ value }: TransformFnParams) => { + try { + return JSON.parse(value); + } catch (error) { + return value; + } + }) + responseContent?: object; + + @IsOptional() + @IsString() + remark?: string; + + @IsDate() + @IsOptional() + createdAt?: Date; + + @IsDate() + @IsOptional() + updatedAt?: Date; +} + +export class RequestStatusDto { + @IsNotEmpty() + @IsEnum(RequestStatusEnum) + status: RequestStatusEnum; +} + +export class RequestFilterDto { + @IsOptional() + @IsEnum(RequestStatusEnum, { each: true }) // Validate that status is a valid enum value + status?: RequestStatusEnum; + + @IsOptional() + @IsInt() // Validate that limit is an integer + limit?: number; + + @IsOptional() + @IsInt() // Validate that offset is an integer + offset?: number; + + @IsOptional() + @IsString() // Validate that orderBy is a string + orderBy?: string; +} diff --git a/src/request/dto/response-request.dto.ts b/src/request/dto/response-request.dto.ts new file mode 100644 index 0000000..2c98010 --- /dev/null +++ b/src/request/dto/response-request.dto.ts @@ -0,0 +1,15 @@ +import { RequestStatusEnum, RequestTypeEnum } from '../enum/request.enum'; + +export class ResponseRequestDto { + requestId: number; + userId: number; + title: string; + type: RequestTypeEnum; + description: string; + status: RequestStatusEnum; + requestContent: object; + responseContent: object; + remark: string; + createdAt: Date; + updatedAt: Date; +} diff --git a/src/request/dto/update-request.dto.ts b/src/request/dto/update-request.dto.ts new file mode 100644 index 0000000..bac9f9f --- /dev/null +++ b/src/request/dto/update-request.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateRequestDto } from './create-request.dto'; + +export class UpdateRequestDto extends PartialType(CreateRequestDto) {} diff --git a/src/request/entities/request.entity.ts b/src/request/entities/request.entity.ts new file mode 100644 index 0000000..d5e892a --- /dev/null +++ b/src/request/entities/request.entity.ts @@ -0,0 +1 @@ +export class Request {} diff --git a/src/request/enum/request.enum.ts b/src/request/enum/request.enum.ts new file mode 100644 index 0000000..884355b --- /dev/null +++ b/src/request/enum/request.enum.ts @@ -0,0 +1,13 @@ +export enum RequestTypeEnum { + REFUND, + CREDIT, + INVOICE_REQUEST, + SETTLEMENT, +} + +export enum RequestStatusEnum { + PENDING, + IN_PROGRESS, + APPROVED, + REJECTED, +} diff --git a/src/request/request.controller.ts b/src/request/request.controller.ts new file mode 100644 index 0000000..cb3f548 --- /dev/null +++ b/src/request/request.controller.ts @@ -0,0 +1,147 @@ +import { + Controller, + Post, + Get, + Body, + Param, + Patch, + Query, + HttpStatus, + HttpException, +} from '@nestjs/common'; +import { RequestService } from './request.service'; +import { + CreateRequestDto, + RequestFilterDto, + RequestStatusDto, +} from './dto/create-request.dto'; +import { UpdateRequestDto } from './dto/update-request.dto'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ResponseRequestDto } from './dto/response-request.dto'; + +@Controller('requests') +@ApiTags('requests') +export class RequestController { + constructor(private readonly requestService: RequestService) {} + + @Post() + @ApiOperation({ summary: 'Create a request' }) + @ApiResponse({ status: HttpStatus.CREATED, type: ResponseRequestDto }) + async createRequest(@Body() createRequestDto: CreateRequestDto) { + try { + const request = await this.requestService.createRequest(createRequestDto); + console.log('request:', request); + return { message: 'Request created successfully', data: request }; + } catch (error) { + console.log('error:', error); + throw new HttpException( + 'Failed to create the request', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @Get('') + @ApiOperation({ summary: 'Get all requests' }) + @ApiResponse({ + status: HttpStatus.OK, + type: ResponseRequestDto, + isArray: true, + }) + async getAllRequestsForAdmin(@Query() filter: RequestFilterDto) { + try { + const requests = await this.requestService.getAllRequestsForAdmin(filter); + return { message: 'Requests fetched successfully', data: requests }; + } catch (error) { + throw new HttpException( + 'Failed to fetch requests', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @Get('user/:userId') + @ApiOperation({ summary: 'Get all requests of a user' }) + @ApiResponse({ + status: HttpStatus.OK, + type: ResponseRequestDto, + isArray: true, + }) + async getAllRequestsForUser( + @Param('userId') userId: number, + @Query() filter: RequestFilterDto, + ) { + try { + const requests = await this.requestService.getAllRequestsForUser( + userId, + filter, + ); + return { message: 'User requests fetched successfully', data: requests }; + } catch (error) { + throw new HttpException( + 'Failed to fetch user requests', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @Get(':requestId') + @ApiOperation({ summary: 'Get a request' }) + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) + async getRequestById(@Param('requestId') requestId: number) { + try { + const request = await this.requestService.getRequestById(requestId); + return { message: 'Request fetched successfully', data: request }; + } catch (error) { + throw new HttpException( + 'Failed to fetch the request', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @Patch('update/:requestId') + @ApiOperation({ summary: 'Update a request' }) + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) + async updateRequestByRequestId( + @Param('requestId') requestId: number, + @Body() updateRequestDto: UpdateRequestDto, + ) { + try { + const updatedRequest = await this.requestService.updateRequestByRequestId( + requestId, + updateRequestDto, + ); + return { message: 'Request updated successfully', data: updatedRequest }; + } catch (error) { + throw new HttpException( + 'Failed to update the request', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @Patch('update/status/:requestId') + @ApiOperation({ summary: 'Update a request status' }) + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) + async updateRequestStatus( + @Param('requestId') requestId: number, + @Body() updateRequestDtoStatus: RequestStatusDto, + ) { + try { + const updatedRequest = await this.requestService.updateRequestStatus( + requestId, + updateRequestDtoStatus, + ); + return { + message: 'Request status updated successfully', + data: updatedRequest, + }; + } catch (error) { + throw new HttpException( + 'Failed to update the request status', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/request/request.module.ts b/src/request/request.module.ts new file mode 100644 index 0000000..83a1df1 --- /dev/null +++ b/src/request/request.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { RequestController } from './request.controller'; +import { RequestService } from './request.service'; +import { PrismaService } from 'src/prisma/prisma.service'; +import { PrismaModule } from 'src/prisma/prisma.module'; + +@Module({ + imports: [PrismaModule], + controllers: [RequestController], + providers: [RequestService, PrismaService], + exports: [RequestService], +}) +export class RequestModule {} diff --git a/src/request/request.service.ts b/src/request/request.service.ts new file mode 100644 index 0000000..cdb389f --- /dev/null +++ b/src/request/request.service.ts @@ -0,0 +1,83 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; // Import your Prisma service +import { + CreateRequestDto, + RequestFilterDto, + RequestStatusDto, +} from './dto/create-request.dto'; +import { UpdateRequestDto } from './dto/update-request.dto'; + +@Injectable() +export class RequestService { + constructor( + @Inject(forwardRef(() => PrismaService)) + private prisma: PrismaService, + ) {} + + async createRequest(createRequestDto: CreateRequestDto) { + return this.prisma.request.create({ + data: createRequestDto, + }); + } + + async getAllRequestsForAdmin(filter: RequestFilterDto) { + const { status, limit, offset, orderBy } = filter; + return this.prisma.request.findMany({ + where: { + status: status || undefined, // Optional status filter + }, + orderBy: { + [orderBy || 'createdAt']: 'asc', // Default sorting by createdAt + }, + skip: offset, + take: limit, + }); + } + + async getAllRequestsForUser(userId: number, filter: any) { + const { status, limit, offset, orderBy } = filter; + return this.prisma.request.findMany({ + where: { + userId: userId, + status: status || undefined, // Optional status filter + }, + orderBy: { + [orderBy || 'createdAt']: 'asc', // Default sorting by createdAt + }, + skip: offset, + take: limit, + }); + } + + async getRequestById(requestId: number) { + return this.prisma.request.findUnique({ + where: { + requestId: requestId, + }, + }); + } + + async updateRequestByRequestId( + requestId: number, + updateRequestDto: UpdateRequestDto, + ) { + return this.prisma.request.update({ + where: { + requestId: requestId, + }, + data: updateRequestDto, + }); + } + + async updateRequestStatus( + requestId: number, + updateRequestStatusDto: RequestStatusDto, + ) { + return this.prisma.request.update({ + where: { + requestId: requestId, + }, + data: updateRequestStatusDto, + }); + } +} From a12142cd3f3e944e6ce4a9113b1eddbeee330c1c Mon Sep 17 00:00:00 2001 From: Ajay Maurya Date: Thu, 21 Sep 2023 11:36:01 +0530 Subject: [PATCH 14/26] Response return and logger messages implemented --- prisma/migrations/migration_lock.toml | 3 - src/app.controller.ts | 5 +- src/request/dto/create-request.dto.ts | 4 + src/request/request.controller.ts | 175 ++++++++++++++++++++------ src/request/request.service.ts | 16 +-- 5 files changed, 150 insertions(+), 53 deletions(-) delete mode 100644 prisma/migrations/migration_lock.toml diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml deleted file mode 100644 index fbffa92..0000000 --- a/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "postgresql" \ No newline at end of file diff --git a/src/app.controller.ts b/src/app.controller.ts index 9df4fd8..8ef5a2d 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -1,13 +1,14 @@ import { Controller, Get, HttpStatus } from '@nestjs/common'; import { AppService } from './app.service'; -import { ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; @Controller() +@ApiTags('get service health') export class AppController { constructor(private readonly appService: AppService) {} @Get() - @ApiOperation({ summary: 'default route for health check' }) // Describes the operation for Swagger. + @ApiOperation({ summary: 'default route for service health check' }) // Describes the operation for Swagger. @ApiResponse({ status: HttpStatus.OK, type: String }) // Describes the response for Swagger. getHealth(): string { return this.appService.getHealth(); diff --git a/src/request/dto/create-request.dto.ts b/src/request/dto/create-request.dto.ts index 01d09a9..2204cf2 100644 --- a/src/request/dto/create-request.dto.ts +++ b/src/request/dto/create-request.dto.ts @@ -85,4 +85,8 @@ export class RequestFilterDto { @IsOptional() @IsString() // Validate that orderBy is a string orderBy?: string; + + @IsOptional() + @IsEnum(RequestTypeEnum, { each: true }) // Validate that request type is a valid enum value + type?: RequestTypeEnum; } diff --git a/src/request/request.controller.ts b/src/request/request.controller.ts index cb3f548..8d9e099 100644 --- a/src/request/request.controller.ts +++ b/src/request/request.controller.ts @@ -7,8 +7,10 @@ import { Patch, Query, HttpStatus, - HttpException, + Logger, + Res, } from '@nestjs/common'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { RequestService } from './request.service'; import { CreateRequestDto, @@ -16,28 +18,41 @@ import { RequestStatusDto, } from './dto/create-request.dto'; import { UpdateRequestDto } from './dto/update-request.dto'; -import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ResponseRequestDto } from './dto/response-request.dto'; @Controller('requests') @ApiTags('requests') export class RequestController { + private readonly logger = new Logger(RequestController.name); + constructor(private readonly requestService: RequestService) {} @Post() @ApiOperation({ summary: 'Create a request' }) @ApiResponse({ status: HttpStatus.CREATED, type: ResponseRequestDto }) - async createRequest(@Body() createRequestDto: CreateRequestDto) { + async createRequest(@Res() res, @Body() createRequestDto: CreateRequestDto) { try { + this.logger.log( + `Creating a new request for user id #${createRequestDto.userId}`, + ); const request = await this.requestService.createRequest(createRequestDto); - console.log('request:', request); - return { message: 'Request created successfully', data: request }; + + this.logger.log( + `Successfully created a new request for user id #${request.userId}, request id ${request.requestId}`, + ); + + return res + .status(HttpStatus.CREATED) + .json({ message: 'Request created successfully', data: request }); } catch (error) { - console.log('error:', error); - throw new HttpException( - 'Failed to create the request', - HttpStatus.INTERNAL_SERVER_ERROR, + this.logger.error( + `Failed to create new request for user id #${createRequestDto.userId}`, + error, ); + + return res.status(HttpStatus.CREATED).json({ + message: `Failed to create the request for user id #${createRequestDto.userId}`, + }); } } @@ -48,15 +63,22 @@ export class RequestController { type: ResponseRequestDto, isArray: true, }) - async getAllRequestsForAdmin(@Query() filter: RequestFilterDto) { + async getAllRequests(@Res() res, @Query() filter: RequestFilterDto) { try { - const requests = await this.requestService.getAllRequestsForAdmin(filter); - return { message: 'Requests fetched successfully', data: requests }; + this.logger.log(`Initiated fetching requests`); + + const requests = await this.requestService.getAllRequests(filter); + + this.logger.log(`Successfully fetched requests`); + + return res + .status(HttpStatus.OK) + .json({ message: 'Successfully fetched requests', data: requests }); } catch (error) { - throw new HttpException( - 'Failed to fetch requests', - HttpStatus.INTERNAL_SERVER_ERROR, - ); + this.logger.error(`Failed to fetch requests`, error); + return res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ message: 'Failed to fetch requests' }); } } @@ -68,35 +90,67 @@ export class RequestController { isArray: true, }) async getAllRequestsForUser( + @Res() res, @Param('userId') userId: number, @Query() filter: RequestFilterDto, ) { try { + this.logger.log( + `Initiated fetching user requests for user id #${userId}`, + ); + const requests = await this.requestService.getAllRequestsForUser( - userId, + +userId, filter, ); - return { message: 'User requests fetched successfully', data: requests }; + + this.logger.log( + `Successfully fetched user requests for user id #${userId}`, + ); + + return res.status(HttpStatus.OK).json({ + message: `Successfully fetched user requests for user id #${userId}`, + data: requests, + }); } catch (error) { - throw new HttpException( - 'Failed to fetch user requests', - HttpStatus.INTERNAL_SERVER_ERROR, + this.logger.error( + `Failed to fetch user requests for user id #${userId}`, + error, ); + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: `Failed to fetch user requests for user id #${userId}`, + }); } } @Get(':requestId') @ApiOperation({ summary: 'Get a request' }) @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) - async getRequestById(@Param('requestId') requestId: number) { + async getRequestById(@Res() res, @Param('requestId') requestId: number) { try { - const request = await this.requestService.getRequestById(requestId); - return { message: 'Request fetched successfully', data: request }; + this.logger.log( + `Initiated fetching request for request id #${requestId}`, + ); + + const request = await this.requestService.getRequestById(+requestId); + + this.logger.log( + `Successfully fetched request for request id #${requestId}`, + ); + + return res.status(HttpStatus.OK).json({ + message: `Successfully fetched request for request id #${requestId}`, + data: request, + }); } catch (error) { - throw new HttpException( - 'Failed to fetch the request', - HttpStatus.INTERNAL_SERVER_ERROR, + this.logger.error( + `Failed to fetch request for request id #${requestId}`, + error, ); + + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: `Failed to fetch request for request id #${requestId}`, + }); } } @@ -104,20 +158,37 @@ export class RequestController { @ApiOperation({ summary: 'Update a request' }) @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) async updateRequestByRequestId( + @Res() res, @Param('requestId') requestId: number, @Body() updateRequestDto: UpdateRequestDto, ) { try { + this.logger.log( + `Initiated updating request for request id #${requestId}`, + ); + const updatedRequest = await this.requestService.updateRequestByRequestId( - requestId, + +requestId, updateRequestDto, ); - return { message: 'Request updated successfully', data: updatedRequest }; + + this.logger.log( + `Successfully updated request for request id #${requestId}`, + ); + + return res.status(HttpStatus.OK).json({ + message: `Successfully updated request for request id #${requestId}`, + data: updatedRequest, + }); } catch (error) { - throw new HttpException( - 'Failed to update the request', - HttpStatus.INTERNAL_SERVER_ERROR, + this.logger.error( + `Failed to update request for request id #${requestId}`, + error, ); + + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: `Failed to updated request for request id #${requestId}`, + }); } } @@ -125,23 +196,45 @@ export class RequestController { @ApiOperation({ summary: 'Update a request status' }) @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) async updateRequestStatus( + @Res() res, @Param('requestId') requestId: number, - @Body() updateRequestDtoStatus: RequestStatusDto, + @Body() updateRequestStatusDto: RequestStatusDto, ) { try { + this.logger.log( + `Initiated updating request status to ${JSON.stringify( + updateRequestStatusDto, + )} for request id #${requestId}`, + ); + const updatedRequest = await this.requestService.updateRequestStatus( - requestId, - updateRequestDtoStatus, + +requestId, + updateRequestStatusDto, ); - return { - message: 'Request status updated successfully', + + this.logger.log( + `Successfully updated request status to ${JSON.stringify( + updateRequestStatusDto, + )} for request id #${requestId}`, + ); + + return res.status(HttpStatus.OK).json({ + message: `Successfully updated request status for request id #${requestId}`, data: updatedRequest, - }; + }); } catch (error) { - throw new HttpException( - 'Failed to update the request status', - HttpStatus.INTERNAL_SERVER_ERROR, + this.logger.error( + `Failed to update request status to ${JSON.stringify( + updateRequestStatusDto, + )} for request id #${requestId}`, + error, ); + + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: `Failed to updated request status to ${JSON.stringify( + updateRequestStatusDto, + )} for request id #${requestId}`, + }); } } } diff --git a/src/request/request.service.ts b/src/request/request.service.ts index cdb389f..6e19fba 100644 --- a/src/request/request.service.ts +++ b/src/request/request.service.ts @@ -20,26 +20,28 @@ export class RequestService { }); } - async getAllRequestsForAdmin(filter: RequestFilterDto) { - const { status, limit, offset, orderBy } = filter; + async getAllRequests(filter: RequestFilterDto) { + const { status, type, limit = 10, offset = 0, orderBy } = filter; return this.prisma.request.findMany({ where: { - status: status || undefined, // Optional status filter + status: status ?? undefined, // Optional status filter + type: type ?? undefined, // Optional type filter }, orderBy: { [orderBy || 'createdAt']: 'asc', // Default sorting by createdAt }, - skip: offset, - take: limit, + skip: +offset, + take: +limit, }); } async getAllRequestsForUser(userId: number, filter: any) { - const { status, limit, offset, orderBy } = filter; + const { status, type, limit = 10, offset = 0, orderBy } = filter; return this.prisma.request.findMany({ where: { userId: userId, - status: status || undefined, // Optional status filter + status: status ?? undefined, // Optional status filter + type: type ?? undefined, // Optional type filter }, orderBy: { [orderBy || 'createdAt']: 'asc', // Default sorting by createdAt From 11a129f5108ffeb7d3f321ec06feb395c4c6dd86 Mon Sep 17 00:00:00 2001 From: Ajay Maurya Date: Thu, 21 Sep 2023 12:36:30 +0530 Subject: [PATCH 15/26] Added comments and minor updates --- src/app.controller.spec.ts | 6 +++-- src/main.ts | 4 +-- src/request/dto/create-request.dto.ts | 35 +++++++++++++++++++++---- src/request/dto/response-request.dto.ts | 22 ++++++++-------- src/request/request.controller.ts | 33 ++++++++++++++--------- src/request/request.module.ts | 7 +++++ src/request/request.service.ts | 13 +++++---- src/utils/constants.ts | 4 ++- 8 files changed, 86 insertions(+), 38 deletions(-) diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts index d22f389..2b9d42f 100644 --- a/src/app.controller.spec.ts +++ b/src/app.controller.spec.ts @@ -15,8 +15,10 @@ describe('AppController', () => { }); describe('root', () => { - it('should return "Hello World!"', () => { - expect(appController.getHello()).toBe('Hello World!'); + it('should return "marketplace query service running successfully"', () => { + expect(appController.getHealth()).toBe( + 'marketplace query service running successfully', + ); }); }); }); diff --git a/src/main.ts b/src/main.ts index a32dcf3..cff42bc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -35,7 +35,7 @@ async function bootstrap() { .addApiKey( { type: 'apiKey', - name: 'x-thats-my-college-api-config-key', + name: 'x-marketplace-request-service-api-config-key', in: 'header', }, SWAGGER_CONSTANTS.SWAGGER_AUTH_SECURITY_SCHEMA_API_KEY, // API key security scheme name @@ -52,7 +52,7 @@ async function bootstrap() { .setTitle(SWAGGER_CONSTANTS.TITLE) .setDescription(SWAGGER_CONSTANTS.DESCRIPTION) .setVersion(SWAGGER_CONSTANTS.VERSION) - // .addTag(SWAGGER_TAGS. ) // Add a tag for API grouping + .addTag(SWAGGER_TAGS.REQUEST) // Add a tag for API grouping .build(); diff --git a/src/request/dto/create-request.dto.ts b/src/request/dto/create-request.dto.ts index 2204cf2..5c44f08 100644 --- a/src/request/dto/create-request.dto.ts +++ b/src/request/dto/create-request.dto.ts @@ -9,84 +9,109 @@ import { IsString, } from 'class-validator'; +// CreateRequestDto is used for creating a new request. export class CreateRequestDto { + // User ID associated with the request. @IsNotEmpty() @IsInt() userId: number; + // Title of the request. @IsNotEmpty() @IsString() title: string; + // Status of the request, should be one of the valid RequestStatusEnum values. @IsNotEmpty() @IsEnum(RequestStatusEnum) status: RequestStatusEnum; + // Description of the request. @IsNotEmpty() @IsString() description: string; + // Type of the request, should be one of the valid RequestTypeEnum values. @IsNotEmpty() @IsEnum(RequestTypeEnum) type: RequestTypeEnum; + // Optional content of the request in JSON format. @IsOptional() + // Use the @Transform decorator to apply a custom transformation to a property. @Transform(({ value }: TransformFnParams) => { try { + // Try to parse the input value as JSON to convert it into a JavaScript object. return JSON.parse(value); } catch (error) { + // If parsing fails (e.g., if value is not a valid JSON string), return the original value. return value; } }) requestContent?: object; + // Optional content of the response in JSON format. @IsOptional() + // Use the @Transform decorator to apply a custom transformation to a property. @Transform(({ value }: TransformFnParams) => { try { + // Try to parse the input value as JSON to convert it into a JavaScript object. return JSON.parse(value); } catch (error) { + // If parsing fails (e.g., if value is not a valid JSON string), return the original value. return value; } }) responseContent?: object; + // Optional remark or additional information about the request. @IsOptional() @IsString() remark?: string; + // Optional creation date of the request. @IsDate() @IsOptional() createdAt?: Date; + // Optional update date of the request. @IsDate() @IsOptional() updatedAt?: Date; } +// RequestStatusDto is used for updating the status of a request. export class RequestStatusDto { + // New status for the request, should be one of the valid RequestStatusEnum values. @IsNotEmpty() @IsEnum(RequestStatusEnum) status: RequestStatusEnum; } +// RequestFilterDto is used for filtering and paginating requests. export class RequestFilterDto { + // Optional status filter, validate that it's a valid enum value. @IsOptional() - @IsEnum(RequestStatusEnum, { each: true }) // Validate that status is a valid enum value + @IsEnum(RequestStatusEnum, { each: true }) status?: RequestStatusEnum; + // Optional limit for pagination, validate that it's an integer. @IsOptional() - @IsInt() // Validate that limit is an integer + @IsInt() limit?: number; + // Optional offset for pagination, validate that it's an integer. @IsOptional() - @IsInt() // Validate that offset is an integer + @IsInt() offset?: number; + // Optional field to specify the order of results, validate that it's a string. @IsOptional() - @IsString() // Validate that orderBy is a string + @IsString() orderBy?: string; + // Optional request type filter, validate that it's a valid enum value. @IsOptional() - @IsEnum(RequestTypeEnum, { each: true }) // Validate that request type is a valid enum value + @IsEnum(RequestTypeEnum, { each: true }) type?: RequestTypeEnum; } diff --git a/src/request/dto/response-request.dto.ts b/src/request/dto/response-request.dto.ts index 2c98010..f614f35 100644 --- a/src/request/dto/response-request.dto.ts +++ b/src/request/dto/response-request.dto.ts @@ -1,15 +1,15 @@ import { RequestStatusEnum, RequestTypeEnum } from '../enum/request.enum'; export class ResponseRequestDto { - requestId: number; - userId: number; - title: string; - type: RequestTypeEnum; - description: string; - status: RequestStatusEnum; - requestContent: object; - responseContent: object; - remark: string; - createdAt: Date; - updatedAt: Date; + readonly requestId: number; + readonly userId: number; + readonly title: string; + readonly type: RequestTypeEnum; + readonly description: string; + readonly status: RequestStatusEnum; + readonly requestContent: object; + readonly responseContent: object; + readonly remark: string; + readonly createdAt: Date; + readonly updatedAt: Date; } diff --git a/src/request/request.controller.ts b/src/request/request.controller.ts index 8d9e099..54949db 100644 --- a/src/request/request.controller.ts +++ b/src/request/request.controller.ts @@ -23,33 +23,41 @@ import { ResponseRequestDto } from './dto/response-request.dto'; @Controller('requests') @ApiTags('requests') export class RequestController { + // Create a logger instance for this controller to log events and errors. private readonly logger = new Logger(RequestController.name); + // Constructor for the RequestController class, injecting the RequestService. constructor(private readonly requestService: RequestService) {} @Post() - @ApiOperation({ summary: 'Create a request' }) - @ApiResponse({ status: HttpStatus.CREATED, type: ResponseRequestDto }) + @ApiOperation({ summary: 'Create a request' }) // Describes the api operation for Swagger. + @ApiResponse({ status: HttpStatus.CREATED, type: ResponseRequestDto }) // Describes the response for Swagger. async createRequest(@Res() res, @Body() createRequestDto: CreateRequestDto) { try { + // Log the initiation of request creation this.logger.log( `Creating a new request for user id #${createRequestDto.userId}`, ); + const request = await this.requestService.createRequest(createRequestDto); + // Log the successful creation of the request this.logger.log( `Successfully created a new request for user id #${request.userId}, request id ${request.requestId}`, ); + // Return a success response with the created request data return res .status(HttpStatus.CREATED) .json({ message: 'Request created successfully', data: request }); } catch (error) { + // Log the error if request creation fails this.logger.error( `Failed to create new request for user id #${createRequestDto.userId}`, error, ); + // Return an error response return res.status(HttpStatus.CREATED).json({ message: `Failed to create the request for user id #${createRequestDto.userId}`, }); @@ -57,12 +65,12 @@ export class RequestController { } @Get('') - @ApiOperation({ summary: 'Get all requests' }) + @ApiOperation({ summary: 'Get all requests' }) // Describes the api operation for Swagger. @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto, isArray: true, - }) + }) // Describes the response for Swagger. async getAllRequests(@Res() res, @Query() filter: RequestFilterDto) { try { this.logger.log(`Initiated fetching requests`); @@ -83,12 +91,12 @@ export class RequestController { } @Get('user/:userId') - @ApiOperation({ summary: 'Get all requests of a user' }) + @ApiOperation({ summary: 'Get all requests of a user' }) // Describes the api operation for Swagger. @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto, isArray: true, - }) + }) // Describes the response for Swagger. async getAllRequestsForUser( @Res() res, @Param('userId') userId: number, @@ -117,6 +125,7 @@ export class RequestController { `Failed to fetch user requests for user id #${userId}`, error, ); + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ message: `Failed to fetch user requests for user id #${userId}`, }); @@ -124,8 +133,8 @@ export class RequestController { } @Get(':requestId') - @ApiOperation({ summary: 'Get a request' }) - @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) + @ApiOperation({ summary: 'Get a request' }) // Describes the api operation for Swagger. + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) // Describes the response for Swagger. async getRequestById(@Res() res, @Param('requestId') requestId: number) { try { this.logger.log( @@ -155,8 +164,8 @@ export class RequestController { } @Patch('update/:requestId') - @ApiOperation({ summary: 'Update a request' }) - @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) + @ApiOperation({ summary: 'Update a request' }) // Describes the api operation for Swagger. + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) // Describes the response for Swagger. async updateRequestByRequestId( @Res() res, @Param('requestId') requestId: number, @@ -193,8 +202,8 @@ export class RequestController { } @Patch('update/status/:requestId') - @ApiOperation({ summary: 'Update a request status' }) - @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) + @ApiOperation({ summary: 'Update a request status' }) // Describes the api operation for Swagger. + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) // Describes the response for Swagger. async updateRequestStatus( @Res() res, @Param('requestId') requestId: number, diff --git a/src/request/request.module.ts b/src/request/request.module.ts index 83a1df1..91c477b 100644 --- a/src/request/request.module.ts +++ b/src/request/request.module.ts @@ -5,9 +5,16 @@ import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaModule } from 'src/prisma/prisma.module'; @Module({ + // Import the PrismaModule to make Prisma service available within this module imports: [PrismaModule], + + // Declare the RequestController as a controller for this module controllers: [RequestController], + + // Declare the RequestService and PrismaService as providers for this module providers: [RequestService, PrismaService], + + // Export the RequestService to make it available for other modules that import this module exports: [RequestService], }) export class RequestModule {} diff --git a/src/request/request.service.ts b/src/request/request.service.ts index 6e19fba..8646ce7 100644 --- a/src/request/request.service.ts +++ b/src/request/request.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; // Import your Prisma service import { CreateRequestDto, @@ -9,17 +9,16 @@ import { UpdateRequestDto } from './dto/update-request.dto'; @Injectable() export class RequestService { - constructor( - @Inject(forwardRef(() => PrismaService)) - private prisma: PrismaService, - ) {} + constructor(private prisma: PrismaService) {} + // Create a new request async createRequest(createRequestDto: CreateRequestDto) { return this.prisma.request.create({ data: createRequestDto, }); } + // Get all requests with optional filters and pagination async getAllRequests(filter: RequestFilterDto) { const { status, type, limit = 10, offset = 0, orderBy } = filter; return this.prisma.request.findMany({ @@ -35,6 +34,7 @@ export class RequestService { }); } + // Get all requests for a specific user with optional filters and pagination async getAllRequestsForUser(userId: number, filter: any) { const { status, type, limit = 10, offset = 0, orderBy } = filter; return this.prisma.request.findMany({ @@ -51,6 +51,7 @@ export class RequestService { }); } + // Get a request by its ID async getRequestById(requestId: number) { return this.prisma.request.findUnique({ where: { @@ -59,6 +60,7 @@ export class RequestService { }); } + // Update a request by its ID async updateRequestByRequestId( requestId: number, updateRequestDto: UpdateRequestDto, @@ -71,6 +73,7 @@ export class RequestService { }); } + // Update the status of a request by its ID async updateRequestStatus( requestId: number, updateRequestStatusDto: RequestStatusDto, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 2881a49..ad17a45 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -11,4 +11,6 @@ export const SWAGGER_CONSTANTS = { SWAGGER_AUTH_SECURITY_SCHEMA_JWT: 'jwt', }; -export enum SWAGGER_TAGS {} +export enum SWAGGER_TAGS { + REQUEST = 'request', +} From df648e8b1be0e40e7a6e9f9e10b5d3edc0072ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CVamshiBatta=E2=80=9D?= <“batta.v@esmagico.in”> Date: Tue, 3 Oct 2023 11:57:46 +0530 Subject: [PATCH 16/26] Fix: Removing "Refund" capability from the app. --- prisma/schema.prisma | 1 - src/request/enum/request.enum.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0703dab..523c3bc 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -39,7 +39,6 @@ model Request { } enum RequestTypeEnum { - REFUND CREDIT INVOICE_REQUEST SETTLEMENT diff --git a/src/request/enum/request.enum.ts b/src/request/enum/request.enum.ts index 884355b..25e5a40 100644 --- a/src/request/enum/request.enum.ts +++ b/src/request/enum/request.enum.ts @@ -1,5 +1,4 @@ export enum RequestTypeEnum { - REFUND, CREDIT, INVOICE_REQUEST, SETTLEMENT, From da79a8582f790fea3c8b835c7a02800e9c98f5c5 Mon Sep 17 00:00:00 2001 From: Ajay Maurya Date: Tue, 3 Oct 2023 13:23:29 +0530 Subject: [PATCH 17/26] Error handling modified --- src/request/dto/update-request.dto.ts | 83 ++++++++++++++++++++++++++- src/request/request.controller.ts | 37 +++++++----- src/request/request.service.ts | 8 ++- 3 files changed, 108 insertions(+), 20 deletions(-) diff --git a/src/request/dto/update-request.dto.ts b/src/request/dto/update-request.dto.ts index bac9f9f..c3bff54 100644 --- a/src/request/dto/update-request.dto.ts +++ b/src/request/dto/update-request.dto.ts @@ -1,4 +1,81 @@ -import { PartialType } from '@nestjs/mapped-types'; -import { CreateRequestDto } from './create-request.dto'; +import { + IsDate, + IsEnum, + IsNotEmpty, + IsOptional, + IsString, +} from 'class-validator'; +import { RequestStatusEnum, RequestTypeEnum } from '@prisma/client'; +import { Transform, TransformFnParams } from 'class-transformer'; -export class UpdateRequestDto extends PartialType(CreateRequestDto) {} +export class UpdateRequestDto { + // Title of the request. + @IsOptional() + @IsNotEmpty() + @IsString() + title: string; + + // Status of the request, should be one of the valid RequestStatusEnum values. + @IsOptional() + @IsNotEmpty() + @IsEnum(RequestStatusEnum) + status: RequestStatusEnum; + + // Description of the request. + @IsOptional() + @IsString() + @IsNotEmpty() + description: string; + + // Type of the request, should be one of the valid RequestTypeEnum values. + @IsNotEmpty() + @IsOptional() + @IsEnum(RequestTypeEnum) + type: RequestTypeEnum; + + // Optional content of the request in JSON format. + @IsNotEmpty() + @IsOptional() + // Use the @Transform decorator to apply a custom transformation to a property. + @Transform(({ value }: TransformFnParams) => { + try { + // Try to parse the input value as JSON to convert it into a JavaScript object. + return JSON.parse(value); + } catch (error) { + // If parsing fails (e.g., if value is not a valid JSON string), return the original value. + return value; + } + }) + requestContent?: object; + + // Optional content of the response in JSON format. + @IsNotEmpty() + @IsOptional() + // Use the @Transform decorator to apply a custom transformation to a property. + @Transform(({ value }: TransformFnParams) => { + try { + // Try to parse the input value as JSON to convert it into a JavaScript object. + return JSON.parse(value); + } catch (error) { + // If parsing fails (e.g., if value is not a valid JSON string), return the original value. + return value; + } + }) + responseContent?: object; + + // Optional remark or additional information about the request. + @IsOptional() + @IsNotEmpty() + @IsString() + remark?: string; + + // Optional creation date of the request. + @IsDate() + @IsOptional() + createdAt?: Date; + + // Optional update date of the request. + @IsDate() + @IsOptional() + updatedAt?: Date; +} diff --git a/src/request/request.controller.ts b/src/request/request.controller.ts index 54949db..5aaca68 100644 --- a/src/request/request.controller.ts +++ b/src/request/request.controller.ts @@ -65,12 +65,12 @@ export class RequestController { } @Get('') - @ApiOperation({ summary: 'Get all requests' }) // Describes the api operation for Swagger. + @ApiOperation({ summary: 'Get all requests' }) @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto, isArray: true, - }) // Describes the response for Swagger. + }) async getAllRequests(@Res() res, @Query() filter: RequestFilterDto) { try { this.logger.log(`Initiated fetching requests`); @@ -91,12 +91,12 @@ export class RequestController { } @Get('user/:userId') - @ApiOperation({ summary: 'Get all requests of a user' }) // Describes the api operation for Swagger. + @ApiOperation({ summary: 'Get all requests of a user' }) @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto, isArray: true, - }) // Describes the response for Swagger. + }) async getAllRequestsForUser( @Res() res, @Param('userId') userId: number, @@ -133,8 +133,8 @@ export class RequestController { } @Get(':requestId') - @ApiOperation({ summary: 'Get a request' }) // Describes the api operation for Swagger. - @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) // Describes the response for Swagger. + @ApiOperation({ summary: 'Get a request' }) + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) async getRequestById(@Res() res, @Param('requestId') requestId: number) { try { this.logger.log( @@ -158,14 +158,16 @@ export class RequestController { ); return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ - message: `Failed to fetch request for request id #${requestId}`, + message: + error.message || + `Failed to fetch request for request id #${requestId}`, }); } } @Patch('update/:requestId') - @ApiOperation({ summary: 'Update a request' }) // Describes the api operation for Swagger. - @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) // Describes the response for Swagger. + @ApiOperation({ summary: 'Update a request' }) + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) async updateRequestByRequestId( @Res() res, @Param('requestId') requestId: number, @@ -190,20 +192,23 @@ export class RequestController { data: updatedRequest, }); } catch (error) { + console.log('error:', error); this.logger.error( `Failed to update request for request id #${requestId}`, error, ); return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ - message: `Failed to updated request for request id #${requestId}`, + message: + error.meta.cause || + `Failed to updated request for request id #${requestId}`, }); } } @Patch('update/status/:requestId') - @ApiOperation({ summary: 'Update a request status' }) // Describes the api operation for Swagger. - @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) // Describes the response for Swagger. + @ApiOperation({ summary: 'Update a request status' }) + @ApiResponse({ status: HttpStatus.OK, type: ResponseRequestDto }) async updateRequestStatus( @Res() res, @Param('requestId') requestId: number, @@ -240,9 +245,11 @@ export class RequestController { ); return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ - message: `Failed to updated request status to ${JSON.stringify( - updateRequestStatusDto, - )} for request id #${requestId}`, + message: + error.meta.cause || + `Failed to updated request status to ${JSON.stringify( + updateRequestStatusDto, + )} for request id #${requestId}`, }); } } diff --git a/src/request/request.service.ts b/src/request/request.service.ts index 8646ce7..a77234f 100644 --- a/src/request/request.service.ts +++ b/src/request/request.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; // Import your Prisma service import { CreateRequestDto, @@ -53,11 +53,15 @@ export class RequestService { // Get a request by its ID async getRequestById(requestId: number) { - return this.prisma.request.findUnique({ + const request = await this.prisma.request.findUnique({ where: { requestId: requestId, }, }); + if (!request) { + throw new NotFoundException(`Request not found with id #${requestId}`); + } + return request; } // Update a request by its ID From 5eea7e716d2c9cd521f16f6c3e066ff660556f73 Mon Sep 17 00:00:00 2001 From: Ajay Maurya Date: Tue, 3 Oct 2023 13:33:10 +0530 Subject: [PATCH 18/26] added filter dto --- src/request/request.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/request/request.service.ts b/src/request/request.service.ts index a77234f..bf20357 100644 --- a/src/request/request.service.ts +++ b/src/request/request.service.ts @@ -35,7 +35,7 @@ export class RequestService { } // Get all requests for a specific user with optional filters and pagination - async getAllRequestsForUser(userId: number, filter: any) { + async getAllRequestsForUser(userId: number, filter: RequestFilterDto) { const { status, type, limit = 10, offset = 0, orderBy } = filter; return this.prisma.request.findMany({ where: { From f5262f5fd362e0ab4f7487b52e34ac21d0a4d364 Mon Sep 17 00:00:00 2001 From: Prashant Kumar Date: Wed, 4 Oct 2023 14:26:56 +0530 Subject: [PATCH 19/26] Changes as per PR review --- prisma/schema.prisma | 26 ++++++------- src/settlement/dto/create-settlement.dto.ts | 11 +++--- src/settlement/dto/update-settlement.dto.ts | 41 +++++++++++++++++++-- src/settlement/settlement.controller.ts | 12 +++--- 4 files changed, 63 insertions(+), 27 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c1db1ab..9783f5c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -57,19 +57,19 @@ model Request { } model Settlement { - settlementId Int @id @default(autoincrement()) - requestId Int - userId Int - adminId Int - requestStatus SettlementStatusEnum + settlementId Int @id @default(autoincrement()) + requestId Int + userId Int + adminId Int + requestStatus SettlementStatusEnum thirdPartyResponseStatus thirdPartyResponseStatusEnum - transactionId Int? - content Json? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - user User @relation(fields: [userId], references: [id]) // Specify fields and references here - admin Admin @relation(fields: [adminId], references: [id]) // Specify Admin field reference here - request Request @relation(fields: [requestId], references: [requestId]) + transactionId Int + content Json? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id]) // Specify fields and references here + admin Admin @relation(fields: [adminId], references: [id]) // Specify Admin field reference here + request Request @relation(fields: [requestId], references: [requestId]) } enum RequestTypeEnum { @@ -94,4 +94,4 @@ enum SettlementStatusEnum { enum thirdPartyResponseStatusEnum { PENDING VERIFIED -} +} \ No newline at end of file diff --git a/src/settlement/dto/create-settlement.dto.ts b/src/settlement/dto/create-settlement.dto.ts index a3d0a3f..0ce04f4 100644 --- a/src/settlement/dto/create-settlement.dto.ts +++ b/src/settlement/dto/create-settlement.dto.ts @@ -5,7 +5,6 @@ import { IsEnum, IsInt, IsNotEmpty, - IsNumber, IsOptional, IsString, } from 'class-validator'; @@ -21,7 +20,7 @@ export class CreateSettlementDto { @IsNotEmpty() @IsInt() userId: number; - + // Admin Id associated with the Admin @IsNotEmpty() @IsInt() @@ -37,10 +36,10 @@ export class CreateSettlementDto { @IsEnum(thirdPartyResponseStatusEnum) thirdPartyResponseStatus: thirdPartyResponseStatusEnum; - // Optional content of the request for the transaction ID. - @IsOptional() - @IsNumber() - transactionID: number; + // Mandatory field transaction ID for the settlement request. + @IsNotEmpty() + @IsInt() + transactionId: number; // Optional content of the request in JSON format. @IsOptional() diff --git a/src/settlement/dto/update-settlement.dto.ts b/src/settlement/dto/update-settlement.dto.ts index 64bdde7..e49c765 100644 --- a/src/settlement/dto/update-settlement.dto.ts +++ b/src/settlement/dto/update-settlement.dto.ts @@ -1,4 +1,39 @@ -import { PartialType } from '@nestjs/mapped-types'; -import { CreateSettlementDto } from './create-settlement.dto'; +import { SettlementStatusEnum, thirdPartyResponseStatusEnum } from "@prisma/client"; +import { Transform, TransformFnParams } from "class-transformer"; +import { IsDate, IsEnum, IsNotEmpty, IsNumber, IsOptional } from "class-validator"; -export class UpdateSettlementDto extends PartialType(CreateSettlementDto) {} +export class UpdateSettlementDto { + // Status of the request, should be one of the valid SettlementStatusEnum values. + @IsNotEmpty() + @IsEnum(SettlementStatusEnum) + requestStatus: SettlementStatusEnum; + + // Status of the request, should be one of the valid UserResponseStatusEnum values. + @IsNotEmpty() + @IsEnum(thirdPartyResponseStatusEnum) + thirdPartyResponseStatus: thirdPartyResponseStatusEnum; + + // Optional content of the request in JSON format. + @IsOptional() + // Use the @Transform decorator to apply a custom transformation to a property. + @Transform(({ value }: TransformFnParams) => { + try { + // Try to parse the input value as JSON to convert it into a JavaScript object. + return JSON.parse(value); + } catch (error) { + // If parsing fails (e.g., if value is not a valid JSON string), return the original value. + return value; + } + }) + content?: object; + + // Optional creation date of the request. + @IsDate() + @IsOptional() + createdAt?: Date; + + // Optional update date of the request. + @IsDate() + @IsOptional() + updatedAt?: Date; +} diff --git a/src/settlement/settlement.controller.ts b/src/settlement/settlement.controller.ts index 03aee9e..41e0520 100644 --- a/src/settlement/settlement.controller.ts +++ b/src/settlement/settlement.controller.ts @@ -5,7 +5,6 @@ import { Body, Patch, Param, - Delete, Query, Logger, HttpStatus, @@ -139,7 +138,8 @@ export class SettlementController { ); return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ - message: `Failed to fetch user settlements for user id #${userId}`, + message: + error.message || `Failed to fetch request for request id #${userId}`, }); } } @@ -166,14 +166,15 @@ export class SettlementController { message: `Successfully fetched settlement for request id #${requestId}`, data: settlement, }); - } catch (error) { + } catch (error) { this.logger.error( `Failed to fetch settlement for request id #${requestId}`, error, ); return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ - message: `Failed to fetch settlement for request id #${requestId}`, + message: + error.message || `Failed to fetch request for request id #${requestId}`, }); } } @@ -212,7 +213,8 @@ export class SettlementController { ); return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ - message: `Failed to update settlement request for request id #${requestId}`, + message: + error.message || `Failed to fetch request for request id #${requestId}`, }); } } From 57017737618bda0dad9b1c08574a5da7be798399 Mon Sep 17 00:00:00 2001 From: Prashant Kumar Date: Wed, 4 Oct 2023 16:38:49 +0530 Subject: [PATCH 20/26] Refactor and changes as per PR review --- prisma/schema.prisma | 6 +++--- src/main.ts | 4 ++-- src/utils/constants.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9783f5c..c1a9572 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -53,12 +53,12 @@ model Request { user User @relation(fields: [userId], references: [id]) // Specify fields and references here Admin Admin? @relation(fields: [adminId], references: [id]) adminId Int? - Settlement Settlement[] + Settlement Settlement? } model Settlement { settlementId Int @id @default(autoincrement()) - requestId Int + requestId Int @unique userId Int adminId Int requestStatus SettlementStatusEnum @@ -94,4 +94,4 @@ enum SettlementStatusEnum { enum thirdPartyResponseStatusEnum { PENDING VERIFIED -} \ No newline at end of file +} diff --git a/src/main.ts b/src/main.ts index c403bfd..62eff54 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,7 +21,7 @@ async function bootstrap() { .addApiKey( { type: 'apiKey', - name: 'x-marketplace-request-service-api-config-key', + name: 'x-marketplace-settlement-service-api-config-key', in: 'header', }, SWAGGER_CONSTANTS.SWAGGER_AUTH_SECURITY_SCHEMA_API_KEY, // API key security scheme name @@ -38,7 +38,7 @@ async function bootstrap() { .setTitle(SWAGGER_CONSTANTS.TITLE) .setDescription(SWAGGER_CONSTANTS.DESCRIPTION) .setVersion(SWAGGER_CONSTANTS.VERSION) - .addTag(SWAGGER_TAGS.REQUEST) // Add a tag for API grouping + .addTag(SWAGGER_TAGS.SETTLEMENT) // Add a tag for API grouping .build(); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index ad17a45..32747ac 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -4,7 +4,7 @@ export const CONSTANTS = { export const SWAGGER_CONSTANTS = { TITLE: 'API', - DESCRIPTION: 'Marketplace Query Service API description', + DESCRIPTION: 'Marketplace Settlement Request API description', VERSION: '1.0', PATH: 'api/doc', SWAGGER_AUTH_SECURITY_SCHEMA_API_KEY: 'api-config-key', @@ -12,5 +12,5 @@ export const SWAGGER_CONSTANTS = { }; export enum SWAGGER_TAGS { - REQUEST = 'request', + SETTLEMENT = 'settlement', } From 93f129d1e1d74ffce392d52341309a014ad8e976 Mon Sep 17 00:00:00 2001 From: Prashant Kumar Date: Wed, 4 Oct 2023 17:00:15 +0530 Subject: [PATCH 21/26] Refactor and changes as per review --- src/main.ts | 3 ++- src/utils/constants.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main.ts b/src/main.ts index 62eff54..c2d71c8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,7 +21,7 @@ async function bootstrap() { .addApiKey( { type: 'apiKey', - name: 'x-marketplace-settlement-service-api-config-key', + name: 'x-marketplace-request-service-api-config-key', in: 'header', }, SWAGGER_CONSTANTS.SWAGGER_AUTH_SECURITY_SCHEMA_API_KEY, // API key security scheme name @@ -38,6 +38,7 @@ async function bootstrap() { .setTitle(SWAGGER_CONSTANTS.TITLE) .setDescription(SWAGGER_CONSTANTS.DESCRIPTION) .setVersion(SWAGGER_CONSTANTS.VERSION) + .addTag(SWAGGER_TAGS.REQUEST) // Add a tag for API grouping .addTag(SWAGGER_TAGS.SETTLEMENT) // Add a tag for API grouping .build(); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 32747ac..e94c7d0 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -4,7 +4,7 @@ export const CONSTANTS = { export const SWAGGER_CONSTANTS = { TITLE: 'API', - DESCRIPTION: 'Marketplace Settlement Request API description', + DESCRIPTION: 'Marketplace Request Service API description', VERSION: '1.0', PATH: 'api/doc', SWAGGER_AUTH_SECURITY_SCHEMA_API_KEY: 'api-config-key', @@ -12,5 +12,6 @@ export const SWAGGER_CONSTANTS = { }; export enum SWAGGER_TAGS { + REQUEST = 'request', SETTLEMENT = 'settlement', } From c7868c2d2d0de338b1ba0729d9f9f348c11dd5fe Mon Sep 17 00:00:00 2001 From: Ajay Maurya Date: Wed, 4 Oct 2023 17:16:55 +0530 Subject: [PATCH 22/26] updated --- src/utils/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/constants.ts b/src/utils/constants.ts index e94c7d0..6b7befc 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -12,6 +12,6 @@ export const SWAGGER_CONSTANTS = { }; export enum SWAGGER_TAGS { - REQUEST = 'request', + REQUEST = 'requests', SETTLEMENT = 'settlement', } From 93f42868318ce6523eefccf9a84db4e49dc14f77 Mon Sep 17 00:00:00 2001 From: Ajay Maurya Date: Wed, 4 Oct 2023 18:37:27 +0530 Subject: [PATCH 23/26] custom decorator IsSwaggerEnum created and implemented --- src/request/dto/create-request.dto.ts | 4 ++++ src/request/dto/update-request.dto.ts | 3 +++ src/settlement/dto/create-settlement.dto.ts | 8 +++++++- src/settlement/dto/update-settlement.dto.ts | 12 +++++++++--- src/settlement/enum/settlement.enum.ts | 7 +++---- src/utils/decorator/decorators.ts | 13 +++++++++++++ 6 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 src/utils/decorator/decorators.ts diff --git a/src/request/dto/create-request.dto.ts b/src/request/dto/create-request.dto.ts index 5c44f08..ab1354c 100644 --- a/src/request/dto/create-request.dto.ts +++ b/src/request/dto/create-request.dto.ts @@ -8,6 +8,7 @@ import { IsOptional, IsString, } from 'class-validator'; +import { IsSwaggerEnum } from 'src/utils/decorator/decorators'; // CreateRequestDto is used for creating a new request. export class CreateRequestDto { @@ -24,6 +25,7 @@ export class CreateRequestDto { // Status of the request, should be one of the valid RequestStatusEnum values. @IsNotEmpty() @IsEnum(RequestStatusEnum) + @IsSwaggerEnum(RequestStatusEnum) status: RequestStatusEnum; // Description of the request. @@ -34,6 +36,7 @@ export class CreateRequestDto { // Type of the request, should be one of the valid RequestTypeEnum values. @IsNotEmpty() @IsEnum(RequestTypeEnum) + @IsSwaggerEnum(RequestTypeEnum) type: RequestTypeEnum; // Optional content of the request in JSON format. @@ -85,6 +88,7 @@ export class RequestStatusDto { // New status for the request, should be one of the valid RequestStatusEnum values. @IsNotEmpty() @IsEnum(RequestStatusEnum) + @IsSwaggerEnum(RequestStatusEnum) status: RequestStatusEnum; } diff --git a/src/request/dto/update-request.dto.ts b/src/request/dto/update-request.dto.ts index c3bff54..c50456b 100644 --- a/src/request/dto/update-request.dto.ts +++ b/src/request/dto/update-request.dto.ts @@ -7,6 +7,7 @@ import { } from 'class-validator'; import { RequestStatusEnum, RequestTypeEnum } from '@prisma/client'; import { Transform, TransformFnParams } from 'class-transformer'; +import { IsSwaggerEnum } from 'src/utils/decorator/decorators'; export class UpdateRequestDto { // Title of the request. @@ -19,6 +20,7 @@ export class UpdateRequestDto { @IsOptional() @IsNotEmpty() @IsEnum(RequestStatusEnum) + @IsSwaggerEnum(RequestStatusEnum) status: RequestStatusEnum; // Description of the request. @@ -31,6 +33,7 @@ export class UpdateRequestDto { @IsNotEmpty() @IsOptional() @IsEnum(RequestTypeEnum) + @IsSwaggerEnum(RequestTypeEnum) type: RequestTypeEnum; // Optional content of the request in JSON format. diff --git a/src/settlement/dto/create-settlement.dto.ts b/src/settlement/dto/create-settlement.dto.ts index 0ce04f4..bd03660 100644 --- a/src/settlement/dto/create-settlement.dto.ts +++ b/src/settlement/dto/create-settlement.dto.ts @@ -1,4 +1,7 @@ -import { SettlementStatusEnum, thirdPartyResponseStatusEnum } from '@prisma/client'; +import { + SettlementStatusEnum, + thirdPartyResponseStatusEnum, +} from '@prisma/client'; import { Transform, TransformFnParams } from 'class-transformer'; import { IsDate, @@ -8,6 +11,7 @@ import { IsOptional, IsString, } from 'class-validator'; +import { IsSwaggerEnum } from 'src/utils/decorator/decorators'; // createSettlementDto is used for creating the new settlement request export class CreateSettlementDto { @@ -29,11 +33,13 @@ export class CreateSettlementDto { // Status of the request, should be one of the valid SettlementStatusEnum values. @IsNotEmpty() @IsEnum(SettlementStatusEnum) + @IsSwaggerEnum(SettlementStatusEnum) requestStatus: SettlementStatusEnum; // Status of the request, should be one of the valid UserResponseStatusEnum values. @IsNotEmpty() @IsEnum(thirdPartyResponseStatusEnum) + @IsSwaggerEnum(thirdPartyResponseStatusEnum) thirdPartyResponseStatus: thirdPartyResponseStatusEnum; // Mandatory field transaction ID for the settlement request. diff --git a/src/settlement/dto/update-settlement.dto.ts b/src/settlement/dto/update-settlement.dto.ts index e49c765..2da5013 100644 --- a/src/settlement/dto/update-settlement.dto.ts +++ b/src/settlement/dto/update-settlement.dto.ts @@ -1,16 +1,22 @@ -import { SettlementStatusEnum, thirdPartyResponseStatusEnum } from "@prisma/client"; -import { Transform, TransformFnParams } from "class-transformer"; -import { IsDate, IsEnum, IsNotEmpty, IsNumber, IsOptional } from "class-validator"; +import { + SettlementStatusEnum, + thirdPartyResponseStatusEnum, +} from '@prisma/client'; +import { Transform, TransformFnParams } from 'class-transformer'; +import { IsDate, IsEnum, IsNotEmpty, IsOptional } from 'class-validator'; +import { IsSwaggerEnum } from 'src/utils/decorator/decorators'; export class UpdateSettlementDto { // Status of the request, should be one of the valid SettlementStatusEnum values. @IsNotEmpty() @IsEnum(SettlementStatusEnum) + @IsSwaggerEnum(SettlementStatusEnum) requestStatus: SettlementStatusEnum; // Status of the request, should be one of the valid UserResponseStatusEnum values. @IsNotEmpty() @IsEnum(thirdPartyResponseStatusEnum) + @IsSwaggerEnum(thirdPartyResponseStatusEnum) thirdPartyResponseStatus: thirdPartyResponseStatusEnum; // Optional content of the request in JSON format. diff --git a/src/settlement/enum/settlement.enum.ts b/src/settlement/enum/settlement.enum.ts index 753a831..eb4bb64 100644 --- a/src/settlement/enum/settlement.enum.ts +++ b/src/settlement/enum/settlement.enum.ts @@ -1,10 +1,9 @@ - export enum SettlementStatusEnum { APPROVED, - REJECTED + REJECTED, } export enum thirdPartyResponseStatusEnum { PENDING, - VERIFIED -} \ No newline at end of file + VERIFIED, +} diff --git a/src/utils/decorator/decorators.ts b/src/utils/decorator/decorators.ts new file mode 100644 index 0000000..acbfc46 --- /dev/null +++ b/src/utils/decorator/decorators.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; + +// eslint-disable-next-line @typescript-eslint/ban-types +export function IsSwaggerEnum(enumObject: Record): Function { + return (target: any, propertyKey: string) => { + const values = Object.values(enumObject); + ApiProperty({ + type: enumObject, + enum: values, + example: values.join(' || '), // Set an example enum value for documentation purposes + })(target, propertyKey); + }; +} From daae9025881797ca353ddff8eacd8f5c6ebeae28 Mon Sep 17 00:00:00 2001 From: Ajay Maurya Date: Wed, 4 Oct 2023 20:03:03 +0530 Subject: [PATCH 24/26] updated IsSwaggerEnum decorator for optional fields --- prisma/schema.prisma | 3 -- src/request/dto/create-request.dto.ts | 2 + src/request/dto/response-request.dto.ts | 5 ++- src/settlement/dto/create-settlement.dto.ts | 2 + src/settlement/dto/response-settlement.dto.ts | 5 ++- src/settlement/settlement.controller.ts | 8 ++-- src/utils/decorator/decorators.ts | 42 +++++++++++++++---- 7 files changed, 51 insertions(+), 16 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index cb88375..f672423 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -34,7 +34,6 @@ model Admin { profilePicture String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - requests Request[] Settlement Settlement[] } @@ -51,8 +50,6 @@ model Request { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) // Specify fields and references here - Admin Admin? @relation(fields: [adminId], references: [id]) - adminId Int? Settlement Settlement? } diff --git a/src/request/dto/create-request.dto.ts b/src/request/dto/create-request.dto.ts index ab1354c..1ed0367 100644 --- a/src/request/dto/create-request.dto.ts +++ b/src/request/dto/create-request.dto.ts @@ -96,6 +96,7 @@ export class RequestStatusDto { export class RequestFilterDto { // Optional status filter, validate that it's a valid enum value. @IsOptional() + @IsSwaggerEnum(RequestStatusEnum, { isOptional: true }) @IsEnum(RequestStatusEnum, { each: true }) status?: RequestStatusEnum; @@ -116,6 +117,7 @@ export class RequestFilterDto { // Optional request type filter, validate that it's a valid enum value. @IsOptional() + @IsSwaggerEnum(RequestTypeEnum, { isOptional: true }) @IsEnum(RequestTypeEnum, { each: true }) type?: RequestTypeEnum; } diff --git a/src/request/dto/response-request.dto.ts b/src/request/dto/response-request.dto.ts index f614f35..18dedcc 100644 --- a/src/request/dto/response-request.dto.ts +++ b/src/request/dto/response-request.dto.ts @@ -1,11 +1,14 @@ -import { RequestStatusEnum, RequestTypeEnum } from '../enum/request.enum'; +import { RequestStatusEnum, RequestTypeEnum } from '@prisma/client'; +import { IsSwaggerEnum } from 'src/utils/decorator/decorators'; export class ResponseRequestDto { readonly requestId: number; readonly userId: number; readonly title: string; + @IsSwaggerEnum(RequestTypeEnum) readonly type: RequestTypeEnum; readonly description: string; + @IsSwaggerEnum(RequestStatusEnum) readonly status: RequestStatusEnum; readonly requestContent: object; readonly responseContent: object; diff --git a/src/settlement/dto/create-settlement.dto.ts b/src/settlement/dto/create-settlement.dto.ts index bd03660..06ca3e1 100644 --- a/src/settlement/dto/create-settlement.dto.ts +++ b/src/settlement/dto/create-settlement.dto.ts @@ -76,11 +76,13 @@ export class CreateSettlementDto { export class SettlementFilterDto { // Optional settlement status filter, validated that it's valid SettlementStatusEnum value @IsOptional() + @IsSwaggerEnum(SettlementStatusEnum, { isOptional: true }) @IsEnum(SettlementStatusEnum, { each: true }) requestStatus?: SettlementStatusEnum; // Optional third party status filter, validate that it's valid thirdPartyResponseStatusEnum value @IsOptional() + @IsSwaggerEnum(thirdPartyResponseStatusEnum, { isOptional: true }) @IsEnum(thirdPartyResponseStatusEnum, { each: true }) thirdPartyResponseStatus?: thirdPartyResponseStatusEnum; diff --git a/src/settlement/dto/response-settlement.dto.ts b/src/settlement/dto/response-settlement.dto.ts index bb46a40..3b2f144 100644 --- a/src/settlement/dto/response-settlement.dto.ts +++ b/src/settlement/dto/response-settlement.dto.ts @@ -1,15 +1,18 @@ import { SettlementStatusEnum, thirdPartyResponseStatusEnum, -} from '../enum/settlement.enum'; +} from '@prisma/client'; +import { IsSwaggerEnum } from 'src/utils/decorator/decorators'; export class ResponseSettlementDto { readonly SettlementId: number; readonly requestId: number; readonly userId: number; readonly adminId: number; + @IsSwaggerEnum(thirdPartyResponseStatusEnum) readonly type: thirdPartyResponseStatusEnum; readonly transactionId: number; + @IsSwaggerEnum(SettlementStatusEnum) readonly requestStatus: SettlementStatusEnum; readonly createdAt: Date; readonly updatedAt: Date; diff --git a/src/settlement/settlement.controller.ts b/src/settlement/settlement.controller.ts index 41e0520..a63575c 100644 --- a/src/settlement/settlement.controller.ts +++ b/src/settlement/settlement.controller.ts @@ -166,7 +166,7 @@ export class SettlementController { message: `Successfully fetched settlement for request id #${requestId}`, data: settlement, }); - } catch (error) { + } catch (error) { this.logger.error( `Failed to fetch settlement for request id #${requestId}`, error, @@ -174,7 +174,8 @@ export class SettlementController { return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ message: - error.message || `Failed to fetch request for request id #${requestId}`, + error.message || + `Failed to fetch request for request id #${requestId}`, }); } } @@ -214,7 +215,8 @@ export class SettlementController { return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ message: - error.message || `Failed to fetch request for request id #${requestId}`, + error.message || + `Failed to fetch request for request id #${requestId}`, }); } } diff --git a/src/utils/decorator/decorators.ts b/src/utils/decorator/decorators.ts index acbfc46..4772838 100644 --- a/src/utils/decorator/decorators.ts +++ b/src/utils/decorator/decorators.ts @@ -1,13 +1,39 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptions } from '@nestjs/swagger'; -// eslint-disable-next-line @typescript-eslint/ban-types -export function IsSwaggerEnum(enumObject: Record): Function { +// Decorator to document enum properties in Swagger +// @param enumObject: The enum object to document +// @param options: An object with options for the decorator (including isOptional) +export function IsSwaggerEnum( + enumObject: Record, + options: { + isOptional?: boolean; + apiPropertyOptions?: ApiPropertyOptions; // Additional decorator options for ApiProperty + } = {}, +) { return (target: any, propertyKey: string) => { + const { isOptional = false, apiPropertyOptions = {} } = options; // Destructure options + + // Get the enum values from the provided enumObject const values = Object.values(enumObject); - ApiProperty({ - type: enumObject, - enum: values, - example: values.join(' || '), // Set an example enum value for documentation purposes - })(target, propertyKey); + + // Create decorator options for ApiProperty + const decoratorOptions = { + type: enumObject, // Use the cloned enumObject + enum: values, // Specify the enum values + ...apiPropertyOptions, // Merge additional ApiProperty options + }; + + // If the property is not optional, add the example property + if (!isOptional) { + decoratorOptions.example = values.join(' || '); // Set an example enum value for documentation purposes + } + + // If the property is optional, mark it as such in Swagger documentation + if (isOptional) { + decoratorOptions['required'] = false; + } + + // Apply the ApiProperty decorator with the specified options to the property + ApiProperty(decoratorOptions)(target, propertyKey); }; } From d1f6477b49a686612e4bcdbe7c7f4c3b1fc41054 Mon Sep 17 00:00:00 2001 From: Ajay Maurya Date: Thu, 5 Oct 2023 15:13:32 +0530 Subject: [PATCH 25/26] updated swagger endpoint --- src/utils/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 6b7befc..6794332 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -6,7 +6,7 @@ export const SWAGGER_CONSTANTS = { TITLE: 'API', DESCRIPTION: 'Marketplace Request Service API description', VERSION: '1.0', - PATH: 'api/doc', + PATH: 'api/docs', SWAGGER_AUTH_SECURITY_SCHEMA_API_KEY: 'api-config-key', SWAGGER_AUTH_SECURITY_SCHEMA_JWT: 'jwt', }; From 3379cd2f1684b042ec8d64377504f97cd178c2b3 Mon Sep 17 00:00:00 2001 From: Ajay Maurya Date: Mon, 23 Oct 2023 12:18:22 +0530 Subject: [PATCH 26/26] Resolved review changes --- prisma/schema.prisma | 10 +++++----- src/request/dto/create-request.dto.ts | 5 +++-- src/request/request.controller.ts | 6 +++--- src/request/request.service.ts | 2 +- src/settlement/dto/create-settlement.dto.ts | 11 ++++++----- src/settlement/settlement.controller.ts | 6 +++--- src/settlement/settlement.service.ts | 2 +- 7 files changed, 22 insertions(+), 20 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f672423..58a8293 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -12,7 +12,7 @@ datasource db { // Dummy user model model User { - id Int @id @default(autoincrement()) + id String @id @unique() @default(uuid()) @db.Uuid email String @unique role String username String @@ -26,7 +26,7 @@ model User { // Dummy Admin Model model Admin { - id Int @id @default(autoincrement()) + id String @id @unique() @default(uuid()) @db.Uuid email String @unique role String username String @@ -39,7 +39,7 @@ model Admin { model Request { requestId Int @id @default(autoincrement()) - userId Int + userId String @db.Uuid title String type RequestTypeEnum description String @@ -56,8 +56,8 @@ model Request { model Settlement { settlementId Int @id @default(autoincrement()) requestId Int @unique - userId Int - adminId Int + userId String @db.Uuid + adminId String @db.Uuid requestStatus SettlementStatusEnum thirdPartyResponseStatus thirdPartyResponseStatusEnum transactionId Int diff --git a/src/request/dto/create-request.dto.ts b/src/request/dto/create-request.dto.ts index 1ed0367..fd2857f 100644 --- a/src/request/dto/create-request.dto.ts +++ b/src/request/dto/create-request.dto.ts @@ -7,6 +7,7 @@ import { IsNotEmpty, IsOptional, IsString, + IsUUID, } from 'class-validator'; import { IsSwaggerEnum } from 'src/utils/decorator/decorators'; @@ -14,8 +15,8 @@ import { IsSwaggerEnum } from 'src/utils/decorator/decorators'; export class CreateRequestDto { // User ID associated with the request. @IsNotEmpty() - @IsInt() - userId: number; + @IsUUID() + userId: string; // Title of the request. @IsNotEmpty() diff --git a/src/request/request.controller.ts b/src/request/request.controller.ts index 5aaca68..4bc8efe 100644 --- a/src/request/request.controller.ts +++ b/src/request/request.controller.ts @@ -64,7 +64,7 @@ export class RequestController { } } - @Get('') + @Get() @ApiOperation({ summary: 'Get all requests' }) @ApiResponse({ status: HttpStatus.OK, @@ -99,7 +99,7 @@ export class RequestController { }) async getAllRequestsForUser( @Res() res, - @Param('userId') userId: number, + @Param('userId') userId: string, @Query() filter: RequestFilterDto, ) { try { @@ -108,7 +108,7 @@ export class RequestController { ); const requests = await this.requestService.getAllRequestsForUser( - +userId, + userId, filter, ); diff --git a/src/request/request.service.ts b/src/request/request.service.ts index bf20357..6c5adc9 100644 --- a/src/request/request.service.ts +++ b/src/request/request.service.ts @@ -35,7 +35,7 @@ export class RequestService { } // Get all requests for a specific user with optional filters and pagination - async getAllRequestsForUser(userId: number, filter: RequestFilterDto) { + async getAllRequestsForUser(userId: string, filter: RequestFilterDto) { const { status, type, limit = 10, offset = 0, orderBy } = filter; return this.prisma.request.findMany({ where: { diff --git a/src/settlement/dto/create-settlement.dto.ts b/src/settlement/dto/create-settlement.dto.ts index 06ca3e1..1728e9e 100644 --- a/src/settlement/dto/create-settlement.dto.ts +++ b/src/settlement/dto/create-settlement.dto.ts @@ -10,6 +10,7 @@ import { IsNotEmpty, IsOptional, IsString, + IsUUID, } from 'class-validator'; import { IsSwaggerEnum } from 'src/utils/decorator/decorators'; @@ -22,13 +23,13 @@ export class CreateSettlementDto { // User ID associated with the request. @IsNotEmpty() - @IsInt() - userId: number; + @IsUUID() + userId: string; // Admin Id associated with the Admin @IsNotEmpty() - @IsInt() - adminId: number; + @IsUUID() + adminId: string; // Status of the request, should be one of the valid SettlementStatusEnum values. @IsNotEmpty() @@ -64,7 +65,7 @@ export class CreateSettlementDto { // Optional creation date of the request. @IsDate() @IsOptional() - createdAt?: Date; + createdAt?: Date = new Date(); // Optional update date of the request. @IsDate() diff --git a/src/settlement/settlement.controller.ts b/src/settlement/settlement.controller.ts index a63575c..6cbaf9b 100644 --- a/src/settlement/settlement.controller.ts +++ b/src/settlement/settlement.controller.ts @@ -71,7 +71,7 @@ export class SettlementController { } // API to get all the settlements - @Get('') + @Get() @ApiOperation({ summary: 'Get all settlements' }) // Api oepration for swagger. @ApiResponse({ status: HttpStatus.OK, @@ -109,7 +109,7 @@ export class SettlementController { isArray: true, }) // Describes the response for Swagger. async getUserSettlementsById( - @Param('userId') userId: number, + @Param('userId') userId: string, @Res() res, @Query() filter: SettlementFilterDto, ) { @@ -119,7 +119,7 @@ export class SettlementController { ); const settlements = await this.settlementService.getAllSettlementForUser( - +userId, + userId, filter, ); diff --git a/src/settlement/settlement.service.ts b/src/settlement/settlement.service.ts index 8628770..af17126 100644 --- a/src/settlement/settlement.service.ts +++ b/src/settlement/settlement.service.ts @@ -65,7 +65,7 @@ export class SettlementService { } // Get settlement for a specific user using the userId - async getAllSettlementForUser(userId: number, filter: SettlementFilterDto) { + async getAllSettlementForUser(userId: string, filter: SettlementFilterDto) { // Check if the user exists const user = await this.prisma.user.findUnique({ where: {