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,1002 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ export function generateUtils(projectDir) {
4
+ const basePath = path.join(projectDir, "src", "utils");
5
+ if (!fs.existsSync(basePath)) {
6
+ fs.mkdirSync(basePath, { recursive: true });
7
+ }
8
+ /* ================= iam.redis ================= */
9
+ const redisContent = `
10
+ import { createClient, RedisClientType } from "redis";
11
+ import { BOOTSTRAP_CONFIG } from "./consts";
12
+
13
+ let redis: RedisClientType;
14
+
15
+ redis = createClient({
16
+ url: BOOTSTRAP_CONFIG.REDIS.IAM_REDIS_URL,
17
+ });
18
+
19
+ redis.on("connect", () => {
20
+ console.log("✅ Redis Connected");
21
+ });
22
+
23
+ redis.on("error", (err) => {
24
+ console.error("❌ Redis Error", err);
25
+ });
26
+
27
+
28
+ export const ConnectIamRedis = async () => {
29
+ if (!redis.isOpen) {
30
+ await redis.connect();
31
+ }
32
+ };
33
+
34
+ export class IamCacheService {
35
+ async set(key: string, value: any, ttl?: number) {
36
+ const stringValue = JSON.stringify(value);
37
+
38
+ if (ttl) {
39
+ await redis.set(key, stringValue, { EX: ttl });
40
+ } else {
41
+ await redis.set(key, stringValue);
42
+ }
43
+ }
44
+
45
+ async get<T>(key: string): Promise<T | null> {
46
+ const data = await redis.get(key);
47
+ return data ? JSON.parse(data) : null;
48
+ }
49
+
50
+ async del(key: string) {
51
+ await redis.del(key);
52
+ }
53
+ }
54
+
55
+ export const iamCacheService = new IamCacheService();
56
+
57
+ export default redis;
58
+ `;
59
+ fs.writeFileSync(path.join(basePath, "iam.redis"), redisContent.trim());
60
+ /* ================= cache.ts ================= */
61
+ const cacheContent = `
62
+ // Centralized realm secret cache with TTL support
63
+
64
+ interface CachedSecret {
65
+ value: string;
66
+ expiresAt: number;
67
+ }
68
+
69
+ const realmSecretCache = new Map<string, CachedSecret>();
70
+ const DEFAULT_TTL_MS = 5 * 60 * 1000; // 5 minutes
71
+
72
+ export const getCachedSecret = (realmId: string): string | null => {
73
+ const cached = realmSecretCache.get(realmId);
74
+ if (!cached) return null;
75
+
76
+ if (cached.expiresAt < Date.now()) {
77
+ realmSecretCache.delete(realmId);
78
+ return null;
79
+ }
80
+
81
+ return cached.value;
82
+ };
83
+
84
+ export const setCachedSecret = (realmId: string, secret: string, ttlMs = DEFAULT_TTL_MS) => {
85
+ realmSecretCache.set(realmId, {
86
+ value: secret,
87
+ expiresAt: Date.now() + ttlMs,
88
+ });
89
+ };
90
+
91
+ // Optional: invalidate manually (on secret rotation)
92
+ export const invalidateSecret = (realmId: string) => {
93
+ realmSecretCache.delete(realmId);
94
+ };
95
+ `;
96
+ fs.writeFileSync(path.join(basePath, "cache.ts"), cacheContent.trim());
97
+ /* ================= redis.dto.ts ================= */
98
+ const redisDtoContent = `
99
+ export interface CachedUserProfilePermissionDto {
100
+ userId: string;
101
+ username: string;
102
+ email: string;
103
+ realm: {
104
+ realmId: string;
105
+ name: string;
106
+ };
107
+ roles: string[];
108
+ permissions: string[];
109
+ }
110
+ `;
111
+ fs.writeFileSync(path.join(basePath, "redis.dto.ts"), redisDtoContent.trim());
112
+ /* ================= logger.ts ================= */
113
+ const loggerContent = `
114
+ import winston from "winston";
115
+
116
+ const logger = winston.createLogger({
117
+ level: "info",
118
+ format: winston.format.combine(
119
+ winston.format.colorize(),
120
+ winston.format.timestamp(),
121
+ winston.format.printf(
122
+ ({ timestamp, level, message }) => \`[\${timestamp}] \${level}: \${message}\`
123
+ )
124
+ ),
125
+ transports: [new winston.transports.Console()],
126
+ });
127
+
128
+ export default logger;
129
+ `;
130
+ fs.writeFileSync(path.join(basePath, "logger.ts"), loggerContent.trim());
131
+ /* ================= bootstrap.ts ================= */
132
+ const bootstrapContent = `
133
+ import prisma from "../prisma/client";
134
+ import { RESET} from "./consts";
135
+ import logger from "./logger";
136
+
137
+ async function bootstrapSystem() {
138
+ if (RESET) await wipeAllTables();
139
+
140
+ console.log("🚀 System bootstrap complete");
141
+ }
142
+ export async function wipeAllTables() {
143
+ try {
144
+ await prisma.$executeRawUnsafe(\`
145
+ TRUNCATE TABLE
146
+ "Common",
147
+ RESTART IDENTITY CASCADE;
148
+ \`);
149
+
150
+ logger.info("✅ All tables wiped successfully");
151
+ } catch (error) {
152
+ logger.error("❌ Failed to wipe tables:", error);
153
+ throw error;
154
+ }
155
+ }
156
+
157
+ export default bootstrapSystem
158
+ `;
159
+ fs.writeFileSync(path.join(basePath, "bootstrap.ts"), bootstrapContent.trim());
160
+ /* ================= consts.ts ================= */
161
+ const constsContent = `
162
+ import 'dotenv/config'
163
+ export const CLIENT_NAME = process.env.CLIENT_NAME ?? "IAM_CLIENT"
164
+ export const CLIENT_ID = process.env.BOOTSTRAP_CLIENT_ID ?? "iam-client"
165
+
166
+ export const RESET = process.env.RESET || false;
167
+ export const JWT_SECRET = process.env.JWT_SECRET || "supersecret";
168
+ export const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || "1h";
169
+ export const REFRESH_TOKEN_EXPIRES_IN = 7 * 24 * 60 * 60 * 1000; // 7 days
170
+
171
+ export const BOOTSTRAP_CONFIG = {
172
+ REDIS: {
173
+ IAM_REDIS_URL: process.env.IAM_REDIS_URL ?? "redis://localhost:6374",
174
+ },
175
+ };
176
+
177
+ export enum Role {
178
+ ADMIN = "ADMIN",
179
+ MANAGER = "MANAGER",
180
+ VIEWER = "VIEWER",
181
+ AUDITOR = "AUDITOR",
182
+ USER = "USER",
183
+ }
184
+
185
+ export const ROLE_COMPOSITION = [
186
+ ...Object.values(Role),
187
+ ];
188
+
189
+ export const ROLES_ARRAY =
190
+ ROLE_COMPOSITION.map((role) => ({
191
+ name: CLIENT_NAME + ":" + role,
192
+ description: \`Role for CLIENT: \${CLIENT_NAME + ":" + role}\`,
193
+ isComposite: false,
194
+ }));
195
+
196
+ export enum TypeAction {
197
+ CREATE = "CREATE",
198
+ READ = "READ",
199
+ READ_ALL = "READ_ALL",
200
+ UPDATE = "UPDATE",
201
+ DELETE = "DELETE",
202
+ }
203
+
204
+ export enum Resource {
205
+ COMMON = "COMMON",
206
+ }
207
+
208
+ export enum TypeResource {
209
+ API_ENDPOINT = "API_ENDPOINT",
210
+ UI_PAGE = "UI_PAGE",
211
+ FILE = "FILE",
212
+ SERVICE = "SERVICE",
213
+ DATASET = "DATASET",
214
+ }
215
+
216
+ export const RESOURCES = Object.values(Resource).map((resource) => ({
217
+ name: resource,
218
+ type: TypeResource.API_ENDPOINT,
219
+ }));
220
+
221
+ export const permissions: string[] = [];
222
+
223
+ for (const resource of RESOURCES) {
224
+ for (const action of Object.values(TypeAction)) {
225
+ permissions.push(\`\${resource.name}:\${action}:\${resource.type}\`);
226
+ }
227
+ }
228
+ `;
229
+ fs.writeFileSync(path.join(basePath, "consts.ts"), constsContent.trim());
230
+ /* ================= swagger.ts ================= */
231
+ const swaggerContent = `
232
+ import swaggerJsdoc from "swagger-jsdoc";
233
+
234
+
235
+ const servers = [
236
+ {
237
+ url: \`http://localhost:\${process.env.PORT}\`,
238
+ description: "Local server",
239
+ },
240
+ {
241
+ url: process.env.API_URL_DEV,
242
+ description: "Development server",
243
+ },
244
+ {
245
+ url: process.env.API_URL_PROD,
246
+ description: "Production server",
247
+ },
248
+ ].filter((s) => s.url); // remove undefined
249
+
250
+ export const swaggerSpec = swaggerJsdoc({
251
+ definition: {
252
+ openapi: "3.0.0",
253
+ info: {
254
+ title: "Express REST API",
255
+ version: "1.0.0",
256
+ description: "RESTful API with Express + TypeScript + Swagger",
257
+ },
258
+ components: {
259
+ securitySchemes: {
260
+ bearerAuth: {
261
+ type: "http",
262
+ scheme: "bearer",
263
+ bearerFormat: "JWT",
264
+ },
265
+ },
266
+ },
267
+ security: [
268
+ {
269
+ bearerAuth: [],
270
+ },
271
+ ],
272
+ servers,
273
+ },
274
+ apis: ["./src/routes.ts", "./src/**/*.routes.ts", "./src/**/*.dto.ts"],
275
+ });
276
+ `;
277
+ fs.writeFileSync(path.join(basePath, "swagger.ts"), swaggerContent.trim());
278
+ /* ================= swagger.ts ================= */
279
+ const scriptDir = path.join(projectDir, "scripts");
280
+ if (!fs.existsSync(scriptDir)) {
281
+ fs.mkdirSync(scriptDir, { recursive: true });
282
+ }
283
+ const generateModule = `
284
+ // generate-module.ts
285
+ import * as fs from 'fs';
286
+ import * as path from 'path';
287
+
288
+ // get module name from command line
289
+ const args = process.argv.slice(2);
290
+
291
+ // Check that exactly **one argument** is provided
292
+ if (args.length !== 1) {
293
+ console.error('Invalid module name: provide exactly one module name without spaces.');
294
+ console.error('Example: npm run generate-module clientRole');
295
+ process.exit(1);
296
+ }
297
+
298
+ const moduleInput = args[0];
299
+
300
+ // Reject if input contains spaces
301
+ if (/\\s/.test(moduleInput)) {
302
+ console.error('Invalid module name: spaces are not allowed. Use camelCase, kebab-case or snake_case.');
303
+ process.exit(1);
304
+ }
305
+
306
+ if (!moduleInput) {
307
+ console.error('Please provide a module name: npm run generate-module <module-name>');
308
+ process.exit(1);
309
+ }
310
+
311
+ // Convert any input to camelCase
312
+ const moduleName = moduleInput.includes('-') || moduleInput.includes('_') || moduleInput.includes(' ')
313
+ ? moduleInput
314
+ .toLowerCase()
315
+ .replace(/[-_\\s]+(.)/g, (_, c) => c.toUpperCase())
316
+ : moduleInput; // preserve existing camelCase
317
+
318
+ console.log("moduleName: ", moduleName)
319
+
320
+ // Convert any input (camelCase, PascalCase, snake_case, spaces) to kebab-case
321
+ const fileName = moduleName
322
+ .replace(/([a-z])([A-Z])/g, '$1-$2') // add dash before uppercase letters
323
+ .toLowerCase(); // lowercase everything
324
+
325
+ console.log("fileName: ", fileName)
326
+
327
+
328
+ const basePath = path.join(__dirname, '..', 'src', 'modules', fileName);
329
+
330
+ // Create folder
331
+ if (!fs.existsSync(basePath)) {
332
+ fs.mkdirSync(basePath, { recursive: true });
333
+ }
334
+
335
+ // Capitalize first letter
336
+ const ModuleClassName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1);
337
+
338
+ // Service
339
+ const serviceContent = \`import { Create\${ModuleClassName}Dto, Update\${ModuleClassName}Dto, Create\${ModuleClassName}Response, Update\${ModuleClassName}Response, Get\${ModuleClassName}Response, List\${ModuleClassName}Response } from "./\${fileName}.dto";
340
+ import { Prisma } from "@prisma/client";
341
+ import prisma from '../../prisma/client';
342
+ import { PaginationRequestDto } from "../common/common.dto";
343
+
344
+
345
+ export class \${ModuleClassName}Service {
346
+ constructor() {}
347
+
348
+ async create(payload: Create\${ModuleClassName}Dto): Promise<Create\${ModuleClassName}Response> {
349
+ try {
350
+ // TODO: create \${ModuleClassName} in database
351
+ return {
352
+ \${moduleName}Id: "generated-\${moduleName}Id",
353
+ ...payload,
354
+ createdAt: new Date().toISOString(),
355
+ updatedAt: new Date().toISOString(),
356
+ };
357
+ } catch (error) {
358
+ throw error;
359
+ }
360
+ }
361
+
362
+ async update(\${moduleName}Id: string, payload: Update\${ModuleClassName}Dto): Promise<Update\${ModuleClassName}Response> {
363
+ try {
364
+ // TODO: update \${ModuleClassName} by \${moduleName}Id
365
+ return {
366
+ \${moduleName}Id,
367
+ ...payload,
368
+ updatedAt: new Date().toISOString(),
369
+ };
370
+ } catch (error) {
371
+ throw error;
372
+ }
373
+ }
374
+
375
+ async getById(\${moduleName}Id: string): Promise<Get\${ModuleClassName}Response> {
376
+ try {
377
+ // TODO: fetch \${ModuleClassName} by \${moduleName}Id
378
+ return {
379
+ \${moduleName}Id,
380
+ name: "Sample \${ModuleClassName}",
381
+ createdAt: new Date().toISOString(),
382
+ updatedAt: new Date().toISOString(),
383
+ };
384
+ } catch (error) {
385
+ throw error;
386
+ }
387
+ }
388
+
389
+ async list({
390
+ offset = 0,
391
+ limit = 20,
392
+ search,
393
+ sortBy = "createdAt",
394
+ sortOrder = "desc",
395
+ }: PaginationRequestDto): Promise<{ items: List\${ModuleClassName}Response; total: number }> {
396
+ try {
397
+ // TODO: fetch list of \${ModuleClassName}
398
+ return {
399
+ total: 1,
400
+ items: [
401
+ {
402
+ \${moduleName}Id: "1",
403
+ name: "Sample \${ModuleClassName} 1",
404
+ createdAt: new Date().toISOString(),
405
+ updatedAt: new Date().toISOString(),
406
+ },
407
+ ]
408
+ }
409
+ } catch (error) {
410
+ throw error;
411
+ }
412
+ }
413
+
414
+ async delete(\${moduleName}Id: string): Promise<void> {
415
+ try {
416
+ // TODO: delete \${ModuleClassName} by \${moduleName}Id
417
+ return;
418
+ } catch (error) {
419
+ throw error;
420
+ }
421
+ }
422
+ }
423
+
424
+ export default new \${ModuleClassName}Service()
425
+ \`;
426
+
427
+ fs.writeFileSync(path.join(basePath, \`\${fileName}.service.ts\`), serviceContent);
428
+
429
+ // Controller
430
+ const controllerContent = \`
431
+ import { Request, Response } from 'express';
432
+ import { \${ModuleClassName}Service } from './\${fileName}.service';
433
+ import {
434
+ Create\${ModuleClassName}Dto,
435
+ Update\${ModuleClassName}Dto,
436
+ Create\${ModuleClassName}ResponseDto,
437
+ Update\${ModuleClassName}ResponseDto,
438
+ Get\${ModuleClassName}ResponseDto,
439
+ List\${ModuleClassName}ResponseDto
440
+ } from './\${fileName}.dto';
441
+ import { PaginationRequestDto } from '../common/common.dto';
442
+
443
+ export class \${ModuleClassName}Controller {
444
+ constructor(private \${moduleName}Service: \${ModuleClassName}Service) {}
445
+
446
+ async create(req: Request, res: Response) {
447
+ try {
448
+ const payload: Create\${ModuleClassName}Dto = req.body;
449
+ const data = await this.\${moduleName}Service.create(payload);
450
+ const response: Create\${ModuleClassName}ResponseDto = {
451
+ success: true,
452
+ status: 201,
453
+ message: '\${ModuleClassName} created successfully',
454
+ data,
455
+ };
456
+ res.status(201).json(response);
457
+ } catch (error: any) {
458
+ res.status(500).json({ success: false, status: 500, message: error.message });
459
+ }
460
+ }
461
+
462
+ async update(req: Request, res: Response) {
463
+ try {
464
+ const \${moduleName}Id = req.params.\${moduleName}Id as string;
465
+ const payload: Update\${ModuleClassName}Dto = req.body;
466
+ const data = await this.\${moduleName}Service.update(\${moduleName}Id, payload);
467
+ const response: Update\${ModuleClassName}ResponseDto = {
468
+ success: true,
469
+ status: 200,
470
+ message: '\${ModuleClassName} updated successfully',
471
+ data,
472
+ };
473
+ res.json(response);
474
+ } catch (error: any) {
475
+ res.status(500).json({ success: false, status: 500, message: error.message });
476
+ }
477
+ }
478
+
479
+ async getById(req: Request, res: Response) {
480
+ try {
481
+ const \${moduleName}Id = req.params.\${moduleName}Id as string;
482
+ const data = await this.\${moduleName}Service.getById(\${moduleName}Id);
483
+ const response: Get\${ModuleClassName}ResponseDto = {
484
+ success: true,
485
+ status: 200,
486
+ message: '\${ModuleClassName} fetched successfully',
487
+ data,
488
+ };
489
+ res.json(response);
490
+ } catch (error: any) {
491
+ res.status(404).json({ success: false, status: 404, message: error.message });
492
+ }
493
+ }
494
+
495
+ async list(req: Request, res: Response) {
496
+ try {
497
+ const query = req.query as PaginationRequestDto;
498
+
499
+ const offset = query.offset ? Number(query.offset) : 0;
500
+ const limit = query.limit ? Number(query.limit) : 20;
501
+ const search = query.search;
502
+ const sortBy = query.sortBy || "createdAt";
503
+ const sortOrder = query.sortOrder || "desc";
504
+ const { items, total } =
505
+ await this.\${moduleName}Service.list({
506
+ offset,
507
+ limit,
508
+ search,
509
+ sortBy,
510
+ sortOrder,
511
+ });
512
+
513
+ const response: List\${ModuleClassName}ResponseDto = {
514
+ success: true,
515
+ status: 200,
516
+ message: "\${ModuleClassName} list fetched successfully",
517
+ data: {
518
+ items,
519
+ total,
520
+ offset,
521
+ limit,
522
+ },
523
+ };
524
+ res.json(response);
525
+ } catch (error: any) {
526
+ res.status(500).json({ success: false, status: 500, message: error.message });
527
+ }
528
+ }
529
+
530
+ async delete(req: Request, res: Response) {
531
+ try {
532
+ const \${moduleName}Id = req.params.\${moduleName}Id as string;
533
+ await this.\${moduleName}Service.delete(\${moduleName}Id);
534
+ res.json({
535
+ success: true,
536
+ status: 200,
537
+ message: '\${ModuleClassName} deleted successfully',
538
+ });
539
+ } catch (error: any) {
540
+ res.status(500).json({ success: false, status: 500, message: error.message });
541
+ }
542
+ }
543
+ }
544
+ \`;
545
+
546
+ fs.writeFileSync(path.join(basePath, \`\${fileName}.controller.ts\`), controllerContent);
547
+
548
+ // Routes
549
+ const routesContent = \`
550
+ import express, { Router } from 'express';
551
+ import { \${ModuleClassName}Controller } from './\${fileName}.controller';
552
+ import { \${moduleName}Middleware } from './\${fileName}.middlewares';
553
+ import { authMiddleware } from '../../middlewares/auth.middleware';
554
+ import \${moduleName}Service from './\${fileName}.service';
555
+ import { TypeAction, Resource, TypeResource } from '../../utils/consts';
556
+ import { authorizationMiddleware } from '../../middlewares/authorization.middleware';
557
+
558
+ const router: Router = express.Router();
559
+ const controller = new \${ModuleClassName}Controller(\${moduleName}Service);
560
+ router.use(authMiddleware);
561
+ const guard = (actions: TypeAction[]) => authorizationMiddleware({ resource: Resource.COMMON, actions, resourceType: TypeResource.API_ENDPOINT });
562
+
563
+ /**
564
+ * @openapi
565
+ * tags:
566
+ * - name: \${ModuleClassName}
567
+ * description: \${ModuleClassName} management and operations
568
+ */
569
+
570
+ /**
571
+ * @openapi
572
+ * /api/\${moduleName}:
573
+ * post:
574
+ * tags: [\${ModuleClassName}]
575
+ * summary: Create a new \${ModuleClassName}
576
+ * requestBody:
577
+ * required: true
578
+ * content:
579
+ * application/json:
580
+ * schema:
581
+ * $ref: '#/components/schemas/Create\${ModuleClassName}Dto'
582
+ * responses:
583
+ * 201:
584
+ * description: \${ModuleClassName} created successfully
585
+ * content:
586
+ * application/json:
587
+ * schema:
588
+ * $ref: '#/components/schemas/Create\${ModuleClassName}ResponseDto'
589
+ */
590
+ router.post('/', \${moduleName}Middleware, guard([TypeAction.CREATE]), controller.create.bind(controller));
591
+
592
+ /**
593
+ * @openapi
594
+ * /api/\${moduleName}/{\${moduleName}Id}:
595
+ * get:
596
+ * tags: [\${ModuleClassName}]
597
+ * summary: Get a \${ModuleClassName} by ID
598
+ * parameters:
599
+ * - name: \${moduleName}Id
600
+ * in: path
601
+ * required: true
602
+ * schema:
603
+ * type: string
604
+ * responses:
605
+ * 200:
606
+ * description: \${ModuleClassName} fetched successfully
607
+ * content:
608
+ * application/json:
609
+ * schema:
610
+ * $ref: '#/components/schemas/Get\${ModuleClassName}ResponseDto'
611
+ */
612
+ router.get('/:\${moduleName}Id', \${moduleName}Middleware, guard([TypeAction.READ]), controller.getById.bind(controller));
613
+
614
+ /**
615
+ * @openapi
616
+ * /api/\${moduleName}/{\${moduleName}Id}:
617
+ * put:
618
+ * tags: [\${ModuleClassName}]
619
+ * summary: Update a \${ModuleClassName} by ID
620
+ * parameters:
621
+ * - name: \${moduleName}Id
622
+ * in: path
623
+ * required: true
624
+ * schema:
625
+ * type: string
626
+ * requestBody:
627
+ * required: true
628
+ * content:
629
+ * application/json:
630
+ * schema:
631
+ * $ref: '#/components/schemas/Update\${ModuleClassName}Dto'
632
+ * responses:
633
+ * 200:
634
+ * description: \${ModuleClassName} updated successfully
635
+ * content:
636
+ * application/json:
637
+ * schema:
638
+ * $ref: '#/components/schemas/Update\${ModuleClassName}ResponseDto'
639
+ */
640
+ router.put('/:\${moduleName}Id', \${moduleName}Middleware, guard([TypeAction.UPDATE]), controller.update.bind(controller));
641
+
642
+ /**
643
+ * @openapi
644
+ * /api/\${moduleName}/{\${moduleName}Id}:
645
+ * delete:
646
+ * tags: [\${ModuleClassName}]
647
+ * summary: Delete a \${ModuleClassName} by ID
648
+ * parameters:
649
+ * - name: \${moduleName}Id
650
+ * in: path
651
+ * required: true
652
+ * schema:
653
+ * type: string
654
+ * responses:
655
+ * 200:
656
+ * description: \${ModuleClassName} deleted successfully
657
+ * content:
658
+ * application/json:
659
+ * schema:
660
+ * type: object
661
+ * properties:
662
+ * success:
663
+ * type: boolean
664
+ * status:
665
+ * type: integer
666
+ * message:
667
+ * type: string
668
+ */
669
+ router.delete('/:\${moduleName}Id', \${moduleName}Middleware, guard([TypeAction.DELETE]), controller.delete.bind(controller));
670
+
671
+ /**
672
+ * @openapi
673
+ * /api/\${moduleName}:
674
+ * get:
675
+ * tags: [\${ModuleClassName}]
676
+ * summary: List all \${ModuleClassName}s with pagination
677
+ * parameters:
678
+ * - in: query
679
+ * name: offset
680
+ * schema:
681
+ * type: integer
682
+ * description: Offset for pagination
683
+ * - in: query
684
+ * name: limit
685
+ * schema:
686
+ * type: integer
687
+ * description: Limit for pagination
688
+ * - in: query
689
+ * name: search
690
+ * schema:
691
+ * type: string
692
+ * description: Search keyword
693
+ * - in: query
694
+ * name: sortBy
695
+ * schema:
696
+ * type: string
697
+ * description: Field to sort by
698
+ * - in: query
699
+ * name: sortOrder
700
+ * schema:
701
+ * type: string
702
+ * enum: [asc, desc]
703
+ * description: Sort order
704
+ * responses:
705
+ * 200:
706
+ * description: List of \${ModuleClassName}s
707
+ * content:
708
+ * application/json:
709
+ * schema:
710
+ * $ref: '#/components/schemas/List\${ModuleClassName}ResponseDto'
711
+ */
712
+ router.get('/', \${moduleName}Middleware, guard([TypeAction.READ_ALL]), controller.list.bind(controller));
713
+
714
+ /**
715
+ * ======================================================
716
+ * Optional: Health Check
717
+ * ======================================================
718
+ */
719
+ router.get('/health/check', (_req, res) => {
720
+ res.json({
721
+ success: true,
722
+ status: 200,
723
+ message: "\${ModuleClassName} module is healthy",
724
+ });
725
+ });
726
+
727
+ export default router;
728
+ \`;
729
+
730
+
731
+ fs.writeFileSync(path.join(basePath, \`\${fileName}.routes.ts\`), routesContent);
732
+ const dtoContent = \`import { ApiResponse, PaginatedData } from "../common/common.dto";
733
+
734
+ /**
735
+ * Main \${ModuleClassName} entity
736
+ *
737
+ * @openapi
738
+ * components:
739
+ * schemas:
740
+ * \${ModuleClassName}:
741
+ * type: object
742
+ * required:
743
+ * - name
744
+ * properties:
745
+ * \${moduleName}Id:
746
+ * type: string
747
+ * example: "c3f2a9b4-8d21-4f3b-a91c-1a2b3c4d5e6f"
748
+ * description: Unique identifier of the \${ModuleClassName}
749
+ * name:
750
+ * type: string
751
+ * example: Sample \${ModuleClassName}
752
+ * description: Name of the \${ModuleClassName}
753
+ * description:
754
+ * type: string
755
+ * example: Optional description
756
+ * nullable: true
757
+ * description: Description of the \${ModuleClassName}
758
+ * createdAt:
759
+ * type: string
760
+ * format: date-time
761
+ * example: "2025-01-01T10:00:00.000Z"
762
+ * description: Creation timestamp
763
+ * updatedAt:
764
+ * type: string
765
+ * format: date-time
766
+ * example: "2025-01-02T12:00:00.000Z"
767
+ * description: Last update timestamp
768
+ */
769
+ export interface \${ModuleClassName} {
770
+ \${moduleName}Id: string;
771
+ name: string;
772
+ description?: string;
773
+ createdAt: string;
774
+ updatedAt: string;
775
+ }
776
+
777
+ /**
778
+ * @openapi
779
+ * components:
780
+ * schemas:
781
+ * Create\${ModuleClassName}Dto:
782
+ * type: object
783
+ * required:
784
+ * - name
785
+ * properties:
786
+ * name:
787
+ * type: string
788
+ * example: Sample \${ModuleClassName}
789
+ * description: Name of the \${ModuleClassName}
790
+ * description:
791
+ * type: string
792
+ * example: Sample description
793
+ */
794
+ export interface Create\${ModuleClassName}Dto {
795
+ name: string;
796
+ description?: string;
797
+ }
798
+
799
+ /**
800
+ * @openapi
801
+ * components:
802
+ * schemas:
803
+ * Update\${ModuleClassName}Dto:
804
+ * type: object
805
+ * properties:
806
+ * name:
807
+ * type: string
808
+ * example: Updated \${ModuleClassName} Name
809
+ * description:
810
+ * type: string
811
+ * example: Updated description
812
+ */
813
+ export interface Update\${ModuleClassName}Dto {
814
+ name?: string;
815
+ description?: string;
816
+ }
817
+
818
+ /**
819
+ * ======================================================
820
+ * Service return types (data only, controller wraps response)
821
+ * ======================================================
822
+ */
823
+
824
+ // data returned from service
825
+ export type Create\${ModuleClassName}Response = Partial<\${ModuleClassName}>;
826
+ export type Update\${ModuleClassName}Response = Partial<\${ModuleClassName}>;
827
+ export type Get\${ModuleClassName}Response = Partial<\${ModuleClassName}>;
828
+ export type List\${ModuleClassName}Response = Partial<\${ModuleClassName}>[];
829
+
830
+ /**
831
+ * ======================================================
832
+ * API Response DTOs (Swagger documented)
833
+ * ======================================================
834
+ */
835
+
836
+ /**
837
+ * @openapi
838
+ * components:
839
+ * schemas:
840
+ * Create\${ModuleClassName}ResponseDto:
841
+ * allOf:
842
+ * - $ref: '#/components/schemas/ApiResponse'
843
+ * - type: object
844
+ * properties:
845
+ * data:
846
+ * $ref: '#/components/schemas/\${ModuleClassName}'
847
+ */
848
+ export interface Create\${ModuleClassName}ResponseDto
849
+ extends ApiResponse<Partial<\${ModuleClassName}>> {}
850
+
851
+ /**
852
+ * @openapi
853
+ * components:
854
+ * schemas:
855
+ * Update\${ModuleClassName}ResponseDto:
856
+ * allOf:
857
+ * - $ref: '#/components/schemas/ApiResponse'
858
+ * - type: object
859
+ * properties:
860
+ * data:
861
+ * $ref: '#/components/schemas/\${ModuleClassName}'
862
+ */
863
+ export interface Update\${ModuleClassName}ResponseDto
864
+ extends ApiResponse<Partial<\${ModuleClassName}>> {}
865
+
866
+ /**
867
+ * @openapi
868
+ * components:
869
+ * schemas:
870
+ * Get\${ModuleClassName}ResponseDto:
871
+ * allOf:
872
+ * - $ref: '#/components/schemas/ApiResponse'
873
+ * - type: object
874
+ * properties:
875
+ * data:
876
+ * $ref: '#/components/schemas/\${ModuleClassName}'
877
+ */
878
+ export interface Get\${ModuleClassName}ResponseDto
879
+ extends ApiResponse<Partial<\${ModuleClassName}>> {}
880
+
881
+ /**
882
+ * @openapi
883
+ * components:
884
+ * schemas:
885
+ * List\${ModuleClassName}ResponseDto:
886
+ * allOf:
887
+ * - $ref: '#/components/schemas/ApiResponse'
888
+ * - type: object
889
+ * properties:
890
+ * data:
891
+ * type: object
892
+ * properties:
893
+ * items:
894
+ * type: array
895
+ * items:
896
+ * $ref: '#/components/schemas/\${ModuleClassName}'
897
+ * total:
898
+ * type: integer
899
+ * offset:
900
+ * type: integer
901
+ * limit:
902
+ * type: integer
903
+ */
904
+ export interface List\${ModuleClassName}ResponseDto
905
+ extends ApiResponse<PaginatedData<Partial<\${ModuleClassName}>>> {}
906
+ \`;
907
+
908
+
909
+ fs.writeFileSync(path.join(basePath, \`\${fileName}.dto.ts\`), dtoContent);
910
+
911
+ // Middleware
912
+ const middlewareContent = \`
913
+ import { Request, Response, NextFunction } from "express";
914
+ import { iamCacheService } from "../../utils/iam.redis";
915
+ import { CachedUserProfilePermissionDto } from "../../utils/redis.dto";
916
+
917
+ export async function \${moduleName}Middleware(req: Request, res: Response, next: NextFunction) {
918
+ try {
919
+ const { userId } = req.user!
920
+ const profile = await iamCacheService.get<CachedUserProfilePermissionDto>(\\\`user:\\\${userId}:permissions\\\`);
921
+ if(!profile) throw new Error("No permission found")
922
+ const userPermissions = profile;
923
+
924
+ const method = req.method;
925
+ const url = req.originalUrl;
926
+ const path = req.path;
927
+ const \${moduleName}Id = req?.params?.\${moduleName}Id
928
+
929
+ console.log("---- REALM MIDDLEWARE ----");
930
+ console.log("Method:", method);
931
+ console.log("URL:", url);
932
+ console.log("Path:", path);
933
+
934
+ // Example condition checking
935
+ if (method === "POST" && path === "/") {
936
+ console.log("👉 Creating new realm");
937
+ }
938
+
939
+ if (method === "GET" && path === "/") {
940
+ console.log("👉 Listing realms");
941
+ }
942
+
943
+ if (method === "GET" && \${moduleName}Id) {
944
+ console.log("👉 Getting realm by ID:", \${moduleName}Id);
945
+ }
946
+
947
+ if (method === "PUT") {
948
+ console.log("👉 Updating realm");
949
+ }
950
+
951
+ if (method === "DELETE") {
952
+ console.log("👉 Deleting realm");
953
+ }
954
+
955
+ next();
956
+ } catch (error) {
957
+ return res.status(500).json({
958
+ message: "Authorization error",
959
+ });
960
+ }
961
+ }
962
+ \`;
963
+
964
+ fs.writeFileSync(path.join(basePath, \`\${fileName}.middlewares.ts\`), middlewareContent);
965
+
966
+
967
+
968
+
969
+ const routesFilePath = path.join(__dirname, '..', 'src', 'routes.ts');
970
+
971
+ if (fs.existsSync(routesFilePath)) {
972
+ let routesFileContent = fs.readFileSync(routesFilePath, 'utf-8');
973
+
974
+ const importLine = \`import \${moduleName}Routes from ".\/modules\/\${fileName}\/\${fileName}.routes";\`;
975
+ const useLine = \`routers.use("\/api\/\${fileName}", \${moduleName}Routes);\`;
976
+
977
+ if (!routesFileContent.includes(importLine)) {
978
+ routesFileContent = routesFileContent.replace(
979
+ '\\nconst routers: Router = express.Router();',
980
+ \`\${importLine}\\n\\nconst routers: Router = express.Router();\`
981
+ );
982
+ }
983
+
984
+ if (!routesFileContent.includes(useLine)) {
985
+ routesFileContent = routesFileContent.replace(
986
+ '\\nexport default routers;',
987
+ \`\${useLine}\\n\\nexport default routers;\`
988
+ );
989
+ }
990
+
991
+ fs.writeFileSync(routesFilePath, routesFileContent);
992
+ console.log('routes.ts updated successfully');
993
+ }
994
+
995
+
996
+
997
+ console.log(\`Module '\${moduleName}' generated successfully at \${basePath}\`);
998
+
999
+ `;
1000
+ fs.writeFileSync(path.join(scriptDir, "generate-module.ts"), generateModule.trim());
1001
+ console.log("✅ Utils files generated successfully inside src/utils");
1002
+ }