nestcraftx 0.3.0 → 1.0.0
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/.github/workflows/ci.yml +35 -0
- package/CHANGELOG.fr.md +74 -0
- package/CHANGELOG.md +74 -0
- package/PROGRESS.md +70 -67
- package/README.fr.md +60 -69
- package/bin/nestcraft.js +8 -0
- package/commands/demo.js +12 -46
- package/commands/generate.js +551 -2
- package/commands/generateConf.js +4 -4
- package/commands/help.js +11 -0
- package/commands/info.js +2 -3
- package/commands/list.js +93 -0
- package/commands/new.js +28 -56
- package/jest.config.js +9 -0
- package/package.json +7 -2
- package/readme.md +59 -68
- package/tests/e2e/generator.spec.js +71 -0
- package/tests/unit/appModuleUpdater.spec.js +111 -0
- package/tests/unit/cliParser.spec.js +74 -0
- package/tests/unit/controllerGenerator.spec.js +145 -0
- package/tests/unit/dtoGenerator.spec.js +87 -0
- package/tests/unit/entityGenerator.spec.js +65 -0
- package/tests/unit/generateCommand.spec.js +100 -0
- package/tests/unit/health.spec.js +69 -0
- package/tests/unit/listCommand.spec.js +91 -0
- package/tests/unit/middlewareGenerator.spec.js +64 -0
- package/tests/unit/newCommand.spec.js +88 -0
- package/tests/unit/relationCommand.spec.js +202 -0
- package/tests/unit/throttler.spec.js +117 -0
- package/utils/app-module.updater.js +6 -0
- package/utils/configs/setupCleanArchitecture.js +1 -1
- package/utils/configs/setupLightArchitecture.js +98 -26
- package/utils/envGenerator.js +9 -1
- package/utils/file-system.js +15 -0
- package/utils/file-utils/packageJsonUtils.js +6 -0
- package/utils/file-utils/saveProjectConfig.js +9 -0
- package/utils/fullModeInput.js +12 -229
- package/utils/generators/application/dtoGenerator.js +26 -8
- package/utils/generators/application/dtoUpdater.js +5 -0
- package/utils/generators/cleanModuleGenerator.js +40 -11
- package/utils/generators/domain/entityUpdater.js +5 -0
- package/utils/generators/infrastructure/mapperUpdater.js +5 -0
- package/utils/generators/infrastructure/middlewareGenerator.js +112 -315
- package/utils/generators/infrastructure/mongooseSchemaGenerator.js +114 -4
- package/utils/generators/infrastructure/repositoryGenerator.js +114 -14
- package/utils/generators/lightModuleGenerator.js +14 -0
- package/utils/generators/presentation/controllerGenerator.js +70 -19
- package/utils/helpers.js +8 -2
- package/utils/interactive/askEntityInputs.js +5 -68
- package/utils/interactive/askRelationCommand.js +98 -0
- package/utils/interactive/entityBuilder.js +411 -0
- package/utils/lightModeInput.js +22 -241
- package/utils/setups/orms/typeOrmSetup.js +42 -8
- package/utils/setups/projectSetup.js +88 -10
- package/utils/setups/setupAuth.js +335 -20
- package/utils/setups/setupDatabase.js +4 -1
- package/utils/setups/setupHealth.js +74 -0
- package/utils/setups/setupMongoose.js +84 -32
- package/utils/setups/setupPrisma.js +151 -4
- package/utils/setups/setupSwagger.js +15 -8
- package/utils/setups/setupThrottler.js +92 -0
- package/utils/shell.js +61 -9
|
@@ -33,42 +33,10 @@ const { createDirectory, createFile, updateFile } = require("../../file-system")
|
|
|
33
33
|
* @returns {string}
|
|
34
34
|
*/
|
|
35
35
|
function getExceptionFilterContent(orm) {
|
|
36
|
+
let dbSpecificChecks = "";
|
|
36
37
|
if (orm === "prisma") {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
ExceptionFilter,
|
|
40
|
-
Catch,
|
|
41
|
-
ArgumentsHost,
|
|
42
|
-
HttpException,
|
|
43
|
-
HttpStatus,
|
|
44
|
-
Logger,
|
|
45
|
-
} from '@nestjs/common';
|
|
46
|
-
import { Request, Response } from 'express';
|
|
47
|
-
|
|
48
|
-
@Catch()
|
|
49
|
-
export class AllExceptionsFilter implements ExceptionFilter {
|
|
50
|
-
private readonly logger = new Logger(AllExceptionsFilter.name);
|
|
51
|
-
|
|
52
|
-
catch(exception: unknown, host: ArgumentsHost) {
|
|
53
|
-
const ctx = host.switchToHttp();
|
|
54
|
-
const response = ctx.getResponse<Response>();
|
|
55
|
-
const request = ctx.getRequest<Request>();
|
|
56
|
-
|
|
57
|
-
let status = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
58
|
-
let message: string | string[] = 'Internal server error';
|
|
59
|
-
let errorDetails: any = null;
|
|
60
|
-
|
|
61
|
-
if (exception instanceof HttpException) {
|
|
62
|
-
status = exception.getStatus();
|
|
63
|
-
const res = exception.getResponse();
|
|
64
|
-
if (typeof res === 'string') {
|
|
65
|
-
message = res;
|
|
66
|
-
} else if (typeof res === 'object' && res !== null) {
|
|
67
|
-
const resObj = res as any;
|
|
68
|
-
message = resObj.message || resObj.error || 'HttpException';
|
|
69
|
-
errorDetails = resObj;
|
|
70
|
-
}
|
|
71
|
-
} else if (
|
|
38
|
+
dbSpecificChecks = `
|
|
39
|
+
else if (
|
|
72
40
|
typeof exception === 'object' &&
|
|
73
41
|
exception &&
|
|
74
42
|
exception.constructor &&
|
|
@@ -78,75 +46,13 @@ export class AllExceptionsFilter implements ExceptionFilter {
|
|
|
78
46
|
)
|
|
79
47
|
) {
|
|
80
48
|
status = HttpStatus.BAD_REQUEST;
|
|
81
|
-
message = (exception as any).message || 'Prisma error';
|
|
49
|
+
message = (exception as any).message || 'Prisma database error';
|
|
50
|
+
errorResponseName = exception.constructor.name;
|
|
82
51
|
errorDetails = exception;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
stack: exception.stack,
|
|
88
|
-
};
|
|
89
|
-
} else {
|
|
90
|
-
message = 'Une erreur inattendue est survenue';
|
|
91
|
-
errorDetails = exception;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
95
|
-
this.logger.error(
|
|
96
|
-
\`Exception on \${request.method} \${request.url}\`,
|
|
97
|
-
JSON.stringify({ message, status, errorDetails, exception }),
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
response.status(status).json({
|
|
102
|
-
statusCode: status,
|
|
103
|
-
timestamp: new Date().toISOString(),
|
|
104
|
-
path: request.url,
|
|
105
|
-
method: request.method,
|
|
106
|
-
message,
|
|
107
|
-
error: errorDetails,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
`.trim();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (orm === "mongoose") {
|
|
115
|
-
return `
|
|
116
|
-
import {
|
|
117
|
-
ExceptionFilter,
|
|
118
|
-
Catch,
|
|
119
|
-
ArgumentsHost,
|
|
120
|
-
HttpException,
|
|
121
|
-
HttpStatus,
|
|
122
|
-
Logger,
|
|
123
|
-
} from '@nestjs/common';
|
|
124
|
-
import { Request, Response } from 'express';
|
|
125
|
-
|
|
126
|
-
@Catch()
|
|
127
|
-
export class AllExceptionsFilter implements ExceptionFilter {
|
|
128
|
-
private readonly logger = new Logger(AllExceptionsFilter.name);
|
|
129
|
-
|
|
130
|
-
catch(exception: unknown, host: ArgumentsHost) {
|
|
131
|
-
const ctx = host.switchToHttp();
|
|
132
|
-
const response = ctx.getResponse<Response>();
|
|
133
|
-
const request = ctx.getRequest<Request>();
|
|
134
|
-
|
|
135
|
-
let status = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
136
|
-
let message: string | string[] = 'Internal server error';
|
|
137
|
-
let errorDetails: any = null;
|
|
138
|
-
|
|
139
|
-
if (exception instanceof HttpException) {
|
|
140
|
-
status = exception.getStatus();
|
|
141
|
-
const res = exception.getResponse();
|
|
142
|
-
if (typeof res === 'string') {
|
|
143
|
-
message = res;
|
|
144
|
-
} else if (typeof res === 'object' && res !== null) {
|
|
145
|
-
const resObj = res as any;
|
|
146
|
-
message = resObj.message || resObj.error || 'HttpException';
|
|
147
|
-
errorDetails = resObj;
|
|
148
|
-
}
|
|
149
|
-
} else if (
|
|
52
|
+
}`;
|
|
53
|
+
} else if (orm === "mongoose") {
|
|
54
|
+
dbSpecificChecks = `
|
|
55
|
+
else if (
|
|
150
56
|
typeof exception === 'object' &&
|
|
151
57
|
exception &&
|
|
152
58
|
'name' in exception &&
|
|
@@ -156,75 +62,78 @@ export class AllExceptionsFilter implements ExceptionFilter {
|
|
|
156
62
|
)
|
|
157
63
|
) {
|
|
158
64
|
status = HttpStatus.BAD_REQUEST;
|
|
159
|
-
message = (exception as any).message || '
|
|
65
|
+
message = (exception as any).message || 'Mongoose database error';
|
|
66
|
+
errorResponseName = (exception as any).name;
|
|
160
67
|
errorDetails = exception;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
68
|
+
}`;
|
|
69
|
+
} else if (orm === "typeorm") {
|
|
70
|
+
dbSpecificChecks = `
|
|
71
|
+
else if (
|
|
72
|
+
typeof exception === 'object' &&
|
|
73
|
+
exception &&
|
|
74
|
+
'name' in exception &&
|
|
75
|
+
(
|
|
76
|
+
(exception as any).name === 'QueryFailedError' ||
|
|
77
|
+
(exception as any).name === 'EntityNotFoundError' ||
|
|
78
|
+
(exception as any).name === 'CannotCreateEntityIdMapError'
|
|
79
|
+
)
|
|
80
|
+
) {
|
|
81
|
+
status = HttpStatus.BAD_REQUEST;
|
|
82
|
+
message = (exception as any).message || 'TypeORM database error';
|
|
83
|
+
errorResponseName = (exception as any).name;
|
|
84
|
+
errorDetails = exception;
|
|
85
|
+
}`;
|
|
86
|
+
} else if (orm === "sequelize") {
|
|
87
|
+
dbSpecificChecks = `
|
|
88
|
+
else if (
|
|
89
|
+
typeof exception === 'object' &&
|
|
90
|
+
exception &&
|
|
91
|
+
exception.constructor &&
|
|
92
|
+
(
|
|
93
|
+
exception.constructor.name === 'SequelizeDatabaseError' ||
|
|
94
|
+
exception.constructor.name === 'SequelizeValidationError'
|
|
95
|
+
)
|
|
96
|
+
) {
|
|
97
|
+
status = HttpStatus.BAD_REQUEST;
|
|
98
|
+
message = (exception as any).message || 'Sequelize database error';
|
|
99
|
+
errorResponseName = exception.constructor.name;
|
|
100
|
+
errorDetails = exception;
|
|
101
|
+
}`;
|
|
102
|
+
} else {
|
|
103
|
+
// fallback global / universal
|
|
104
|
+
dbSpecificChecks = `
|
|
105
|
+
// Prisma
|
|
106
|
+
else if (
|
|
107
|
+
typeof exception === 'object' &&
|
|
108
|
+
exception &&
|
|
109
|
+
exception.constructor &&
|
|
110
|
+
(
|
|
111
|
+
exception.constructor.name === 'PrismaClientKnownRequestError' ||
|
|
112
|
+
exception.constructor.name === 'PrismaClientValidationError'
|
|
113
|
+
)
|
|
114
|
+
) {
|
|
115
|
+
status = HttpStatus.BAD_REQUEST;
|
|
116
|
+
message = (exception as any).message || 'Prisma database error';
|
|
117
|
+
errorResponseName = exception.constructor.name;
|
|
169
118
|
errorDetails = exception;
|
|
170
119
|
}
|
|
171
|
-
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
120
|
+
// Mongoose/Mongo
|
|
121
|
+
else if (
|
|
122
|
+
typeof exception === 'object' &&
|
|
123
|
+
exception &&
|
|
124
|
+
'name' in exception &&
|
|
125
|
+
(
|
|
126
|
+
(exception as any).name === 'MongoError' ||
|
|
127
|
+
(exception as any).name === 'MongooseError'
|
|
128
|
+
)
|
|
129
|
+
) {
|
|
130
|
+
status = HttpStatus.BAD_REQUEST;
|
|
131
|
+
message = (exception as any).message || 'Mongoose database error';
|
|
132
|
+
errorResponseName = (exception as any).name;
|
|
133
|
+
errorDetails = exception;
|
|
177
134
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
statusCode: status,
|
|
181
|
-
timestamp: new Date().toISOString(),
|
|
182
|
-
path: request.url,
|
|
183
|
-
method: request.method,
|
|
184
|
-
message,
|
|
185
|
-
error: errorDetails,
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
`.trim();
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (orm === "typeorm") {
|
|
193
|
-
return `
|
|
194
|
-
import {
|
|
195
|
-
ExceptionFilter,
|
|
196
|
-
Catch,
|
|
197
|
-
ArgumentsHost,
|
|
198
|
-
HttpException,
|
|
199
|
-
HttpStatus,
|
|
200
|
-
Logger,
|
|
201
|
-
} from '@nestjs/common';
|
|
202
|
-
import { Request, Response } from 'express';
|
|
203
|
-
|
|
204
|
-
@Catch()
|
|
205
|
-
export class AllExceptionsFilter implements ExceptionFilter {
|
|
206
|
-
private readonly logger = new Logger(AllExceptionsFilter.name);
|
|
207
|
-
|
|
208
|
-
catch(exception: unknown, host: ArgumentsHost) {
|
|
209
|
-
const ctx = host.switchToHttp();
|
|
210
|
-
const response = ctx.getResponse<Response>();
|
|
211
|
-
const request = ctx.getRequest<Request>();
|
|
212
|
-
|
|
213
|
-
let status = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
214
|
-
let message: string | string[] = 'Internal server error';
|
|
215
|
-
let errorDetails: any = null;
|
|
216
|
-
|
|
217
|
-
if (exception instanceof HttpException) {
|
|
218
|
-
status = exception.getStatus();
|
|
219
|
-
const res = exception.getResponse();
|
|
220
|
-
if (typeof res === 'string') {
|
|
221
|
-
message = res;
|
|
222
|
-
} else if (typeof res === 'object' && res !== null) {
|
|
223
|
-
const resObj = res as any;
|
|
224
|
-
message = resObj.message || resObj.error || 'HttpException';
|
|
225
|
-
errorDetails = resObj;
|
|
226
|
-
}
|
|
227
|
-
} else if (
|
|
135
|
+
// TypeORM
|
|
136
|
+
else if (
|
|
228
137
|
typeof exception === 'object' &&
|
|
229
138
|
exception &&
|
|
230
139
|
'name' in exception &&
|
|
@@ -235,75 +144,12 @@ export class AllExceptionsFilter implements ExceptionFilter {
|
|
|
235
144
|
)
|
|
236
145
|
) {
|
|
237
146
|
status = HttpStatus.BAD_REQUEST;
|
|
238
|
-
message = (exception as any).message || 'TypeORM error';
|
|
239
|
-
|
|
240
|
-
} else if (exception instanceof Error) {
|
|
241
|
-
message = exception.message;
|
|
242
|
-
errorDetails = {
|
|
243
|
-
name: exception.name,
|
|
244
|
-
stack: exception.stack,
|
|
245
|
-
};
|
|
246
|
-
} else {
|
|
247
|
-
message = 'Une erreur inattendue est survenue';
|
|
147
|
+
message = (exception as any).message || 'TypeORM database error';
|
|
148
|
+
errorResponseName = (exception as any).name;
|
|
248
149
|
errorDetails = exception;
|
|
249
150
|
}
|
|
250
|
-
|
|
251
|
-
if (
|
|
252
|
-
this.logger.error(
|
|
253
|
-
\`Exception on \${request.method} \${request.url}\`,
|
|
254
|
-
JSON.stringify({ message, status, errorDetails, exception }),
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
response.status(status).json({
|
|
259
|
-
statusCode: status,
|
|
260
|
-
timestamp: new Date().toISOString(),
|
|
261
|
-
path: request.url,
|
|
262
|
-
method: request.method,
|
|
263
|
-
message,
|
|
264
|
-
error: errorDetails,
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
`.trim();
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (orm === "sequelize") {
|
|
272
|
-
return `
|
|
273
|
-
import {
|
|
274
|
-
ExceptionFilter,
|
|
275
|
-
Catch,
|
|
276
|
-
ArgumentsHost,
|
|
277
|
-
HttpException,
|
|
278
|
-
HttpStatus,
|
|
279
|
-
Logger,
|
|
280
|
-
} from '@nestjs/common';
|
|
281
|
-
import { Request, Response } from 'express';
|
|
282
|
-
|
|
283
|
-
@Catch()
|
|
284
|
-
export class AllExceptionsFilter implements ExceptionFilter {
|
|
285
|
-
private readonly logger = new Logger(AllExceptionsFilter.name);
|
|
286
|
-
|
|
287
|
-
catch(exception: unknown, host: ArgumentsHost) {
|
|
288
|
-
const ctx = host.switchToHttp();
|
|
289
|
-
const response = ctx.getResponse<Response>();
|
|
290
|
-
const request = ctx.getRequest<Request>();
|
|
291
|
-
|
|
292
|
-
let status = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
293
|
-
let message: string | string[] = 'Internal server error';
|
|
294
|
-
let errorDetails: any = null;
|
|
295
|
-
|
|
296
|
-
if (exception instanceof HttpException) {
|
|
297
|
-
status = exception.getStatus();
|
|
298
|
-
const res = exception.getResponse();
|
|
299
|
-
if (typeof res === 'string') {
|
|
300
|
-
message = res;
|
|
301
|
-
} else if (typeof res === 'object' && res !== null) {
|
|
302
|
-
const resObj = res as any;
|
|
303
|
-
message = resObj.message || resObj.error || 'HttpException';
|
|
304
|
-
errorDetails = resObj;
|
|
305
|
-
}
|
|
306
|
-
} else if (
|
|
151
|
+
// Sequelize
|
|
152
|
+
else if (
|
|
307
153
|
typeof exception === 'object' &&
|
|
308
154
|
exception &&
|
|
309
155
|
exception.constructor &&
|
|
@@ -313,40 +159,12 @@ export class AllExceptionsFilter implements ExceptionFilter {
|
|
|
313
159
|
)
|
|
314
160
|
) {
|
|
315
161
|
status = HttpStatus.BAD_REQUEST;
|
|
316
|
-
message = (exception as any).message || 'Sequelize error';
|
|
317
|
-
|
|
318
|
-
} else if (exception instanceof Error) {
|
|
319
|
-
message = exception.message;
|
|
320
|
-
errorDetails = {
|
|
321
|
-
name: exception.name,
|
|
322
|
-
stack: exception.stack,
|
|
323
|
-
};
|
|
324
|
-
} else {
|
|
325
|
-
message = 'Une erreur inattendue est survenue';
|
|
162
|
+
message = (exception as any).message || 'Sequelize database error';
|
|
163
|
+
errorResponseName = exception.constructor.name;
|
|
326
164
|
errorDetails = exception;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
330
|
-
this.logger.error(
|
|
331
|
-
\`Exception on \${request.method} \${request.url}\`,
|
|
332
|
-
JSON.stringify({ message, status, errorDetails, exception }),
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
response.status(status).json({
|
|
337
|
-
statusCode: status,
|
|
338
|
-
timestamp: new Date().toISOString(),
|
|
339
|
-
path: request.url,
|
|
340
|
-
method: request.method,
|
|
341
|
-
message,
|
|
342
|
-
error: errorDetails,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
`.trim();
|
|
165
|
+
}`;
|
|
347
166
|
}
|
|
348
167
|
|
|
349
|
-
// Version universelle (multi-ORM / fallback)
|
|
350
168
|
return `
|
|
351
169
|
import {
|
|
352
170
|
ExceptionFilter,
|
|
@@ -358,6 +176,15 @@ import {
|
|
|
358
176
|
} from '@nestjs/common';
|
|
359
177
|
import { Request, Response } from 'express';
|
|
360
178
|
|
|
179
|
+
export interface ErrorResponse {
|
|
180
|
+
statusCode: number;
|
|
181
|
+
timestamp: string;
|
|
182
|
+
path: string;
|
|
183
|
+
method: string;
|
|
184
|
+
message: string | string[];
|
|
185
|
+
error: string;
|
|
186
|
+
}
|
|
187
|
+
|
|
361
188
|
@Catch()
|
|
362
189
|
export class AllExceptionsFilter implements ExceptionFilter {
|
|
363
190
|
private readonly logger = new Logger(AllExceptionsFilter.name);
|
|
@@ -369,11 +196,13 @@ export class AllExceptionsFilter implements ExceptionFilter {
|
|
|
369
196
|
|
|
370
197
|
let status = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
371
198
|
let message: string | string[] = 'Internal server error';
|
|
199
|
+
let errorResponseName = 'InternalServerError';
|
|
372
200
|
let errorDetails: any = null;
|
|
373
201
|
|
|
374
202
|
if (exception instanceof HttpException) {
|
|
375
203
|
status = exception.getStatus();
|
|
376
204
|
const res = exception.getResponse();
|
|
205
|
+
errorResponseName = exception.name || 'HttpException';
|
|
377
206
|
if (typeof res === 'string') {
|
|
378
207
|
message = res;
|
|
379
208
|
} else if (typeof res === 'object' && res !== null) {
|
|
@@ -381,51 +210,10 @@ export class AllExceptionsFilter implements ExceptionFilter {
|
|
|
381
210
|
message = resObj.message || resObj.error || 'HttpException';
|
|
382
211
|
errorDetails = resObj;
|
|
383
212
|
}
|
|
384
|
-
}
|
|
385
|
-
// Prisma
|
|
386
|
-
else if (
|
|
387
|
-
typeof exception === 'object' &&
|
|
388
|
-
exception &&
|
|
389
|
-
exception.constructor &&
|
|
390
|
-
(
|
|
391
|
-
exception.constructor.name === 'PrismaClientKnownRequestError' ||
|
|
392
|
-
exception.constructor.name === 'PrismaClientValidationError'
|
|
393
|
-
)
|
|
394
|
-
) {
|
|
395
|
-
status = HttpStatus.BAD_REQUEST;
|
|
396
|
-
message = (exception as any).message || 'Prisma error';
|
|
397
|
-
errorDetails = exception;
|
|
398
|
-
}
|
|
399
|
-
// Mongoose/Mongo
|
|
400
|
-
else if (
|
|
401
|
-
typeof exception === 'object' &&
|
|
402
|
-
exception &&
|
|
403
|
-
'name' in exception &&
|
|
404
|
-
(
|
|
405
|
-
(exception as any).name === 'MongoError' ||
|
|
406
|
-
(exception as any).name === 'MongooseError'
|
|
407
|
-
)
|
|
408
|
-
) {
|
|
409
|
-
status = HttpStatus.BAD_REQUEST;
|
|
410
|
-
message = (exception as any).message || 'MongoDB error';
|
|
411
|
-
errorDetails = exception;
|
|
412
|
-
}
|
|
413
|
-
// Sequelize
|
|
414
|
-
else if (
|
|
415
|
-
typeof exception === 'object' &&
|
|
416
|
-
exception &&
|
|
417
|
-
exception.constructor &&
|
|
418
|
-
(
|
|
419
|
-
exception.constructor.name === 'SequelizeDatabaseError' ||
|
|
420
|
-
exception.constructor.name === 'SequelizeValidationError'
|
|
421
|
-
)
|
|
422
|
-
) {
|
|
423
|
-
status = HttpStatus.BAD_REQUEST;
|
|
424
|
-
message = (exception as any).message || 'Sequelize error';
|
|
425
|
-
errorDetails = exception;
|
|
426
|
-
}
|
|
213
|
+
}${dbSpecificChecks}
|
|
427
214
|
else if (exception instanceof Error) {
|
|
428
215
|
message = exception.message;
|
|
216
|
+
errorResponseName = exception.name;
|
|
429
217
|
errorDetails = {
|
|
430
218
|
name: exception.name,
|
|
431
219
|
stack: exception.stack,
|
|
@@ -436,18 +224,27 @@ export class AllExceptionsFilter implements ExceptionFilter {
|
|
|
436
224
|
}
|
|
437
225
|
|
|
438
226
|
this.logger.error(
|
|
439
|
-
\`
|
|
440
|
-
|
|
227
|
+
\`[\${request.method}] \${request.url} - Error status: \${status}\`,
|
|
228
|
+
exception instanceof Error ? exception.stack : JSON.stringify(exception),
|
|
441
229
|
);
|
|
442
230
|
|
|
443
|
-
|
|
231
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
232
|
+
const payload: ErrorResponse = {
|
|
444
233
|
statusCode: status,
|
|
445
234
|
timestamp: new Date().toISOString(),
|
|
446
235
|
path: request.url,
|
|
447
236
|
method: request.method,
|
|
448
|
-
message
|
|
449
|
-
|
|
450
|
-
|
|
237
|
+
message: (status === HttpStatus.INTERNAL_SERVER_ERROR && isProduction)
|
|
238
|
+
? 'Internal server error'
|
|
239
|
+
: message,
|
|
240
|
+
error: isProduction ? errorResponseName : (errorDetails?.error || errorResponseName),
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
if (!isProduction && errorDetails) {
|
|
244
|
+
(payload as any).details = errorDetails;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
response.status(status).json(payload);
|
|
451
248
|
}
|
|
452
249
|
}
|
|
453
250
|
`.trim();
|
|
@@ -119,6 +119,7 @@ async function generateMongooseSchemaFileContent(
|
|
|
119
119
|
"number",
|
|
120
120
|
"int",
|
|
121
121
|
"float",
|
|
122
|
+
"decimal",
|
|
122
123
|
"boolean",
|
|
123
124
|
"date",
|
|
124
125
|
"uuid",
|
|
@@ -144,35 +145,67 @@ async function generateMongooseSchemaFileContent(
|
|
|
144
145
|
|
|
145
146
|
// 2. Mapping des types CLI -> TypeScript/Mongoose
|
|
146
147
|
let tsType = "string";
|
|
147
|
-
let
|
|
148
|
+
let propOpts = [];
|
|
148
149
|
|
|
149
150
|
switch (rawType) {
|
|
150
151
|
case "text":
|
|
151
152
|
case "uuid":
|
|
152
153
|
case "string":
|
|
153
154
|
tsType = "string";
|
|
155
|
+
propOpts.push("type: String");
|
|
154
156
|
break;
|
|
155
157
|
case "int":
|
|
156
158
|
case "number":
|
|
157
159
|
case "float":
|
|
158
160
|
case "decimal":
|
|
159
161
|
tsType = "number";
|
|
162
|
+
propOpts.push("type: Number");
|
|
160
163
|
break;
|
|
161
164
|
case "boolean":
|
|
162
165
|
tsType = "boolean";
|
|
166
|
+
propOpts.push("type: Boolean");
|
|
163
167
|
break;
|
|
164
168
|
case "date":
|
|
165
169
|
tsType = "Date";
|
|
170
|
+
propOpts.push("type: Date");
|
|
166
171
|
break;
|
|
167
172
|
case "json":
|
|
168
173
|
tsType = "Record<string, any>";
|
|
169
|
-
|
|
174
|
+
propOpts.push("type: Object");
|
|
170
175
|
break;
|
|
171
176
|
default:
|
|
172
177
|
tsType = "any";
|
|
173
178
|
}
|
|
174
179
|
|
|
175
|
-
|
|
180
|
+
const isRequired = f.nullable !== undefined ? !f.nullable : (fieldName.toLowerCase() !== "isactive" && fieldName.toLowerCase() !== "password");
|
|
181
|
+
if (isRequired) {
|
|
182
|
+
propOpts.push("required: true");
|
|
183
|
+
}
|
|
184
|
+
if (f.unique) {
|
|
185
|
+
propOpts.push("unique: true");
|
|
186
|
+
}
|
|
187
|
+
if (f.default !== undefined && f.default !== null && f.default !== "") {
|
|
188
|
+
let defaultVal = f.default;
|
|
189
|
+
if (typeof defaultVal === "string") {
|
|
190
|
+
if (defaultVal === "true") defaultVal = true;
|
|
191
|
+
else if (defaultVal === "false") defaultVal = false;
|
|
192
|
+
else if (defaultVal.toLowerCase() === "now" || defaultVal.toLowerCase() === "now()") {
|
|
193
|
+
defaultVal = "Date.now";
|
|
194
|
+
} else if (!isNaN(defaultVal) && defaultVal.trim() !== "") {
|
|
195
|
+
defaultVal = Number(defaultVal);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (defaultVal === "Date.now") {
|
|
199
|
+
propOpts.push("default: Date.now");
|
|
200
|
+
} else if (typeof defaultVal === "boolean" || typeof defaultVal === "number") {
|
|
201
|
+
propOpts.push(`default: ${defaultVal}`);
|
|
202
|
+
} else {
|
|
203
|
+
propOpts.push(`default: '${defaultVal}'`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const propOptions = propOpts.join(", ");
|
|
208
|
+
return ` @Prop({ ${propOptions} })\n ${fieldName}${f.nullable ? "?" : ""}: ${tsType};`;
|
|
176
209
|
});
|
|
177
210
|
|
|
178
211
|
const allFields = [...new Set([...directFields, ...dynamicRelations])].join(
|
|
@@ -193,4 +226,81 @@ ${allFields}
|
|
|
193
226
|
export const ${entityName}Schema = SchemaFactory.createForClass(${entityName});`.trim();
|
|
194
227
|
}
|
|
195
228
|
|
|
196
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Ajoute une relation entre deux schémas Mongoose EXISTANTS.
|
|
231
|
+
* Patche les deux fichiers .schema.ts pour ajouter les champs de référence.
|
|
232
|
+
*
|
|
233
|
+
* @param {string} source - Nom du module source (ex: "post")
|
|
234
|
+
* @param {string} target - Nom du module cible (ex: "user")
|
|
235
|
+
* @param {string} relationType - "n-1" | "1-n" | "1-1" | "n-n"
|
|
236
|
+
* @param {string} mode - "full" | "light"
|
|
237
|
+
*/
|
|
238
|
+
async function addMongooseRelation(source, target, relationType, mode = "full") {
|
|
239
|
+
const fs = require("fs");
|
|
240
|
+
const { updateFile } = require("../../userInput");
|
|
241
|
+
|
|
242
|
+
const sourceLow = source.toLowerCase();
|
|
243
|
+
const targetLow = target.toLowerCase();
|
|
244
|
+
const sourceCap = capitalize(source);
|
|
245
|
+
const targetCap = capitalize(target);
|
|
246
|
+
|
|
247
|
+
const isFull = mode === "full";
|
|
248
|
+
|
|
249
|
+
const sourceSchemaPath = isFull
|
|
250
|
+
? `src/${sourceLow}/infrastructure/persistence/mongoose/${sourceLow}.schema.ts`
|
|
251
|
+
: `src/${sourceLow}/entities/${sourceLow}.schema.ts`;
|
|
252
|
+
|
|
253
|
+
const targetSchemaPath = isFull
|
|
254
|
+
? `src/${targetLow}/infrastructure/persistence/mongoose/${targetLow}.schema.ts`
|
|
255
|
+
: `src/${targetLow}/entities/${targetLow}.schema.ts`;
|
|
256
|
+
|
|
257
|
+
// --- Champs à injecter dans SOURCE ---
|
|
258
|
+
let sourceField = "";
|
|
259
|
+
switch (relationType) {
|
|
260
|
+
case "n-1":
|
|
261
|
+
case "1-1":
|
|
262
|
+
// FK : référence vers un document cible
|
|
263
|
+
sourceField = `\n @Prop({ type: 'ObjectId', ref: '${targetCap}' })\n ${targetLow}Id: string;\n`;
|
|
264
|
+
break;
|
|
265
|
+
case "1-n":
|
|
266
|
+
case "n-n":
|
|
267
|
+
// Tableau de références vers la cible
|
|
268
|
+
sourceField = `\n @Prop([{ type: 'ObjectId', ref: '${targetCap}' }])\n ${targetLow}Ids: string[];\n`;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// --- Champs inverses à injecter dans TARGET ---
|
|
273
|
+
let targetField = "";
|
|
274
|
+
switch (relationType) {
|
|
275
|
+
case "n-1":
|
|
276
|
+
case "n-n":
|
|
277
|
+
// Le target a un tableau de sources
|
|
278
|
+
targetField = `\n @Prop([{ type: 'ObjectId', ref: '${sourceCap}' }])\n ${sourceLow}Ids: string[];\n`;
|
|
279
|
+
break;
|
|
280
|
+
case "1-n":
|
|
281
|
+
case "1-1":
|
|
282
|
+
// Le target a une référence unique vers source
|
|
283
|
+
targetField = `\n @Prop({ type: 'ObjectId', ref: '${sourceCap}' })\n ${sourceLow}Id: string;\n`;
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Patch source schema
|
|
288
|
+
if (fs.existsSync(sourceSchemaPath) && sourceField) {
|
|
289
|
+
await updateFile({
|
|
290
|
+
path: sourceSchemaPath,
|
|
291
|
+
pattern: new RegExp(`(export class ${sourceCap} \\{)`),
|
|
292
|
+
replacement: `$1${sourceField}`,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Patch target schema
|
|
297
|
+
if (fs.existsSync(targetSchemaPath) && targetField) {
|
|
298
|
+
await updateFile({
|
|
299
|
+
path: targetSchemaPath,
|
|
300
|
+
pattern: new RegExp(`(export class ${targetCap} \\{)`),
|
|
301
|
+
replacement: `$1${targetField}`,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
module.exports = { generateMongooseSchemaFileContent, addMongooseRelation };
|