nestjs-prisma-cli 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/bin/index.js +193 -0
  2. package/bin/template/.github/workflows/deploy-to-ecr.yml +68 -0
  3. package/bin/template/prisma/schema.prisma +20 -0
  4. package/bin/template/prisma/seed.ts +30 -0
  5. package/bin/template/src/app.controller.spec.ts +22 -0
  6. package/bin/template/src/app.controller.ts +14 -0
  7. package/bin/template/src/app.module.ts +42 -0
  8. package/bin/template/src/app.service.ts +8 -0
  9. package/bin/template/src/common/classes/base64.ts +22 -0
  10. package/bin/template/src/common/decorator/public.decorator.ts +4 -0
  11. package/bin/template/src/common/dto/index.ts +4 -0
  12. package/bin/template/src/common/dto/paginated-response.dto.ts +15 -0
  13. package/bin/template/src/common/dto/pagination.dto.ts +34 -0
  14. package/bin/template/src/common/enums/db-error-code.enum.ts +5 -0
  15. package/bin/template/src/common/enums/index.ts +4 -0
  16. package/bin/template/src/common/enums/message-code.enum.ts +20 -0
  17. package/bin/template/src/common/http-interceptor/http-error-type.ts +31 -0
  18. package/bin/template/src/common/http-interceptor/http-exception.filter.ts +94 -0
  19. package/bin/template/src/common/http-interceptor/index.ts +5 -0
  20. package/bin/template/src/common/http-interceptor/logging.interceptor.ts +45 -0
  21. package/bin/template/src/common/http-interceptor/response.interceptor.ts +41 -0
  22. package/bin/template/src/common/logger/winston.logger.ts +54 -0
  23. package/bin/template/src/common/s3/s3.module.ts +8 -0
  24. package/bin/template/src/common/s3/s3.service.spec.ts +18 -0
  25. package/bin/template/src/common/s3/s3.service.ts +118 -0
  26. package/bin/template/src/common/utils/pagination.util.ts +45 -0
  27. package/bin/template/src/main.ts +60 -0
  28. package/bin/template/src/modules/auth/auth.controller.spec.ts +18 -0
  29. package/bin/template/src/modules/auth/auth.controller.ts +98 -0
  30. package/bin/template/src/modules/auth/auth.module.ts +26 -0
  31. package/bin/template/src/modules/auth/auth.service.spec.ts +18 -0
  32. package/bin/template/src/modules/auth/auth.service.ts +64 -0
  33. package/bin/template/src/modules/auth/dto/login.dto.ts +12 -0
  34. package/bin/template/src/modules/auth/jwt/jwt.guard.spec.ts +7 -0
  35. package/bin/template/src/modules/auth/jwt/jwt.guard.ts +21 -0
  36. package/bin/template/src/modules/auth/jwt/jwt.strategy.ts +26 -0
  37. package/bin/template/src/modules/user/dto/create-user.dto.ts +21 -0
  38. package/bin/template/src/modules/user/dto/update-user.dto.ts +4 -0
  39. package/bin/template/src/modules/user/user.controller.spec.ts +20 -0
  40. package/bin/template/src/modules/user/user.controller.ts +54 -0
  41. package/bin/template/src/modules/user/user.module.ts +9 -0
  42. package/bin/template/src/modules/user/user.service.spec.ts +18 -0
  43. package/bin/template/src/modules/user/user.service.ts +132 -0
  44. package/bin/template/src/prisma/prisma.module.ts +9 -0
  45. package/bin/template/src/prisma/prisma.service.spec.ts +18 -0
  46. package/bin/template/src/prisma/prisma.service.ts +9 -0
  47. package/bin/template/test/app.e2e-spec.ts +25 -0
  48. package/bin/template/test/jest-e2e.json +9 -0
  49. package/package.json +38 -0
package/bin/index.js ADDED
@@ -0,0 +1,193 @@
1
+ import inquirer from "inquirer";
2
+ import chalk from "chalk";
3
+ import fs from "fs-extra";
4
+ import path from "path";
5
+ import { execa } from "execa";
6
+ import { fileURLToPath } from "url";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ async function main() {
12
+ console.log(chalk.blue("🚀 Welcome to NestJS + Prisma Project Generator!"));
13
+
14
+ const { projectName } = await inquirer.prompt([
15
+ { type: "input", name: "projectName", message: "Enter your project name:" }
16
+ ]);
17
+
18
+ const dbSafeName = projectName.replace(/-/g, "_") + "_db";
19
+ const projectPath = path.join(process.cwd(), projectName);
20
+
21
+ if (fs.existsSync(projectPath)) {
22
+ console.log(chalk.red(`❌ Folder "${projectName}" already exists!`));
23
+ process.exit(1);
24
+ }
25
+
26
+ const { database } = await inquirer.prompt([
27
+ {
28
+ type: "list",
29
+ name: "database",
30
+ message: "Select your database:",
31
+ choices: ["PostgreSQL", "MySQL", "SQLite", "MongoDB", "CockroachDB", "SQLServer"]
32
+ }
33
+ ]);
34
+
35
+ console.log(chalk.green(`📦 Creating NestJS project "${projectName}"...`));
36
+ await execa("npx", ["@nestjs/cli", "new", projectName, "--skip-install"], { stdio: "inherit" });
37
+
38
+ console.log(chalk.green("📥 Installing dependencies..."));
39
+ await execa("npm", ["install"], { cwd: projectPath, stdio: "inherit" });
40
+
41
+ await execa("npm", [
42
+ "install",
43
+ "--save",
44
+ "@aws-sdk/client-s3",
45
+ "@aws-sdk/s3-request-presigner",
46
+ "@nestjs/config",
47
+ "@nestjs/jwt",
48
+ "@nestjs/passport",
49
+ "@nestjs/swagger",
50
+ "argon2",
51
+ "class-transformer",
52
+ "class-validator",
53
+ "moment",
54
+ "multer",
55
+ "passport",
56
+ "passport-jwt",
57
+ "prisma",
58
+ "@prisma/client",
59
+ "reflect-metadata",
60
+ "rxjs",
61
+ "swagger-ui-express",
62
+ "winston",
63
+ "winston-daily-rotate-file"
64
+ ], { cwd: projectPath, stdio: "inherit" });
65
+
66
+ await execa("npm", [
67
+ "install",
68
+ "--save-dev",
69
+ "@eslint/eslintrc",
70
+ "@eslint/js",
71
+ "@nestjs/cli",
72
+ "@nestjs/schematics",
73
+ "@nestjs/testing",
74
+ "@swc/cli",
75
+ "@swc/core",
76
+ "@types/express",
77
+ "@types/jest",
78
+ "@types/multer",
79
+ "@types/node",
80
+ "@types/supertest",
81
+ "eslint",
82
+ "eslint-config-prettier",
83
+ "eslint-plugin-prettier",
84
+ "globals",
85
+ "jest",
86
+ "prettier",
87
+ "source-map-support",
88
+ "supertest",
89
+ "ts-jest",
90
+ "ts-loader",
91
+ "ts-node",
92
+ "tsconfig-paths",
93
+ "typescript",
94
+ "typescript-eslint"
95
+ ], { cwd: projectPath, stdio: "inherit" });
96
+
97
+ console.log(chalk.green("✅ Dependencies installed!"));
98
+
99
+ const provider = database.toLowerCase() === "sqlserver" ? "sqlserver" : database.toLowerCase();
100
+ const prismaSchema = `generator client {
101
+ provider = "prisma-client-js"
102
+ }
103
+
104
+ datasource db {
105
+ provider = "${provider}"
106
+ url = env("DATABASE_URL")
107
+ }
108
+
109
+ model User {
110
+ id Int @id @default(autoincrement())
111
+ userId String @unique
112
+ name String?
113
+ email String
114
+ password String
115
+ isActive Boolean @default(true)
116
+ createdAt DateTime @default(now())
117
+ updatedAt DateTime @updatedAt
118
+
119
+ @@map("tbl_user")
120
+ }
121
+ `;
122
+ await fs.outputFile(path.join(projectPath, "prisma/schema.prisma"), prismaSchema);
123
+
124
+ const defaultUrlMap = {
125
+ postgresql: `postgresql://root:password@localhost:5432/${dbSafeName}?schema=public`,
126
+ mysql: `mysql://root:password@localhost:3306/${dbSafeName}?schema=public`,
127
+ sqlite: `file:./dev.db`,
128
+ mongodb: `mongodb://localhost:27017/${dbSafeName}`,
129
+ cockroachdb: `postgresql://root:password@localhost:26257/${dbSafeName}?sslmode=disable`,
130
+ sqlserver: `sqlserver://localhost:1433;database=${dbSafeName};user=sa;password=your_password;encrypt=false`
131
+ };
132
+
133
+ const envContent = `DATABASE_URL="${defaultUrlMap[provider]}"
134
+
135
+ JWT_ACCESS_SECRET="JWT_ACCESS_SECRET"
136
+ JWT_REFRESH_SECRET="JWT_REFRESH_SECRET"
137
+ JWT_ACCESS_EXPIRATION_TIME="1d"
138
+ JWT_REFRESH_EXPIRATION_TIME="30d"
139
+
140
+ AWS_ACCESS_KEY_ID="AWS_ACCESS_KEY_ID"
141
+ AWS_SECRET_ACCESS_KEY="AWS_SECRET_ACCESS_KEY"
142
+ AWS_REGION=ap-southeast-1
143
+ S3_BUCKET="S3_BUCKET"
144
+
145
+ NODE_ENV=dev
146
+ PROJECT_NAME=${dbSafeName}
147
+ PORT=3000
148
+ `;
149
+
150
+ await fs.outputFile(path.join(projectPath, ".env"), envContent);
151
+ console.log(chalk.green("✅ Prisma schema + .env created!"));
152
+
153
+ await execa("npx", ["prisma", "generate"], { cwd: projectPath, stdio: "inherit" });
154
+ console.log(chalk.green("✅ Prisma client generated!"));
155
+
156
+ const templatePath = path.join(__dirname, "template");
157
+ if (!fs.existsSync(templatePath)) {
158
+ console.log(chalk.red("❌ Template folder not found!"));
159
+ process.exit(1);
160
+ }
161
+ await fs.copy(templatePath, projectPath, { overwrite: true });
162
+ console.log(chalk.green("✅ Template files copied successfully!"));
163
+
164
+ const packageJsonPath = path.join(projectPath, "package.json");
165
+ const packageJson = await fs.readJSON(packageJsonPath);
166
+ packageJson.scripts = {
167
+ ...packageJson.scripts,
168
+ "prisma:migrate": "prisma migrate dev --name init",
169
+ "prisma:generate": "prisma generate",
170
+ "prisma:deploy": "prisma migrate deploy",
171
+ "prisma:studio": "prisma studio",
172
+ "prisma:reset": "prisma migrate reset --force",
173
+ "postinstall": "prisma generate",
174
+ "migrate:dev": "dotenv -e .env -- prisma migrate dev",
175
+ "migrate:staging": "dotenv -e .env -- prisma migrate deploy",
176
+ "migrate:prod": "dotenv -e .env -- prisma migrate deploy",
177
+ "seed": "ts-node prisma/seed.ts"
178
+ };
179
+ await fs.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
180
+ console.log(chalk.green("✅ package.json scripts updated successfully!"));
181
+
182
+ console.log(chalk.yellow("🎉 Project is ready! Next steps:"));
183
+ console.log(chalk.cyan(`cd ${projectName}`));
184
+ console.log(chalk.cyan("Check .env"));
185
+ console.log(chalk.cyan("npm run prisma:migrate"));
186
+ console.log(chalk.cyan("npm run seed"));
187
+ console.log(chalk.cyan("npm run start:dev"));
188
+ }
189
+
190
+ main().catch(err => {
191
+ console.error(chalk.red("❌ Error:"), err);
192
+ process.exit(1);
193
+ });
@@ -0,0 +1,68 @@
1
+ name: Build, Push to ECR, and Deploy to App Runner
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - dev-server
7
+ - staging-server
8
+ - prod-server
9
+
10
+ env:
11
+ AWS_REGION: ap-southeast-1
12
+ ECR_REGISTRY: 339713129923.dkr.ecr.ap-southeast-1.amazonaws.com
13
+ ECR_REPOSITORY: freshmoe-hrms-api
14
+
15
+ jobs:
16
+ deploy:
17
+ runs-on: ubuntu-latest
18
+
19
+ steps:
20
+ - name: Checkout source code
21
+ uses: actions/checkout@v3
22
+
23
+ - name: Configure AWS credentials
24
+ uses: aws-actions/configure-aws-credentials@v2
25
+ with:
26
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
27
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
28
+ aws-region: ${{ env.AWS_REGION }}
29
+
30
+ - name: Login to Amazon ECR
31
+ id: login-ecr
32
+ uses: aws-actions/amazon-ecr-login@v1
33
+
34
+ - name: Determine image tag based on branch
35
+ id: vars
36
+ run: |
37
+ SHORT_SHA=${GITHUB_SHA::7}
38
+
39
+ case "${GITHUB_REF##*/}" in
40
+ dev-server)
41
+ BRANCH_TAG="dev"
42
+ ;;
43
+ staging-server)
44
+ BRANCH_TAG="staging"
45
+ ;;
46
+ prod-server)
47
+ BRANCH_TAG="prod"
48
+ ;;
49
+ *)
50
+ echo "Unsupported branch: ${GITHUB_REF##*/}"
51
+ exit 1
52
+ ;;
53
+ esac
54
+
55
+ echo "SHORT_SHA=${SHORT_SHA}" >> $GITHUB_ENV
56
+ echo "BRANCH_TAG=${BRANCH_TAG}" >> $GITHUB_ENV
57
+
58
+ - name: Build, tag, and push Docker image to ECR
59
+ run: |
60
+ docker build --no-cache -t $ECR_REGISTRY/$ECR_REPOSITORY:$BRANCH_TAG .
61
+ docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$BRANCH_TAG $ECR_REGISTRY/$ECR_REPOSITORY:$SHORT_SHA
62
+
63
+ docker push $ECR_REGISTRY/$ECR_REPOSITORY:$BRANCH_TAG
64
+ docker push $ECR_REGISTRY/$ECR_REPOSITORY:$SHORT_SHA
65
+
66
+ echo "Docker images pushed:"
67
+ echo " - $ECR_REGISTRY/$ECR_REPOSITORY:$BRANCH_TAG"
68
+ echo " - $ECR_REGISTRY/$ECR_REPOSITORY:$SHORT_SHA"
@@ -0,0 +1,20 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "mysql"
7
+ url = env("DATABASE_URL")
8
+ }
9
+
10
+ model User {
11
+ id Int @id @default(autoincrement())
12
+ userId String @unique
13
+ name String?
14
+ email String
15
+ password String
16
+ createdAt DateTime @default(now())
17
+ updatedAt DateTime @updatedAt
18
+ @@map("tbl_user")
19
+ }
20
+
@@ -0,0 +1,30 @@
1
+ import { PrismaClient } from "@prisma/client";
2
+ import * as argon2 from "argon2";
3
+
4
+ const prisma = new PrismaClient();
5
+
6
+ async function main() {
7
+ const hashedPassword = await argon2.hash("Asdfasdf@123");
8
+
9
+ const user = await prisma.user.upsert({
10
+ where: { userId: "USER_20250815001" },
11
+ update: {},
12
+ create: {
13
+ userId: "USER_20250815001",
14
+ name: "Kyaw Soe",
15
+ email: "kyawsoe@gmail.com",
16
+ password: hashedPassword,
17
+ },
18
+ });
19
+
20
+ console.log("Seeded user successfully.");
21
+ }
22
+
23
+ main()
24
+ .catch((e) => {
25
+ console.error(e);
26
+ process.exit(1);
27
+ })
28
+ .finally(async () => {
29
+ await prisma.$disconnect();
30
+ });
@@ -0,0 +1,22 @@
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { AppController } from './app.controller';
3
+ import { AppService } from './app.service';
4
+
5
+ describe('AppController', () => {
6
+ let appController: AppController;
7
+
8
+ beforeEach(async () => {
9
+ const app: TestingModule = await Test.createTestingModule({
10
+ controllers: [AppController],
11
+ providers: [AppService],
12
+ }).compile();
13
+
14
+ appController = app.get<AppController>(AppController);
15
+ });
16
+
17
+ describe('root', () => {
18
+ it('should return "Hello World!"', () => {
19
+ expect(appController.getHello()).toBe('Hello World!');
20
+ });
21
+ });
22
+ });
@@ -0,0 +1,14 @@
1
+ import { Controller, Get } from '@nestjs/common';
2
+ import { AppService } from './app.service';
3
+ import { Public } from './common/decorator/public.decorator';
4
+
5
+ @Public()
6
+ @Controller()
7
+ export class AppController {
8
+ constructor(private readonly appService: AppService) {}
9
+
10
+ @Get()
11
+ getHello(): string {
12
+ return this.appService.getHello();
13
+ }
14
+ }
@@ -0,0 +1,42 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { APP_INTERCEPTOR } from '@nestjs/core';
3
+ import { APP_GUARD } from '@nestjs/core';
4
+ import { AppController } from './app.controller';
5
+ import { AppService } from './app.service';
6
+ import { PrismaModule } from './prisma/prisma.module';
7
+ import {
8
+ HttpResponseInterceptor,
9
+ LoggingInterceptor,
10
+ } from './common/http-interceptor/index';
11
+ import { JwtAuthGuard } from './modules/auth/jwt/jwt.guard';
12
+ import { ConfigModule } from '@nestjs/config';
13
+ import { UserModule } from './modules/user/user.module';
14
+ import { AuthModule } from './modules/auth/auth.module';
15
+
16
+ @Module({
17
+ imports: [
18
+ ConfigModule.forRoot({
19
+ isGlobal: true,
20
+ }),
21
+ PrismaModule,
22
+ UserModule,
23
+ AuthModule
24
+ ],
25
+ controllers: [AppController],
26
+ providers: [
27
+ {
28
+ provide: APP_INTERCEPTOR,
29
+ useClass: LoggingInterceptor,
30
+ },
31
+ {
32
+ provide: APP_INTERCEPTOR,
33
+ useClass: HttpResponseInterceptor,
34
+ },
35
+ {
36
+ provide: APP_GUARD,
37
+ useClass: JwtAuthGuard,
38
+ },
39
+ AppService,
40
+ ],
41
+ })
42
+ export class AppModule {}
@@ -0,0 +1,8 @@
1
+ import { Injectable } from '@nestjs/common';
2
+
3
+ @Injectable()
4
+ export class AppService {
5
+ getHello(): string {
6
+ return 'Hello World!';
7
+ }
8
+ }
@@ -0,0 +1,22 @@
1
+ import { IsString, IsNotEmpty, Matches } from 'class-validator';
2
+
3
+ export default class Base64File {
4
+ @IsString()
5
+ @IsNotEmpty()
6
+ name: string;
7
+
8
+ @IsString()
9
+ @IsNotEmpty()
10
+ @Matches(
11
+ /^data:([A-Za-z0-9]+\/[A-Za-z0-9\-\.]+);base64,[A-Za-z0-9\-_+=/]+$/,
12
+ {
13
+ message:
14
+ 'content must be a valid base64-encoded string with a valid data URI scheme',
15
+ },
16
+ )
17
+ content: string;
18
+
19
+ @IsString()
20
+ @IsNotEmpty()
21
+ mimeType: string;
22
+ }
@@ -0,0 +1,4 @@
1
+ import { SetMetadata } from '@nestjs/common';
2
+
3
+ export const IS_PUBLIC_KEY = 'isPublic';
4
+ export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
@@ -0,0 +1,4 @@
1
+ import { PaginatedResponseDto } from './paginated-response.dto';
2
+ import { PaginationDto } from './pagination.dto';
3
+
4
+ export { PaginationDto, PaginatedResponseDto };
@@ -0,0 +1,15 @@
1
+ import { ApiProperty } from '@nestjs/swagger';
2
+
3
+ export class PaginatedResponseDto<T> {
4
+ @ApiProperty()
5
+ total: number;
6
+
7
+ @ApiProperty()
8
+ page: number;
9
+
10
+ @ApiProperty()
11
+ limit: number;
12
+
13
+ @ApiProperty({ isArray: true })
14
+ data: T[];
15
+ }
@@ -0,0 +1,34 @@
1
+ import { ApiPropertyOptional } from '@nestjs/swagger';
2
+ import { Type } from 'class-transformer';
3
+ import {
4
+ IsInt,
5
+ IsOptional,
6
+ IsString,
7
+ Min,
8
+ IsBooleanString,
9
+ } from 'class-validator';
10
+
11
+ export class PaginationDto {
12
+ @ApiPropertyOptional({ example: 1, description: 'Page number' })
13
+ @IsOptional()
14
+ @Type(() => Number)
15
+ @IsInt()
16
+ @Min(1)
17
+ page?: number = 1;
18
+
19
+ @ApiPropertyOptional({ example: 20, description: 'Items per page' })
20
+ @IsOptional()
21
+ @Type(() => Number)
22
+ @IsInt()
23
+ @Min(1)
24
+ limit?: number = 20;
25
+
26
+ @ApiPropertyOptional({ example: '', description: 'Search term' })
27
+ @IsOptional()
28
+ @IsString()
29
+ search?: string;
30
+
31
+ @IsOptional()
32
+ @IsBooleanString()
33
+ all?: string;
34
+ }
@@ -0,0 +1,5 @@
1
+ export enum DBErrorCode {
2
+ PgNotNullConstraintViolation = '23502',
3
+ PgForeignKeyConstraintViolation = '23503',
4
+ PgUniqueConstraintViolation = '23505',
5
+ }
@@ -0,0 +1,4 @@
1
+ import { DBErrorCode } from './db-error-code.enum';
2
+ import { MESSAGE_CODE } from './message-code.enum';
3
+
4
+ export { DBErrorCode, MESSAGE_CODE };
@@ -0,0 +1,20 @@
1
+ export enum MESSAGE_CODE {
2
+ SAVE_SUCCESS = 201,
3
+ SIGNIN_SUCCESS = 202,
4
+ SUCCESS = 203,
5
+ PASSWORD_SUCCESS = 204,
6
+ DATA_NOT_FOUND = 205,
7
+ USER_VERIFY = 206,
8
+ WRONG_USER_PW = 401,
9
+ INVALID_USER_ID = 402,
10
+ MISSED_TOKEN = 403,
11
+ UNAUTHORIZED = 404,
12
+ USER_EXIST = 406,
13
+ TOKEN_ERROR = 407,
14
+ INVALID = 408,
15
+ NOT_FOUND = 409,
16
+ ROUTE_NOT_FOUND = 410,
17
+ REQUEST_FIELD_REQUIRED = 411,
18
+ DELETE_USER = 412,
19
+ SERVER_ERROR = 501,
20
+ }
@@ -0,0 +1,31 @@
1
+ export const HttpErrorType = {
2
+ 400: 'BAD_REQUEST',
3
+ 401: 'UNAUTHORIZED',
4
+ 402: 'PAYMENT_REQUIRED',
5
+ 403: 'FORBIDDEN',
6
+ 404: 'NOT_FOUND',
7
+ 405: 'METHOD_NOT_ALLOWED',
8
+ 406: 'NOT_ACCEPTABLE',
9
+ 407: 'PROXY_AUTHENTICATION_REQUIRED',
10
+ 408: 'REQUEST_TIMEOUT',
11
+ 409: 'CONFLICT',
12
+ 410: 'GONE',
13
+ 411: 'LENGTH_REQUIRED',
14
+ 412: 'PRECONDITION_FAILED',
15
+ 413: 'PAYLOAD_TOO_LARGE',
16
+ 414: 'URI_TOO_LONG',
17
+ 415: 'UNSUPPORTED_MEDIA_TYPE',
18
+ 416: 'REQUESTED_RANGE_NOT_SATISFIABLE',
19
+ 417: 'EXPECTATION_FAILED',
20
+ 418: 'I_AM_A_TEAPOT',
21
+ 421: 'MISDIRECTED',
22
+ 422: 'UNPROCESSABLE_ENTITY',
23
+ 424: 'FAILED_DEPENDENCY',
24
+ 429: 'TOO_MANY_REQUESTS',
25
+ 500: 'INTERNAL_SERVER_ERROR',
26
+ 501: 'NOT_IMPLEMENTED',
27
+ 502: 'BAD_GATEWAY',
28
+ 503: 'SERVICE_UNAVAILABLE',
29
+ 504: 'GATEWAY_TIMEOUT',
30
+ 505: 'HTTP_VERSION_NOT_SUPPORTED',
31
+ };
@@ -0,0 +1,94 @@
1
+ import {
2
+ ExceptionFilter,
3
+ Catch,
4
+ ArgumentsHost,
5
+ HttpException,
6
+ HttpStatus,
7
+ } from '@nestjs/common';
8
+ import { Request, Response } from 'express';
9
+ import { DBErrorCode, MESSAGE_CODE } from '../enums';
10
+ import { AppLogger } from '../logger/winston.logger';
11
+
12
+ @Catch()
13
+ export class HttpExceptionFilter implements ExceptionFilter {
14
+ catch(exception: unknown, host: ArgumentsHost) {
15
+ console.log(exception, 'exception');
16
+ const ctx = host.switchToHttp();
17
+ const request = ctx.getRequest<Request>();
18
+ const response = ctx.getResponse<Response>();
19
+
20
+ let status = HttpStatus.INTERNAL_SERVER_ERROR;
21
+ let message = 'An unexpected error occurred';
22
+ let messageCode: string | number = status;
23
+
24
+ if (exception instanceof HttpException) {
25
+ status = exception.getStatus();
26
+ const exceptionResponse = exception.getResponse();
27
+
28
+ if (typeof exceptionResponse === 'string') {
29
+ message = capitalizeFirst(exceptionResponse);
30
+ messageCode = MESSAGE_CODE.INVALID;
31
+ } else if (
32
+ typeof exceptionResponse === 'object' &&
33
+ exceptionResponse !== null
34
+ ) {
35
+ const res: any = exceptionResponse;
36
+ const rawMessage = Array.isArray(res.message)
37
+ ? res.message[0]
38
+ : (res.message ?? message);
39
+
40
+ message = capitalizeFirst(rawMessage);
41
+ messageCode =
42
+ res.messageCode ??
43
+ (status === HttpStatus.BAD_REQUEST ? MESSAGE_CODE.INVALID : status);
44
+ } else {
45
+ message = capitalizeFirst(exception.message);
46
+ messageCode = status;
47
+ }
48
+ } else if (typeof exception === 'object' && exception !== null) {
49
+ const ex: any = exception;
50
+ if (ex.code) {
51
+ switch (ex.code) {
52
+ case DBErrorCode.PgUniqueConstraintViolation:
53
+ status = HttpStatus.CONFLICT;
54
+ message = 'Unique constraint violated';
55
+ messageCode = MESSAGE_CODE.INVALID;
56
+ break;
57
+ case DBErrorCode.PgForeignKeyConstraintViolation:
58
+ status = HttpStatus.CONFLICT;
59
+ message = 'Foreign key constraint violated';
60
+ messageCode = MESSAGE_CODE.INVALID;
61
+ break;
62
+ case DBErrorCode.PgNotNullConstraintViolation:
63
+ status = HttpStatus.BAD_REQUEST;
64
+ message = 'Not null constraint violated';
65
+ messageCode = MESSAGE_CODE.INVALID;
66
+ break;
67
+ default:
68
+ message = capitalizeFirst(ex.message || 'Database exception');
69
+ messageCode = status;
70
+ }
71
+ }
72
+ }
73
+
74
+ const duration = Date.now() - (request as any).__startTime || 0;
75
+ const logMessage = `${request.method} ${request.originalUrl} ${status} - ${duration}ms | Message Code: ${messageCode} | Message: ${message} | Headers: ${JSON.stringify(request.headers)}`;
76
+
77
+ if (status >= 500) {
78
+ AppLogger.error(logMessage);
79
+ } else {
80
+ AppLogger.warn(logMessage);
81
+ }
82
+
83
+ response.status(status).json({
84
+ statusCode: status,
85
+ messageCode,
86
+ message,
87
+ });
88
+ }
89
+ }
90
+
91
+ function capitalizeFirst(text: string): string {
92
+ if (!text || typeof text !== 'string') return text;
93
+ return text.charAt(0).toUpperCase() + text.slice(1);
94
+ }
@@ -0,0 +1,5 @@
1
+ import { HttpExceptionFilter } from './http-exception.filter';
2
+ import { LoggingInterceptor } from './logging.interceptor';
3
+ import { HttpResponseInterceptor } from './response.interceptor';
4
+
5
+ export { HttpResponseInterceptor, LoggingInterceptor, HttpExceptionFilter };