create-efc-app 0.3.6 → 0.4.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/dist/index.js CHANGED
@@ -19,7 +19,6 @@ async function scaffold(opts) {
19
19
  await writeGitignore(dest);
20
20
  await writeEnvFiles(dest, opts);
21
21
  await writeExampleRoute(dest, opts);
22
- if (opts.rbac) await writeRequireRoleMiddleware(dest, opts);
23
22
  if (opts.userPortal) await writeUserModel(dest, opts);
24
23
  if (opts.adminPortal) await writeAdminModel(dest, opts);
25
24
  await writeAuthRoutes(dest, opts);
@@ -149,6 +148,7 @@ async function writeEnvFiles(dest, opts) {
149
148
  const resolvedPort = isGmail ? "465" : opts.smtpPort ?? "587";
150
149
  const passComment = isGmail ? " # Gmail App Password (16 chars) \u2014 NOT your regular Gmail password. Generate at: Google Account > Security > 2-Step Verification > App passwords" : "";
151
150
  const smtpVars = opts.mailer ? `
151
+ APP_URL=http://localhost:3000
152
152
  SMTP_HOST=${resolvedHost}
153
153
  SMTP_PORT=${resolvedPort}
154
154
  SMTP_USER=${opts.smtpUser ?? ""}
@@ -156,6 +156,7 @@ SMTP_PASS=${opts.smtpPass ?? ""}${passComment}
156
156
  SMTP_FROM=${opts.smtpUser ?? "noreply@example.com"}
157
157
  ` : "";
158
158
  const smtpExample = opts.mailer ? `
159
+ APP_URL=http://localhost:3000
159
160
  SMTP_HOST=${resolvedHost}
160
161
  SMTP_PORT=${resolvedPort}
161
162
  SMTP_USER=your@email.com
@@ -182,14 +183,18 @@ async function writeExampleRoute(dest, opts) {
182
183
  const metaTs = opts.routeDocs ? `import type { RouteMeta } from 'express-file-cluster';
183
184
 
184
185
  export const meta: RouteMeta = {
185
- description: 'Health check \u2014 returns server status and current timestamp.',
186
- response: { status: 200, body: { status: 'OK', timestamp: '2024-01-01T00:00:00.000Z' } },
186
+ GET: {
187
+ description: 'Health check \u2014 returns server status and current timestamp.',
188
+ response: { status: 200, body: { status: 'OK', timestamp: '2024-01-01T00:00:00.000Z' } },
189
+ },
187
190
  };
188
191
 
189
192
  ` : "";
190
193
  const metaJs = opts.routeDocs ? `export const meta = {
191
- description: 'Health check \u2014 returns server status and current timestamp.',
192
- response: { status: 200, body: { status: 'OK', timestamp: '2024-01-01T00:00:00.000Z' } },
194
+ GET: {
195
+ description: 'Health check \u2014 returns server status and current timestamp.',
196
+ response: { status: 200, body: { status: 'OK', timestamp: '2024-01-01T00:00:00.000Z' } },
197
+ },
193
198
  };
194
199
 
195
200
  ` : "";
@@ -291,27 +296,42 @@ export interface UserDocument {
291
296
  avatar?: string;
292
297
  isVerified: boolean;
293
298
  isActive: boolean;
299
+ verifyToken?: string;
300
+ resetToken?: string;
301
+ resetTokenExpiry?: Date;
302
+ refreshToken?: string;
303
+ refreshTokenExpiry?: Date;
294
304
  }
295
305
 
296
306
  export const User = defineModel<UserDocument>('User', {
297
- name: { type: 'string', required: true },
298
- email: { type: 'string', required: true, unique: true },
299
- password: { type: 'string', required: true },
300
- role: { type: 'string', required: true, default: 'user' },
301
- avatar: { type: 'string' },
302
- isVerified: { type: 'boolean', default: false },
303
- isActive: { type: 'boolean', default: true },
307
+ name: { type: 'string', required: true },
308
+ email: { type: 'string', required: true, unique: true },
309
+ password: { type: 'string', required: true },
310
+ role: { type: 'string', required: true, default: 'user' },
311
+ avatar: { type: 'string' },
312
+ isVerified: { type: 'boolean', default: false },
313
+ isActive: { type: 'boolean', default: true },
314
+ verifyToken: { type: 'string' },
315
+ resetToken: { type: 'string' },
316
+ resetTokenExpiry: { type: 'date' },
317
+ refreshToken: { type: 'string' },
318
+ refreshTokenExpiry: { type: 'date' },
304
319
  });
305
320
  ` : `import { defineModel } from 'express-file-cluster';
306
321
 
307
322
  export const User = defineModel('User', {
308
- name: { type: 'string', required: true },
309
- email: { type: 'string', required: true, unique: true },
310
- password: { type: 'string', required: true },
311
- role: { type: 'string', required: true, default: 'user' },
312
- avatar: { type: 'string' },
313
- isVerified: { type: 'boolean', default: false },
314
- isActive: { type: 'boolean', default: true },
323
+ name: { type: 'string', required: true },
324
+ email: { type: 'string', required: true, unique: true },
325
+ password: { type: 'string', required: true },
326
+ role: { type: 'string', required: true, default: 'user' },
327
+ avatar: { type: 'string' },
328
+ isVerified: { type: 'boolean', default: false },
329
+ isActive: { type: 'boolean', default: true },
330
+ verifyToken: { type: 'string' },
331
+ resetToken: { type: 'string' },
332
+ resetTokenExpiry: { type: 'date' },
333
+ refreshToken: { type: 'string' },
334
+ refreshTokenExpiry: { type: 'date' },
315
335
  });
316
336
  ` : ts ? `// TODO: define your Drizzle schema for User
317
337
  // import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';
@@ -346,25 +366,37 @@ export interface AdminDocument {
346
366
  role: string;
347
367
  permissions: string[];
348
368
  isActive: boolean;
369
+ resetToken?: string;
370
+ resetTokenExpiry?: Date;
371
+ refreshToken?: string;
372
+ refreshTokenExpiry?: Date;
349
373
  }
350
374
 
351
375
  export const Admin = defineModel<AdminDocument>('Admin', {
352
- name: { type: 'string', required: true },
353
- email: { type: 'string', required: true, unique: true },
354
- password: { type: 'string', required: true },
355
- role: { type: 'string', required: true, default: 'admin' },
356
- permissions: { type: 'array', default: [] },
357
- isActive: { type: 'boolean', default: true },
376
+ name: { type: 'string', required: true },
377
+ email: { type: 'string', required: true, unique: true },
378
+ password: { type: 'string', required: true },
379
+ role: { type: 'string', required: true, default: 'admin' },
380
+ permissions: { type: 'array', default: [] },
381
+ isActive: { type: 'boolean', default: true },
382
+ resetToken: { type: 'string' },
383
+ resetTokenExpiry: { type: 'date' },
384
+ refreshToken: { type: 'string' },
385
+ refreshTokenExpiry: { type: 'date' },
358
386
  });
359
387
  ` : `import { defineModel } from 'express-file-cluster';
360
388
 
361
389
  export const Admin = defineModel('Admin', {
362
- name: { type: 'string', required: true },
363
- email: { type: 'string', required: true, unique: true },
364
- password: { type: 'string', required: true },
365
- role: { type: 'string', required: true, default: 'admin' },
366
- permissions: { type: 'array', default: [] },
367
- isActive: { type: 'boolean', default: true },
390
+ name: { type: 'string', required: true },
391
+ email: { type: 'string', required: true, unique: true },
392
+ password: { type: 'string', required: true },
393
+ role: { type: 'string', required: true, default: 'admin' },
394
+ permissions: { type: 'array', default: [] },
395
+ isActive: { type: 'boolean', default: true },
396
+ resetToken: { type: 'string' },
397
+ resetTokenExpiry: { type: 'date' },
398
+ refreshToken: { type: 'string' },
399
+ refreshTokenExpiry: { type: 'date' },
368
400
  });
369
401
  ` : ts ? `// TODO: define your Drizzle schema for Admin
370
402
  // import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';
@@ -392,28 +424,51 @@ async function writeAuthRoutes(dest, opts) {
392
424
  const loginMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
393
425
 
394
426
  export const meta: RouteMeta = {
395
- description: 'Authenticate a user and issue a JWT.',
396
- request: { body: { email: 'user@example.com', password: 'user' } },
397
- response: { status: 200, body: { message: 'Logged in as user' } },
427
+ POST: {
428
+ description: 'Authenticate a user or admin and issue a JWT.',
429
+ request: { body: { email: 'user@example.com', password: 'user' } },
430
+ response: { status: 200, body: { message: 'Logged in as user' } },
431
+ },
398
432
  };
399
433
 
400
434
  ` : `export const meta = {
401
- description: 'Authenticate a user and issue a JWT.',
402
- request: { body: { email: 'user@example.com', password: 'user' } },
403
- response: { status: 200, body: { message: 'Logged in as user' } },
435
+ POST: {
436
+ description: 'Authenticate a user or admin and issue a JWT.',
437
+ request: { body: { email: 'user@example.com', password: 'user' } },
438
+ response: { status: 200, body: { message: 'Logged in as user' } },
439
+ },
404
440
  };
405
441
 
406
442
  ` : "";
407
443
  const loginDbImports = opts.database === "mongodb" ? ts ? `import bcrypt from 'bcrypt';
444
+ import crypto from 'node:crypto';
408
445
  import { User } from '../../model/User.js';
409
446
  import { Admin } from '../../model/Admin.js';
410
447
  ` : `import bcrypt from 'bcrypt';
448
+ import crypto from 'node:crypto';
411
449
  import { User } from '../../model/User.js';
412
450
  import { Admin } from '../../model/Admin.js';
413
451
  ` : "";
414
452
  const loginContent = opts.database === "mongodb" ? ts ? `import { issueToken } from 'express-file-cluster/auth';
415
453
  import type { Request, Response } from 'express';
416
- ${loginDbImports}${loginMeta}export const POST = async (req: Request, res: Response) => {
454
+ ${loginDbImports}${loginMeta}const REFRESH_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
455
+
456
+ async function issueRefreshToken(
457
+ res: Response,
458
+ model: { update: (id: string, data: Record<string, unknown>) => Promise<unknown> },
459
+ id: string,
460
+ ): Promise<void> {
461
+ const refreshToken = crypto.randomBytes(40).toString('hex');
462
+ await model.update(id, { refreshToken, refreshTokenExpiry: new Date(Date.now() + REFRESH_TOKEN_TTL_MS) });
463
+ res.cookie('efc_refresh_token', refreshToken, {
464
+ httpOnly: true,
465
+ secure: process.env.NODE_ENV === 'production',
466
+ sameSite: 'strict',
467
+ maxAge: REFRESH_TOKEN_TTL_MS,
468
+ });
469
+ }
470
+
471
+ export const POST = async (req: Request, res: Response) => {
417
472
  const { email, password } = req.body;
418
473
  if (!email || !password) return res.status(400).json({ error: 'email and password are required' });
419
474
 
@@ -423,6 +478,7 @@ ${loginDbImports}${loginMeta}export const POST = async (req: Request, res: Respo
423
478
  if (!match) return res.status(401).json({ error: 'Invalid credentials' });
424
479
  if (!admin.isActive) return res.status(403).json({ error: 'Account suspended' });
425
480
  await issueToken(res, { id: admin.id, role: admin.role, email: admin.email });
481
+ await issueRefreshToken(res, Admin, admin.id);
426
482
  return res.json({ message: 'Logged in as admin' });
427
483
  }
428
484
 
@@ -432,10 +488,24 @@ ${loginDbImports}${loginMeta}export const POST = async (req: Request, res: Respo
432
488
  if (!match) return res.status(401).json({ error: 'Invalid credentials' });
433
489
  if (!user.isActive) return res.status(403).json({ error: 'Account suspended' });
434
490
  await issueToken(res, { id: user.id, role: user.role, email: user.email });
491
+ await issueRefreshToken(res, User, user.id);
435
492
  res.json({ message: 'Logged in' });
436
493
  };
437
494
  ` : `import { issueToken } from 'express-file-cluster/auth';
438
- ${loginDbImports}${loginMeta}export const POST = async (req, res) => {
495
+ ${loginDbImports}${loginMeta}const REFRESH_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
496
+
497
+ async function issueRefreshToken(res, model, id) {
498
+ const refreshToken = crypto.randomBytes(40).toString('hex');
499
+ await model.update(id, { refreshToken, refreshTokenExpiry: new Date(Date.now() + REFRESH_TOKEN_TTL_MS) });
500
+ res.cookie('efc_refresh_token', refreshToken, {
501
+ httpOnly: true,
502
+ secure: process.env.NODE_ENV === 'production',
503
+ sameSite: 'strict',
504
+ maxAge: REFRESH_TOKEN_TTL_MS,
505
+ });
506
+ }
507
+
508
+ export const POST = async (req, res) => {
439
509
  const { email, password } = req.body;
440
510
  if (!email || !password) return res.status(400).json({ error: 'email and password are required' });
441
511
 
@@ -445,6 +515,7 @@ ${loginDbImports}${loginMeta}export const POST = async (req, res) => {
445
515
  if (!match) return res.status(401).json({ error: 'Invalid credentials' });
446
516
  if (!admin.isActive) return res.status(403).json({ error: 'Account suspended' });
447
517
  await issueToken(res, { id: admin.id, role: admin.role, email: admin.email });
518
+ await issueRefreshToken(res, Admin, admin.id);
448
519
  return res.json({ message: 'Logged in as admin' });
449
520
  }
450
521
 
@@ -454,6 +525,7 @@ ${loginDbImports}${loginMeta}export const POST = async (req, res) => {
454
525
  if (!match) return res.status(401).json({ error: 'Invalid credentials' });
455
526
  if (!user.isActive) return res.status(403).json({ error: 'Account suspended' });
456
527
  await issueToken(res, { id: user.id, role: user.role, email: user.email });
528
+ await issueRefreshToken(res, User, user.id);
457
529
  res.json({ message: 'Logged in' });
458
530
  };
459
531
  ` : ts ? `import { issueToken } from 'express-file-cluster/auth';
@@ -473,13 +545,17 @@ ${loginMeta}export const POST = async (req, res) => {
473
545
  const logoutMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
474
546
 
475
547
  export const meta: RouteMeta = {
476
- description: 'Clear the auth cookie and log the user out.',
477
- response: { status: 200, body: { message: 'Logged out successfully' } },
548
+ POST: {
549
+ description: 'Clear the auth cookie and log the user out.',
550
+ response: { status: 200, body: { message: 'Logged out successfully' } },
551
+ },
478
552
  };
479
553
 
480
554
  ` : `export const meta = {
481
- description: 'Clear the auth cookie and log the user out.',
482
- response: { status: 200, body: { message: 'Logged out successfully' } },
555
+ POST: {
556
+ description: 'Clear the auth cookie and log the user out.',
557
+ response: { status: 200, body: { message: 'Logged out successfully' } },
558
+ },
483
559
  };
484
560
 
485
561
  ` : "";
@@ -487,11 +563,13 @@ export const meta: RouteMeta = {
487
563
  import type { Request, Response } from 'express';
488
564
  ${logoutMeta}export const POST = async (_req: Request, res: Response) => {
489
565
  revokeToken(res);
566
+ res.clearCookie('efc_refresh_token');
490
567
  res.json({ message: 'Logged out successfully' });
491
568
  };
492
569
  ` : `import { revokeToken } from 'express-file-cluster/auth';
493
570
  ${logoutMeta}export const POST = async (_req, res) => {
494
571
  revokeToken(res);
572
+ res.clearCookie('efc_refresh_token');
495
573
  res.json({ message: 'Logged out successfully' });
496
574
  };
497
575
  `;
@@ -501,23 +579,36 @@ ${logoutMeta}export const POST = async (_req, res) => {
501
579
  const registerMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
502
580
 
503
581
  export const meta: RouteMeta = {
504
- description: 'Register a new user account.',
505
- request: { body: { name: 'Jane Doe', email: 'jane@example.com', password: 'secret' } },
506
- response: { status: 201, body: { message: 'Account created successfully' } },
582
+ POST: {
583
+ description: 'Register a new user account.',
584
+ request: { body: { name: 'Jane Doe', email: 'jane@example.com', password: 'secret' } },
585
+ response: { status: 201, body: { message: 'Account created successfully' } },
586
+ },
507
587
  };
508
588
 
509
589
  ` : `export const meta = {
510
- description: 'Register a new user account.',
511
- request: { body: { name: 'Jane Doe', email: 'jane@example.com', password: 'secret' } },
512
- response: { status: 201, body: { message: 'Account created successfully' } },
590
+ POST: {
591
+ description: 'Register a new user account.',
592
+ request: { body: { name: 'Jane Doe', email: 'jane@example.com', password: 'secret' } },
593
+ response: { status: 201, body: { message: 'Account created successfully' } },
594
+ },
513
595
  };
514
596
 
515
597
  ` : "";
516
- const registerDbImports = opts.database === "mongodb" ? ts ? `import bcrypt from 'bcrypt';
517
- import { User } from '../../model/User.js';
518
- ` : `import bcrypt from 'bcrypt';
519
- import { User } from '../../model/User.js';
598
+ const registerDbImports = opts.database === "mongodb" ? `import bcrypt from 'bcrypt';
599
+ import crypto from 'node:crypto';
600
+ ${opts.mailer ? "import { enqueue } from 'express-file-cluster/tasks';\n" : ""}import { User } from '../../model/User.js';
520
601
  ` : "";
602
+ const sendVerifyEmail = opts.mailer ? `
603
+ var appUrl = process.env.APP_URL || 'http://localhost:3000';
604
+ await enqueue('SendEmail', {
605
+ to: email,
606
+ subject: 'Verify your email address',
607
+ body: 'Welcome, ' + name + '! Verify your email: ' + appUrl + '/auth/verify-email?token=' + verifyToken,
608
+ });
609
+ ` : `
610
+ // TODO: email this token to the user \u2014 enable the Mailer feature to auto-wire SendEmail
611
+ `;
521
612
  const registerContent = opts.database === "mongodb" ? ts ? `import type { Request, Response } from 'express';
522
613
  ${registerDbImports}${registerMeta}export const POST = async (req: Request, res: Response) => {
523
614
  const { name, email, password } = req.body;
@@ -527,8 +618,10 @@ ${registerDbImports}${registerMeta}export const POST = async (req: Request, res:
527
618
  const existing = await User.findOne({ email });
528
619
  if (existing) return res.status(409).json({ error: 'Email already in use' });
529
620
  const hashed = await bcrypt.hash(password, 10);
530
- const user = await User.create({ name, email, password: hashed });
531
- const { password: _, ...safe } = user;
621
+ const verifyToken = crypto.randomBytes(32).toString('hex');
622
+ const user = await User.create({ name, email, password: hashed, verifyToken });
623
+ ${sendVerifyEmail}
624
+ const { password: _, verifyToken: __, ...safe } = user;
532
625
  res.status(201).json({ message: 'Account created successfully', user: safe });
533
626
  };
534
627
  ` : `${registerDbImports}${registerMeta}export const POST = async (req, res) => {
@@ -539,8 +632,10 @@ ${registerDbImports}${registerMeta}export const POST = async (req: Request, res:
539
632
  const existing = await User.findOne({ email });
540
633
  if (existing) return res.status(409).json({ error: 'Email already in use' });
541
634
  const hashed = await bcrypt.hash(password, 10);
542
- const user = await User.create({ name, email, password: hashed });
543
- const { password: _, ...safe } = user;
635
+ const verifyToken = crypto.randomBytes(32).toString('hex');
636
+ const user = await User.create({ name, email, password: hashed, verifyToken });
637
+ ${sendVerifyEmail}
638
+ const { password: _, verifyToken: __, ...safe } = user;
544
639
  res.status(201).json({ message: 'Account created successfully', user: safe });
545
640
  };
546
641
  ` : ts ? `import type { Request, Response } from 'express';
@@ -564,68 +659,42 @@ ${registerMeta}export const POST = async (req: Request, res: Response) => {
564
659
  const meMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
565
660
 
566
661
  export const meta: RouteMeta = {
567
- description: 'Return the currently authenticated user.',
568
- response: { status: 200, body: { user: { id: '1', role: 'user', email: 'user@example.com' } } },
662
+ GET: {
663
+ description: 'Return the currently authenticated user.',
664
+ response: { status: 200, body: { user: { id: '1', role: 'user', email: 'user@example.com' } } },
665
+ },
569
666
  };
570
667
 
571
668
  ` : `export const meta = {
572
- description: 'Return the currently authenticated user.',
573
- response: { status: 200, body: { user: { id: '1', role: 'user', email: 'user@example.com' } } },
669
+ GET: {
670
+ description: 'Return the currently authenticated user.',
671
+ response: { status: 200, body: { user: { id: '1', role: 'user', email: 'user@example.com' } } },
672
+ },
574
673
  };
575
674
 
576
675
  ` : "";
577
- const requireRoleImport = opts.rbac ? ts ? `import { requireRole } from '../../middleware/requireRole.js';
578
- ` : `import { requireRole } from '../../middleware/requireRole.js';
579
- ` : "";
580
- const meMiddlewares = opts.rbac ? `export const middlewares = [requireAuth, requireRole('user', 'admin')];
676
+ const meMiddlewares = opts.rbac ? `export const middlewares = [requireAuth('user', 'admin')];
581
677
 
582
678
  ` : `export const middlewares = [requireAuth];
583
679
 
584
680
  `;
585
681
  const meContent = ts ? `import { requireAuth } from 'express-file-cluster/auth';
586
- ${requireRoleImport}import type { Request, Response } from 'express';
682
+ import type { Request, Response } from 'express';
587
683
  ${meMeta}${meMiddlewares}export const GET = async (req: Request, res: Response) => {
588
684
  res.json({ user: (req as any).user });
589
685
  };
590
686
  ` : `import { requireAuth } from 'express-file-cluster/auth';
591
- ${requireRoleImport}${meMeta}${meMiddlewares}export const GET = async (req, res) => {
687
+ ${meMeta}${meMiddlewares}export const GET = async (req, res) => {
592
688
  res.json({ user: req.user });
593
689
  };
594
690
  `;
595
691
  await fs.outputFile(path.join(dest, "src", "api", "auth", `register.${ext}`), registerContent);
596
692
  await fs.outputFile(path.join(dest, "src", "api", "auth", `me.${ext}`), meContent);
597
693
  }
598
- async function writeRequireRoleMiddleware(dest, opts) {
599
- const ext = opts.language === "typescript" ? "ts" : "js";
600
- const content = opts.language === "typescript" ? `import type { Request, Response, NextFunction } from 'express';
601
-
602
- export function requireRole(...roles: string[]) {
603
- return (req: Request, res: Response, next: NextFunction) => {
604
- const user = (req as any).user;
605
- if (!user) return res.status(401).json({ error: 'Unauthorized' });
606
- if (!roles.includes(user.role)) return res.status(403).json({ error: 'Forbidden' });
607
- next();
608
- };
609
- }
610
- ` : `export function requireRole(...roles) {
611
- return (req, res, next) => {
612
- const user = req.user;
613
- if (!user) return res.status(401).json({ error: 'Unauthorized' });
614
- if (!roles.includes(user.role)) return res.status(403).json({ error: 'Forbidden' });
615
- next();
616
- };
617
- }
618
- `;
619
- await fs.outputFile(path.join(dest, "src", "middleware", `requireRole.${ext}`), content);
620
- }
621
694
  async function writeAdminRoutes(dest, opts) {
622
695
  const ext = opts.language === "typescript" ? "ts" : "js";
623
696
  const ts = opts.language === "typescript";
624
- const requireRoleImport = opts.rbac ? `import { requireRole } from '../../middleware/requireRole.js';
625
- ` : "";
626
- const usersRequireRoleImport = opts.rbac ? `import { requireRole } from '../../../middleware/requireRole.js';
627
- ` : "";
628
- const middlewares = opts.rbac ? `export const middlewares = [requireAuth, requireRole('admin')];
697
+ const middlewares = opts.rbac ? `export const middlewares = [requireAuth('admin')];
629
698
  ` : `export const middlewares = [requireAuth];
630
699
  `;
631
700
  const roleGuard = opts.rbac ? "" : ts ? ` const user = (req as any).user;
@@ -642,20 +711,24 @@ async function writeAdminRoutes(dest, opts) {
642
711
  const dashboardMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
643
712
 
644
713
  export const meta: RouteMeta = {
645
- description: 'Admin dashboard stats. Requires admin role.',
646
- response: { status: 200, body: { stats: { users: 120, revenue: 5000 } } },
714
+ GET: {
715
+ description: 'Admin dashboard stats. Requires admin role.',
716
+ response: { status: 200, body: { stats: { totalUsers: 120, activeUsers: 98, verifiedUsers: 84 } } },
717
+ },
647
718
  };
648
719
 
649
720
  ` : `export const meta = {
650
- description: 'Admin dashboard stats. Requires admin role.',
651
- response: { status: 200, body: { stats: { users: 120, revenue: 5000 } } },
721
+ GET: {
722
+ description: 'Admin dashboard stats. Requires admin role.',
723
+ response: { status: 200, body: { stats: { totalUsers: 120, activeUsers: 98, verifiedUsers: 84 } } },
724
+ },
652
725
  };
653
726
 
654
727
  ` : "";
655
728
  const dashboardDbImport = opts.database === "mongodb" ? `import { User } from '../../model/User.js';
656
729
  ` : "";
657
730
  const dashboardContent = opts.database === "mongodb" ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
658
- ${requireRoleImport}import type { Request, Response } from 'express';
731
+ import type { Request, Response } from 'express';
659
732
  ${dashboardDbImport}${dashboardMeta}${middlewares}
660
733
  export const GET = async (_req: Request, res: Response) => {
661
734
  ${roleGuard} const [totalUsers, activeUsers, verifiedUsers] = await Promise.all([
@@ -666,7 +739,7 @@ ${roleGuard} const [totalUsers, activeUsers, verifiedUsers] = await Promise.all
666
739
  res.json({ stats: { totalUsers, activeUsers, verifiedUsers } });
667
740
  };
668
741
  ` : `import { requireAuth } from 'express-file-cluster/auth';
669
- ${requireRoleImport}${dashboardDbImport}${dashboardMeta}${middlewares}
742
+ ${dashboardDbImport}${dashboardMeta}${middlewares}
670
743
  export const GET = async (_req, res) => {
671
744
  ${roleGuard} const [totalUsers, activeUsers, verifiedUsers] = await Promise.all([
672
745
  User.count({}),
@@ -676,14 +749,14 @@ ${roleGuard} const [totalUsers, activeUsers, verifiedUsers] = await Promise.all
676
749
  res.json({ stats: { totalUsers, activeUsers, verifiedUsers } });
677
750
  };
678
751
  ` : ts ? `import { requireAuth } from 'express-file-cluster/auth';
679
- ${requireRoleImport}import type { Request, Response } from 'express';
752
+ import type { Request, Response } from 'express';
680
753
  ${dashboardMeta}${middlewares}
681
754
  export const GET = async (_req: Request, res: Response) => {
682
755
  ${roleGuard} // TODO: aggregate stats from DB
683
756
  res.json({ stats: { users: 0 } });
684
757
  };
685
758
  ` : `import { requireAuth } from 'express-file-cluster/auth';
686
- ${requireRoleImport}${dashboardMeta}${middlewares}
759
+ ${dashboardMeta}${middlewares}
687
760
  export const GET = async (_req, res) => {
688
761
  ${roleGuard} // TODO: aggregate stats from DB
689
762
  res.json({ stats: { users: 0 } });
@@ -693,13 +766,29 @@ ${roleGuard} // TODO: aggregate stats from DB
693
766
  const usersListMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
694
767
 
695
768
  export const meta: RouteMeta = {
696
- description: 'List all users (admin only).',
697
- response: { status: 200, body: { users: [], total: 0 } },
769
+ GET: {
770
+ description: 'List all users, paginated (admin only).',
771
+ request: { query: { page: '1', limit: '20' } },
772
+ response: { status: 200, body: { users: [], total: 0, page: 1, limit: 20 } },
773
+ },
774
+ POST: {
775
+ description: 'Create a new user account (admin only).',
776
+ request: { body: { name: 'Jane Doe', email: 'jane@example.com', password: 'secret', role: 'user' } },
777
+ response: { status: 201, body: { message: 'User created', user: { id: 'new-id', name: 'Jane Doe', email: 'jane@example.com', role: 'user' } } },
778
+ },
698
779
  };
699
780
 
700
781
  ` : `export const meta = {
701
- description: 'List all users (admin only).',
702
- response: { status: 200, body: { users: [], total: 0 } },
782
+ GET: {
783
+ description: 'List all users, paginated (admin only).',
784
+ request: { query: { page: '1', limit: '20' } },
785
+ response: { status: 200, body: { users: [], total: 0, page: 1, limit: 20 } },
786
+ },
787
+ POST: {
788
+ description: 'Create a new user account (admin only).',
789
+ request: { body: { name: 'Jane Doe', email: 'jane@example.com', password: 'secret', role: 'user' } },
790
+ response: { status: 201, body: { message: 'User created', user: { id: 'new-id', name: 'Jane Doe', email: 'jane@example.com', role: 'user' } } },
791
+ },
703
792
  };
704
793
 
705
794
  ` : "";
@@ -709,7 +798,7 @@ import { User } from '../../../model/User.js';
709
798
  import { User } from '../../../model/User.js';
710
799
  ` : "";
711
800
  const usersListContent = opts.database === "mongodb" ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
712
- ${usersRequireRoleImport}import type { Request, Response } from 'express';
801
+ import type { Request, Response } from 'express';
713
802
  ${adminUsersDbImport}${usersListMeta}${middlewares}
714
803
  export const GET = async (req: Request, res: Response) => {
715
804
  ${roleGuard} const page = Math.max(1, Number(req.query.page) || 1);
@@ -730,7 +819,7 @@ ${roleGuard} const { name, email, password, role } = req.body;
730
819
  res.status(201).json({ message: 'User created', user: safe });
731
820
  };
732
821
  ` : `import { requireAuth } from 'express-file-cluster/auth';
733
- ${usersRequireRoleImport}${adminUsersDbImport}${usersListMeta}${middlewares}
822
+ ${adminUsersDbImport}${usersListMeta}${middlewares}
734
823
  export const GET = async (req, res) => {
735
824
  ${roleGuard} const page = Math.max(1, Number(req.query.page) || 1);
736
825
  const limit = Math.min(100, Number(req.query.limit) || 20);
@@ -750,7 +839,7 @@ ${roleGuard} const { name, email, password, role } = req.body;
750
839
  res.status(201).json({ message: 'User created', user: safe });
751
840
  };
752
841
  ` : ts ? `import { requireAuth } from 'express-file-cluster/auth';
753
- ${usersRequireRoleImport}import type { Request, Response } from 'express';
842
+ import type { Request, Response } from 'express';
754
843
  ${usersListMeta}${middlewares}
755
844
  export const GET = async (_req: Request, res: Response) => {
756
845
  ${roleGuard} // TODO: fetch users from DB with pagination
@@ -764,7 +853,7 @@ ${roleGuard} const { name, email, role } = req.body;
764
853
  res.status(201).json({ message: 'User created', user: { id: 'new-id', name, email, role: role ?? 'user' } });
765
854
  };
766
855
  ` : `import { requireAuth } from 'express-file-cluster/auth';
767
- ${usersRequireRoleImport}${usersListMeta}${middlewares}
856
+ ${usersListMeta}${middlewares}
768
857
  export const GET = async (_req, res) => {
769
858
  ${roleGuard} // TODO: fetch users from DB with pagination
770
859
  res.json({ users: [], total: 0 });
@@ -781,18 +870,46 @@ ${roleGuard} const { name, email, role } = req.body;
781
870
  const userByIdMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
782
871
 
783
872
  export const meta: RouteMeta = {
784
- description: 'Get, update, or delete a single user by ID (admin only).',
873
+ GET: {
874
+ description: 'Fetch a single user by ID (admin only).',
875
+ request: { params: { id: 'usr_01HXZ' } },
876
+ response: { status: 200, body: { user: { id: 'usr_01HXZ', name: 'Jane Doe', email: 'jane@example.com', role: 'user' } } },
877
+ },
878
+ PUT: {
879
+ description: 'Update a user by ID (admin only).',
880
+ request: { params: { id: 'usr_01HXZ' }, body: { name: 'Jane Doe', email: 'jane@example.com', role: 'user', isActive: true } },
881
+ response: { status: 200, body: { message: 'User updated', user: { id: 'usr_01HXZ', name: 'Jane Doe', email: 'jane@example.com', role: 'user' } } },
882
+ },
883
+ DELETE: {
884
+ description: 'Delete a user by ID (admin only).',
885
+ request: { params: { id: 'usr_01HXZ' } },
886
+ response: { status: 200, body: { message: 'User usr_01HXZ deleted' } },
887
+ },
785
888
  };
786
889
 
787
890
  ` : `export const meta = {
788
- description: 'Get, update, or delete a single user by ID (admin only).',
891
+ GET: {
892
+ description: 'Fetch a single user by ID (admin only).',
893
+ request: { params: { id: 'usr_01HXZ' } },
894
+ response: { status: 200, body: { user: { id: 'usr_01HXZ', name: 'Jane Doe', email: 'jane@example.com', role: 'user' } } },
895
+ },
896
+ PUT: {
897
+ description: 'Update a user by ID (admin only).',
898
+ request: { params: { id: 'usr_01HXZ' }, body: { name: 'Jane Doe', email: 'jane@example.com', role: 'user', isActive: true } },
899
+ response: { status: 200, body: { message: 'User updated', user: { id: 'usr_01HXZ', name: 'Jane Doe', email: 'jane@example.com', role: 'user' } } },
900
+ },
901
+ DELETE: {
902
+ description: 'Delete a user by ID (admin only).',
903
+ request: { params: { id: 'usr_01HXZ' } },
904
+ response: { status: 200, body: { message: 'User usr_01HXZ deleted' } },
905
+ },
789
906
  };
790
907
 
791
908
  ` : "";
792
909
  const adminUserByIdDbImport = opts.database === "mongodb" ? `import { User } from '../../../model/User.js';
793
910
  ` : "";
794
911
  const userByIdContent = opts.database === "mongodb" ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
795
- ${usersRequireRoleImport}import type { Request, Response } from 'express';
912
+ import type { Request, Response } from 'express';
796
913
  ${adminUserByIdDbImport}${userByIdMeta}${middlewares}
797
914
  export const GET = async (req: Request, res: Response) => {
798
915
  ${roleGuard} const { id } = req.params;
@@ -819,7 +936,7 @@ ${roleGuard} const { id } = req.params;
819
936
  res.json({ message: \`User \${id} deleted\` });
820
937
  };
821
938
  ` : `import { requireAuth } from 'express-file-cluster/auth';
822
- ${usersRequireRoleImport}${adminUserByIdDbImport}${userByIdMeta}${middlewares}
939
+ ${adminUserByIdDbImport}${userByIdMeta}${middlewares}
823
940
  export const GET = async (req, res) => {
824
941
  ${roleGuard} const { id } = req.params;
825
942
  const user = await User.findById(id);
@@ -845,7 +962,7 @@ ${roleGuard} const { id } = req.params;
845
962
  res.json({ message: \`User \${id} deleted\` });
846
963
  };
847
964
  ` : ts ? `import { requireAuth } from 'express-file-cluster/auth';
848
- ${usersRequireRoleImport}import type { Request, Response } from 'express';
965
+ import type { Request, Response } from 'express';
849
966
  ${userByIdMeta}${middlewares}
850
967
  export const GET = async (req: Request, res: Response) => {
851
968
  ${roleGuard} const { id } = req.params;
@@ -865,7 +982,7 @@ ${roleGuard} const { id } = req.params;
865
982
  res.json({ message: \`User \${id} deleted\` });
866
983
  };
867
984
  ` : `import { requireAuth } from 'express-file-cluster/auth';
868
- ${usersRequireRoleImport}${userByIdMeta}${middlewares}
985
+ ${userByIdMeta}${middlewares}
869
986
  export const GET = async (req, res) => {
870
987
  ${roleGuard} const { id } = req.params;
871
988
  // TODO: fetch user from DB
@@ -889,28 +1006,40 @@ ${roleGuard} const { id } = req.params;
889
1006
  async function writeUserRoutes(dest, opts) {
890
1007
  const ext = opts.language === "typescript" ? "ts" : "js";
891
1008
  const ts = opts.language === "typescript";
892
- const requireRoleImport = opts.rbac ? `import { requireRole } from '../../middleware/requireRole.js';
893
- ` : "";
894
- const middlewares = opts.rbac ? `export const middlewares = [requireAuth, requireRole('user', 'admin')];
1009
+ const middlewares = opts.rbac ? `export const middlewares = [requireAuth('user', 'admin')];
895
1010
  ` : `export const middlewares = [requireAuth];
896
1011
  `;
897
1012
  const profileMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
898
1013
 
899
1014
  export const meta: RouteMeta = {
900
- description: "View or update the authenticated user's profile.",
901
- response: { status: 200, body: { user: { id: '1', role: 'user', email: 'user@example.com' } } },
1015
+ GET: {
1016
+ description: "Fetch the authenticated user's profile.",
1017
+ response: { status: 200, body: { user: { id: '1', role: 'user', email: 'user@example.com' } } },
1018
+ },
1019
+ PUT: {
1020
+ description: "Update the authenticated user's profile.",
1021
+ request: { body: { name: 'Jane Doe', email: 'jane@example.com' } },
1022
+ response: { status: 200, body: { message: 'Profile updated', user: { id: '1', role: 'user', email: 'jane@example.com' } } },
1023
+ },
902
1024
  };
903
1025
 
904
1026
  ` : `export const meta = {
905
- description: "View or update the authenticated user's profile.",
906
- response: { status: 200, body: { user: { id: '1', role: 'user', email: 'user@example.com' } } },
1027
+ GET: {
1028
+ description: "Fetch the authenticated user's profile.",
1029
+ response: { status: 200, body: { user: { id: '1', role: 'user', email: 'user@example.com' } } },
1030
+ },
1031
+ PUT: {
1032
+ description: "Update the authenticated user's profile.",
1033
+ request: { body: { name: 'Jane Doe', email: 'jane@example.com' } },
1034
+ response: { status: 200, body: { message: 'Profile updated', user: { id: '1', role: 'user', email: 'jane@example.com' } } },
1035
+ },
907
1036
  };
908
1037
 
909
1038
  ` : "";
910
1039
  const profileDbImport = opts.database === "mongodb" ? `import { User } from '../../model/User.js';
911
1040
  ` : "";
912
1041
  const profileContent = opts.database === "mongodb" ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
913
- ${requireRoleImport}import type { Request, Response } from 'express';
1042
+ import type { Request, Response } from 'express';
914
1043
  ${profileDbImport}${profileMeta}${middlewares}
915
1044
  export const GET = async (req: Request, res: Response) => {
916
1045
  const { id } = (req as any).user;
@@ -929,7 +1058,7 @@ export const PUT = async (req: Request, res: Response) => {
929
1058
  res.json({ message: 'Profile updated', user: safe });
930
1059
  };
931
1060
  ` : `import { requireAuth } from 'express-file-cluster/auth';
932
- ${requireRoleImport}${profileDbImport}${profileMeta}${middlewares}
1061
+ ${profileDbImport}${profileMeta}${middlewares}
933
1062
  export const GET = async (req, res) => {
934
1063
  const { id } = req.user;
935
1064
  const user = await User.findById(id);
@@ -947,7 +1076,7 @@ export const PUT = async (req, res) => {
947
1076
  res.json({ message: 'Profile updated', user: safe });
948
1077
  };
949
1078
  ` : ts ? `import { requireAuth } from 'express-file-cluster/auth';
950
- ${requireRoleImport}import type { Request, Response } from 'express';
1079
+ import type { Request, Response } from 'express';
951
1080
  ${profileMeta}${middlewares}
952
1081
  export const GET = async (req: Request, res: Response) => {
953
1082
  res.json({ user: (req as any).user });
@@ -959,7 +1088,7 @@ export const PUT = async (req: Request, res: Response) => {
959
1088
  res.json({ message: 'Profile updated', user: { ...(req as any).user, name, email } });
960
1089
  };
961
1090
  ` : `import { requireAuth } from 'express-file-cluster/auth';
962
- ${requireRoleImport}${profileMeta}${middlewares}
1091
+ ${profileMeta}${middlewares}
963
1092
  export const GET = async (req, res) => {
964
1093
  res.json({ user: req.user });
965
1094
  };
@@ -972,20 +1101,32 @@ export const PUT = async (req, res) => {
972
1101
  `;
973
1102
  await fs.outputFile(path.join(dest, "src", "api", "user", `profile.${ext}`), profileContent);
974
1103
  }
975
- function mkMeta(opts, description, body, req) {
1104
+ function mkMeta(opts, methods) {
976
1105
  if (!opts.routeDocs) return "";
977
- const reqLine = req ? ` request: { body: ${req} },
1106
+ const blocks = Object.entries(methods).map(([method, m]) => {
1107
+ const reqParts = [];
1108
+ if (m.params) reqParts.push(`params: ${m.params}`);
1109
+ if (m.query) reqParts.push(`query: ${m.query}`);
1110
+ if (m.request) reqParts.push(`body: ${m.request}`);
1111
+ const reqLine = reqParts.length ? ` request: { ${reqParts.join(", ")} },
978
1112
  ` : "";
1113
+ const status = m.status ?? 200;
1114
+ const respLine = m.response !== void 0 ? ` response: { status: ${status}, body: ${m.response} },
1115
+ ` : ` response: { status: ${status} },
1116
+ `;
1117
+ const description = m.description.replace(/'/g, "\\'");
1118
+ return ` ${method}: {
1119
+ description: '${description}',
1120
+ ${reqLine}${respLine} },`;
1121
+ }).join("\n");
979
1122
  return opts.language === "typescript" ? `import type { RouteMeta } from 'express-file-cluster';
980
1123
 
981
1124
  export const meta: RouteMeta = {
982
- description: '${description}',
983
- ${reqLine} response: { status: 200, body: ${body} },
1125
+ ${blocks}
984
1126
  };
985
1127
 
986
1128
  ` : `export const meta = {
987
- description: '${description}',
988
- ${reqLine} response: { status: 200, body: ${body} },
1129
+ ${blocks}
989
1130
  };
990
1131
 
991
1132
  `;
@@ -1700,11 +1841,7 @@ export {};
1700
1841
  async function writeAuthExtendedRoutes(dest, opts) {
1701
1842
  const ext = opts.language === "typescript" ? "ts" : "js";
1702
1843
  const ts = opts.language === "typescript";
1703
- const rr2 = opts.rbac ? `import { requireRole } from '../../middleware/requireRole.js';
1704
- ` : "";
1705
- const rr3 = opts.rbac ? `import { requireRole } from '../../../middleware/requireRole.js';
1706
- ` : "";
1707
- const mwUser2 = opts.rbac ? `export const middlewares = [requireAuth, requireRole('user', 'admin')];
1844
+ const mwUser2 = opts.rbac ? `export const middlewares = [requireAuth('user', 'admin')];
1708
1845
  ` : `export const middlewares = [requireAuth];
1709
1846
  `;
1710
1847
  const mwUser3 = mwUser2;
@@ -1712,8 +1849,96 @@ async function writeAuthExtendedRoutes(dest, opts) {
1712
1849
  ` : "";
1713
1850
  const RA = `import { requireAuth } from 'express-file-cluster/auth';
1714
1851
  `;
1715
- const refreshMeta = mkMeta(opts, "Refresh the JWT and issue a new token.", `{ message: 'Token refreshed' }`);
1716
- const refreshContent = ts ? `${RA}import type { Request, Response } from 'express';
1852
+ const mongo = opts.database === "mongodb";
1853
+ const sendResetEmail = opts.mailer ? ` var appUrl = process.env.APP_URL || 'http://localhost:3000';
1854
+ await enqueue('SendEmail', {
1855
+ to: email,
1856
+ subject: 'Reset your password',
1857
+ body: 'Reset your password: ' + appUrl + '/auth/reset-password?token=' + resetToken,
1858
+ });
1859
+ ` : ` // TODO: email this token to the user \u2014 enable the Mailer feature to auto-wire SendEmail
1860
+ `;
1861
+ const sendVerifyEmailResend = opts.mailer ? ` var appUrl = process.env.APP_URL || 'http://localhost:3000';
1862
+ await enqueue('SendEmail', {
1863
+ to: email,
1864
+ subject: 'Verify your email address',
1865
+ body: 'Verify your email: ' + appUrl + '/auth/verify-email?token=' + verifyToken,
1866
+ });
1867
+ ` : ` // TODO: email this token to the user \u2014 enable the Mailer feature to auto-wire SendEmail
1868
+ `;
1869
+ const mailerTaskImport = opts.mailer ? `import { enqueue } from 'express-file-cluster/tasks';
1870
+ ` : "";
1871
+ const refreshMeta = mkMeta(opts, {
1872
+ POST: { description: "Refresh the JWT using the refresh-token cookie and issue a new access token.", response: `{ message: 'Token refreshed' }` }
1873
+ });
1874
+ const refreshContent = mongo ? ts ? `import { issueToken } from 'express-file-cluster/auth';
1875
+ import type { Request, Response } from 'express';
1876
+ import crypto from 'node:crypto';
1877
+ import { User } from '../../model/User.js';
1878
+ import { Admin } from '../../model/Admin.js';
1879
+ ${refreshMeta}const REFRESH_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
1880
+
1881
+ export const POST = async (req: Request, res: Response) => {
1882
+ const token = req.cookies?.['efc_refresh_token'] || req.body?.refreshToken;
1883
+ if (!token) return res.status(401).json({ error: 'Refresh token required' });
1884
+
1885
+ const user = await User.findOne({ refreshToken: token });
1886
+ const admin = user ? null : await Admin.findOne({ refreshToken: token });
1887
+ const account = user || admin;
1888
+
1889
+ if (!account || !account.refreshTokenExpiry || new Date(account.refreshTokenExpiry) < new Date()) {
1890
+ return res.status(401).json({ error: 'Invalid or expired refresh token' });
1891
+ }
1892
+
1893
+ const newRefreshToken = crypto.randomBytes(40).toString('hex');
1894
+ const refreshTokenExpiry = new Date(Date.now() + REFRESH_TOKEN_TTL_MS);
1895
+ if (user) await User.update(user.id, { refreshToken: newRefreshToken, refreshTokenExpiry });
1896
+ else if (admin) await Admin.update(admin.id, { refreshToken: newRefreshToken, refreshTokenExpiry });
1897
+
1898
+ res.cookie('efc_refresh_token', newRefreshToken, {
1899
+ httpOnly: true,
1900
+ secure: process.env.NODE_ENV === 'production',
1901
+ sameSite: 'strict',
1902
+ maxAge: REFRESH_TOKEN_TTL_MS,
1903
+ });
1904
+
1905
+ await issueToken(res, { id: account.id, role: account.role, email: account.email });
1906
+ res.json({ message: 'Token refreshed' });
1907
+ };
1908
+ ` : `import { issueToken } from 'express-file-cluster/auth';
1909
+ import crypto from 'node:crypto';
1910
+ import { User } from '../../model/User.js';
1911
+ import { Admin } from '../../model/Admin.js';
1912
+ ${refreshMeta}const REFRESH_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
1913
+
1914
+ export const POST = async (req, res) => {
1915
+ const token = req.cookies?.efc_refresh_token || req.body?.refreshToken;
1916
+ if (!token) return res.status(401).json({ error: 'Refresh token required' });
1917
+
1918
+ const user = await User.findOne({ refreshToken: token });
1919
+ const admin = user ? null : await Admin.findOne({ refreshToken: token });
1920
+ const account = user || admin;
1921
+
1922
+ if (!account || !account.refreshTokenExpiry || new Date(account.refreshTokenExpiry) < new Date()) {
1923
+ return res.status(401).json({ error: 'Invalid or expired refresh token' });
1924
+ }
1925
+
1926
+ const newRefreshToken = crypto.randomBytes(40).toString('hex');
1927
+ const refreshTokenExpiry = new Date(Date.now() + REFRESH_TOKEN_TTL_MS);
1928
+ if (user) await User.update(user.id, { refreshToken: newRefreshToken, refreshTokenExpiry });
1929
+ else if (admin) await Admin.update(admin.id, { refreshToken: newRefreshToken, refreshTokenExpiry });
1930
+
1931
+ res.cookie('efc_refresh_token', newRefreshToken, {
1932
+ httpOnly: true,
1933
+ secure: process.env.NODE_ENV === 'production',
1934
+ sameSite: 'strict',
1935
+ maxAge: REFRESH_TOKEN_TTL_MS,
1936
+ });
1937
+
1938
+ await issueToken(res, { id: account.id, role: account.role, email: account.email });
1939
+ res.json({ message: 'Token refreshed' });
1940
+ };
1941
+ ` : ts ? `${RA}import type { Request, Response } from 'express';
1717
1942
  ${refreshMeta}export const POST = async (_req: Request, res: Response) => {
1718
1943
  // TODO: validate refresh token, issue new JWT
1719
1944
  res.json({ message: 'Token refreshed' });
@@ -1724,8 +1949,62 @@ ${refreshMeta}export const POST = async (_req: Request, res: Response) => {
1724
1949
  };
1725
1950
  `;
1726
1951
  await fs.outputFile(path.join(dest, "src", "api", "auth", `refresh.${ext}`), refreshContent);
1727
- const veMeta = mkMeta(opts, "Verify email address via token or resend verification email.", `{ message: 'Email verified' }`);
1728
- const veContent = ts ? `${RA}import type { Request, Response } from 'express';
1952
+ const veMeta = mkMeta(opts, {
1953
+ GET: { description: "Verify an email address using the token from the verification link.", query: `{ token: 'a1b2c3d4' }`, response: `{ message: 'Email verified' }` },
1954
+ POST: { description: "Resend the verification email to a given address.", request: `{ email: 'user@example.com' }`, response: `{ message: 'Verification email sent' }` }
1955
+ });
1956
+ const veContent = mongo ? ts ? `import type { Request, Response } from 'express';
1957
+ import crypto from 'node:crypto';
1958
+ ${mailerTaskImport}import { User } from '../../model/User.js';
1959
+ ${veMeta}export const GET = async (req: Request, res: Response) => {
1960
+ const { token } = req.query;
1961
+ if (!token || typeof token !== 'string') return res.status(400).json({ error: 'token is required' });
1962
+
1963
+ const user = await User.findOne({ verifyToken: token });
1964
+ if (!user) return res.status(400).json({ error: 'Invalid or expired verification token' });
1965
+
1966
+ await User.update(user.id, { isVerified: true, verifyToken: '' });
1967
+ res.json({ message: 'Email verified' });
1968
+ };
1969
+
1970
+ export const POST = async (req: Request, res: Response) => {
1971
+ const { email } = req.body;
1972
+ if (!email) return res.status(400).json({ error: 'email is required' });
1973
+
1974
+ const user = await User.findOne({ email });
1975
+ if (user && !user.isVerified) {
1976
+ const verifyToken = crypto.randomBytes(32).toString('hex');
1977
+ await User.update(user.id, { verifyToken });
1978
+ ${sendVerifyEmailResend} }
1979
+
1980
+ res.json({ message: 'Verification email sent' });
1981
+ };
1982
+ ` : `import crypto from 'node:crypto';
1983
+ ${mailerTaskImport}import { User } from '../../model/User.js';
1984
+ ${veMeta}export const GET = async (req, res) => {
1985
+ const { token } = req.query;
1986
+ if (!token || typeof token !== 'string') return res.status(400).json({ error: 'token is required' });
1987
+
1988
+ const user = await User.findOne({ verifyToken: token });
1989
+ if (!user) return res.status(400).json({ error: 'Invalid or expired verification token' });
1990
+
1991
+ await User.update(user.id, { isVerified: true, verifyToken: '' });
1992
+ res.json({ message: 'Email verified' });
1993
+ };
1994
+
1995
+ export const POST = async (req, res) => {
1996
+ const { email } = req.body;
1997
+ if (!email) return res.status(400).json({ error: 'email is required' });
1998
+
1999
+ const user = await User.findOne({ email });
2000
+ if (user && !user.isVerified) {
2001
+ const verifyToken = crypto.randomBytes(32).toString('hex');
2002
+ await User.update(user.id, { verifyToken });
2003
+ ${sendVerifyEmailResend} }
2004
+
2005
+ res.json({ message: 'Verification email sent' });
2006
+ };
2007
+ ` : ts ? `${RA}import type { Request, Response } from 'express';
1729
2008
  ${veMeta}export const GET = async (req: Request, res: Response) => {
1730
2009
  const { token } = req.query;
1731
2010
  // TODO: verify token and mark user as verified
@@ -1748,8 +2027,57 @@ export const POST = async (_req, res) => {
1748
2027
  };
1749
2028
  `;
1750
2029
  await fs.outputFile(path.join(dest, "src", "api", "auth", `verify-email.${ext}`), veContent);
1751
- const fpMeta = mkMeta(opts, "Send a password reset email to the given address.", `{ message: 'Reset email sent' }`, `{ email: 'user@example.com' }`);
1752
- const fpContent = ts ? `${RA}import type { Request, Response } from 'express';
2030
+ const fpMeta = mkMeta(opts, {
2031
+ POST: { description: "Send a password reset email to the given address.", request: `{ email: 'user@example.com' }`, response: `{ message: 'Reset email sent' }` }
2032
+ });
2033
+ const fpContent = mongo ? ts ? `import type { Request, Response } from 'express';
2034
+ import crypto from 'node:crypto';
2035
+ ${mailerTaskImport}import { User } from '../../model/User.js';
2036
+ import { Admin } from '../../model/Admin.js';
2037
+ ${fpMeta}const RESET_TOKEN_TTL_MS = 1000 * 60 * 60; // 1 hour
2038
+
2039
+ export const POST = async (req: Request, res: Response) => {
2040
+ const { email } = req.body;
2041
+ if (!email) return res.status(400).json({ error: 'email is required' });
2042
+
2043
+ const user = await User.findOne({ email });
2044
+ const admin = user ? null : await Admin.findOne({ email });
2045
+
2046
+ // Always respond the same way whether or not the account exists, so this
2047
+ // endpoint can't be used to enumerate registered emails.
2048
+ if (user || admin) {
2049
+ const resetToken = crypto.randomBytes(32).toString('hex');
2050
+ const resetTokenExpiry = new Date(Date.now() + RESET_TOKEN_TTL_MS);
2051
+ if (user) await User.update(user.id, { resetToken, resetTokenExpiry });
2052
+ else if (admin) await Admin.update(admin.id, { resetToken, resetTokenExpiry });
2053
+ ${sendResetEmail} }
2054
+
2055
+ res.json({ message: 'Reset email sent' });
2056
+ };
2057
+ ` : `import crypto from 'node:crypto';
2058
+ ${mailerTaskImport}import { User } from '../../model/User.js';
2059
+ import { Admin } from '../../model/Admin.js';
2060
+ ${fpMeta}const RESET_TOKEN_TTL_MS = 1000 * 60 * 60; // 1 hour
2061
+
2062
+ export const POST = async (req, res) => {
2063
+ const { email } = req.body;
2064
+ if (!email) return res.status(400).json({ error: 'email is required' });
2065
+
2066
+ const user = await User.findOne({ email });
2067
+ const admin = user ? null : await Admin.findOne({ email });
2068
+
2069
+ // Always respond the same way whether or not the account exists, so this
2070
+ // endpoint can't be used to enumerate registered emails.
2071
+ if (user || admin) {
2072
+ const resetToken = crypto.randomBytes(32).toString('hex');
2073
+ const resetTokenExpiry = new Date(Date.now() + RESET_TOKEN_TTL_MS);
2074
+ if (user) await User.update(user.id, { resetToken, resetTokenExpiry });
2075
+ else if (admin) await Admin.update(admin.id, { resetToken, resetTokenExpiry });
2076
+ ${sendResetEmail} }
2077
+
2078
+ res.json({ message: 'Reset email sent' });
2079
+ };
2080
+ ` : ts ? `${RA}import type { Request, Response } from 'express';
1753
2081
  ${fpMeta}export const POST = async (req: Request, res: Response) => {
1754
2082
  const { email } = req.body;
1755
2083
  if (!email) return res.status(400).json({ error: 'email is required' });
@@ -1764,8 +2092,53 @@ ${fpMeta}export const POST = async (req: Request, res: Response) => {
1764
2092
  };
1765
2093
  `;
1766
2094
  await fs.outputFile(path.join(dest, "src", "api", "auth", `forgot-password.${ext}`), fpContent);
1767
- const rpMeta = mkMeta(opts, "Reset password using a valid reset token.", `{ message: 'Password reset successfully' }`, `{ token: 'reset-token', password: 'newpassword' }`);
1768
- const rpContent = ts ? `${RA}import type { Request, Response } from 'express';
2095
+ const rpMeta = mkMeta(opts, {
2096
+ POST: { description: "Reset password using a valid reset token.", request: `{ token: 'reset-token', password: 'newpassword' }`, response: `{ message: 'Password reset successfully' }` }
2097
+ });
2098
+ const rpContent = mongo ? ts ? `import type { Request, Response } from 'express';
2099
+ import bcrypt from 'bcrypt';
2100
+ import { User } from '../../model/User.js';
2101
+ import { Admin } from '../../model/Admin.js';
2102
+ ${rpMeta}export const POST = async (req: Request, res: Response) => {
2103
+ const { token, password } = req.body;
2104
+ if (!token || !password) return res.status(400).json({ error: 'token and password are required' });
2105
+
2106
+ const user = await User.findOne({ resetToken: token });
2107
+ const admin = user ? null : await Admin.findOne({ resetToken: token });
2108
+ const account = user || admin;
2109
+
2110
+ if (!account || !account.resetTokenExpiry || new Date(account.resetTokenExpiry) < new Date()) {
2111
+ return res.status(400).json({ error: 'Invalid or expired reset token' });
2112
+ }
2113
+
2114
+ const hashed = await bcrypt.hash(password, 10);
2115
+ if (user) await User.update(user.id, { password: hashed, resetToken: '' });
2116
+ else if (admin) await Admin.update(admin.id, { password: hashed, resetToken: '' });
2117
+
2118
+ res.json({ message: 'Password reset successfully' });
2119
+ };
2120
+ ` : `import bcrypt from 'bcrypt';
2121
+ import { User } from '../../model/User.js';
2122
+ import { Admin } from '../../model/Admin.js';
2123
+ ${rpMeta}export const POST = async (req, res) => {
2124
+ const { token, password } = req.body;
2125
+ if (!token || !password) return res.status(400).json({ error: 'token and password are required' });
2126
+
2127
+ const user = await User.findOne({ resetToken: token });
2128
+ const admin = user ? null : await Admin.findOne({ resetToken: token });
2129
+ const account = user || admin;
2130
+
2131
+ if (!account || !account.resetTokenExpiry || new Date(account.resetTokenExpiry) < new Date()) {
2132
+ return res.status(400).json({ error: 'Invalid or expired reset token' });
2133
+ }
2134
+
2135
+ const hashed = await bcrypt.hash(password, 10);
2136
+ if (user) await User.update(user.id, { password: hashed, resetToken: '' });
2137
+ else if (admin) await Admin.update(admin.id, { password: hashed, resetToken: '' });
2138
+
2139
+ res.json({ message: 'Password reset successfully' });
2140
+ };
2141
+ ` : ts ? `${RA}import type { Request, Response } from 'express';
1769
2142
  ${rpMeta}export const POST = async (req: Request, res: Response) => {
1770
2143
  const { token, password } = req.body;
1771
2144
  if (!token || !password) return res.status(400).json({ error: 'token and password are required' });
@@ -1780,8 +2153,10 @@ ${rpMeta}export const POST = async (req: Request, res: Response) => {
1780
2153
  };
1781
2154
  `;
1782
2155
  await fs.outputFile(path.join(dest, "src", "api", "auth", `reset-password.${ext}`), rpContent);
1783
- const cpMeta = mkMeta(opts, "Change password for the authenticated user.", `{ message: 'Password changed successfully' }`, `{ oldPassword: 'current', newPassword: 'newpassword' }`);
1784
- const cpContent = ts ? `${RA}${rr2}import type { Request, Response } from 'express';
2156
+ const cpMeta = mkMeta(opts, {
2157
+ POST: { description: "Change password for the authenticated user.", request: `{ oldPassword: 'current', newPassword: 'newpassword' }`, response: `{ message: 'Password changed successfully' }` }
2158
+ });
2159
+ const cpContent = ts ? `${RA}import type { Request, Response } from 'express';
1785
2160
  ${cpMeta}${mwUser2}
1786
2161
  export const POST = async (req: Request, res: Response) => {
1787
2162
  const { oldPassword, newPassword } = req.body;
@@ -1789,7 +2164,7 @@ export const POST = async (req: Request, res: Response) => {
1789
2164
  // TODO: verify old password, hash and update new password
1790
2165
  res.json({ message: 'Password changed successfully' });
1791
2166
  };
1792
- ` : `${RA}${rr2}${cpMeta}${mwUser2}
2167
+ ` : `${RA}${cpMeta}${mwUser2}
1793
2168
  export const POST = async (req, res) => {
1794
2169
  const { oldPassword, newPassword } = req.body;
1795
2170
  if (!oldPassword || !newPassword) return res.status(400).json({ error: 'oldPassword and newPassword are required' });
@@ -1798,8 +2173,11 @@ export const POST = async (req, res) => {
1798
2173
  };
1799
2174
  `;
1800
2175
  await fs.outputFile(path.join(dest, "src", "api", "auth", `change-password.${ext}`), cpContent);
1801
- const tfaSetupMeta = mkMeta(opts, "Get 2FA QR code (GET) or enable 2FA with a verified TOTP code (POST).", `{ message: '2FA enabled' }`);
1802
- const tfaSetupContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2176
+ const tfaSetupMeta = mkMeta(opts, {
2177
+ GET: { description: "Generate a TOTP secret and QR code to set up 2FA.", response: `{ qrCode: 'otpauth://totp/...', secret: 'BASE32SECRET' }` },
2178
+ POST: { description: "Confirm a TOTP code to enable 2FA for the authenticated user.", request: `{ code: '123456' }`, response: `{ message: '2FA enabled' }` }
2179
+ });
2180
+ const tfaSetupContent = ts ? `${RA}import type { Request, Response } from 'express';
1803
2181
  ${tfaSetupMeta}${mwUser3}
1804
2182
  export const GET = async (req: Request, res: Response) => {
1805
2183
  // TODO: generate TOTP secret and return QR code URL
@@ -1812,7 +2190,7 @@ export const POST = async (req: Request, res: Response) => {
1812
2190
  // TODO: verify TOTP code and enable 2FA
1813
2191
  res.json({ message: '2FA enabled' });
1814
2192
  };
1815
- ` : `${RA}${rr3}${tfaSetupMeta}${mwUser3}
2193
+ ` : `${RA}${tfaSetupMeta}${mwUser3}
1816
2194
  export const GET = async (req, res) => {
1817
2195
  // TODO: generate TOTP secret and return QR code URL
1818
2196
  res.json({ qrCode: 'otpauth://totp/...', secret: 'BASE32SECRET' });
@@ -1826,7 +2204,9 @@ export const POST = async (req, res) => {
1826
2204
  };
1827
2205
  `;
1828
2206
  await fs.outputFile(path.join(dest, "src", "api", "auth", "2fa", `setup.${ext}`), tfaSetupContent);
1829
- const tfaVerifyMeta = mkMeta(opts, "Verify a TOTP code during login.", `{ message: '2FA verified' }`);
2207
+ const tfaVerifyMeta = mkMeta(opts, {
2208
+ POST: { description: "Verify a TOTP code during login.", request: `{ code: '123456' }`, response: `{ message: '2FA verified' }` }
2209
+ });
1830
2210
  const tfaVerifyContent = ts ? `${RA}import type { Request, Response } from 'express';
1831
2211
  ${tfaVerifyMeta}export const POST = async (req: Request, res: Response) => {
1832
2212
  const { code } = req.body;
@@ -1842,8 +2222,10 @@ ${tfaVerifyMeta}export const POST = async (req: Request, res: Response) => {
1842
2222
  };
1843
2223
  `;
1844
2224
  await fs.outputFile(path.join(dest, "src", "api", "auth", "2fa", `verify.${ext}`), tfaVerifyContent);
1845
- const tfaDisableMeta = mkMeta(opts, "Disable 2FA for the authenticated user.", `{ message: '2FA disabled' }`);
1846
- const tfaDisableContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2225
+ const tfaDisableMeta = mkMeta(opts, {
2226
+ POST: { description: "Disable 2FA for the authenticated user.", request: `{ code: '123456' }`, response: `{ message: '2FA disabled' }` }
2227
+ });
2228
+ const tfaDisableContent = ts ? `${RA}import type { Request, Response } from 'express';
1847
2229
  ${tfaDisableMeta}${mwUser3}
1848
2230
  export const POST = async (req: Request, res: Response) => {
1849
2231
  const { code } = req.body;
@@ -1851,7 +2233,7 @@ export const POST = async (req: Request, res: Response) => {
1851
2233
  // TODO: verify code and disable 2FA
1852
2234
  res.json({ message: '2FA disabled' });
1853
2235
  };
1854
- ` : `${RA}${rr3}${tfaDisableMeta}${mwUser3}
2236
+ ` : `${RA}${tfaDisableMeta}${mwUser3}
1855
2237
  export const POST = async (req, res) => {
1856
2238
  const { code } = req.body;
1857
2239
  if (!code) return res.status(400).json({ error: 'code is required' });
@@ -1860,28 +2242,33 @@ export const POST = async (req, res) => {
1860
2242
  };
1861
2243
  `;
1862
2244
  await fs.outputFile(path.join(dest, "src", "api", "auth", "2fa", `disable.${ext}`), tfaDisableContent);
1863
- const sessListMeta = mkMeta(opts, "List all active sessions for the authenticated user.", `{ sessions: [] }`);
1864
- const sessListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2245
+ const sessListMeta = mkMeta(opts, {
2246
+ GET: { description: "List all active sessions for the authenticated user.", response: `{ sessions: [] }` }
2247
+ });
2248
+ const sessListContent = ts ? `${RA}import type { Request, Response } from 'express';
1865
2249
  ${sessListMeta}${mwUser3}
1866
2250
  export const GET = async (req: Request, res: Response) => {
1867
2251
  // TODO: fetch sessions for req.user.id
1868
2252
  res.json({ sessions: [] });
1869
2253
  };
1870
- ` : `${RA}${rr3}${sessListMeta}${mwUser3}
2254
+ ` : `${RA}${sessListMeta}${mwUser3}
1871
2255
  export const GET = async (req, res) => {
1872
2256
  // TODO: fetch sessions for req.user.id
1873
2257
  res.json({ sessions: [] });
1874
2258
  };
1875
2259
  `;
1876
2260
  await fs.outputFile(path.join(dest, "src", "api", "auth", "sessions", `index.${ext}`), sessListContent);
1877
- const sessRevokeContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
1878
- ${mwUser3}
2261
+ const sessRevokeMeta = mkMeta(opts, {
2262
+ DELETE: { description: "Revoke a single active session by ID.", params: `{ id: 'sess_01HXZ' }`, response: `{ message: 'Session sess_01HXZ revoked' }` }
2263
+ });
2264
+ const sessRevokeContent = ts ? `${RA}import type { Request, Response } from 'express';
2265
+ ${sessRevokeMeta}${mwUser3}
1879
2266
  export const DELETE = async (req: Request, res: Response) => {
1880
2267
  const { id } = req.params;
1881
2268
  // TODO: revoke session by id
1882
2269
  res.json({ message: \`Session \${id} revoked\` });
1883
2270
  };
1884
- ` : `${RA}${rr3}${mwUser3}
2271
+ ` : `${RA}${sessRevokeMeta}${mwUser3}
1885
2272
  export const DELETE = async (req, res) => {
1886
2273
  const { id } = req.params;
1887
2274
  // TODO: revoke session by id
@@ -1893,19 +2280,18 @@ export const DELETE = async (req, res) => {
1893
2280
  async function writeUserExtendedRoutes(dest, opts) {
1894
2281
  const ext = opts.language === "typescript" ? "ts" : "js";
1895
2282
  const ts = opts.language === "typescript";
1896
- const rr2 = opts.rbac ? `import { requireRole } from '../../middleware/requireRole.js';
1897
- ` : "";
1898
- const rr3 = opts.rbac ? `import { requireRole } from '../../../middleware/requireRole.js';
1899
- ` : "";
1900
- const mw2 = opts.rbac ? `export const middlewares = [requireAuth, requireRole('user', 'admin')];
2283
+ const mw2 = opts.rbac ? `export const middlewares = [requireAuth('user', 'admin')];
1901
2284
  ` : `export const middlewares = [requireAuth];
1902
2285
  `;
1903
2286
  const mw3 = mw2;
1904
2287
  const RA = `import { requireAuth } from 'express-file-cluster/auth';
1905
2288
  `;
1906
2289
  const user = ts ? `(req as any).user` : `req.user`;
1907
- const avatarMeta = mkMeta(opts, "Upload (POST) or remove (DELETE) the authenticated user avatar.", `{ message: 'Avatar updated', url: 'https://...' }`);
1908
- const avatarContent = ts ? `${RA}${rr2}import type { Request, Response } from 'express';
2290
+ const avatarMeta = mkMeta(opts, {
2291
+ POST: { description: "Upload a new avatar image for the authenticated user.", response: `{ message: 'Avatar updated', url: 'https://example.com/avatar.jpg' }` },
2292
+ DELETE: { description: "Remove the authenticated user's avatar.", response: `{ message: 'Avatar removed' }` }
2293
+ });
2294
+ const avatarContent = ts ? `${RA}import type { Request, Response } from 'express';
1909
2295
  ${avatarMeta}${mw2}
1910
2296
  export const POST = async (req: Request, res: Response) => {
1911
2297
  // TODO: handle multipart upload, store file, update user.avatar
@@ -1916,7 +2302,7 @@ export const DELETE = async (req: Request, res: Response) => {
1916
2302
  // TODO: remove avatar and clear user.avatar
1917
2303
  res.json({ message: 'Avatar removed' });
1918
2304
  };
1919
- ` : `${RA}${rr2}${avatarMeta}${mw2}
2305
+ ` : `${RA}${avatarMeta}${mw2}
1920
2306
  export const POST = async (req, res) => {
1921
2307
  // TODO: handle multipart upload, store file, update user.avatar
1922
2308
  res.json({ message: 'Avatar updated', url: 'https://example.com/avatar.jpg' });
@@ -1928,8 +2314,11 @@ export const DELETE = async (req, res) => {
1928
2314
  };
1929
2315
  `;
1930
2316
  await fs.outputFile(path.join(dest, "src", "api", "user", `avatar.${ext}`), avatarContent);
1931
- const settingsMeta = mkMeta(opts, "Get or update account settings (notifications, language, theme, privacy).", `{ settings: { notifications: true, language: 'en', theme: 'system' } }`);
1932
- const settingsContent = ts ? `${RA}${rr2}import type { Request, Response } from 'express';
2317
+ const settingsMeta = mkMeta(opts, {
2318
+ GET: { description: "Get account settings (notifications, language, theme, privacy) for the authenticated user.", response: `{ settings: { notifications: true, language: 'en', theme: 'system', privacy: 'public' } }` },
2319
+ PUT: { description: "Update account settings for the authenticated user.", request: `{ notifications: true, language: 'en', theme: 'system', privacy: 'public' }`, response: `{ message: 'Settings updated', settings: { notifications: true, language: 'en', theme: 'system', privacy: 'public' } }` }
2320
+ });
2321
+ const settingsContent = ts ? `${RA}import type { Request, Response } from 'express';
1933
2322
  ${settingsMeta}${mw2}
1934
2323
  export const GET = async (req: Request, res: Response) => {
1935
2324
  // TODO: fetch user settings from DB
@@ -1941,7 +2330,7 @@ export const PUT = async (req: Request, res: Response) => {
1941
2330
  // TODO: update user settings in DB
1942
2331
  res.json({ message: 'Settings updated', settings: { notifications, language, theme, privacy } });
1943
2332
  };
1944
- ` : `${RA}${rr2}${settingsMeta}${mw2}
2333
+ ` : `${RA}${settingsMeta}${mw2}
1945
2334
  export const GET = async (req, res) => {
1946
2335
  // TODO: fetch user settings from DB
1947
2336
  res.json({ settings: { notifications: true, language: 'en', theme: 'system', privacy: 'public' } });
@@ -1954,8 +2343,11 @@ export const PUT = async (req, res) => {
1954
2343
  };
1955
2344
  `;
1956
2345
  await fs.outputFile(path.join(dest, "src", "api", "user", `settings.${ext}`), settingsContent);
1957
- const accountMeta = mkMeta(opts, "Delete account (DELETE) or download personal data (GET).", `{ message: 'Account deleted' }`);
1958
- const accountContent = ts ? `${RA}${rr2}import type { Request, Response } from 'express';
2346
+ const accountMeta = mkMeta(opts, {
2347
+ GET: { description: "Download a personal data export for the authenticated user.", response: `{ data: { user: { id: '1', role: 'user', email: 'user@example.com' }, exportedAt: '2026-01-01T00:00:00.000Z' } }` },
2348
+ DELETE: { description: "Schedule the authenticated account for deletion (requires password confirmation).", request: `{ password: 'current' }`, response: `{ message: 'Account scheduled for deletion' }` }
2349
+ });
2350
+ const accountContent = ts ? `${RA}import type { Request, Response } from 'express';
1959
2351
  ${accountMeta}${mw2}
1960
2352
  export const GET = async (req: Request, res: Response) => {
1961
2353
  // TODO: compile and return personal data export
@@ -1968,7 +2360,7 @@ export const DELETE = async (req: Request, res: Response) => {
1968
2360
  // TODO: verify password, schedule account deletion
1969
2361
  res.json({ message: 'Account scheduled for deletion' });
1970
2362
  };
1971
- ` : `${RA}${rr2}${accountMeta}${mw2}
2363
+ ` : `${RA}${accountMeta}${mw2}
1972
2364
  export const GET = async (req, res) => {
1973
2365
  // TODO: compile and return personal data export
1974
2366
  res.json({ data: { user: ${user}, exportedAt: new Date().toISOString() } });
@@ -1982,29 +2374,33 @@ export const DELETE = async (req, res) => {
1982
2374
  };
1983
2375
  `;
1984
2376
  await fs.outputFile(path.join(dest, "src", "api", "user", `account.${ext}`), accountContent);
1985
- const dashMeta = mkMeta(opts, "Personal dashboard: stats, recent activity, and quick actions.", `{ stats: {}, recentActivity: [], quickActions: [] }`);
1986
- const dashContent = ts ? `${RA}${rr2}import type { Request, Response } from 'express';
2377
+ const dashMeta = mkMeta(opts, {
2378
+ GET: { description: "Personal dashboard: stats, recent activity, and quick actions.", response: `{ stats: {}, recentActivity: [], quickActions: [] }` }
2379
+ });
2380
+ const dashContent = ts ? `${RA}import type { Request, Response } from 'express';
1987
2381
  ${dashMeta}${mw2}
1988
2382
  export const GET = async (req: Request, res: Response) => {
1989
2383
  // TODO: aggregate personal stats and activity
1990
2384
  res.json({ stats: {}, recentActivity: [], quickActions: [] });
1991
2385
  };
1992
- ` : `${RA}${rr2}${dashMeta}${mw2}
2386
+ ` : `${RA}${dashMeta}${mw2}
1993
2387
  export const GET = async (req, res) => {
1994
2388
  // TODO: aggregate personal stats and activity
1995
2389
  res.json({ stats: {}, recentActivity: [], quickActions: [] });
1996
2390
  };
1997
2391
  `;
1998
2392
  await fs.outputFile(path.join(dest, "src", "api", "user", `dashboard.${ext}`), dashContent);
1999
- const actMeta = mkMeta(opts, "Paginated activity history for the authenticated user.", `{ activities: [], total: 0, page: 1, limit: 20 }`);
2000
- const actContent = ts ? `${RA}${rr2}import type { Request, Response } from 'express';
2393
+ const actMeta = mkMeta(opts, {
2394
+ GET: { description: "Paginated activity history for the authenticated user.", query: `{ page: '1', limit: '20' }`, response: `{ activities: [], total: 0, page: 1, limit: 20 }` }
2395
+ });
2396
+ const actContent = ts ? `${RA}import type { Request, Response } from 'express';
2001
2397
  ${actMeta}${mw2}
2002
2398
  export const GET = async (req: Request, res: Response) => {
2003
2399
  const { page = 1, limit = 20 } = req.query;
2004
2400
  // TODO: fetch paginated activity log
2005
2401
  res.json({ activities: [], total: 0, page: Number(page), limit: Number(limit) });
2006
2402
  };
2007
- ` : `${RA}${rr2}${actMeta}${mw2}
2403
+ ` : `${RA}${actMeta}${mw2}
2008
2404
  export const GET = async (req, res) => {
2009
2405
  const { page = 1, limit = 20 } = req.query;
2010
2406
  // TODO: fetch paginated activity log
@@ -2012,8 +2408,11 @@ export const GET = async (req, res) => {
2012
2408
  };
2013
2409
  `;
2014
2410
  await fs.outputFile(path.join(dest, "src", "api", "user", `activity.${ext}`), actContent);
2015
- const notifListMeta = mkMeta(opts, "List notifications (GET) or mark all as read (POST).", `{ notifications: [], total: 0, unread: 0 }`);
2016
- const notifListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2411
+ const notifListMeta = mkMeta(opts, {
2412
+ GET: { description: "List notifications for the authenticated user.", response: `{ notifications: [], total: 0, unread: 0 }` },
2413
+ POST: { description: "Mark all notifications as read for the authenticated user.", response: `{ message: 'All notifications marked as read' }` }
2414
+ });
2415
+ const notifListContent = ts ? `${RA}import type { Request, Response } from 'express';
2017
2416
  ${notifListMeta}${mw3}
2018
2417
  export const GET = async (req: Request, res: Response) => {
2019
2418
  // TODO: fetch notifications for user
@@ -2024,7 +2423,7 @@ export const POST = async (req: Request, res: Response) => {
2024
2423
  // TODO: mark all notifications as read
2025
2424
  res.json({ message: 'All notifications marked as read' });
2026
2425
  };
2027
- ` : `${RA}${rr3}${notifListMeta}${mw3}
2426
+ ` : `${RA}${notifListMeta}${mw3}
2028
2427
  export const GET = async (req, res) => {
2029
2428
  // TODO: fetch notifications for user
2030
2429
  res.json({ notifications: [], total: 0, unread: 0 });
@@ -2036,8 +2435,13 @@ export const POST = async (req, res) => {
2036
2435
  };
2037
2436
  `;
2038
2437
  await fs.outputFile(path.join(dest, "src", "api", "user", "notifications", `index.${ext}`), notifListContent);
2039
- const notifByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2040
- ${mw3}
2438
+ const notifByIdMeta = mkMeta(opts, {
2439
+ GET: { description: "Fetch a single notification by ID.", params: `{ id: 'notif_01HXZ' }`, response: `{ notification: { id: 'notif_01HXZ' } }` },
2440
+ PATCH: { description: "Mark a single notification as read.", params: `{ id: 'notif_01HXZ' }`, response: `{ message: 'Notification marked as read', id: 'notif_01HXZ' }` },
2441
+ DELETE: { description: "Delete a single notification.", params: `{ id: 'notif_01HXZ' }`, response: `{ message: 'Notification notif_01HXZ deleted' }` }
2442
+ });
2443
+ const notifByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2444
+ ${notifByIdMeta}${mw3}
2041
2445
  export const GET = async (req: Request, res: Response) => {
2042
2446
  const { id } = req.params;
2043
2447
  // TODO: fetch notification by id
@@ -2055,7 +2459,7 @@ export const DELETE = async (req: Request, res: Response) => {
2055
2459
  // TODO: delete notification
2056
2460
  res.json({ message: \`Notification \${id} deleted\` });
2057
2461
  };
2058
- ` : `${RA}${rr3}${mw3}
2462
+ ` : `${RA}${notifByIdMeta}${mw3}
2059
2463
  export const GET = async (req, res) => {
2060
2464
  const { id } = req.params;
2061
2465
  // TODO: fetch notification by id
@@ -2075,8 +2479,11 @@ export const DELETE = async (req, res) => {
2075
2479
  };
2076
2480
  `;
2077
2481
  await fs.outputFile(path.join(dest, "src", "api", "user", "notifications", `[id].${ext}`), notifByIdContent);
2078
- const filesListMeta = mkMeta(opts, "List uploaded files with storage usage (GET) or upload a new file (POST).", `{ files: [], total: 0, storageUsed: 0 }`);
2079
- const filesListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2482
+ const filesListMeta = mkMeta(opts, {
2483
+ GET: { description: "List uploaded files with storage usage for the authenticated user.", response: `{ files: [], total: 0, storageUsed: 0 }` },
2484
+ POST: { description: "Upload a new file for the authenticated user.", response: `{ message: 'File uploaded', file: { id: 'new-id' } }`, status: 201 }
2485
+ });
2486
+ const filesListContent = ts ? `${RA}import type { Request, Response } from 'express';
2080
2487
  ${filesListMeta}${mw3}
2081
2488
  export const GET = async (req: Request, res: Response) => {
2082
2489
  // TODO: fetch user files and compute storage usage
@@ -2087,7 +2494,7 @@ export const POST = async (req: Request, res: Response) => {
2087
2494
  // TODO: handle multipart upload, persist file record
2088
2495
  res.status(201).json({ message: 'File uploaded', file: { id: 'new-id' } });
2089
2496
  };
2090
- ` : `${RA}${rr3}${filesListMeta}${mw3}
2497
+ ` : `${RA}${filesListMeta}${mw3}
2091
2498
  export const GET = async (req, res) => {
2092
2499
  // TODO: fetch user files and compute storage usage
2093
2500
  res.json({ files: [], total: 0, storageUsed: 0 });
@@ -2099,8 +2506,12 @@ export const POST = async (req, res) => {
2099
2506
  };
2100
2507
  `;
2101
2508
  await fs.outputFile(path.join(dest, "src", "api", "user", "files", `index.${ext}`), filesListContent);
2102
- const fileByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2103
- ${mw3}
2509
+ const fileByIdMeta = mkMeta(opts, {
2510
+ GET: { description: "Get a download/preview URL for a single file by ID.", params: `{ id: 'file_01HXZ' }`, response: `{ file: { id: 'file_01HXZ', url: 'https://example.com/files/file_01HXZ' } }` },
2511
+ DELETE: { description: "Delete a file from storage.", params: `{ id: 'file_01HXZ' }`, response: `{ message: 'File file_01HXZ deleted' }` }
2512
+ });
2513
+ const fileByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2514
+ ${fileByIdMeta}${mw3}
2104
2515
  export const GET = async (req: Request, res: Response) => {
2105
2516
  const { id } = req.params;
2106
2517
  // TODO: stream or redirect to file download/preview URL
@@ -2112,7 +2523,7 @@ export const DELETE = async (req: Request, res: Response) => {
2112
2523
  // TODO: delete file from storage and DB
2113
2524
  res.json({ message: \`File \${id} deleted\` });
2114
2525
  };
2115
- ` : `${RA}${rr3}${mw3}
2526
+ ` : `${RA}${fileByIdMeta}${mw3}
2116
2527
  export const GET = async (req, res) => {
2117
2528
  const { id } = req.params;
2118
2529
  // TODO: stream or redirect to file download/preview URL
@@ -2126,8 +2537,12 @@ export const DELETE = async (req, res) => {
2126
2537
  };
2127
2538
  `;
2128
2539
  await fs.outputFile(path.join(dest, "src", "api", "user", "files", `[id].${ext}`), fileByIdContent);
2129
- const favListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2130
- ${mw3}
2540
+ const favListMeta = mkMeta(opts, {
2541
+ GET: { description: "List favorites for the authenticated user.", response: `{ favorites: [] }` },
2542
+ POST: { description: "Add an entity to the authenticated user's favorites.", request: `{ entityId: 'post_01HXZ', entityType: 'post' }`, response: `{ message: 'Added to favorites' }`, status: 201 }
2543
+ });
2544
+ const favListContent = ts ? `${RA}import type { Request, Response } from 'express';
2545
+ ${favListMeta}${mw3}
2131
2546
  export const GET = async (req: Request, res: Response) => {
2132
2547
  // TODO: fetch user favorites
2133
2548
  res.json({ favorites: [] });
@@ -2139,7 +2554,7 @@ export const POST = async (req: Request, res: Response) => {
2139
2554
  // TODO: add to favorites
2140
2555
  res.status(201).json({ message: 'Added to favorites' });
2141
2556
  };
2142
- ` : `${RA}${rr3}${mw3}
2557
+ ` : `${RA}${favListMeta}${mw3}
2143
2558
  export const GET = async (req, res) => {
2144
2559
  // TODO: fetch user favorites
2145
2560
  res.json({ favorites: [] });
@@ -2153,14 +2568,17 @@ export const POST = async (req, res) => {
2153
2568
  };
2154
2569
  `;
2155
2570
  await fs.outputFile(path.join(dest, "src", "api", "user", "favorites", `index.${ext}`), favListContent);
2156
- const favByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2157
- ${mw3}
2571
+ const favByIdMeta = mkMeta(opts, {
2572
+ DELETE: { description: "Remove an entity from the authenticated user favorites.", params: `{ id: 'fav_01HXZ' }`, response: `{ message: 'Removed favorite fav_01HXZ' }` }
2573
+ });
2574
+ const favByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2575
+ ${favByIdMeta}${mw3}
2158
2576
  export const DELETE = async (req: Request, res: Response) => {
2159
2577
  const { id } = req.params;
2160
2578
  // TODO: remove from favorites
2161
2579
  res.json({ message: \`Removed favorite \${id}\` });
2162
2580
  };
2163
- ` : `${RA}${rr3}${mw3}
2581
+ ` : `${RA}${favByIdMeta}${mw3}
2164
2582
  export const DELETE = async (req, res) => {
2165
2583
  const { id } = req.params;
2166
2584
  // TODO: remove from favorites
@@ -2168,8 +2586,12 @@ export const DELETE = async (req, res) => {
2168
2586
  };
2169
2587
  `;
2170
2588
  await fs.outputFile(path.join(dest, "src", "api", "user", "favorites", `[id].${ext}`), favByIdContent);
2171
- const bkListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2172
- ${mw3}
2589
+ const bkListMeta = mkMeta(opts, {
2590
+ GET: { description: "List bookmarks for the authenticated user.", response: `{ bookmarks: [] }` },
2591
+ POST: { description: "Save a new bookmark for the authenticated user.", request: `{ url: 'https://example.com', title: 'Example' }`, response: `{ message: 'Bookmark saved' }`, status: 201 }
2592
+ });
2593
+ const bkListContent = ts ? `${RA}import type { Request, Response } from 'express';
2594
+ ${bkListMeta}${mw3}
2173
2595
  export const GET = async (req: Request, res: Response) => {
2174
2596
  // TODO: fetch user bookmarks
2175
2597
  res.json({ bookmarks: [] });
@@ -2181,7 +2603,7 @@ export const POST = async (req: Request, res: Response) => {
2181
2603
  // TODO: save bookmark
2182
2604
  res.status(201).json({ message: 'Bookmark saved' });
2183
2605
  };
2184
- ` : `${RA}${rr3}${mw3}
2606
+ ` : `${RA}${bkListMeta}${mw3}
2185
2607
  export const GET = async (req, res) => {
2186
2608
  // TODO: fetch user bookmarks
2187
2609
  res.json({ bookmarks: [] });
@@ -2195,14 +2617,17 @@ export const POST = async (req, res) => {
2195
2617
  };
2196
2618
  `;
2197
2619
  await fs.outputFile(path.join(dest, "src", "api", "user", "bookmarks", `index.${ext}`), bkListContent);
2198
- const bkByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2199
- ${mw3}
2620
+ const bkByIdMeta = mkMeta(opts, {
2621
+ DELETE: { description: "Delete a bookmark by ID.", params: `{ id: 'bm_01HXZ' }`, response: `{ message: 'Bookmark bm_01HXZ removed' }` }
2622
+ });
2623
+ const bkByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2624
+ ${bkByIdMeta}${mw3}
2200
2625
  export const DELETE = async (req: Request, res: Response) => {
2201
2626
  const { id } = req.params;
2202
2627
  // TODO: delete bookmark
2203
2628
  res.json({ message: \`Bookmark \${id} removed\` });
2204
2629
  };
2205
- ` : `${RA}${rr3}${mw3}
2630
+ ` : `${RA}${bkByIdMeta}${mw3}
2206
2631
  export const DELETE = async (req, res) => {
2207
2632
  const { id } = req.params;
2208
2633
  // TODO: delete bookmark
@@ -2210,15 +2635,17 @@ export const DELETE = async (req, res) => {
2210
2635
  };
2211
2636
  `;
2212
2637
  await fs.outputFile(path.join(dest, "src", "api", "user", "bookmarks", `[id].${ext}`), bkByIdContent);
2213
- const searchMeta = mkMeta(opts, "Search with filters, sort, and pagination.", `{ results: [], total: 0, page: 1, limit: 20 }`);
2214
- const searchContent = ts ? `${RA}${rr2}import type { Request, Response } from 'express';
2638
+ const searchMeta = mkMeta(opts, {
2639
+ GET: { description: "Search with filters, sort, and pagination.", query: `{ q: 'query', filter: 'active', sort: 'newest', page: '1', limit: '20' }`, response: `{ results: [], total: 0, page: 1, limit: 20 }` }
2640
+ });
2641
+ const searchContent = ts ? `${RA}import type { Request, Response } from 'express';
2215
2642
  ${searchMeta}${mw2}
2216
2643
  export const GET = async (req: Request, res: Response) => {
2217
2644
  const { q, filter, sort, page = 1, limit = 20 } = req.query;
2218
2645
  // TODO: implement search
2219
2646
  res.json({ results: [], total: 0, page: Number(page), limit: Number(limit) });
2220
2647
  };
2221
- ` : `${RA}${rr2}${searchMeta}${mw2}
2648
+ ` : `${RA}${searchMeta}${mw2}
2222
2649
  export const GET = async (req, res) => {
2223
2650
  const { q, filter, sort, page = 1, limit = 20 } = req.query;
2224
2651
  // TODO: implement search
@@ -2226,8 +2653,11 @@ export const GET = async (req, res) => {
2226
2653
  };
2227
2654
  `;
2228
2655
  await fs.outputFile(path.join(dest, "src", "api", "user", `search.${ext}`), searchContent);
2229
- const akListMeta = mkMeta(opts, "List API keys (GET) or create a new one (POST).", `{ apiKeys: [] }`);
2230
- const akListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2656
+ const akListMeta = mkMeta(opts, {
2657
+ GET: { description: "List API keys for the authenticated user.", response: `{ apiKeys: [] }` },
2658
+ POST: { description: "Generate a new API key for the authenticated user.", request: `{ name: 'CI key' }`, response: `{ message: 'API key created', key: 'efc_...' }`, status: 201 }
2659
+ });
2660
+ const akListContent = ts ? `${RA}import type { Request, Response } from 'express';
2231
2661
  ${akListMeta}${mw3}
2232
2662
  export const GET = async (req: Request, res: Response) => {
2233
2663
  // TODO: fetch api keys for user
@@ -2240,7 +2670,7 @@ export const POST = async (req: Request, res: Response) => {
2240
2670
  // TODO: generate and store API key
2241
2671
  res.status(201).json({ message: 'API key created', key: 'efc_...' });
2242
2672
  };
2243
- ` : `${RA}${rr3}${akListMeta}${mw3}
2673
+ ` : `${RA}${akListMeta}${mw3}
2244
2674
  export const GET = async (req, res) => {
2245
2675
  // TODO: fetch api keys for user
2246
2676
  res.json({ apiKeys: [] });
@@ -2254,14 +2684,17 @@ export const POST = async (req, res) => {
2254
2684
  };
2255
2685
  `;
2256
2686
  await fs.outputFile(path.join(dest, "src", "api", "user", "api-keys", `index.${ext}`), akListContent);
2257
- const akByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2258
- ${mw3}
2687
+ const akByIdMeta = mkMeta(opts, {
2688
+ DELETE: { description: "Revoke and delete an API key by ID.", params: `{ id: 'key_01HXZ' }`, response: `{ message: 'API key key_01HXZ revoked' }` }
2689
+ });
2690
+ const akByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2691
+ ${akByIdMeta}${mw3}
2259
2692
  export const DELETE = async (req: Request, res: Response) => {
2260
2693
  const { id } = req.params;
2261
2694
  // TODO: revoke and delete API key
2262
2695
  res.json({ message: \`API key \${id} revoked\` });
2263
2696
  };
2264
- ` : `${RA}${rr3}${mw3}
2697
+ ` : `${RA}${akByIdMeta}${mw3}
2265
2698
  export const DELETE = async (req, res) => {
2266
2699
  const { id } = req.params;
2267
2700
  // TODO: revoke and delete API key
@@ -2273,32 +2706,34 @@ export const DELETE = async (req, res) => {
2273
2706
  async function writeUserBillingRoutes(dest, opts) {
2274
2707
  const ext = opts.language === "typescript" ? "ts" : "js";
2275
2708
  const ts = opts.language === "typescript";
2276
- const rr3 = opts.rbac ? `import { requireRole } from '../../../middleware/requireRole.js';
2277
- ` : "";
2278
- const rr4 = opts.rbac ? `import { requireRole } from '../../../../middleware/requireRole.js';
2279
- ` : "";
2280
- const mw3 = opts.rbac ? `export const middlewares = [requireAuth, requireRole('user', 'admin')];
2709
+ const mw3 = opts.rbac ? `export const middlewares = [requireAuth('user', 'admin')];
2281
2710
  ` : `export const middlewares = [requireAuth];
2282
2711
  `;
2283
2712
  const mw4 = mw3;
2284
2713
  const RA = `import { requireAuth } from 'express-file-cluster/auth';
2285
2714
  `;
2286
- const plansMeta = mkMeta(opts, "List all available subscription plans.", `{ plans: [] }`);
2287
- const plansContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2715
+ const plansMeta = mkMeta(opts, {
2716
+ GET: { description: "List all available subscription plans.", response: `{ plans: [] }` }
2717
+ });
2718
+ const plansContent = ts ? `${RA}import type { Request, Response } from 'express';
2288
2719
  ${plansMeta}${mw3}
2289
2720
  export const GET = async (_req: Request, res: Response) => {
2290
2721
  // TODO: fetch active plans from DB
2291
2722
  res.json({ plans: [] });
2292
2723
  };
2293
- ` : `${RA}${rr3}${plansMeta}${mw3}
2724
+ ` : `${RA}${plansMeta}${mw3}
2294
2725
  export const GET = async (_req, res) => {
2295
2726
  // TODO: fetch active plans from DB
2296
2727
  res.json({ plans: [] });
2297
2728
  };
2298
2729
  `;
2299
2730
  await fs.outputFile(path.join(dest, "src", "api", "user", "billing", `plans.${ext}`), plansContent);
2300
- const subMeta = mkMeta(opts, "Get current subscription (GET), subscribe to a plan (POST), or cancel (DELETE).", `{ subscription: null }`);
2301
- const subContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2731
+ const subMeta = mkMeta(opts, {
2732
+ GET: { description: "Get the current subscription for the authenticated user.", response: `{ subscription: null }` },
2733
+ POST: { description: "Subscribe the authenticated user to a plan.", request: `{ planId: 'plan_01HXZ' }`, response: `{ message: 'Subscribed', subscription: { planId: 'plan_01HXZ' } }`, status: 201 },
2734
+ DELETE: { description: "Cancel the current subscription at period end.", response: `{ message: 'Subscription cancelled' }` }
2735
+ });
2736
+ const subContent = ts ? `${RA}import type { Request, Response } from 'express';
2302
2737
  ${subMeta}${mw3}
2303
2738
  export const GET = async (req: Request, res: Response) => {
2304
2739
  // TODO: fetch current subscription for user
@@ -2316,7 +2751,7 @@ export const DELETE = async (req: Request, res: Response) => {
2316
2751
  // TODO: cancel subscription at period end
2317
2752
  res.json({ message: 'Subscription cancelled' });
2318
2753
  };
2319
- ` : `${RA}${rr3}${subMeta}${mw3}
2754
+ ` : `${RA}${subMeta}${mw3}
2320
2755
  export const GET = async (req, res) => {
2321
2756
  // TODO: fetch current subscription for user
2322
2757
  res.json({ subscription: null });
@@ -2335,8 +2770,12 @@ export const DELETE = async (req, res) => {
2335
2770
  };
2336
2771
  `;
2337
2772
  await fs.outputFile(path.join(dest, "src", "api", "user", "billing", `subscription.${ext}`), subContent);
2338
- const pmListContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2339
- ${mw4}
2773
+ const pmListMeta = mkMeta(opts, {
2774
+ GET: { description: "List payment methods for the authenticated user.", response: `{ paymentMethods: [] }` },
2775
+ POST: { description: "Attach a new payment method via the payment gateway.", request: `{ token: 'tok_...' }`, response: `{ message: 'Payment method added' }`, status: 201 }
2776
+ });
2777
+ const pmListContent = ts ? `${RA}import type { Request, Response } from 'express';
2778
+ ${pmListMeta}${mw4}
2340
2779
  export const GET = async (req: Request, res: Response) => {
2341
2780
  // TODO: fetch payment methods for user
2342
2781
  res.json({ paymentMethods: [] });
@@ -2348,7 +2787,7 @@ export const POST = async (req: Request, res: Response) => {
2348
2787
  // TODO: attach payment method via payment gateway
2349
2788
  res.status(201).json({ message: 'Payment method added' });
2350
2789
  };
2351
- ` : `${RA}${rr4}${mw4}
2790
+ ` : `${RA}${pmListMeta}${mw4}
2352
2791
  export const GET = async (req, res) => {
2353
2792
  // TODO: fetch payment methods for user
2354
2793
  res.json({ paymentMethods: [] });
@@ -2362,14 +2801,17 @@ export const POST = async (req, res) => {
2362
2801
  };
2363
2802
  `;
2364
2803
  await fs.outputFile(path.join(dest, "src", "api", "user", "billing", "payment-methods", `index.${ext}`), pmListContent);
2365
- const pmByIdContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2366
- ${mw4}
2804
+ const pmByIdMeta = mkMeta(opts, {
2805
+ DELETE: { description: "Detach a payment method from the payment gateway.", params: `{ id: 'pm_01HXZ' }`, response: `{ message: 'Payment method pm_01HXZ removed' }` }
2806
+ });
2807
+ const pmByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2808
+ ${pmByIdMeta}${mw4}
2367
2809
  export const DELETE = async (req: Request, res: Response) => {
2368
2810
  const { id } = req.params;
2369
2811
  // TODO: detach payment method from payment gateway
2370
2812
  res.json({ message: \`Payment method \${id} removed\` });
2371
2813
  };
2372
- ` : `${RA}${rr4}${mw4}
2814
+ ` : `${RA}${pmByIdMeta}${mw4}
2373
2815
  export const DELETE = async (req, res) => {
2374
2816
  const { id } = req.params;
2375
2817
  // TODO: detach payment method from payment gateway
@@ -2377,28 +2819,33 @@ export const DELETE = async (req, res) => {
2377
2819
  };
2378
2820
  `;
2379
2821
  await fs.outputFile(path.join(dest, "src", "api", "user", "billing", "payment-methods", `[id].${ext}`), pmByIdContent);
2380
- const invListMeta = mkMeta(opts, "List all invoices for the authenticated user.", `{ invoices: [], total: 0 }`);
2381
- const invListContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2822
+ const invListMeta = mkMeta(opts, {
2823
+ GET: { description: "List all invoices for the authenticated user.", response: `{ invoices: [], total: 0 }` }
2824
+ });
2825
+ const invListContent = ts ? `${RA}import type { Request, Response } from 'express';
2382
2826
  ${invListMeta}${mw4}
2383
2827
  export const GET = async (req: Request, res: Response) => {
2384
2828
  // TODO: fetch invoices for user
2385
2829
  res.json({ invoices: [], total: 0 });
2386
2830
  };
2387
- ` : `${RA}${rr4}${invListMeta}${mw4}
2831
+ ` : `${RA}${invListMeta}${mw4}
2388
2832
  export const GET = async (req, res) => {
2389
2833
  // TODO: fetch invoices for user
2390
2834
  res.json({ invoices: [], total: 0 });
2391
2835
  };
2392
2836
  `;
2393
2837
  await fs.outputFile(path.join(dest, "src", "api", "user", "billing", "invoices", `index.${ext}`), invListContent);
2394
- const invByIdContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2395
- ${mw4}
2838
+ const invByIdMeta = mkMeta(opts, {
2839
+ GET: { description: "Get the download URL for a single invoice by ID.", params: `{ id: 'inv_01HXZ' }`, response: `{ invoice: { id: 'inv_01HXZ', downloadUrl: 'https://example.com/invoices/inv_01HXZ.pdf' } }` }
2840
+ });
2841
+ const invByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2842
+ ${invByIdMeta}${mw4}
2396
2843
  export const GET = async (req: Request, res: Response) => {
2397
2844
  const { id } = req.params;
2398
2845
  // TODO: return invoice PDF URL or inline data
2399
2846
  res.json({ invoice: { id, downloadUrl: 'https://...' } });
2400
2847
  };
2401
- ` : `${RA}${rr4}${mw4}
2848
+ ` : `${RA}${invByIdMeta}${mw4}
2402
2849
  export const GET = async (req, res) => {
2403
2850
  const { id } = req.params;
2404
2851
  // TODO: return invoice PDF URL or inline data
@@ -2410,15 +2857,16 @@ export const GET = async (req, res) => {
2410
2857
  async function writeSupportRoutes(dest, opts) {
2411
2858
  const ext = opts.language === "typescript" ? "ts" : "js";
2412
2859
  const ts = opts.language === "typescript";
2413
- const rr3 = opts.rbac ? `import { requireRole } from '../../../middleware/requireRole.js';
2414
- ` : "";
2415
- const mw3 = opts.rbac ? `export const middlewares = [requireAuth, requireRole('user', 'admin')];
2860
+ const mw3 = opts.rbac ? `export const middlewares = [requireAuth('user', 'admin')];
2416
2861
  ` : `export const middlewares = [requireAuth];
2417
2862
  `;
2418
2863
  const RA = `import { requireAuth } from 'express-file-cluster/auth';
2419
2864
  `;
2420
- const ticketListMeta = mkMeta(opts, "List own support tickets (GET) or create a new one (POST).", `{ tickets: [], total: 0 }`, `{ subject: 'Issue with login', message: 'I cannot log in', priority: 'normal' }`);
2421
- const ticketListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2865
+ const ticketListMeta = mkMeta(opts, {
2866
+ GET: { description: "List the authenticated user's own support tickets.", response: `{ tickets: [], total: 0 }` },
2867
+ POST: { description: "Create a new support ticket.", request: `{ subject: 'Issue with login', message: 'I cannot log in', priority: 'normal' }`, response: `{ message: 'Ticket created', ticket: { id: 'new-id', subject: 'Issue with login', status: 'open' } }`, status: 201 }
2868
+ });
2869
+ const ticketListContent = ts ? `${RA}import type { Request, Response } from 'express';
2422
2870
  ${ticketListMeta}${mw3}
2423
2871
  export const GET = async (req: Request, res: Response) => {
2424
2872
  // TODO: fetch tickets for current user
@@ -2431,7 +2879,7 @@ export const POST = async (req: Request, res: Response) => {
2431
2879
  // TODO: create support ticket in DB
2432
2880
  res.status(201).json({ message: 'Ticket created', ticket: { id: 'new-id', subject, status: 'open' } });
2433
2881
  };
2434
- ` : `${RA}${rr3}${ticketListMeta}${mw3}
2882
+ ` : `${RA}${ticketListMeta}${mw3}
2435
2883
  export const GET = async (req, res) => {
2436
2884
  // TODO: fetch tickets for current user
2437
2885
  res.json({ tickets: [], total: 0 });
@@ -2445,8 +2893,11 @@ export const POST = async (req, res) => {
2445
2893
  };
2446
2894
  `;
2447
2895
  await fs.outputFile(path.join(dest, "src", "api", "support", "tickets", `index.${ext}`), ticketListContent);
2448
- const ticketByIdMeta = mkMeta(opts, "View a support ticket (GET) or add a reply / close it (PUT).", `{ ticket: { id: '1', subject: 'Issue', status: 'open', replies: [] } }`);
2449
- const ticketByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2896
+ const ticketByIdMeta = mkMeta(opts, {
2897
+ GET: { description: "Fetch a single support ticket by ID.", params: `{ id: 'tk_01HXZ' }`, response: `{ ticket: { id: 'tk_01HXZ', subject: 'Issue', status: 'open', replies: [] } }` },
2898
+ PUT: { description: "Add a reply to a support ticket or update its status.", params: `{ id: 'tk_01HXZ' }`, request: `{ reply: 'Thanks, resolved.', status: 'closed' }`, response: `{ message: 'Ticket updated', ticket: { id: 'tk_01HXZ', status: 'closed' } }` }
2899
+ });
2900
+ const ticketByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2450
2901
  ${ticketByIdMeta}${mw3}
2451
2902
  export const GET = async (req: Request, res: Response) => {
2452
2903
  const { id } = req.params;
@@ -2460,7 +2911,7 @@ export const PUT = async (req: Request, res: Response) => {
2460
2911
  // TODO: add reply or update status
2461
2912
  res.json({ message: 'Ticket updated', ticket: { id, status: status ?? 'open' } });
2462
2913
  };
2463
- ` : `${RA}${rr3}${ticketByIdMeta}${mw3}
2914
+ ` : `${RA}${ticketByIdMeta}${mw3}
2464
2915
  export const GET = async (req, res) => {
2465
2916
  const { id } = req.params;
2466
2917
  // TODO: fetch ticket by id, verify ownership
@@ -2479,11 +2930,7 @@ export const PUT = async (req, res) => {
2479
2930
  async function writeAdminExtendedRoutes(dest, opts) {
2480
2931
  const ext = opts.language === "typescript" ? "ts" : "js";
2481
2932
  const ts = opts.language === "typescript";
2482
- const rr3 = opts.rbac ? `import { requireRole } from '../../../middleware/requireRole.js';
2483
- ` : "";
2484
- const rr4 = opts.rbac ? `import { requireRole } from '../../../../middleware/requireRole.js';
2485
- ` : "";
2486
- const mwAdmin3 = opts.rbac ? `export const middlewares = [requireAuth, requireRole('admin')];
2933
+ const mwAdmin3 = opts.rbac ? `export const middlewares = [requireAuth('admin')];
2487
2934
  ` : `export const middlewares = [requireAuth];
2488
2935
  `;
2489
2936
  const mwAdmin4 = mwAdmin3;
@@ -2494,29 +2941,33 @@ async function writeAdminExtendedRoutes(dest, opts) {
2494
2941
  `;
2495
2942
  const RA = `import { requireAuth } from 'express-file-cluster/auth';
2496
2943
  `;
2497
- const analyticsOverviewMeta = mkMeta(opts, "Analytics overview: users, revenue, traffic.", `{ users: {}, revenue: {}, traffic: {} }`);
2498
- const analyticsOverviewContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2944
+ const analyticsOverviewMeta = mkMeta(opts, {
2945
+ GET: { description: "Analytics overview: users, revenue, traffic (admin only).", response: `{ users: {}, revenue: {}, traffic: {} }` }
2946
+ });
2947
+ const analyticsOverviewContent = ts ? `${RA}import type { Request, Response } from 'express';
2499
2948
  ${analyticsOverviewMeta}${mwAdmin3}
2500
2949
  export const GET = async (req: Request, res: Response) => {
2501
2950
  ${roleGuard} // TODO: aggregate analytics overview
2502
2951
  res.json({ users: {}, revenue: {}, traffic: {} });
2503
2952
  };
2504
- ` : `${RA}${rr3}${analyticsOverviewMeta}${mwAdmin3}
2953
+ ` : `${RA}${analyticsOverviewMeta}${mwAdmin3}
2505
2954
  export const GET = async (req, res) => {
2506
2955
  ${roleGuard} // TODO: aggregate analytics overview
2507
2956
  res.json({ users: {}, revenue: {}, traffic: {} });
2508
2957
  };
2509
2958
  `;
2510
2959
  await fs.outputFile(path.join(dest, "src", "api", "admin", "analytics", `index.${ext}`), analyticsOverviewContent);
2511
- const analyticsUsersMeta = mkMeta(opts, "User analytics: registrations, active users, churn.", `{ registrations: [], activeUsers: 0, churn: 0 }`);
2512
- const analyticsUsersContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2960
+ const analyticsUsersMeta = mkMeta(opts, {
2961
+ GET: { description: "User analytics: registrations, active users, churn (admin only).", query: `{ period: '30d' }`, response: `{ registrations: [], activeUsers: 0, churn: 0, period: '30d' }` }
2962
+ });
2963
+ const analyticsUsersContent = ts ? `${RA}import type { Request, Response } from 'express';
2513
2964
  ${analyticsUsersMeta}${mwAdmin3}
2514
2965
  export const GET = async (req: Request, res: Response) => {
2515
2966
  ${roleGuard} const { period = '30d' } = req.query;
2516
2967
  // TODO: fetch user analytics for period
2517
2968
  res.json({ registrations: [], activeUsers: 0, churn: 0, period });
2518
2969
  };
2519
- ` : `${RA}${rr3}${analyticsUsersMeta}${mwAdmin3}
2970
+ ` : `${RA}${analyticsUsersMeta}${mwAdmin3}
2520
2971
  export const GET = async (req, res) => {
2521
2972
  ${roleGuard} const { period = '30d' } = req.query;
2522
2973
  // TODO: fetch user analytics for period
@@ -2524,15 +2975,17 @@ ${roleGuard} const { period = '30d' } = req.query;
2524
2975
  };
2525
2976
  `;
2526
2977
  await fs.outputFile(path.join(dest, "src", "api", "admin", "analytics", `users.${ext}`), analyticsUsersContent);
2527
- const analyticsRevenueMeta = mkMeta(opts, "Revenue analytics: MRR, ARR, payment history.", `{ mrr: 0, arr: 0, history: [] }`);
2528
- const analyticsRevenueContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2978
+ const analyticsRevenueMeta = mkMeta(opts, {
2979
+ GET: { description: "Revenue analytics: MRR, ARR, payment history (admin only).", query: `{ period: '30d' }`, response: `{ mrr: 0, arr: 0, history: [], period: '30d' }` }
2980
+ });
2981
+ const analyticsRevenueContent = ts ? `${RA}import type { Request, Response } from 'express';
2529
2982
  ${analyticsRevenueMeta}${mwAdmin3}
2530
2983
  export const GET = async (req: Request, res: Response) => {
2531
2984
  ${roleGuard} const { period = '30d' } = req.query;
2532
2985
  // TODO: fetch revenue analytics for period
2533
2986
  res.json({ mrr: 0, arr: 0, history: [], period });
2534
2987
  };
2535
- ` : `${RA}${rr3}${analyticsRevenueMeta}${mwAdmin3}
2988
+ ` : `${RA}${analyticsRevenueMeta}${mwAdmin3}
2536
2989
  export const GET = async (req, res) => {
2537
2990
  ${roleGuard} const { period = '30d' } = req.query;
2538
2991
  // TODO: fetch revenue analytics for period
@@ -2540,15 +2993,17 @@ ${roleGuard} const { period = '30d' } = req.query;
2540
2993
  };
2541
2994
  `;
2542
2995
  await fs.outputFile(path.join(dest, "src", "api", "admin", "analytics", `revenue.${ext}`), analyticsRevenueContent);
2543
- const analyticsTrafficMeta = mkMeta(opts, "Traffic analytics: page views, devices, countries.", `{ pageViews: 0, devices: {}, countries: {} }`);
2544
- const analyticsTrafficContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2996
+ const analyticsTrafficMeta = mkMeta(opts, {
2997
+ GET: { description: "Traffic analytics: page views, devices, countries (admin only).", query: `{ period: '30d' }`, response: `{ pageViews: 0, devices: {}, countries: {}, period: '30d' }` }
2998
+ });
2999
+ const analyticsTrafficContent = ts ? `${RA}import type { Request, Response } from 'express';
2545
3000
  ${analyticsTrafficMeta}${mwAdmin3}
2546
3001
  export const GET = async (req: Request, res: Response) => {
2547
3002
  ${roleGuard} const { period = '30d' } = req.query;
2548
3003
  // TODO: fetch traffic analytics for period
2549
3004
  res.json({ pageViews: 0, devices: {}, countries: {}, period });
2550
3005
  };
2551
- ` : `${RA}${rr3}${analyticsTrafficMeta}${mwAdmin3}
3006
+ ` : `${RA}${analyticsTrafficMeta}${mwAdmin3}
2552
3007
  export const GET = async (req, res) => {
2553
3008
  ${roleGuard} const { period = '30d' } = req.query;
2554
3009
  // TODO: fetch traffic analytics for period
@@ -2556,15 +3011,18 @@ ${roleGuard} const { period = '30d' } = req.query;
2556
3011
  };
2557
3012
  `;
2558
3013
  await fs.outputFile(path.join(dest, "src", "api", "admin", "analytics", `traffic.${ext}`), analyticsTrafficContent);
2559
- const suspendContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2560
- ${mwAdmin4}
3014
+ const suspendMeta = mkMeta(opts, {
3015
+ POST: { description: "Suspend a user account (admin only).", params: `{ id: 'usr_01HXZ' }`, request: `{ reason: 'Terms of service violation' }`, response: `{ message: 'User usr_01HXZ suspended', reason: 'Terms of service violation' }` }
3016
+ });
3017
+ const suspendContent = ts ? `${RA}import type { Request, Response } from 'express';
3018
+ ${suspendMeta}${mwAdmin4}
2561
3019
  export const POST = async (req: Request, res: Response) => {
2562
3020
  ${roleGuard} const { id } = req.params;
2563
3021
  const { reason } = req.body;
2564
3022
  // TODO: set user.isActive = false, log audit event
2565
3023
  res.json({ message: \`User \${id} suspended\`, reason });
2566
3024
  };
2567
- ` : `${RA}${rr4}${mwAdmin4}
3025
+ ` : `${RA}${suspendMeta}${mwAdmin4}
2568
3026
  export const POST = async (req, res) => {
2569
3027
  ${roleGuard} const { id } = req.params;
2570
3028
  const { reason } = req.body;
@@ -2573,14 +3031,17 @@ ${roleGuard} const { id } = req.params;
2573
3031
  };
2574
3032
  `;
2575
3033
  await fs.outputFile(path.join(dest, "src", "api", "admin", "users", "[id]", `suspend.${ext}`), suspendContent);
2576
- const activateContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2577
- ${mwAdmin4}
3034
+ const activateMeta = mkMeta(opts, {
3035
+ POST: { description: "Reactivate a suspended user account (admin only).", params: `{ id: 'usr_01HXZ' }`, response: `{ message: 'User usr_01HXZ activated' }` }
3036
+ });
3037
+ const activateContent = ts ? `${RA}import type { Request, Response } from 'express';
3038
+ ${activateMeta}${mwAdmin4}
2578
3039
  export const POST = async (req: Request, res: Response) => {
2579
3040
  ${roleGuard} const { id } = req.params;
2580
3041
  // TODO: set user.isActive = true, log audit event
2581
3042
  res.json({ message: \`User \${id} activated\` });
2582
3043
  };
2583
- ` : `${RA}${rr4}${mwAdmin4}
3044
+ ` : `${RA}${activateMeta}${mwAdmin4}
2584
3045
  export const POST = async (req, res) => {
2585
3046
  ${roleGuard} const { id } = req.params;
2586
3047
  // TODO: set user.isActive = true, log audit event
@@ -2588,14 +3049,17 @@ ${roleGuard} const { id } = req.params;
2588
3049
  };
2589
3050
  `;
2590
3051
  await fs.outputFile(path.join(dest, "src", "api", "admin", "users", "[id]", `activate.${ext}`), activateContent);
2591
- const verifyUserContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2592
- ${mwAdmin4}
3052
+ const verifyUserMeta = mkMeta(opts, {
3053
+ POST: { description: "Mark a user's email as verified (admin only).", params: `{ id: 'usr_01HXZ' }`, response: `{ message: 'User usr_01HXZ verified' }` }
3054
+ });
3055
+ const verifyUserContent = ts ? `${RA}import type { Request, Response } from 'express';
3056
+ ${verifyUserMeta}${mwAdmin4}
2593
3057
  export const POST = async (req: Request, res: Response) => {
2594
3058
  ${roleGuard} const { id } = req.params;
2595
3059
  // TODO: set user.isVerified = true
2596
3060
  res.json({ message: \`User \${id} verified\` });
2597
3061
  };
2598
- ` : `${RA}${rr4}${mwAdmin4}
3062
+ ` : `${RA}${verifyUserMeta}${mwAdmin4}
2599
3063
  export const POST = async (req, res) => {
2600
3064
  ${roleGuard} const { id } = req.params;
2601
3065
  // TODO: set user.isVerified = true
@@ -2603,8 +3067,10 @@ ${roleGuard} const { id } = req.params;
2603
3067
  };
2604
3068
  `;
2605
3069
  await fs.outputFile(path.join(dest, "src", "api", "admin", "users", "[id]", `verify.${ext}`), verifyUserContent);
2606
- const exportMeta = mkMeta(opts, "Export all users as CSV.", `{ csv: '...' }`);
2607
- const exportContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
3070
+ const exportMeta = mkMeta(opts, {
3071
+ GET: { description: "Export all users as a CSV download (admin only).", response: `'id,name,email,role,createdAt\\n'` }
3072
+ });
3073
+ const exportContent = ts ? `${RA}import type { Request, Response } from 'express';
2608
3074
  ${exportMeta}${mwAdmin3}
2609
3075
  export const GET = async (_req: Request, res: Response) => {
2610
3076
  ${roleGuard} // TODO: generate CSV of all users and stream response
@@ -2612,7 +3078,7 @@ ${roleGuard} // TODO: generate CSV of all users and stream response
2612
3078
  res.setHeader('Content-Disposition', 'attachment; filename="users.csv"');
2613
3079
  res.send('id,name,email,role,createdAt\\n');
2614
3080
  };
2615
- ` : `${RA}${rr3}${exportMeta}${mwAdmin3}
3081
+ ` : `${RA}${exportMeta}${mwAdmin3}
2616
3082
  export const GET = async (_req, res) => {
2617
3083
  ${roleGuard} // TODO: generate CSV of all users and stream response
2618
3084
  res.setHeader('Content-Type', 'text/csv');
@@ -2621,8 +3087,11 @@ ${roleGuard} // TODO: generate CSV of all users and stream response
2621
3087
  };
2622
3088
  `;
2623
3089
  await fs.outputFile(path.join(dest, "src", "api", "admin", "users", `export.${ext}`), exportContent);
2624
- const adminsListMeta = mkMeta(opts, "List all admins (GET) or create a new admin (POST).", `{ admins: [], total: 0 }`);
2625
- const adminsListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
3090
+ const adminsListMeta = mkMeta(opts, {
3091
+ GET: { description: "List all admin accounts (admin only).", response: `{ admins: [], total: 0 }` },
3092
+ POST: { description: "Create a new admin account (admin only).", request: `{ name: 'Jane Doe', email: 'jane@example.com', role: 'admin' }`, response: `{ message: 'Admin created', admin: { id: 'new-id', name: 'Jane Doe', email: 'jane@example.com', role: 'admin' } }`, status: 201 }
3093
+ });
3094
+ const adminsListContent = ts ? `${RA}import type { Request, Response } from 'express';
2626
3095
  ${adminsListMeta}${mwAdmin3}
2627
3096
  export const GET = async (_req: Request, res: Response) => {
2628
3097
  ${roleGuard} // TODO: fetch admins from DB
@@ -2635,7 +3104,7 @@ ${roleGuard} const { name, email, role } = req.body;
2635
3104
  // TODO: create admin account
2636
3105
  res.status(201).json({ message: 'Admin created', admin: { id: 'new-id', name, email, role: role ?? 'admin' } });
2637
3106
  };
2638
- ` : `${RA}${rr3}${adminsListMeta}${mwAdmin3}
3107
+ ` : `${RA}${adminsListMeta}${mwAdmin3}
2639
3108
  export const GET = async (_req, res) => {
2640
3109
  ${roleGuard} // TODO: fetch admins from DB
2641
3110
  res.json({ admins: [], total: 0 });
@@ -2649,8 +3118,13 @@ ${roleGuard} const { name, email, role } = req.body;
2649
3118
  };
2650
3119
  `;
2651
3120
  await fs.outputFile(path.join(dest, "src", "api", "admin", "admins", `index.${ext}`), adminsListContent);
2652
- const adminByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2653
- ${mwAdmin3}
3121
+ const adminByIdMeta = mkMeta(opts, {
3122
+ GET: { description: "Fetch a single admin account by ID (admin only).", params: `{ id: 'adm_01HXZ' }`, response: `{ admin: { id: 'adm_01HXZ' } }` },
3123
+ PUT: { description: "Update an admin account by ID (admin only).", params: `{ id: 'adm_01HXZ' }`, request: `{ name: 'Jane Doe', role: 'admin' }`, response: `{ message: 'Admin updated', admin: { id: 'adm_01HXZ', name: 'Jane Doe', role: 'admin' } }` },
3124
+ DELETE: { description: "Delete an admin account by ID (admin only).", params: `{ id: 'adm_01HXZ' }`, response: `{ message: 'Admin adm_01HXZ deleted' }` }
3125
+ });
3126
+ const adminByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
3127
+ ${adminByIdMeta}${mwAdmin3}
2654
3128
  export const GET = async (req: Request, res: Response) => {
2655
3129
  ${roleGuard} const { id } = req.params;
2656
3130
  // TODO: fetch admin by id
@@ -2668,7 +3142,7 @@ ${roleGuard} const { id } = req.params;
2668
3142
  // TODO: delete admin
2669
3143
  res.json({ message: \`Admin \${id} deleted\` });
2670
3144
  };
2671
- ` : `${RA}${rr3}${mwAdmin3}
3145
+ ` : `${RA}${adminByIdMeta}${mwAdmin3}
2672
3146
  export const GET = async (req, res) => {
2673
3147
  ${roleGuard} const { id } = req.params;
2674
3148
  // TODO: fetch admin by id
@@ -2689,8 +3163,12 @@ ${roleGuard} const { id } = req.params;
2689
3163
  `;
2690
3164
  await fs.outputFile(path.join(dest, "src", "api", "admin", "admins", `[id].${ext}`), adminByIdContent);
2691
3165
  if (opts.rbac) {
2692
- const rolesListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2693
- ${mwAdmin3}
3166
+ const rolesListMeta = mkMeta(opts, {
3167
+ GET: { description: "List all roles (admin only).", response: `{ roles: [] }` },
3168
+ POST: { description: "Create a new role (admin only).", request: `{ name: 'editor', description: 'Can edit content', permissions: ['content:write'] }`, response: `{ message: 'Role created', role: { id: 'new-id', name: 'editor', permissions: ['content:write'] } }`, status: 201 }
3169
+ });
3170
+ const rolesListContent = ts ? `${RA}import type { Request, Response } from 'express';
3171
+ ${rolesListMeta}${mwAdmin3}
2694
3172
  export const GET = async (_req: Request, res: Response) => {
2695
3173
  // TODO: fetch all roles
2696
3174
  res.json({ roles: [] });
@@ -2702,7 +3180,7 @@ export const POST = async (req: Request, res: Response) => {
2702
3180
  // TODO: create role
2703
3181
  res.status(201).json({ message: 'Role created', role: { id: 'new-id', name, permissions: permissions ?? [] } });
2704
3182
  };
2705
- ` : `${RA}${rr3}${mwAdmin3}
3183
+ ` : `${RA}${rolesListMeta}${mwAdmin3}
2706
3184
  export const GET = async (_req, res) => {
2707
3185
  // TODO: fetch all roles
2708
3186
  res.json({ roles: [] });
@@ -2716,8 +3194,13 @@ export const POST = async (req, res) => {
2716
3194
  };
2717
3195
  `;
2718
3196
  await fs.outputFile(path.join(dest, "src", "api", "admin", "roles", `index.${ext}`), rolesListContent);
2719
- const roleByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2720
- ${mwAdmin3}
3197
+ const roleByIdMeta = mkMeta(opts, {
3198
+ GET: { description: "Fetch a single role by ID (admin only).", params: `{ id: 'role_01HXZ' }`, response: `{ role: { id: 'role_01HXZ', name: 'editor', permissions: ['content:write'] } }` },
3199
+ PUT: { description: "Update a role by ID (admin only).", params: `{ id: 'role_01HXZ' }`, request: `{ name: 'editor', permissions: ['content:write'] }`, response: `{ message: 'Role updated', role: { id: 'role_01HXZ', name: 'editor', permissions: ['content:write'] } }` },
3200
+ DELETE: { description: "Delete a role by ID (admin only).", params: `{ id: 'role_01HXZ' }`, response: `{ message: 'Role role_01HXZ deleted' }` }
3201
+ });
3202
+ const roleByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
3203
+ ${roleByIdMeta}${mwAdmin3}
2721
3204
  export const GET = async (req: Request, res: Response) => {
2722
3205
  const { id } = req.params;
2723
3206
  // TODO: fetch role by id
@@ -2735,7 +3218,7 @@ export const DELETE = async (req: Request, res: Response) => {
2735
3218
  // TODO: delete role
2736
3219
  res.json({ message: \`Role \${id} deleted\` });
2737
3220
  };
2738
- ` : `${RA}${rr3}${mwAdmin3}
3221
+ ` : `${RA}${roleByIdMeta}${mwAdmin3}
2739
3222
  export const GET = async (req, res) => {
2740
3223
  const { id } = req.params;
2741
3224
  // TODO: fetch role by id
@@ -2756,8 +3239,12 @@ export const DELETE = async (req, res) => {
2756
3239
  `;
2757
3240
  await fs.outputFile(path.join(dest, "src", "api", "admin", "roles", `[id].${ext}`), roleByIdContent);
2758
3241
  }
2759
- const adminNotifContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2760
- ${mwAdmin3}
3242
+ const adminNotifMeta = mkMeta(opts, {
3243
+ GET: { description: "List all sent notifications (admin only).", response: `{ notifications: [], total: 0 }` },
3244
+ POST: { description: "Send a notification to a specific user (admin only).", request: `{ userId: 'usr_01HXZ', title: 'Welcome', message: 'Thanks for joining!', type: 'info' }`, response: `{ message: 'Notification sent' }`, status: 201 }
3245
+ });
3246
+ const adminNotifContent = ts ? `${RA}import type { Request, Response } from 'express';
3247
+ ${adminNotifMeta}${mwAdmin3}
2761
3248
  export const GET = async (_req: Request, res: Response) => {
2762
3249
  ${roleGuard} // TODO: fetch sent notifications
2763
3250
  res.json({ notifications: [], total: 0 });
@@ -2769,7 +3256,7 @@ ${roleGuard} const { userId, title, message, type } = req.body;
2769
3256
  // TODO: create and send notification to user
2770
3257
  res.status(201).json({ message: 'Notification sent' });
2771
3258
  };
2772
- ` : `${RA}${rr3}${mwAdmin3}
3259
+ ` : `${RA}${adminNotifMeta}${mwAdmin3}
2773
3260
  export const GET = async (_req, res) => {
2774
3261
  ${roleGuard} // TODO: fetch sent notifications
2775
3262
  res.json({ notifications: [], total: 0 });
@@ -2783,8 +3270,10 @@ ${roleGuard} const { userId, title, message, type } = req.body;
2783
3270
  };
2784
3271
  `;
2785
3272
  await fs.outputFile(path.join(dest, "src", "api", "admin", "notifications", `index.${ext}`), adminNotifContent);
2786
- const broadcastMeta = mkMeta(opts, "Broadcast a notification to all users.", `{ message: 'Broadcast sent', count: 0 }`, `{ title: 'Announcement', message: 'Hello everyone!', type: 'info' }`);
2787
- const broadcastContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
3273
+ const broadcastMeta = mkMeta(opts, {
3274
+ POST: { description: "Broadcast a notification to all users (admin only).", request: `{ title: 'Announcement', message: 'Hello everyone!', type: 'info' }`, response: `{ message: 'Broadcast sent', count: 0 }` }
3275
+ });
3276
+ const broadcastContent = ts ? `${RA}import type { Request, Response } from 'express';
2788
3277
  ${broadcastMeta}${mwAdmin3}
2789
3278
  export const POST = async (req: Request, res: Response) => {
2790
3279
  ${roleGuard} const { title, message, type } = req.body;
@@ -2792,7 +3281,7 @@ ${roleGuard} const { title, message, type } = req.body;
2792
3281
  // TODO: send notification to all users
2793
3282
  res.json({ message: 'Broadcast sent', count: 0 });
2794
3283
  };
2795
- ` : `${RA}${rr3}${broadcastMeta}${mwAdmin3}
3284
+ ` : `${RA}${broadcastMeta}${mwAdmin3}
2796
3285
  export const POST = async (req, res) => {
2797
3286
  ${roleGuard} const { title, message, type } = req.body;
2798
3287
  if (!title || !message) return res.status(400).json({ error: 'title and message are required' });
@@ -2802,15 +3291,17 @@ ${roleGuard} const { title, message, type } = req.body;
2802
3291
  `;
2803
3292
  await fs.outputFile(path.join(dest, "src", "api", "admin", "notifications", `broadcast.${ext}`), broadcastContent);
2804
3293
  const mkLogContent = (label) => {
2805
- const meta = mkMeta(opts, `Paginated ${label} log entries.`, `{ logs: [], total: 0, page: 1, limit: 50 }`);
2806
- return ts ? `${RA}${rr3}import type { Request, Response } from 'express';
3294
+ const meta = mkMeta(opts, {
3295
+ GET: { description: `Paginated ${label} log entries (admin only).`, query: `{ page: '1', limit: '50' }`, response: `{ logs: [], total: 0, page: 1, limit: 50 }` }
3296
+ });
3297
+ return ts ? `${RA}import type { Request, Response } from 'express';
2807
3298
  ${meta}${mwAdmin3}
2808
3299
  export const GET = async (req: Request, res: Response) => {
2809
3300
  ${roleGuard} const { page = 1, limit = 50 } = req.query;
2810
3301
  // TODO: fetch ${label} logs with pagination
2811
3302
  res.json({ logs: [], total: 0, page: Number(page), limit: Number(limit) });
2812
3303
  };
2813
- ` : `${RA}${rr3}${meta}${mwAdmin3}
3304
+ ` : `${RA}${meta}${mwAdmin3}
2814
3305
  export const GET = async (req, res) => {
2815
3306
  ${roleGuard} const { page = 1, limit = 50 } = req.query;
2816
3307
  // TODO: fetch ${label} logs with pagination
@@ -2821,8 +3312,11 @@ ${roleGuard} const { page = 1, limit = 50 } = req.query;
2821
3312
  await fs.outputFile(path.join(dest, "src", "api", "admin", "logs", `audit.${ext}`), mkLogContent("audit"));
2822
3313
  await fs.outputFile(path.join(dest, "src", "api", "admin", "logs", `activity.${ext}`), mkLogContent("activity"));
2823
3314
  await fs.outputFile(path.join(dest, "src", "api", "admin", "logs", `security.${ext}`), mkLogContent("security"));
2824
- const settingsMeta = mkMeta(opts, "Get or update system-wide settings.", `{ settings: {} }`);
2825
- const settingsContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
3315
+ const settingsMeta = mkMeta(opts, {
3316
+ GET: { description: "Get system-wide settings (admin only).", response: `{ settings: {} }` },
3317
+ PUT: { description: "Update system-wide settings (admin only).", request: `{ maintenanceMode: false }`, response: `{ message: 'Settings updated', settings: { maintenanceMode: false } }` }
3318
+ });
3319
+ const settingsContent = ts ? `${RA}import type { Request, Response } from 'express';
2826
3320
  ${settingsMeta}${mwAdmin3}
2827
3321
  export const GET = async (_req: Request, res: Response) => {
2828
3322
  ${roleGuard} // TODO: fetch system settings from DB
@@ -2833,7 +3327,7 @@ export const PUT = async (req: Request, res: Response) => {
2833
3327
  ${roleGuard} // TODO: update system settings
2834
3328
  res.json({ message: 'Settings updated', settings: req.body });
2835
3329
  };
2836
- ` : `${RA}${rr3}${settingsMeta}${mwAdmin3}
3330
+ ` : `${RA}${settingsMeta}${mwAdmin3}
2837
3331
  export const GET = async (_req, res) => {
2838
3332
  ${roleGuard} // TODO: fetch system settings from DB
2839
3333
  res.json({ settings: {} });
@@ -2845,43 +3339,49 @@ ${roleGuard} // TODO: update system settings
2845
3339
  };
2846
3340
  `;
2847
3341
  await fs.outputFile(path.join(dest, "src", "api", "admin", "settings", `index.${ext}`), settingsContent);
2848
- const healthMeta = mkMeta(opts, "System health check: DB, queue, cache status.", `{ status: 'ok', db: 'ok', queue: 'ok' }`);
2849
- const healthContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
3342
+ const healthMeta = mkMeta(opts, {
3343
+ GET: { description: "System health check: DB, queue, cache status (admin only).", response: `{ status: 'ok', db: 'ok', queue: 'ok', uptime: 12345 }` }
3344
+ });
3345
+ const healthContent = ts ? `${RA}import type { Request, Response } from 'express';
2850
3346
  ${healthMeta}${mwAdmin3}
2851
3347
  export const GET = async (_req: Request, res: Response) => {
2852
3348
  ${roleGuard} // TODO: check DB connection, queue, cache health
2853
3349
  res.json({ status: 'ok', db: 'ok', queue: 'ok', uptime: process.uptime() });
2854
3350
  };
2855
- ` : `${RA}${rr3}${healthMeta}${mwAdmin3}
3351
+ ` : `${RA}${healthMeta}${mwAdmin3}
2856
3352
  export const GET = async (_req, res) => {
2857
3353
  ${roleGuard} // TODO: check DB connection, queue, cache health
2858
3354
  res.json({ status: 'ok', db: 'ok', queue: 'ok', uptime: process.uptime() });
2859
3355
  };
2860
3356
  `;
2861
3357
  await fs.outputFile(path.join(dest, "src", "api", "admin", "system", `health.${ext}`), healthContent);
2862
- const cacheMeta = mkMeta(opts, "Flush the application cache.", `{ message: 'Cache cleared' }`);
2863
- const cacheContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
3358
+ const cacheMeta = mkMeta(opts, {
3359
+ DELETE: { description: "Flush the application cache (admin only).", response: `{ message: 'Cache cleared' }` }
3360
+ });
3361
+ const cacheContent = ts ? `${RA}import type { Request, Response } from 'express';
2864
3362
  ${cacheMeta}${mwAdmin3}
2865
3363
  export const DELETE = async (_req: Request, res: Response) => {
2866
3364
  ${roleGuard} // TODO: flush Redis or in-memory cache
2867
3365
  res.json({ message: 'Cache cleared' });
2868
3366
  };
2869
- ` : `${RA}${rr3}${cacheMeta}${mwAdmin3}
3367
+ ` : `${RA}${cacheMeta}${mwAdmin3}
2870
3368
  export const DELETE = async (_req, res) => {
2871
3369
  ${roleGuard} // TODO: flush Redis or in-memory cache
2872
3370
  res.json({ message: 'Cache cleared' });
2873
3371
  };
2874
3372
  `;
2875
3373
  await fs.outputFile(path.join(dest, "src", "api", "admin", "system", `cache.${ext}`), cacheContent);
2876
- const adminTicketsListMeta = mkMeta(opts, "List all support tickets with filters.", `{ tickets: [], total: 0 }`);
2877
- const adminTicketsListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
3374
+ const adminTicketsListMeta = mkMeta(opts, {
3375
+ GET: { description: "List all support tickets with filters (admin only).", query: `{ status: 'open', priority: 'normal', page: '1', limit: '20' }`, response: `{ tickets: [], total: 0, page: 1, limit: 20 }` }
3376
+ });
3377
+ const adminTicketsListContent = ts ? `${RA}import type { Request, Response } from 'express';
2878
3378
  ${adminTicketsListMeta}${mwAdmin3}
2879
3379
  export const GET = async (req: Request, res: Response) => {
2880
3380
  ${roleGuard} const { status, priority, page = 1, limit = 20 } = req.query;
2881
3381
  // TODO: fetch all tickets with filters
2882
3382
  res.json({ tickets: [], total: 0, page: Number(page), limit: Number(limit) });
2883
3383
  };
2884
- ` : `${RA}${rr3}${adminTicketsListMeta}${mwAdmin3}
3384
+ ` : `${RA}${adminTicketsListMeta}${mwAdmin3}
2885
3385
  export const GET = async (req, res) => {
2886
3386
  ${roleGuard} const { status, priority, page = 1, limit = 20 } = req.query;
2887
3387
  // TODO: fetch all tickets with filters
@@ -2889,8 +3389,12 @@ ${roleGuard} const { status, priority, page = 1, limit = 20 } = req.query;
2889
3389
  };
2890
3390
  `;
2891
3391
  await fs.outputFile(path.join(dest, "src", "api", "admin", "tickets", `index.${ext}`), adminTicketsListContent);
2892
- const adminTicketByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2893
- ${mwAdmin3}
3392
+ const adminTicketByIdMeta = mkMeta(opts, {
3393
+ GET: { description: "Fetch a single support ticket by ID (admin only).", params: `{ id: 'tk_01HXZ' }`, response: `{ ticket: { id: 'tk_01HXZ' } }` },
3394
+ PUT: { description: "Assign, reply to, or change the status of a support ticket (admin only).", params: `{ id: 'tk_01HXZ' }`, request: `{ reply: 'Looking into it.', status: 'in_progress', assignedTo: 'adm_01HXZ' }`, response: `{ message: 'Ticket updated', ticket: { id: 'tk_01HXZ', status: 'in_progress', assignedTo: 'adm_01HXZ' } }` }
3395
+ });
3396
+ const adminTicketByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
3397
+ ${adminTicketByIdMeta}${mwAdmin3}
2894
3398
  export const GET = async (req: Request, res: Response) => {
2895
3399
  ${roleGuard} const { id } = req.params;
2896
3400
  // TODO: fetch ticket by id
@@ -2903,7 +3407,7 @@ ${roleGuard} const { id } = req.params;
2903
3407
  // TODO: update ticket (assign, change status, add reply)
2904
3408
  res.json({ message: 'Ticket updated', ticket: { id, status, assignedTo } });
2905
3409
  };
2906
- ` : `${RA}${rr3}${mwAdmin3}
3410
+ ` : `${RA}${adminTicketByIdMeta}${mwAdmin3}
2907
3411
  export const GET = async (req, res) => {
2908
3412
  ${roleGuard} const { id } = req.params;
2909
3413
  // TODO: fetch ticket by id
@@ -2919,8 +3423,16 @@ ${roleGuard} const { id } = req.params;
2919
3423
  `;
2920
3424
  await fs.outputFile(path.join(dest, "src", "api", "admin", "tickets", `[id].${ext}`), adminTicketByIdContent);
2921
3425
  const mkContentCrud = (name, namePlural, dir, createFields) => {
2922
- const listMeta = mkMeta(opts, `List ${namePlural} (GET) or create a new ${name} (POST).`, `{ ${namePlural}: [], total: 0 }`);
2923
- const listContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
3426
+ const lower = name.toLowerCase();
3427
+ const firstField = createFields.split(", ")[0];
3428
+ const sampleCreateBody = `{ ${createFields.split(", ").map((f) => `${f}: 'sample-${f}'`).join(", ")} }`;
3429
+ const sampleCreatedRecord = `{ id: 'new-id', ${firstField}: 'sample-${firstField}' }`;
3430
+ const sampleFullRecord = `{ id: '${lower}_01HXZ', ${createFields.split(", ").map((f) => `${f}: 'sample-${f}'`).join(", ")} }`;
3431
+ const listMeta = mkMeta(opts, {
3432
+ GET: { description: `List all ${namePlural} (admin only).`, response: `{ ${namePlural}: [], total: 0 }` },
3433
+ POST: { description: `Create a new ${lower} (admin only).`, request: sampleCreateBody, response: `{ message: '${name} created', ${lower}: ${sampleCreatedRecord} }`, status: 201 }
3434
+ });
3435
+ const listContent = ts ? `${RA}import type { Request, Response } from 'express';
2924
3436
  ${listMeta}${mwAdmin4}
2925
3437
  export const GET = async (_req: Request, res: Response) => {
2926
3438
  ${roleGuard} // TODO: fetch ${namePlural}
@@ -2930,9 +3442,9 @@ ${roleGuard} // TODO: fetch ${namePlural}
2930
3442
  export const POST = async (req: Request, res: Response) => {
2931
3443
  ${roleGuard} const { ${createFields} } = req.body;
2932
3444
  // TODO: create ${name}
2933
- res.status(201).json({ message: '${name} created', ${name.toLowerCase()}: { id: 'new-id', ${createFields.split(", ")[0]} } });
3445
+ res.status(201).json({ message: '${name} created', ${lower}: { id: 'new-id', ${firstField} } });
2934
3446
  };
2935
- ` : `${RA}${rr4}${listMeta}${mwAdmin4}
3447
+ ` : `${RA}${listMeta}${mwAdmin4}
2936
3448
  export const GET = async (_req, res) => {
2937
3449
  ${roleGuard} // TODO: fetch ${namePlural}
2938
3450
  res.json({ ${namePlural}: [], total: 0 });
@@ -2941,21 +3453,26 @@ ${roleGuard} // TODO: fetch ${namePlural}
2941
3453
  export const POST = async (req, res) => {
2942
3454
  ${roleGuard} const { ${createFields} } = req.body;
2943
3455
  // TODO: create ${name}
2944
- res.status(201).json({ message: '${name} created', ${name.toLowerCase()}: { id: 'new-id' } });
3456
+ res.status(201).json({ message: '${name} created', ${lower}: { id: 'new-id' } });
2945
3457
  };
2946
3458
  `;
2947
- const byIdContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2948
- ${mwAdmin4}
3459
+ const byIdMeta = mkMeta(opts, {
3460
+ GET: { description: `Fetch a single ${lower} by ID (admin only).`, params: `{ id: '${lower}_01HXZ' }`, response: `{ ${lower}: ${sampleFullRecord} }` },
3461
+ PUT: { description: `Update a ${lower} by ID (admin only).`, params: `{ id: '${lower}_01HXZ' }`, request: sampleCreateBody, response: `{ message: '${name} updated', ${lower}: ${sampleFullRecord} }` },
3462
+ DELETE: { description: `Delete a ${lower} by ID (admin only).`, params: `{ id: '${lower}_01HXZ' }`, response: `{ message: '${name} ${lower}_01HXZ deleted' }` }
3463
+ });
3464
+ const byIdContent = ts ? `${RA}import type { Request, Response } from 'express';
3465
+ ${byIdMeta}${mwAdmin4}
2949
3466
  export const GET = async (req: Request, res: Response) => {
2950
3467
  ${roleGuard} const { id } = req.params;
2951
3468
  // TODO: fetch ${name} by id
2952
- res.json({ ${name.toLowerCase()}: { id } });
3469
+ res.json({ ${lower}: { id } });
2953
3470
  };
2954
3471
 
2955
3472
  export const PUT = async (req: Request, res: Response) => {
2956
3473
  ${roleGuard} const { id } = req.params;
2957
3474
  // TODO: update ${name}
2958
- res.json({ message: '${name} updated', ${name.toLowerCase()}: { id, ...req.body } });
3475
+ res.json({ message: '${name} updated', ${lower}: { id, ...req.body } });
2959
3476
  };
2960
3477
 
2961
3478
  export const DELETE = async (req: Request, res: Response) => {
@@ -2963,17 +3480,17 @@ ${roleGuard} const { id } = req.params;
2963
3480
  // TODO: delete ${name}
2964
3481
  res.json({ message: \`${name} \${id} deleted\` });
2965
3482
  };
2966
- ` : `${RA}${rr4}${mwAdmin4}
3483
+ ` : `${RA}${byIdMeta}${mwAdmin4}
2967
3484
  export const GET = async (req, res) => {
2968
3485
  ${roleGuard} const { id } = req.params;
2969
3486
  // TODO: fetch ${name} by id
2970
- res.json({ ${name.toLowerCase()}: { id } });
3487
+ res.json({ ${lower}: { id } });
2971
3488
  };
2972
3489
 
2973
3490
  export const PUT = async (req, res) => {
2974
3491
  ${roleGuard} const { id } = req.params;
2975
3492
  // TODO: update ${name}
2976
- res.json({ message: '${name} updated', ${name.toLowerCase()}: { id, ...req.body } });
3493
+ res.json({ message: '${name} updated', ${lower}: { id, ...req.body } });
2977
3494
  };
2978
3495
 
2979
3496
  export const DELETE = async (req, res) => {
@@ -2999,15 +3516,17 @@ ${roleGuard} const { id } = req.params;
2999
3516
  const { listContent: couponList, byIdContent: couponById } = mkContentCrud("Coupon", "coupons", [], "code, type, value");
3000
3517
  await fs.outputFile(path.join(dest, "src", "api", "admin", "billing", "coupons", `index.${ext}`), couponList);
3001
3518
  await fs.outputFile(path.join(dest, "src", "api", "admin", "billing", "coupons", `[id].${ext}`), couponById);
3002
- const adminSubsMeta = mkMeta(opts, "List all subscriptions with filters.", `{ subscriptions: [], total: 0 }`);
3003
- const adminSubsContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
3519
+ const adminSubsMeta = mkMeta(opts, {
3520
+ GET: { description: "List all subscriptions with filters (admin only).", query: `{ status: 'active', page: '1', limit: '20' }`, response: `{ subscriptions: [], total: 0, page: 1, limit: 20 }` }
3521
+ });
3522
+ const adminSubsContent = ts ? `${RA}import type { Request, Response } from 'express';
3004
3523
  ${adminSubsMeta}${mwAdmin4}
3005
3524
  export const GET = async (req: Request, res: Response) => {
3006
3525
  ${roleGuard} const { status, page = 1, limit = 20 } = req.query;
3007
3526
  // TODO: fetch all subscriptions with filters
3008
3527
  res.json({ subscriptions: [], total: 0, page: Number(page), limit: Number(limit) });
3009
3528
  };
3010
- ` : `${RA}${rr4}${adminSubsMeta}${mwAdmin4}
3529
+ ` : `${RA}${adminSubsMeta}${mwAdmin4}
3011
3530
  export const GET = async (req, res) => {
3012
3531
  ${roleGuard} const { status, page = 1, limit = 20 } = req.query;
3013
3532
  // TODO: fetch all subscriptions with filters
@@ -3064,7 +3583,7 @@ async function main() {
3064
3583
  { value: "routeDocs", label: "API route documentation", hint: "meta exports + dashboard" },
3065
3584
  { value: "userPortal", label: "User portal", hint: "auth, profile, billing routes" },
3066
3585
  { value: "adminPortal", label: "Admin portal", hint: "dashboard, user mgmt, analytics" },
3067
- { value: "rbac", label: "Role-based access control", hint: "requireRole middleware" },
3586
+ { value: "rbac", label: "Role-based access control", hint: "requireAuth('role') middleware" },
3068
3587
  { value: "mailer", label: "Mailer", hint: "nodemailer + SMTP" }
3069
3588
  ],
3070
3589
  initialValues: ["cluster", "tasks", "routeDocs", "userPortal", "adminPortal", "rbac"],
@@ -3177,6 +3696,13 @@ async function main() {
3177
3696
  await npmInstallGlobal().catch(() => {
3178
3697
  });
3179
3698
  spinner2.stop("efc CLI ready");
3699
+ if (opts.mailer) {
3700
+ p.note(
3701
+ `SMTP_USER and SMTP_PASS were written to ${projectName}/.env \u2014 that file is gitignored, never commit it.
3702
+ ` + (opts.smtpProvider === "gmail" ? "If this Gmail app password ever leaks, revoke it and generate a new one." : "Rotate SMTP_PASS immediately if it is ever exposed."),
3703
+ "Mailer credentials"
3704
+ );
3705
+ }
3180
3706
  p.outro(
3181
3707
  pc.green(`
3182
3708
  Your project is ready!