crypt-express-app 1.3.20

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.
@@ -0,0 +1,658 @@
1
+ // generate-module.ts
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ // get module name from command line
5
+ const args = process.argv.slice(2);
6
+ // Check that exactly **one argument** is provided
7
+ if (args.length !== 1) {
8
+ console.error('Invalid module name: provide exactly one module name without spaces.');
9
+ console.error('Example: npm run generate-module clientRole');
10
+ process.exit(1);
11
+ }
12
+ const moduleInput = args[0];
13
+ // Reject if input contains spaces
14
+ if (/\s/.test(moduleInput)) {
15
+ console.error('Invalid module name: spaces are not allowed. Use camelCase, kebab-case or snake_case.');
16
+ process.exit(1);
17
+ }
18
+ if (!moduleInput) {
19
+ console.error('Please provide a module name: npm run generate-module <module-name>');
20
+ process.exit(1);
21
+ }
22
+ // Convert any input to camelCase
23
+ const moduleName = moduleInput.includes('-') || moduleInput.includes('_') || moduleInput.includes(' ')
24
+ ? moduleInput
25
+ .toLowerCase()
26
+ .replace(/[-_\s]+(.)/g, (_, c) => c.toUpperCase())
27
+ : moduleInput; // preserve existing camelCase
28
+ console.log("moduleName: ", moduleName);
29
+ // Convert any input (camelCase, PascalCase, snake_case, spaces) to kebab-case
30
+ const fileName = moduleName
31
+ .replace(/([a-z])([A-Z])/g, '$1-$2') // add dash before uppercase letters
32
+ .toLowerCase(); // lowercase everything
33
+ console.log("fileName: ", fileName);
34
+ const basePath = path.join(__dirname, '..', 'src', 'modules', fileName);
35
+ // Create folder
36
+ if (!fs.existsSync(basePath)) {
37
+ fs.mkdirSync(basePath, { recursive: true });
38
+ }
39
+ // Capitalize first letter
40
+ const ModuleClassName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1);
41
+ // Service
42
+ const serviceContent = `import { Create${ModuleClassName}Dto, Update${ModuleClassName}Dto, Create${ModuleClassName}Response, Update${ModuleClassName}Response, Get${ModuleClassName}Response, List${ModuleClassName}Response } from "./${fileName}.dto";
43
+ import { Prisma } from "@prisma/client";
44
+ import prisma from '../../prisma/client';
45
+ import { PaginationRequestDto } from "../common/common.dto";
46
+
47
+
48
+ export class ${ModuleClassName}Service {
49
+ constructor() {}
50
+
51
+ async create(payload: Create${ModuleClassName}Dto): Promise<Create${ModuleClassName}Response> {
52
+ try {
53
+ // TODO: create ${ModuleClassName} in database
54
+ return {
55
+ ${moduleName}Id: "generated-${moduleName}Id",
56
+ ...payload,
57
+ createdAt: new Date().toISOString(),
58
+ updatedAt: new Date().toISOString(),
59
+ };
60
+ } catch (error) {
61
+ throw error;
62
+ }
63
+ }
64
+
65
+ async update(${moduleName}Id: string, payload: Update${ModuleClassName}Dto): Promise<Update${ModuleClassName}Response> {
66
+ try {
67
+ // TODO: update ${ModuleClassName} by ${moduleName}Id
68
+ return {
69
+ ${moduleName}Id,
70
+ ...payload,
71
+ updatedAt: new Date().toISOString(),
72
+ };
73
+ } catch (error) {
74
+ throw error;
75
+ }
76
+ }
77
+
78
+ async getById(${moduleName}Id: string): Promise<Get${ModuleClassName}Response> {
79
+ try {
80
+ // TODO: fetch ${ModuleClassName} by ${moduleName}Id
81
+ return {
82
+ ${moduleName}Id,
83
+ name: "Sample ${ModuleClassName}",
84
+ createdAt: new Date().toISOString(),
85
+ updatedAt: new Date().toISOString(),
86
+ };
87
+ } catch (error) {
88
+ throw error;
89
+ }
90
+ }
91
+
92
+ async list({
93
+ offset = 0,
94
+ limit = 20,
95
+ search,
96
+ sortBy = "createdAt",
97
+ sortOrder = "desc",
98
+ }: PaginationRequestDto): Promise<{ items: List${ModuleClassName}Response; total: number }> {
99
+ try {
100
+ // TODO: fetch list of ${ModuleClassName}
101
+ return {
102
+ total: 1,
103
+ items: [
104
+ {
105
+ ${moduleName}Id: "1",
106
+ name: "Sample ${ModuleClassName} 1",
107
+ createdAt: new Date().toISOString(),
108
+ updatedAt: new Date().toISOString(),
109
+ },
110
+ ]
111
+ }
112
+ } catch (error) {
113
+ throw error;
114
+ }
115
+ }
116
+
117
+ async delete(${moduleName}Id: string): Promise<void> {
118
+ try {
119
+ // TODO: delete ${ModuleClassName} by ${moduleName}Id
120
+ return;
121
+ } catch (error) {
122
+ throw error;
123
+ }
124
+ }
125
+ }
126
+
127
+ export default new ${ModuleClassName}Service()
128
+ `;
129
+ fs.writeFileSync(path.join(basePath, `${fileName}.service.ts`), serviceContent);
130
+ // Controller
131
+ const controllerContent = `
132
+ import { Request, Response } from 'express';
133
+ import { ${ModuleClassName}Service } from './${fileName}.service';
134
+ import {
135
+ Create${ModuleClassName}Dto,
136
+ Update${ModuleClassName}Dto,
137
+ Create${ModuleClassName}ResponseDto,
138
+ Update${ModuleClassName}ResponseDto,
139
+ Get${ModuleClassName}ResponseDto,
140
+ List${ModuleClassName}ResponseDto
141
+ } from './${fileName}.dto';
142
+ import { PaginationRequestDto } from '../common/common.dto';
143
+
144
+ export class ${ModuleClassName}Controller {
145
+ constructor(private ${moduleName}Service: ${ModuleClassName}Service) {}
146
+
147
+ async create(req: Request, res: Response) {
148
+ try {
149
+ const payload: Create${ModuleClassName}Dto = req.body;
150
+ const data = await this.${moduleName}Service.create(payload);
151
+ const response: Create${ModuleClassName}ResponseDto = {
152
+ success: true,
153
+ status: 201,
154
+ message: '${ModuleClassName} created successfully',
155
+ data,
156
+ };
157
+ res.status(201).json(response);
158
+ } catch (error: any) {
159
+ res.status(500).json({ success: false, status: 500, message: error.message });
160
+ }
161
+ }
162
+
163
+ async update(req: Request, res: Response) {
164
+ try {
165
+ const ${moduleName}Id = req.params.${moduleName}Id as string;
166
+ const payload: Update${ModuleClassName}Dto = req.body;
167
+ const data = await this.${moduleName}Service.update(${moduleName}Id, payload);
168
+ const response: Update${ModuleClassName}ResponseDto = {
169
+ success: true,
170
+ status: 200,
171
+ message: '${ModuleClassName} updated successfully',
172
+ data,
173
+ };
174
+ res.json(response);
175
+ } catch (error: any) {
176
+ res.status(500).json({ success: false, status: 500, message: error.message });
177
+ }
178
+ }
179
+
180
+ async getById(req: Request, res: Response) {
181
+ try {
182
+ const ${moduleName}Id = req.params.${moduleName}Id as string;
183
+ const data = await this.${moduleName}Service.getById(${moduleName}Id);
184
+ const response: Get${ModuleClassName}ResponseDto = {
185
+ success: true,
186
+ status: 200,
187
+ message: '${ModuleClassName} fetched successfully',
188
+ data,
189
+ };
190
+ res.json(response);
191
+ } catch (error: any) {
192
+ res.status(404).json({ success: false, status: 404, message: error.message });
193
+ }
194
+ }
195
+
196
+ async list(req: Request, res: Response) {
197
+ try {
198
+ const query = req.query as PaginationRequestDto;
199
+
200
+ const offset = query.offset ? Number(query.offset) : 0;
201
+ const limit = query.limit ? Number(query.limit) : 20;
202
+ const search = query.search;
203
+ const sortBy = query.sortBy || "createdAt";
204
+ const sortOrder = query.sortOrder || "desc";
205
+ const { items, total } =
206
+ await this.${moduleName}Service.list({
207
+ offset,
208
+ limit,
209
+ search,
210
+ sortBy,
211
+ sortOrder,
212
+ });
213
+
214
+ const response: List${ModuleClassName}ResponseDto = {
215
+ success: true,
216
+ status: 200,
217
+ message: "${ModuleClassName} list fetched successfully",
218
+ data: {
219
+ items,
220
+ total,
221
+ offset,
222
+ limit,
223
+ },
224
+ };
225
+ res.json(response);
226
+ } catch (error: any) {
227
+ res.status(500).json({ success: false, status: 500, message: error.message });
228
+ }
229
+ }
230
+
231
+ async delete(req: Request, res: Response) {
232
+ try {
233
+ const ${moduleName}Id = req.params.${moduleName}Id as string;
234
+ await this.${moduleName}Service.delete(${moduleName}Id);
235
+ res.json({
236
+ success: true,
237
+ status: 200,
238
+ message: '${ModuleClassName} deleted successfully',
239
+ });
240
+ } catch (error: any) {
241
+ res.status(500).json({ success: false, status: 500, message: error.message });
242
+ }
243
+ }
244
+ }
245
+ `;
246
+ fs.writeFileSync(path.join(basePath, `${fileName}.controller.ts`), controllerContent);
247
+ // Routes
248
+ const routesContent = `
249
+ import express, { Router } from 'express';
250
+ import { ${ModuleClassName}Controller } from './${fileName}.controller';
251
+ import { ${moduleName}Middleware } from './${fileName}.middlewares';
252
+ import { authMiddleware } from '../../middlewares/auth.middleware';
253
+ import ${moduleName}Service from './${fileName}.service';
254
+ import { TypeAction, Resource, TypeResource } from '../../utils/consts';
255
+ import { authorizationMiddleware } from '../../middlewares/authorization.middleware';
256
+
257
+ const router: Router = express.Router();
258
+ const controller = new ${ModuleClassName}Controller(${moduleName}Service);
259
+ router.use(authMiddleware);
260
+ const guard = (actions: TypeAction[]) => authorizationMiddleware({ resource: Resource.COMMON, actions, resourceType: TypeResource.API_ENDPOINT });
261
+
262
+ /**
263
+ * @openapi
264
+ * tags:
265
+ * - name: ${ModuleClassName}
266
+ * description: ${ModuleClassName} management and operations
267
+ */
268
+
269
+ /**
270
+ * @openapi
271
+ * /api/${moduleName}:
272
+ * post:
273
+ * tags: [${ModuleClassName}]
274
+ * summary: Create a new ${ModuleClassName}
275
+ * requestBody:
276
+ * required: true
277
+ * content:
278
+ * application/json:
279
+ * schema:
280
+ * $ref: '#/components/schemas/Create${ModuleClassName}Dto'
281
+ * responses:
282
+ * 201:
283
+ * description: ${ModuleClassName} created successfully
284
+ * content:
285
+ * application/json:
286
+ * schema:
287
+ * $ref: '#/components/schemas/Create${ModuleClassName}ResponseDto'
288
+ */
289
+ router.post('/', ${moduleName}Middleware, guard([TypeAction.CREATE]), controller.create.bind(controller));
290
+
291
+ /**
292
+ * @openapi
293
+ * /api/${moduleName}/{${moduleName}Id}:
294
+ * get:
295
+ * tags: [${ModuleClassName}]
296
+ * summary: Get a ${ModuleClassName} by ID
297
+ * parameters:
298
+ * - name: ${moduleName}Id
299
+ * in: path
300
+ * required: true
301
+ * schema:
302
+ * type: string
303
+ * responses:
304
+ * 200:
305
+ * description: ${ModuleClassName} fetched successfully
306
+ * content:
307
+ * application/json:
308
+ * schema:
309
+ * $ref: '#/components/schemas/Get${ModuleClassName}ResponseDto'
310
+ */
311
+ router.get('/:${moduleName}Id', ${moduleName}Middleware, guard([TypeAction.READ]), controller.getById.bind(controller));
312
+
313
+ /**
314
+ * @openapi
315
+ * /api/${moduleName}/{${moduleName}Id}:
316
+ * put:
317
+ * tags: [${ModuleClassName}]
318
+ * summary: Update a ${ModuleClassName} by ID
319
+ * parameters:
320
+ * - name: ${moduleName}Id
321
+ * in: path
322
+ * required: true
323
+ * schema:
324
+ * type: string
325
+ * requestBody:
326
+ * required: true
327
+ * content:
328
+ * application/json:
329
+ * schema:
330
+ * $ref: '#/components/schemas/Update${ModuleClassName}Dto'
331
+ * responses:
332
+ * 200:
333
+ * description: ${ModuleClassName} updated successfully
334
+ * content:
335
+ * application/json:
336
+ * schema:
337
+ * $ref: '#/components/schemas/Update${ModuleClassName}ResponseDto'
338
+ */
339
+ router.put('/:${moduleName}Id', ${moduleName}Middleware, guard([TypeAction.UPDATE]), controller.update.bind(controller));
340
+
341
+ /**
342
+ * @openapi
343
+ * /api/${moduleName}/{${moduleName}Id}:
344
+ * delete:
345
+ * tags: [${ModuleClassName}]
346
+ * summary: Delete a ${ModuleClassName} by ID
347
+ * parameters:
348
+ * - name: ${moduleName}Id
349
+ * in: path
350
+ * required: true
351
+ * schema:
352
+ * type: string
353
+ * responses:
354
+ * 200:
355
+ * description: ${ModuleClassName} deleted successfully
356
+ * content:
357
+ * application/json:
358
+ * schema:
359
+ * type: object
360
+ * properties:
361
+ * success:
362
+ * type: boolean
363
+ * status:
364
+ * type: integer
365
+ * message:
366
+ * type: string
367
+ */
368
+ router.delete('/:${moduleName}Id', ${moduleName}Middleware, guard([TypeAction.DELETE]), controller.delete.bind(controller));
369
+
370
+ /**
371
+ * @openapi
372
+ * /api/${moduleName}:
373
+ * get:
374
+ * tags: [${ModuleClassName}]
375
+ * summary: List all ${ModuleClassName}s with pagination
376
+ * parameters:
377
+ * - in: query
378
+ * name: offset
379
+ * schema:
380
+ * type: integer
381
+ * description: Offset for pagination
382
+ * - in: query
383
+ * name: limit
384
+ * schema:
385
+ * type: integer
386
+ * description: Limit for pagination
387
+ * - in: query
388
+ * name: search
389
+ * schema:
390
+ * type: string
391
+ * description: Search keyword
392
+ * - in: query
393
+ * name: sortBy
394
+ * schema:
395
+ * type: string
396
+ * description: Field to sort by
397
+ * - in: query
398
+ * name: sortOrder
399
+ * schema:
400
+ * type: string
401
+ * enum: [asc, desc]
402
+ * description: Sort order
403
+ * responses:
404
+ * 200:
405
+ * description: List of ${ModuleClassName}s
406
+ * content:
407
+ * application/json:
408
+ * schema:
409
+ * $ref: '#/components/schemas/List${ModuleClassName}ResponseDto'
410
+ */
411
+ router.get('/', ${moduleName}Middleware, guard([TypeAction.READ_ALL]), controller.list.bind(controller));
412
+
413
+ /**
414
+ * ======================================================
415
+ * Optional: Health Check
416
+ * ======================================================
417
+ */
418
+ router.get('/health/check', (_req, res) => {
419
+ res.json({
420
+ success: true,
421
+ status: 200,
422
+ message: "${ModuleClassName} module is healthy",
423
+ });
424
+ });
425
+
426
+ export default router;
427
+ `;
428
+ fs.writeFileSync(path.join(basePath, `${fileName}.routes.ts`), routesContent);
429
+ const dtoContent = `import { ApiResponse, PaginatedData } from "../common/common.dto";
430
+
431
+ /**
432
+ * Main ${ModuleClassName} entity
433
+ *
434
+ * @openapi
435
+ * components:
436
+ * schemas:
437
+ * ${ModuleClassName}:
438
+ * type: object
439
+ * required:
440
+ * - name
441
+ * properties:
442
+ * ${moduleName}Id:
443
+ * type: string
444
+ * example: "c3f2a9b4-8d21-4f3b-a91c-1a2b3c4d5e6f"
445
+ * description: Unique identifier of the ${ModuleClassName}
446
+ * name:
447
+ * type: string
448
+ * example: Sample ${ModuleClassName}
449
+ * description: Name of the ${ModuleClassName}
450
+ * description:
451
+ * type: string
452
+ * example: Optional description
453
+ * nullable: true
454
+ * description: Description of the ${ModuleClassName}
455
+ * createdAt:
456
+ * type: string
457
+ * format: date-time
458
+ * example: "2025-01-01T10:00:00.000Z"
459
+ * description: Creation timestamp
460
+ * updatedAt:
461
+ * type: string
462
+ * format: date-time
463
+ * example: "2025-01-02T12:00:00.000Z"
464
+ * description: Last update timestamp
465
+ */
466
+ export interface ${ModuleClassName} {
467
+ ${moduleName}Id: string;
468
+ name: string;
469
+ description?: string;
470
+ createdAt: string;
471
+ updatedAt: string;
472
+ }
473
+
474
+ /**
475
+ * @openapi
476
+ * components:
477
+ * schemas:
478
+ * Create${ModuleClassName}Dto:
479
+ * type: object
480
+ * required:
481
+ * - name
482
+ * properties:
483
+ * name:
484
+ * type: string
485
+ * example: Sample ${ModuleClassName}
486
+ * description: Name of the ${ModuleClassName}
487
+ * description:
488
+ * type: string
489
+ * example: Sample description
490
+ */
491
+ export interface Create${ModuleClassName}Dto {
492
+ name: string;
493
+ description?: string;
494
+ }
495
+
496
+ /**
497
+ * @openapi
498
+ * components:
499
+ * schemas:
500
+ * Update${ModuleClassName}Dto:
501
+ * type: object
502
+ * properties:
503
+ * name:
504
+ * type: string
505
+ * example: Updated ${ModuleClassName} Name
506
+ * description:
507
+ * type: string
508
+ * example: Updated description
509
+ */
510
+ export interface Update${ModuleClassName}Dto {
511
+ name?: string;
512
+ description?: string;
513
+ }
514
+
515
+ /**
516
+ * ======================================================
517
+ * Service return types (data only, controller wraps response)
518
+ * ======================================================
519
+ */
520
+
521
+ // data returned from service
522
+ export type Create${ModuleClassName}Response = Partial<${ModuleClassName}>;
523
+ export type Update${ModuleClassName}Response = Partial<${ModuleClassName}>;
524
+ export type Get${ModuleClassName}Response = Partial<${ModuleClassName}>;
525
+ export type List${ModuleClassName}Response = Partial<${ModuleClassName}>[];
526
+
527
+ /**
528
+ * ======================================================
529
+ * API Response DTOs (Swagger documented)
530
+ * ======================================================
531
+ */
532
+
533
+ /**
534
+ * @openapi
535
+ * components:
536
+ * schemas:
537
+ * Create${ModuleClassName}ResponseDto:
538
+ * allOf:
539
+ * - $ref: '#/components/schemas/ApiResponse'
540
+ * - type: object
541
+ * properties:
542
+ * data:
543
+ * $ref: '#/components/schemas/${ModuleClassName}'
544
+ */
545
+ export interface Create${ModuleClassName}ResponseDto
546
+ extends ApiResponse<Partial<${ModuleClassName}>> {}
547
+
548
+ /**
549
+ * @openapi
550
+ * components:
551
+ * schemas:
552
+ * Update${ModuleClassName}ResponseDto:
553
+ * allOf:
554
+ * - $ref: '#/components/schemas/ApiResponse'
555
+ * - type: object
556
+ * properties:
557
+ * data:
558
+ * $ref: '#/components/schemas/${ModuleClassName}'
559
+ */
560
+ export interface Update${ModuleClassName}ResponseDto
561
+ extends ApiResponse<Partial<${ModuleClassName}>> {}
562
+
563
+ /**
564
+ * @openapi
565
+ * components:
566
+ * schemas:
567
+ * Get${ModuleClassName}ResponseDto:
568
+ * allOf:
569
+ * - $ref: '#/components/schemas/ApiResponse'
570
+ * - type: object
571
+ * properties:
572
+ * data:
573
+ * $ref: '#/components/schemas/${ModuleClassName}'
574
+ */
575
+ export interface Get${ModuleClassName}ResponseDto
576
+ extends ApiResponse<Partial<${ModuleClassName}>> {}
577
+
578
+ /**
579
+ * @openapi
580
+ * components:
581
+ * schemas:
582
+ * List${ModuleClassName}ResponseDto:
583
+ * allOf:
584
+ * - $ref: '#/components/schemas/ApiResponse'
585
+ * - type: object
586
+ * properties:
587
+ * data:
588
+ * type: object
589
+ * properties:
590
+ * items:
591
+ * type: array
592
+ * items:
593
+ * $ref: '#/components/schemas/${ModuleClassName}'
594
+ * total:
595
+ * type: integer
596
+ * offset:
597
+ * type: integer
598
+ * limit:
599
+ * type: integer
600
+ */
601
+ export interface List${ModuleClassName}ResponseDto
602
+ extends ApiResponse<PaginatedData<Partial<${ModuleClassName}>>> {}
603
+ `;
604
+ fs.writeFileSync(path.join(basePath, `${fileName}.dto.ts`), dtoContent);
605
+ // Middleware
606
+ const middlewareContent = `
607
+ import { Request, Response, NextFunction } from "express";
608
+ import { iamCacheService } from "../../utils/iam.redis";
609
+ import { CachedUserProfilePermissionDto } from "../../utils/redis.dto";
610
+
611
+ export async function ${moduleName}Middleware(req: Request, res: Response, next: NextFunction) {
612
+ try {
613
+ const { userId } = req.user!
614
+ const profile = await iamCacheService.get<CachedUserProfilePermissionDto>(\`user:\${userId}:permissions\`);
615
+ if(!profile) throw new Error("No permission found")
616
+ const userPermissions = profile;
617
+
618
+ const method = req.method;
619
+ const url = req.originalUrl;
620
+ const path = req.path;
621
+ const ${moduleName}Id = req?.params?.${moduleName}Id
622
+
623
+ console.log("---- REALM MIDDLEWARE ----");
624
+ console.log("Method:", method);
625
+ console.log("URL:", url);
626
+ console.log("Path:", path);
627
+
628
+ // Example condition checking
629
+ if (method === "POST" && path === "/") {
630
+ console.log("👉 Creating new realm");
631
+ }
632
+
633
+ if (method === "GET" && path === "/") {
634
+ console.log("👉 Listing realms");
635
+ }
636
+
637
+ if (method === "GET" && ${moduleName}Id) {
638
+ console.log("👉 Getting realm by ID:", ${moduleName}Id);
639
+ }
640
+
641
+ if (method === "PUT") {
642
+ console.log("👉 Updating realm");
643
+ }
644
+
645
+ if (method === "DELETE") {
646
+ console.log("👉 Deleting realm");
647
+ }
648
+
649
+ next();
650
+ } catch (error) {
651
+ return res.status(500).json({
652
+ message: "Authorization error",
653
+ });
654
+ }
655
+ }
656
+ `;
657
+ fs.writeFileSync(path.join(basePath, `${fileName}.middlewares.ts`), middlewareContent);
658
+ console.log(`Module '${moduleName}' generated successfully at ${basePath}`);