crypt-express-app 1.0.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of crypt-express-app might be problematic. Click here for more details.

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