nestjs-prisma-cli 1.0.8 → 1.0.9
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.
- package/bin/index.js +45 -32
- package/package.json +2 -2
- package/template/prisma/schema.prisma +34 -17
- package/template/src/app.module.ts +7 -2
- package/template/src/common/config/index.ts +4 -0
- package/template/src/common/config/multer-config.ts +11 -0
- package/template/src/common/config/swagger.config.ts +36 -0
- package/template/src/common/decorator/index.ts +4 -0
- package/template/src/common/decorator/response-message.decorator.ts +5 -0
- package/template/src/common/dto/paginated-response.dto.ts +14 -2
- package/template/src/common/dto/pagination.dto.ts +18 -0
- package/template/src/common/enums/http-error-type-enum.ts +31 -0
- package/template/src/common/enums/index.ts +2 -1
- package/template/src/common/enums/message-code.enum.ts +2 -0
- package/template/src/common/http-interceptor/http-exception.filter.ts +99 -57
- package/template/src/common/http-interceptor/logging.interceptor.ts +55 -10
- package/template/src/common/http-interceptor/response.interceptor.ts +68 -28
- package/template/src/common/s3/s3.service.ts +9 -24
- package/template/src/common/utils/pagination.util.ts +46 -16
- package/template/src/main.ts +11 -38
- package/template/src/modules/auth/auth.controller.ts +21 -0
- package/template/src/modules/auth/auth.service.ts +9 -9
- package/template/src/modules/auth/jwt/jwt.guard.ts +1 -1
- package/template/src/modules/user/dto/create-user.dto.ts +8 -1
- package/template/src/modules/user/user.controller.ts +53 -16
- package/template/src/modules/user/user.module.ts +3 -0
- package/template/src/modules/user/user.service.ts +132 -98
- package/template/src/common/classes/base64.ts +0 -22
package/bin/index.js
CHANGED
|
@@ -56,42 +56,42 @@ model User {
|
|
|
56
56
|
name String?
|
|
57
57
|
email String
|
|
58
58
|
password String
|
|
59
|
+
profileUrl String?
|
|
59
60
|
isActive Boolean @default(true)
|
|
60
61
|
createdAt DateTime @default(now())
|
|
61
62
|
updatedAt DateTime @updatedAt
|
|
62
63
|
@@map("tbl_user")
|
|
63
64
|
}
|
|
65
|
+
|
|
66
|
+
model Log {
|
|
67
|
+
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
68
|
+
method String
|
|
69
|
+
path String
|
|
70
|
+
statusCode Int
|
|
71
|
+
messageCode String?
|
|
72
|
+
message String?
|
|
73
|
+
headers Json
|
|
74
|
+
body Json?
|
|
75
|
+
query Json?
|
|
76
|
+
duration Int
|
|
77
|
+
createdAt DateTime @default(now())
|
|
78
|
+
|
|
79
|
+
@@map("tbl_log")
|
|
80
|
+
}
|
|
64
81
|
`;
|
|
65
82
|
}
|
|
66
83
|
|
|
67
84
|
try {
|
|
68
85
|
let prismaContent = await fs.readFile(templatePrismaPath, "utf-8");
|
|
69
|
-
return prismaContent.replace(
|
|
70
|
-
/datasource\s+db\s*{[^}]*provider\s*=\s*".*"/,
|
|
71
|
-
`datasource db {\n provider = "${selectedProvider}"`
|
|
72
|
-
);
|
|
73
|
-
} catch {
|
|
74
|
-
return `generator client {
|
|
75
|
-
provider = "prisma-client-js"
|
|
76
|
-
}
|
|
77
86
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
prismaContent = prismaContent.replace(
|
|
88
|
+
/(datasource\s+db\s*{[^}]*provider\s*=\s*")\w+(")/,
|
|
89
|
+
`$1${selectedProvider}$2`
|
|
90
|
+
);
|
|
82
91
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
name String?
|
|
87
|
-
email String
|
|
88
|
-
password String
|
|
89
|
-
isActive Boolean @default(true)
|
|
90
|
-
createdAt DateTime @default(now())
|
|
91
|
-
updatedAt DateTime @updatedAt
|
|
92
|
-
@@map("tbl_user")
|
|
93
|
-
}
|
|
94
|
-
`;
|
|
92
|
+
return prismaContent;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
throw new Error(`Failed to read Prisma template: ${err.message}`);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -141,12 +141,27 @@ async function main() {
|
|
|
141
141
|
"@aws-sdk/s3-request-presigner",
|
|
142
142
|
"moment",
|
|
143
143
|
];
|
|
144
|
-
await execa(pkgManager, ["install", ...coreDeps], { cwd: projectPath, stdio: "inherit" });
|
|
145
144
|
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
const devDeps = [
|
|
146
|
+
"prisma",
|
|
147
|
+
"@types/multer",
|
|
148
|
+
];
|
|
149
|
+
|
|
148
150
|
|
|
149
|
-
|
|
151
|
+
await execa(pkgManager, ["install", ...coreDeps], {
|
|
152
|
+
cwd: projectPath,
|
|
153
|
+
stdio: "inherit",
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await execa(pkgManager, ["install", "@prisma/client"], {
|
|
157
|
+
cwd: projectPath,
|
|
158
|
+
stdio: "inherit",
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await execa(pkgManager, ["install", "-D", ...devDeps], {
|
|
162
|
+
cwd: projectPath,
|
|
163
|
+
stdio: "inherit",
|
|
164
|
+
});
|
|
150
165
|
|
|
151
166
|
const templatePath = path.resolve(__dirname, "../template");
|
|
152
167
|
const projectPrismaPath = path.join(projectPath, "prisma/schema.prisma");
|
|
@@ -154,7 +169,6 @@ async function main() {
|
|
|
154
169
|
|
|
155
170
|
if (await fs.pathExists(templatePath)) {
|
|
156
171
|
await fs.copy(templatePath, projectPath, { overwrite: true });
|
|
157
|
-
console.log(chalk.green("✅ Template files copied!"));
|
|
158
172
|
}
|
|
159
173
|
|
|
160
174
|
const providerMap = {
|
|
@@ -199,10 +213,9 @@ PORT=3000
|
|
|
199
213
|
await fs.outputFile(path.join(projectPath, ".env"), envContent);
|
|
200
214
|
|
|
201
215
|
console.log(chalk.yellow("🎉 Project ready!"));
|
|
202
|
-
console.log(chalk.green("✅
|
|
203
|
-
console.log(chalk.cyan(
|
|
216
|
+
console.log(chalk.green("✅ Congratulations! Your project has been created successfully."));
|
|
217
|
+
console.log(chalk.cyan(`👉 Next steps: cd ${projectName}`));
|
|
204
218
|
|
|
205
|
-
console.log(chalk.yellow("🔧 Next steps (run manually):"));
|
|
206
219
|
console.log(chalk.cyan(`1. Generate Prisma Client:`));
|
|
207
220
|
console.log(chalk.cyan(` npx prisma generate`));
|
|
208
221
|
if (selectedProvider === "mongodb") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nestjs-prisma-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "A CLI to generate NestJS + Prisma project boilerplate with Swagger, Auth, and AWS S3 setup",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"author": "Kyaw Soe",
|
|
30
30
|
"license": "MIT",
|
|
31
|
-
"homepage": "https://github.com/kyawsoe-dev/nestjs-generator-cli
|
|
31
|
+
"homepage": "https://github.com/kyawsoe-dev/nestjs-generator-cli#readme",
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"chalk": "^4.1.2",
|
|
34
34
|
"execa": "^9.6.0",
|
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
generator client {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
provider = "prisma-client-js"
|
|
3
|
+
}
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
datasource db {
|
|
6
|
+
provider = "mysql"
|
|
7
|
+
url = env("DATABASE_URL")
|
|
8
|
+
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
model User {
|
|
11
|
+
id Int @id @default(autoincrement())
|
|
12
|
+
userId String @unique
|
|
13
|
+
name String?
|
|
14
|
+
email String
|
|
15
|
+
password String
|
|
16
|
+
profileUrl String?
|
|
17
|
+
createdAt DateTime @default(now())
|
|
18
|
+
updatedAt DateTime @updatedAt
|
|
19
|
+
|
|
20
|
+
@@map("tbl_user")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
model Log {
|
|
24
|
+
id Int @id @default(autoincrement())
|
|
25
|
+
method String
|
|
26
|
+
path String
|
|
27
|
+
statusCode Int
|
|
28
|
+
messageCode String?
|
|
29
|
+
message String?
|
|
30
|
+
headers Json
|
|
31
|
+
body Json?
|
|
32
|
+
query Json?
|
|
33
|
+
duration Int
|
|
34
|
+
createdAt DateTime @default(now())
|
|
35
|
+
|
|
36
|
+
@@map("tbl_log")
|
|
37
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Module } from '@nestjs/common';
|
|
2
|
-
import { APP_INTERCEPTOR } from '@nestjs/core';
|
|
2
|
+
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
|
|
3
3
|
import { APP_GUARD } from '@nestjs/core';
|
|
4
4
|
import { AppController } from './app.controller';
|
|
5
5
|
import { AppService } from './app.service';
|
|
@@ -7,6 +7,7 @@ import { PrismaModule } from './prisma/prisma.module';
|
|
|
7
7
|
import {
|
|
8
8
|
HttpResponseInterceptor,
|
|
9
9
|
LoggingInterceptor,
|
|
10
|
+
HttpExceptionFilter,
|
|
10
11
|
} from './common/http-interceptor/index';
|
|
11
12
|
import { JwtAuthGuard } from './modules/auth/jwt/jwt.guard';
|
|
12
13
|
import { ConfigModule } from '@nestjs/config';
|
|
@@ -20,7 +21,7 @@ import { AuthModule } from './modules/auth/auth.module';
|
|
|
20
21
|
}),
|
|
21
22
|
PrismaModule,
|
|
22
23
|
UserModule,
|
|
23
|
-
AuthModule
|
|
24
|
+
AuthModule,
|
|
24
25
|
],
|
|
25
26
|
controllers: [AppController],
|
|
26
27
|
providers: [
|
|
@@ -28,6 +29,10 @@ import { AuthModule } from './modules/auth/auth.module';
|
|
|
28
29
|
provide: APP_INTERCEPTOR,
|
|
29
30
|
useClass: LoggingInterceptor,
|
|
30
31
|
},
|
|
32
|
+
{
|
|
33
|
+
provide: APP_FILTER,
|
|
34
|
+
useClass: HttpExceptionFilter,
|
|
35
|
+
},
|
|
31
36
|
{
|
|
32
37
|
provide: APP_INTERCEPTOR,
|
|
33
38
|
useClass: HttpResponseInterceptor,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
|
|
2
|
+
|
|
3
|
+
export const MulterConfig: MulterOptions = {
|
|
4
|
+
limits: { fileSize: 10 * 1024 * 1024 },
|
|
5
|
+
fileFilter: (req, file, cb) => {
|
|
6
|
+
if (!file.mimetype.match(/^image\/(jpg|jpeg|png|gif)$/)) {
|
|
7
|
+
return cb(new Error('Only image files are allowed!'), false);
|
|
8
|
+
}
|
|
9
|
+
cb(null, true);
|
|
10
|
+
},
|
|
11
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
|
4
|
+
import { INestApplication } from '@nestjs/common';
|
|
5
|
+
|
|
6
|
+
const title = 'API';
|
|
7
|
+
const description = 'API Swagger documentation';
|
|
8
|
+
|
|
9
|
+
export const SwaggerConfig = (app: INestApplication) => {
|
|
10
|
+
const options = new DocumentBuilder()
|
|
11
|
+
.setTitle(title)
|
|
12
|
+
.setDescription(description)
|
|
13
|
+
.setVersion('1.0')
|
|
14
|
+
.addBearerAuth(
|
|
15
|
+
{
|
|
16
|
+
type: 'http',
|
|
17
|
+
scheme: 'bearer',
|
|
18
|
+
bearerFormat: 'JWT',
|
|
19
|
+
},
|
|
20
|
+
'bearerAuth',
|
|
21
|
+
)
|
|
22
|
+
.addSecurityRequirements('bearerAuth')
|
|
23
|
+
.build();
|
|
24
|
+
|
|
25
|
+
const document = SwaggerModule.createDocument(app, options);
|
|
26
|
+
|
|
27
|
+
const outputPath = join(process.cwd(), 'swagger-openapi-v1.json');
|
|
28
|
+
writeFileSync(outputPath, JSON.stringify(document, null, 2));
|
|
29
|
+
console.log(`Swagger JSON saved to ${outputPath}`);
|
|
30
|
+
|
|
31
|
+
SwaggerModule.setup('api', app, document, {
|
|
32
|
+
swaggerOptions: {
|
|
33
|
+
persistAuthorization: true,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
};
|
|
@@ -5,10 +5,22 @@ export class PaginatedResponseDto<T> {
|
|
|
5
5
|
total: number;
|
|
6
6
|
|
|
7
7
|
@ApiProperty()
|
|
8
|
-
|
|
8
|
+
limit: number;
|
|
9
9
|
|
|
10
10
|
@ApiProperty()
|
|
11
|
-
|
|
11
|
+
currentPage: number;
|
|
12
|
+
|
|
13
|
+
@ApiProperty()
|
|
14
|
+
firstPage: number;
|
|
15
|
+
|
|
16
|
+
@ApiProperty()
|
|
17
|
+
lastPage: number;
|
|
18
|
+
|
|
19
|
+
@ApiProperty()
|
|
20
|
+
nextPage: number | null;
|
|
21
|
+
|
|
22
|
+
@ApiProperty()
|
|
23
|
+
previousPage: number | null;
|
|
12
24
|
|
|
13
25
|
@ApiProperty({ isArray: true })
|
|
14
26
|
data: T[];
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
|
+
import { Transform } from 'class-transformer';
|
|
2
3
|
import { Type } from 'class-transformer';
|
|
3
4
|
import {
|
|
4
5
|
IsInt,
|
|
5
6
|
IsOptional,
|
|
6
7
|
IsString,
|
|
7
8
|
Min,
|
|
9
|
+
IsIn,
|
|
8
10
|
IsBooleanString,
|
|
9
11
|
} from 'class-validator';
|
|
10
12
|
|
|
@@ -23,6 +25,22 @@ export class PaginationDto {
|
|
|
23
25
|
@Min(1)
|
|
24
26
|
limit?: number = 20;
|
|
25
27
|
|
|
28
|
+
@ApiPropertyOptional({ example: 'id', description: 'Field to sort by' })
|
|
29
|
+
@IsOptional()
|
|
30
|
+
@IsString()
|
|
31
|
+
sortBy?: string;
|
|
32
|
+
|
|
33
|
+
@ApiPropertyOptional({
|
|
34
|
+
example: 'desc',
|
|
35
|
+
description: 'Sort order: asc or desc',
|
|
36
|
+
})
|
|
37
|
+
@IsOptional()
|
|
38
|
+
@IsIn(['asc', 'desc'])
|
|
39
|
+
@Transform(({ value }) =>
|
|
40
|
+
typeof value === 'string' ? value.toLowerCase() : value,
|
|
41
|
+
)
|
|
42
|
+
sortOrder?: 'asc' | 'desc';
|
|
43
|
+
|
|
26
44
|
@ApiPropertyOptional({ example: '', description: 'Search term' })
|
|
27
45
|
@IsOptional()
|
|
28
46
|
@IsString()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export enum HttpErrorType {
|
|
2
|
+
BAD_REQUEST = 400,
|
|
3
|
+
UNAUTHORIZED = 401,
|
|
4
|
+
PAYMENT_REQUIRED = 402,
|
|
5
|
+
FORBIDDEN = 403,
|
|
6
|
+
NOT_FOUND = 404,
|
|
7
|
+
METHOD_NOT_ALLOWED = 405,
|
|
8
|
+
NOT_ACCEPTABLE = 406,
|
|
9
|
+
PROXY_AUTHENTICATION_REQUIRED = 407,
|
|
10
|
+
REQUEST_TIMEOUT = 408,
|
|
11
|
+
CONFLICT = 409,
|
|
12
|
+
GONE = 410,
|
|
13
|
+
LENGTH_REQUIRED = 411,
|
|
14
|
+
PRECONDITION_FAILED = 412,
|
|
15
|
+
PAYLOAD_TOO_LARGE = 413,
|
|
16
|
+
URI_TOO_LONG = 414,
|
|
17
|
+
UNSUPPORTED_MEDIA_TYPE = 415,
|
|
18
|
+
REQUESTED_RANGE_NOT_SATISFIABLE = 416,
|
|
19
|
+
EXPECTATION_FAILED = 417,
|
|
20
|
+
I_AM_A_TEAPOT = 418,
|
|
21
|
+
MISDIRECTED = 421,
|
|
22
|
+
UNPROCESSABLE_ENTITY = 422,
|
|
23
|
+
FAILED_DEPENDENCY = 424,
|
|
24
|
+
TOO_MANY_REQUESTS = 429,
|
|
25
|
+
INTERNAL_SERVER_ERROR = 500,
|
|
26
|
+
NOT_IMPLEMENTED = 501,
|
|
27
|
+
BAD_GATEWAY = 502,
|
|
28
|
+
SERVICE_UNAVAILABLE = 503,
|
|
29
|
+
GATEWAY_TIMEOUT = 504,
|
|
30
|
+
HTTP_VERSION_NOT_SUPPORTED = 505,
|
|
31
|
+
}
|
|
@@ -5,6 +5,7 @@ export enum MESSAGE_CODE {
|
|
|
5
5
|
PASSWORD_SUCCESS = 204,
|
|
6
6
|
DATA_NOT_FOUND = 205,
|
|
7
7
|
USER_VERIFY = 206,
|
|
8
|
+
|
|
8
9
|
WRONG_USER_PW = 401,
|
|
9
10
|
INVALID_USER_ID = 402,
|
|
10
11
|
MISSED_TOKEN = 403,
|
|
@@ -16,5 +17,6 @@ export enum MESSAGE_CODE {
|
|
|
16
17
|
ROUTE_NOT_FOUND = 410,
|
|
17
18
|
REQUEST_FIELD_REQUIRED = 411,
|
|
18
19
|
DELETE_USER = 412,
|
|
20
|
+
|
|
19
21
|
SERVER_ERROR = 501,
|
|
20
22
|
}
|
|
@@ -1,94 +1,136 @@
|
|
|
1
|
+
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
|
1
2
|
import {
|
|
2
|
-
ExceptionFilter,
|
|
3
|
-
Catch,
|
|
4
3
|
ArgumentsHost,
|
|
4
|
+
Catch,
|
|
5
|
+
ExceptionFilter,
|
|
5
6
|
HttpException,
|
|
6
7
|
HttpStatus,
|
|
7
|
-
} from
|
|
8
|
-
import { Request, Response } from
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
8
|
+
} from "@nestjs/common";
|
|
9
|
+
import { Request, Response } from "express";
|
|
10
|
+
import * as moment from "moment";
|
|
11
|
+
import { PrismaService } from "src/prisma/prisma.service";
|
|
12
|
+
import { AppLogger } from "../logger/winston.logger";
|
|
13
|
+
import { DBErrorCode, MESSAGE_CODE } from "../enums";
|
|
11
14
|
|
|
12
15
|
@Catch()
|
|
13
16
|
export class HttpExceptionFilter implements ExceptionFilter {
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
constructor(private readonly prisma: PrismaService) {}
|
|
18
|
+
|
|
19
|
+
async catch(exception: unknown, host: ArgumentsHost) {
|
|
20
|
+
console.log(exception, "exception");
|
|
16
21
|
const ctx = host.switchToHttp();
|
|
17
22
|
const request = ctx.getRequest<Request>();
|
|
18
23
|
const response = ctx.getResponse<Response>();
|
|
24
|
+
const { method, originalUrl, headers, body, query } = request;
|
|
19
25
|
|
|
20
|
-
let
|
|
21
|
-
let message =
|
|
22
|
-
let messageCode: string | number =
|
|
26
|
+
let statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
27
|
+
let message = "Internal Server Error";
|
|
28
|
+
let messageCode: string | number = statusCode;
|
|
29
|
+
let validationErrors: Record<string, string[]> | null = null;
|
|
23
30
|
|
|
24
31
|
if (exception instanceof HttpException) {
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} else if (
|
|
32
|
-
typeof exceptionResponse === 'object' &&
|
|
33
|
-
exceptionResponse !== null
|
|
32
|
+
statusCode = exception.getStatus();
|
|
33
|
+
const exRes = exception.getResponse();
|
|
34
|
+
if (
|
|
35
|
+
statusCode === HttpStatus.BAD_REQUEST &&
|
|
36
|
+
typeof exRes === "object" &&
|
|
37
|
+
Array.isArray((exRes as any).message)
|
|
34
38
|
) {
|
|
35
|
-
const
|
|
39
|
+
const errors = (exRes as any).message;
|
|
40
|
+
validationErrors = {};
|
|
41
|
+
errors.forEach((msg: string) => {
|
|
42
|
+
const field = msg.split(" ")[0];
|
|
43
|
+
if (!validationErrors![field]) validationErrors![field] = [];
|
|
44
|
+
validationErrors![field].push(msg);
|
|
45
|
+
});
|
|
46
|
+
message = "Validation failed with invalid inputs.";
|
|
47
|
+
messageCode = MESSAGE_CODE.INVALID;
|
|
48
|
+
} else if (typeof exRes === "string") {
|
|
49
|
+
message = capitalizeFirst(exRes);
|
|
50
|
+
messageCode = MESSAGE_CODE.INVALID;
|
|
51
|
+
} else if (typeof exRes === "object" && exRes !== null) {
|
|
52
|
+
const res: any = exRes;
|
|
36
53
|
const rawMessage = Array.isArray(res.message)
|
|
37
54
|
? res.message[0]
|
|
38
|
-
:
|
|
39
|
-
|
|
55
|
+
: res.message ?? message;
|
|
40
56
|
message = capitalizeFirst(rawMessage);
|
|
41
57
|
messageCode =
|
|
42
58
|
res.messageCode ??
|
|
43
|
-
(
|
|
59
|
+
(statusCode === HttpStatus.BAD_REQUEST
|
|
60
|
+
? MESSAGE_CODE.INVALID
|
|
61
|
+
: statusCode);
|
|
44
62
|
} else {
|
|
45
63
|
message = capitalizeFirst(exception.message);
|
|
46
|
-
messageCode =
|
|
64
|
+
messageCode = statusCode;
|
|
47
65
|
}
|
|
48
|
-
} else if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
} else if (exception instanceof PrismaClientKnownRequestError) {
|
|
67
|
+
switch (exception.code) {
|
|
68
|
+
case "P2002":
|
|
69
|
+
statusCode = HttpStatus.CONFLICT;
|
|
70
|
+
const target = (exception.meta?.target as string) || "field";
|
|
71
|
+
message = `Duplicate entry: ${target
|
|
72
|
+
.replace("tbl_", "")
|
|
73
|
+
.replace("_key", "")} already exists.`;
|
|
74
|
+
messageCode = MESSAGE_CODE.INVALID;
|
|
75
|
+
break;
|
|
76
|
+
case DBErrorCode.PgForeignKeyConstraintViolation:
|
|
77
|
+
statusCode = HttpStatus.CONFLICT;
|
|
78
|
+
message = "Foreign key constraint violated";
|
|
79
|
+
messageCode = MESSAGE_CODE.INVALID;
|
|
80
|
+
break;
|
|
81
|
+
case DBErrorCode.PgNotNullConstraintViolation:
|
|
82
|
+
statusCode = HttpStatus.BAD_REQUEST;
|
|
83
|
+
message = "Not null constraint violated";
|
|
84
|
+
messageCode = MESSAGE_CODE.INVALID;
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
message = capitalizeFirst(exception.message || "Database exception");
|
|
88
|
+
messageCode = statusCode;
|
|
71
89
|
}
|
|
90
|
+
} else if (exception instanceof Error) {
|
|
91
|
+
message = capitalizeFirst(exception.message) || message;
|
|
92
|
+
messageCode = statusCode;
|
|
72
93
|
}
|
|
73
94
|
|
|
74
|
-
const duration = Date.now() - (request as any).__startTime ||
|
|
75
|
-
const logMessage = `${
|
|
95
|
+
const duration = Date.now() - ((request as any).__startTime || Date.now());
|
|
96
|
+
const logMessage = `${method} ${originalUrl} ${statusCode} - ${duration}ms | MessageCode: ${messageCode} | Message: ${message} | Headers: ${JSON.stringify(
|
|
97
|
+
headers
|
|
98
|
+
)}`;
|
|
99
|
+
|
|
100
|
+
if (statusCode >= 500) AppLogger.error(logMessage);
|
|
101
|
+
else AppLogger.warn(logMessage);
|
|
76
102
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
103
|
+
try {
|
|
104
|
+
await this.prisma.log.create({
|
|
105
|
+
data: {
|
|
106
|
+
method,
|
|
107
|
+
path: originalUrl,
|
|
108
|
+
statusCode,
|
|
109
|
+
messageCode: messageCode.toString(),
|
|
110
|
+
message,
|
|
111
|
+
headers: headers as any,
|
|
112
|
+
body: body as any,
|
|
113
|
+
query: query as any,
|
|
114
|
+
duration,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
} catch (err) {
|
|
118
|
+
AppLogger.error("Failed to save log to DB: " + err.message);
|
|
81
119
|
}
|
|
82
120
|
|
|
83
|
-
response.status(
|
|
84
|
-
|
|
121
|
+
return response.status(statusCode).json({
|
|
122
|
+
status: false,
|
|
123
|
+
statusCode,
|
|
85
124
|
messageCode,
|
|
125
|
+
path: originalUrl,
|
|
86
126
|
message,
|
|
127
|
+
validationErrors,
|
|
128
|
+
timestamp: moment().format("YYYY-MM-DD HH:mm:ss"),
|
|
87
129
|
});
|
|
88
130
|
}
|
|
89
131
|
}
|
|
90
132
|
|
|
91
133
|
function capitalizeFirst(text: string): string {
|
|
92
|
-
if (!text || typeof text !==
|
|
134
|
+
if (!text || typeof text !== "string") return text;
|
|
93
135
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
94
136
|
}
|