create-efc-app 0.3.6 → 0.3.7

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
@@ -291,27 +292,42 @@ export interface UserDocument {
291
292
  avatar?: string;
292
293
  isVerified: boolean;
293
294
  isActive: boolean;
295
+ verifyToken?: string;
296
+ resetToken?: string;
297
+ resetTokenExpiry?: Date;
298
+ refreshToken?: string;
299
+ refreshTokenExpiry?: Date;
294
300
  }
295
301
 
296
302
  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 },
303
+ name: { type: 'string', required: true },
304
+ email: { type: 'string', required: true, unique: true },
305
+ password: { type: 'string', required: true },
306
+ role: { type: 'string', required: true, default: 'user' },
307
+ avatar: { type: 'string' },
308
+ isVerified: { type: 'boolean', default: false },
309
+ isActive: { type: 'boolean', default: true },
310
+ verifyToken: { type: 'string' },
311
+ resetToken: { type: 'string' },
312
+ resetTokenExpiry: { type: 'date' },
313
+ refreshToken: { type: 'string' },
314
+ refreshTokenExpiry: { type: 'date' },
304
315
  });
305
316
  ` : `import { defineModel } from 'express-file-cluster';
306
317
 
307
318
  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 },
319
+ name: { type: 'string', required: true },
320
+ email: { type: 'string', required: true, unique: true },
321
+ password: { type: 'string', required: true },
322
+ role: { type: 'string', required: true, default: 'user' },
323
+ avatar: { type: 'string' },
324
+ isVerified: { type: 'boolean', default: false },
325
+ isActive: { type: 'boolean', default: true },
326
+ verifyToken: { type: 'string' },
327
+ resetToken: { type: 'string' },
328
+ resetTokenExpiry: { type: 'date' },
329
+ refreshToken: { type: 'string' },
330
+ refreshTokenExpiry: { type: 'date' },
315
331
  });
316
332
  ` : ts ? `// TODO: define your Drizzle schema for User
317
333
  // import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';
@@ -346,25 +362,37 @@ export interface AdminDocument {
346
362
  role: string;
347
363
  permissions: string[];
348
364
  isActive: boolean;
365
+ resetToken?: string;
366
+ resetTokenExpiry?: Date;
367
+ refreshToken?: string;
368
+ refreshTokenExpiry?: Date;
349
369
  }
350
370
 
351
371
  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 },
372
+ name: { type: 'string', required: true },
373
+ email: { type: 'string', required: true, unique: true },
374
+ password: { type: 'string', required: true },
375
+ role: { type: 'string', required: true, default: 'admin' },
376
+ permissions: { type: 'array', default: [] },
377
+ isActive: { type: 'boolean', default: true },
378
+ resetToken: { type: 'string' },
379
+ resetTokenExpiry: { type: 'date' },
380
+ refreshToken: { type: 'string' },
381
+ refreshTokenExpiry: { type: 'date' },
358
382
  });
359
383
  ` : `import { defineModel } from 'express-file-cluster';
360
384
 
361
385
  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 },
386
+ name: { type: 'string', required: true },
387
+ email: { type: 'string', required: true, unique: true },
388
+ password: { type: 'string', required: true },
389
+ role: { type: 'string', required: true, default: 'admin' },
390
+ permissions: { type: 'array', default: [] },
391
+ isActive: { type: 'boolean', default: true },
392
+ resetToken: { type: 'string' },
393
+ resetTokenExpiry: { type: 'date' },
394
+ refreshToken: { type: 'string' },
395
+ refreshTokenExpiry: { type: 'date' },
368
396
  });
369
397
  ` : ts ? `// TODO: define your Drizzle schema for Admin
370
398
  // import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';
@@ -405,15 +433,34 @@ export const meta: RouteMeta = {
405
433
 
406
434
  ` : "";
407
435
  const loginDbImports = opts.database === "mongodb" ? ts ? `import bcrypt from 'bcrypt';
436
+ import crypto from 'node:crypto';
408
437
  import { User } from '../../model/User.js';
409
438
  import { Admin } from '../../model/Admin.js';
410
439
  ` : `import bcrypt from 'bcrypt';
440
+ import crypto from 'node:crypto';
411
441
  import { User } from '../../model/User.js';
412
442
  import { Admin } from '../../model/Admin.js';
413
443
  ` : "";
414
444
  const loginContent = opts.database === "mongodb" ? ts ? `import { issueToken } from 'express-file-cluster/auth';
415
445
  import type { Request, Response } from 'express';
416
- ${loginDbImports}${loginMeta}export const POST = async (req: Request, res: Response) => {
446
+ ${loginDbImports}${loginMeta}const REFRESH_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
447
+
448
+ async function issueRefreshToken(
449
+ res: Response,
450
+ model: { update: (id: string, data: Record<string, unknown>) => Promise<unknown> },
451
+ id: string,
452
+ ): Promise<void> {
453
+ const refreshToken = crypto.randomBytes(40).toString('hex');
454
+ await model.update(id, { refreshToken, refreshTokenExpiry: new Date(Date.now() + REFRESH_TOKEN_TTL_MS) });
455
+ res.cookie('efc_refresh_token', refreshToken, {
456
+ httpOnly: true,
457
+ secure: process.env.NODE_ENV === 'production',
458
+ sameSite: 'strict',
459
+ maxAge: REFRESH_TOKEN_TTL_MS,
460
+ });
461
+ }
462
+
463
+ export const POST = async (req: Request, res: Response) => {
417
464
  const { email, password } = req.body;
418
465
  if (!email || !password) return res.status(400).json({ error: 'email and password are required' });
419
466
 
@@ -423,6 +470,7 @@ ${loginDbImports}${loginMeta}export const POST = async (req: Request, res: Respo
423
470
  if (!match) return res.status(401).json({ error: 'Invalid credentials' });
424
471
  if (!admin.isActive) return res.status(403).json({ error: 'Account suspended' });
425
472
  await issueToken(res, { id: admin.id, role: admin.role, email: admin.email });
473
+ await issueRefreshToken(res, Admin, admin.id);
426
474
  return res.json({ message: 'Logged in as admin' });
427
475
  }
428
476
 
@@ -432,10 +480,24 @@ ${loginDbImports}${loginMeta}export const POST = async (req: Request, res: Respo
432
480
  if (!match) return res.status(401).json({ error: 'Invalid credentials' });
433
481
  if (!user.isActive) return res.status(403).json({ error: 'Account suspended' });
434
482
  await issueToken(res, { id: user.id, role: user.role, email: user.email });
483
+ await issueRefreshToken(res, User, user.id);
435
484
  res.json({ message: 'Logged in' });
436
485
  };
437
486
  ` : `import { issueToken } from 'express-file-cluster/auth';
438
- ${loginDbImports}${loginMeta}export const POST = async (req, res) => {
487
+ ${loginDbImports}${loginMeta}const REFRESH_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
488
+
489
+ async function issueRefreshToken(res, model, id) {
490
+ const refreshToken = crypto.randomBytes(40).toString('hex');
491
+ await model.update(id, { refreshToken, refreshTokenExpiry: new Date(Date.now() + REFRESH_TOKEN_TTL_MS) });
492
+ res.cookie('efc_refresh_token', refreshToken, {
493
+ httpOnly: true,
494
+ secure: process.env.NODE_ENV === 'production',
495
+ sameSite: 'strict',
496
+ maxAge: REFRESH_TOKEN_TTL_MS,
497
+ });
498
+ }
499
+
500
+ export const POST = async (req, res) => {
439
501
  const { email, password } = req.body;
440
502
  if (!email || !password) return res.status(400).json({ error: 'email and password are required' });
441
503
 
@@ -445,6 +507,7 @@ ${loginDbImports}${loginMeta}export const POST = async (req, res) => {
445
507
  if (!match) return res.status(401).json({ error: 'Invalid credentials' });
446
508
  if (!admin.isActive) return res.status(403).json({ error: 'Account suspended' });
447
509
  await issueToken(res, { id: admin.id, role: admin.role, email: admin.email });
510
+ await issueRefreshToken(res, Admin, admin.id);
448
511
  return res.json({ message: 'Logged in as admin' });
449
512
  }
450
513
 
@@ -454,6 +517,7 @@ ${loginDbImports}${loginMeta}export const POST = async (req, res) => {
454
517
  if (!match) return res.status(401).json({ error: 'Invalid credentials' });
455
518
  if (!user.isActive) return res.status(403).json({ error: 'Account suspended' });
456
519
  await issueToken(res, { id: user.id, role: user.role, email: user.email });
520
+ await issueRefreshToken(res, User, user.id);
457
521
  res.json({ message: 'Logged in' });
458
522
  };
459
523
  ` : ts ? `import { issueToken } from 'express-file-cluster/auth';
@@ -487,11 +551,13 @@ export const meta: RouteMeta = {
487
551
  import type { Request, Response } from 'express';
488
552
  ${logoutMeta}export const POST = async (_req: Request, res: Response) => {
489
553
  revokeToken(res);
554
+ res.clearCookie('efc_refresh_token');
490
555
  res.json({ message: 'Logged out successfully' });
491
556
  };
492
557
  ` : `import { revokeToken } from 'express-file-cluster/auth';
493
558
  ${logoutMeta}export const POST = async (_req, res) => {
494
559
  revokeToken(res);
560
+ res.clearCookie('efc_refresh_token');
495
561
  res.json({ message: 'Logged out successfully' });
496
562
  };
497
563
  `;
@@ -513,11 +579,20 @@ export const meta: RouteMeta = {
513
579
  };
514
580
 
515
581
  ` : "";
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';
582
+ const registerDbImports = opts.database === "mongodb" ? `import bcrypt from 'bcrypt';
583
+ import crypto from 'node:crypto';
584
+ ${opts.mailer ? "import { enqueue } from 'express-file-cluster/tasks';\n" : ""}import { User } from '../../model/User.js';
520
585
  ` : "";
586
+ const sendVerifyEmail = opts.mailer ? `
587
+ var appUrl = process.env.APP_URL || 'http://localhost:3000';
588
+ await enqueue('SendEmail', {
589
+ to: email,
590
+ subject: 'Verify your email address',
591
+ body: 'Welcome, ' + name + '! Verify your email: ' + appUrl + '/auth/verify-email?token=' + verifyToken,
592
+ });
593
+ ` : `
594
+ // TODO: email this token to the user \u2014 enable the Mailer feature to auto-wire SendEmail
595
+ `;
521
596
  const registerContent = opts.database === "mongodb" ? ts ? `import type { Request, Response } from 'express';
522
597
  ${registerDbImports}${registerMeta}export const POST = async (req: Request, res: Response) => {
523
598
  const { name, email, password } = req.body;
@@ -527,8 +602,10 @@ ${registerDbImports}${registerMeta}export const POST = async (req: Request, res:
527
602
  const existing = await User.findOne({ email });
528
603
  if (existing) return res.status(409).json({ error: 'Email already in use' });
529
604
  const hashed = await bcrypt.hash(password, 10);
530
- const user = await User.create({ name, email, password: hashed });
531
- const { password: _, ...safe } = user;
605
+ const verifyToken = crypto.randomBytes(32).toString('hex');
606
+ const user = await User.create({ name, email, password: hashed, verifyToken });
607
+ ${sendVerifyEmail}
608
+ const { password: _, verifyToken: __, ...safe } = user;
532
609
  res.status(201).json({ message: 'Account created successfully', user: safe });
533
610
  };
534
611
  ` : `${registerDbImports}${registerMeta}export const POST = async (req, res) => {
@@ -539,8 +616,10 @@ ${registerDbImports}${registerMeta}export const POST = async (req: Request, res:
539
616
  const existing = await User.findOne({ email });
540
617
  if (existing) return res.status(409).json({ error: 'Email already in use' });
541
618
  const hashed = await bcrypt.hash(password, 10);
542
- const user = await User.create({ name, email, password: hashed });
543
- const { password: _, ...safe } = user;
619
+ const verifyToken = crypto.randomBytes(32).toString('hex');
620
+ const user = await User.create({ name, email, password: hashed, verifyToken });
621
+ ${sendVerifyEmail}
622
+ const { password: _, verifyToken: __, ...safe } = user;
544
623
  res.status(201).json({ message: 'Account created successfully', user: safe });
545
624
  };
546
625
  ` : ts ? `import type { Request, Response } from 'express';
@@ -574,58 +653,28 @@ export const meta: RouteMeta = {
574
653
  };
575
654
 
576
655
  ` : "";
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')];
656
+ const meMiddlewares = opts.rbac ? `export const middlewares = [requireAuth('user', 'admin')];
581
657
 
582
658
  ` : `export const middlewares = [requireAuth];
583
659
 
584
660
  `;
585
661
  const meContent = ts ? `import { requireAuth } from 'express-file-cluster/auth';
586
- ${requireRoleImport}import type { Request, Response } from 'express';
662
+ import type { Request, Response } from 'express';
587
663
  ${meMeta}${meMiddlewares}export const GET = async (req: Request, res: Response) => {
588
664
  res.json({ user: (req as any).user });
589
665
  };
590
666
  ` : `import { requireAuth } from 'express-file-cluster/auth';
591
- ${requireRoleImport}${meMeta}${meMiddlewares}export const GET = async (req, res) => {
667
+ ${meMeta}${meMiddlewares}export const GET = async (req, res) => {
592
668
  res.json({ user: req.user });
593
669
  };
594
670
  `;
595
671
  await fs.outputFile(path.join(dest, "src", "api", "auth", `register.${ext}`), registerContent);
596
672
  await fs.outputFile(path.join(dest, "src", "api", "auth", `me.${ext}`), meContent);
597
673
  }
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
674
  async function writeAdminRoutes(dest, opts) {
622
675
  const ext = opts.language === "typescript" ? "ts" : "js";
623
676
  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')];
677
+ const middlewares = opts.rbac ? `export const middlewares = [requireAuth('admin')];
629
678
  ` : `export const middlewares = [requireAuth];
630
679
  `;
631
680
  const roleGuard = opts.rbac ? "" : ts ? ` const user = (req as any).user;
@@ -655,7 +704,7 @@ export const meta: RouteMeta = {
655
704
  const dashboardDbImport = opts.database === "mongodb" ? `import { User } from '../../model/User.js';
656
705
  ` : "";
657
706
  const dashboardContent = opts.database === "mongodb" ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
658
- ${requireRoleImport}import type { Request, Response } from 'express';
707
+ import type { Request, Response } from 'express';
659
708
  ${dashboardDbImport}${dashboardMeta}${middlewares}
660
709
  export const GET = async (_req: Request, res: Response) => {
661
710
  ${roleGuard} const [totalUsers, activeUsers, verifiedUsers] = await Promise.all([
@@ -666,7 +715,7 @@ ${roleGuard} const [totalUsers, activeUsers, verifiedUsers] = await Promise.all
666
715
  res.json({ stats: { totalUsers, activeUsers, verifiedUsers } });
667
716
  };
668
717
  ` : `import { requireAuth } from 'express-file-cluster/auth';
669
- ${requireRoleImport}${dashboardDbImport}${dashboardMeta}${middlewares}
718
+ ${dashboardDbImport}${dashboardMeta}${middlewares}
670
719
  export const GET = async (_req, res) => {
671
720
  ${roleGuard} const [totalUsers, activeUsers, verifiedUsers] = await Promise.all([
672
721
  User.count({}),
@@ -676,14 +725,14 @@ ${roleGuard} const [totalUsers, activeUsers, verifiedUsers] = await Promise.all
676
725
  res.json({ stats: { totalUsers, activeUsers, verifiedUsers } });
677
726
  };
678
727
  ` : ts ? `import { requireAuth } from 'express-file-cluster/auth';
679
- ${requireRoleImport}import type { Request, Response } from 'express';
728
+ import type { Request, Response } from 'express';
680
729
  ${dashboardMeta}${middlewares}
681
730
  export const GET = async (_req: Request, res: Response) => {
682
731
  ${roleGuard} // TODO: aggregate stats from DB
683
732
  res.json({ stats: { users: 0 } });
684
733
  };
685
734
  ` : `import { requireAuth } from 'express-file-cluster/auth';
686
- ${requireRoleImport}${dashboardMeta}${middlewares}
735
+ ${dashboardMeta}${middlewares}
687
736
  export const GET = async (_req, res) => {
688
737
  ${roleGuard} // TODO: aggregate stats from DB
689
738
  res.json({ stats: { users: 0 } });
@@ -709,7 +758,7 @@ import { User } from '../../../model/User.js';
709
758
  import { User } from '../../../model/User.js';
710
759
  ` : "";
711
760
  const usersListContent = opts.database === "mongodb" ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
712
- ${usersRequireRoleImport}import type { Request, Response } from 'express';
761
+ import type { Request, Response } from 'express';
713
762
  ${adminUsersDbImport}${usersListMeta}${middlewares}
714
763
  export const GET = async (req: Request, res: Response) => {
715
764
  ${roleGuard} const page = Math.max(1, Number(req.query.page) || 1);
@@ -730,7 +779,7 @@ ${roleGuard} const { name, email, password, role } = req.body;
730
779
  res.status(201).json({ message: 'User created', user: safe });
731
780
  };
732
781
  ` : `import { requireAuth } from 'express-file-cluster/auth';
733
- ${usersRequireRoleImport}${adminUsersDbImport}${usersListMeta}${middlewares}
782
+ ${adminUsersDbImport}${usersListMeta}${middlewares}
734
783
  export const GET = async (req, res) => {
735
784
  ${roleGuard} const page = Math.max(1, Number(req.query.page) || 1);
736
785
  const limit = Math.min(100, Number(req.query.limit) || 20);
@@ -750,7 +799,7 @@ ${roleGuard} const { name, email, password, role } = req.body;
750
799
  res.status(201).json({ message: 'User created', user: safe });
751
800
  };
752
801
  ` : ts ? `import { requireAuth } from 'express-file-cluster/auth';
753
- ${usersRequireRoleImport}import type { Request, Response } from 'express';
802
+ import type { Request, Response } from 'express';
754
803
  ${usersListMeta}${middlewares}
755
804
  export const GET = async (_req: Request, res: Response) => {
756
805
  ${roleGuard} // TODO: fetch users from DB with pagination
@@ -764,7 +813,7 @@ ${roleGuard} const { name, email, role } = req.body;
764
813
  res.status(201).json({ message: 'User created', user: { id: 'new-id', name, email, role: role ?? 'user' } });
765
814
  };
766
815
  ` : `import { requireAuth } from 'express-file-cluster/auth';
767
- ${usersRequireRoleImport}${usersListMeta}${middlewares}
816
+ ${usersListMeta}${middlewares}
768
817
  export const GET = async (_req, res) => {
769
818
  ${roleGuard} // TODO: fetch users from DB with pagination
770
819
  res.json({ users: [], total: 0 });
@@ -792,7 +841,7 @@ export const meta: RouteMeta = {
792
841
  const adminUserByIdDbImport = opts.database === "mongodb" ? `import { User } from '../../../model/User.js';
793
842
  ` : "";
794
843
  const userByIdContent = opts.database === "mongodb" ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
795
- ${usersRequireRoleImport}import type { Request, Response } from 'express';
844
+ import type { Request, Response } from 'express';
796
845
  ${adminUserByIdDbImport}${userByIdMeta}${middlewares}
797
846
  export const GET = async (req: Request, res: Response) => {
798
847
  ${roleGuard} const { id } = req.params;
@@ -819,7 +868,7 @@ ${roleGuard} const { id } = req.params;
819
868
  res.json({ message: \`User \${id} deleted\` });
820
869
  };
821
870
  ` : `import { requireAuth } from 'express-file-cluster/auth';
822
- ${usersRequireRoleImport}${adminUserByIdDbImport}${userByIdMeta}${middlewares}
871
+ ${adminUserByIdDbImport}${userByIdMeta}${middlewares}
823
872
  export const GET = async (req, res) => {
824
873
  ${roleGuard} const { id } = req.params;
825
874
  const user = await User.findById(id);
@@ -845,7 +894,7 @@ ${roleGuard} const { id } = req.params;
845
894
  res.json({ message: \`User \${id} deleted\` });
846
895
  };
847
896
  ` : ts ? `import { requireAuth } from 'express-file-cluster/auth';
848
- ${usersRequireRoleImport}import type { Request, Response } from 'express';
897
+ import type { Request, Response } from 'express';
849
898
  ${userByIdMeta}${middlewares}
850
899
  export const GET = async (req: Request, res: Response) => {
851
900
  ${roleGuard} const { id } = req.params;
@@ -865,7 +914,7 @@ ${roleGuard} const { id } = req.params;
865
914
  res.json({ message: \`User \${id} deleted\` });
866
915
  };
867
916
  ` : `import { requireAuth } from 'express-file-cluster/auth';
868
- ${usersRequireRoleImport}${userByIdMeta}${middlewares}
917
+ ${userByIdMeta}${middlewares}
869
918
  export const GET = async (req, res) => {
870
919
  ${roleGuard} const { id } = req.params;
871
920
  // TODO: fetch user from DB
@@ -889,9 +938,7 @@ ${roleGuard} const { id } = req.params;
889
938
  async function writeUserRoutes(dest, opts) {
890
939
  const ext = opts.language === "typescript" ? "ts" : "js";
891
940
  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')];
941
+ const middlewares = opts.rbac ? `export const middlewares = [requireAuth('user', 'admin')];
895
942
  ` : `export const middlewares = [requireAuth];
896
943
  `;
897
944
  const profileMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
@@ -910,7 +957,7 @@ export const meta: RouteMeta = {
910
957
  const profileDbImport = opts.database === "mongodb" ? `import { User } from '../../model/User.js';
911
958
  ` : "";
912
959
  const profileContent = opts.database === "mongodb" ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
913
- ${requireRoleImport}import type { Request, Response } from 'express';
960
+ import type { Request, Response } from 'express';
914
961
  ${profileDbImport}${profileMeta}${middlewares}
915
962
  export const GET = async (req: Request, res: Response) => {
916
963
  const { id } = (req as any).user;
@@ -929,7 +976,7 @@ export const PUT = async (req: Request, res: Response) => {
929
976
  res.json({ message: 'Profile updated', user: safe });
930
977
  };
931
978
  ` : `import { requireAuth } from 'express-file-cluster/auth';
932
- ${requireRoleImport}${profileDbImport}${profileMeta}${middlewares}
979
+ ${profileDbImport}${profileMeta}${middlewares}
933
980
  export const GET = async (req, res) => {
934
981
  const { id } = req.user;
935
982
  const user = await User.findById(id);
@@ -947,7 +994,7 @@ export const PUT = async (req, res) => {
947
994
  res.json({ message: 'Profile updated', user: safe });
948
995
  };
949
996
  ` : ts ? `import { requireAuth } from 'express-file-cluster/auth';
950
- ${requireRoleImport}import type { Request, Response } from 'express';
997
+ import type { Request, Response } from 'express';
951
998
  ${profileMeta}${middlewares}
952
999
  export const GET = async (req: Request, res: Response) => {
953
1000
  res.json({ user: (req as any).user });
@@ -959,7 +1006,7 @@ export const PUT = async (req: Request, res: Response) => {
959
1006
  res.json({ message: 'Profile updated', user: { ...(req as any).user, name, email } });
960
1007
  };
961
1008
  ` : `import { requireAuth } from 'express-file-cluster/auth';
962
- ${requireRoleImport}${profileMeta}${middlewares}
1009
+ ${profileMeta}${middlewares}
963
1010
  export const GET = async (req, res) => {
964
1011
  res.json({ user: req.user });
965
1012
  };
@@ -1700,11 +1747,7 @@ export {};
1700
1747
  async function writeAuthExtendedRoutes(dest, opts) {
1701
1748
  const ext = opts.language === "typescript" ? "ts" : "js";
1702
1749
  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')];
1750
+ const mwUser2 = opts.rbac ? `export const middlewares = [requireAuth('user', 'admin')];
1708
1751
  ` : `export const middlewares = [requireAuth];
1709
1752
  `;
1710
1753
  const mwUser3 = mwUser2;
@@ -1712,8 +1755,94 @@ async function writeAuthExtendedRoutes(dest, opts) {
1712
1755
  ` : "";
1713
1756
  const RA = `import { requireAuth } from 'express-file-cluster/auth';
1714
1757
  `;
1758
+ const mongo = opts.database === "mongodb";
1759
+ const sendResetEmail = opts.mailer ? ` var appUrl = process.env.APP_URL || 'http://localhost:3000';
1760
+ await enqueue('SendEmail', {
1761
+ to: email,
1762
+ subject: 'Reset your password',
1763
+ body: 'Reset your password: ' + appUrl + '/auth/reset-password?token=' + resetToken,
1764
+ });
1765
+ ` : ` // TODO: email this token to the user \u2014 enable the Mailer feature to auto-wire SendEmail
1766
+ `;
1767
+ const sendVerifyEmailResend = opts.mailer ? ` var appUrl = process.env.APP_URL || 'http://localhost:3000';
1768
+ await enqueue('SendEmail', {
1769
+ to: email,
1770
+ subject: 'Verify your email address',
1771
+ body: 'Verify your email: ' + appUrl + '/auth/verify-email?token=' + verifyToken,
1772
+ });
1773
+ ` : ` // TODO: email this token to the user \u2014 enable the Mailer feature to auto-wire SendEmail
1774
+ `;
1775
+ const mailerTaskImport = opts.mailer ? `import { enqueue } from 'express-file-cluster/tasks';
1776
+ ` : "";
1715
1777
  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';
1778
+ const refreshContent = mongo ? ts ? `import { issueToken } from 'express-file-cluster/auth';
1779
+ import type { Request, Response } from 'express';
1780
+ import crypto from 'node:crypto';
1781
+ import { User } from '../../model/User.js';
1782
+ import { Admin } from '../../model/Admin.js';
1783
+ ${refreshMeta}const REFRESH_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
1784
+
1785
+ export const POST = async (req: Request, res: Response) => {
1786
+ const token = req.cookies?.['efc_refresh_token'] || req.body?.refreshToken;
1787
+ if (!token) return res.status(401).json({ error: 'Refresh token required' });
1788
+
1789
+ const user = await User.findOne({ refreshToken: token });
1790
+ const admin = user ? null : await Admin.findOne({ refreshToken: token });
1791
+ const account = user || admin;
1792
+
1793
+ if (!account || !account.refreshTokenExpiry || new Date(account.refreshTokenExpiry) < new Date()) {
1794
+ return res.status(401).json({ error: 'Invalid or expired refresh token' });
1795
+ }
1796
+
1797
+ const newRefreshToken = crypto.randomBytes(40).toString('hex');
1798
+ const refreshTokenExpiry = new Date(Date.now() + REFRESH_TOKEN_TTL_MS);
1799
+ if (user) await User.update(user.id, { refreshToken: newRefreshToken, refreshTokenExpiry });
1800
+ else if (admin) await Admin.update(admin.id, { refreshToken: newRefreshToken, refreshTokenExpiry });
1801
+
1802
+ res.cookie('efc_refresh_token', newRefreshToken, {
1803
+ httpOnly: true,
1804
+ secure: process.env.NODE_ENV === 'production',
1805
+ sameSite: 'strict',
1806
+ maxAge: REFRESH_TOKEN_TTL_MS,
1807
+ });
1808
+
1809
+ await issueToken(res, { id: account.id, role: account.role, email: account.email });
1810
+ res.json({ message: 'Token refreshed' });
1811
+ };
1812
+ ` : `import { issueToken } from 'express-file-cluster/auth';
1813
+ import crypto from 'node:crypto';
1814
+ import { User } from '../../model/User.js';
1815
+ import { Admin } from '../../model/Admin.js';
1816
+ ${refreshMeta}const REFRESH_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
1817
+
1818
+ export const POST = async (req, res) => {
1819
+ const token = req.cookies?.efc_refresh_token || req.body?.refreshToken;
1820
+ if (!token) return res.status(401).json({ error: 'Refresh token required' });
1821
+
1822
+ const user = await User.findOne({ refreshToken: token });
1823
+ const admin = user ? null : await Admin.findOne({ refreshToken: token });
1824
+ const account = user || admin;
1825
+
1826
+ if (!account || !account.refreshTokenExpiry || new Date(account.refreshTokenExpiry) < new Date()) {
1827
+ return res.status(401).json({ error: 'Invalid or expired refresh token' });
1828
+ }
1829
+
1830
+ const newRefreshToken = crypto.randomBytes(40).toString('hex');
1831
+ const refreshTokenExpiry = new Date(Date.now() + REFRESH_TOKEN_TTL_MS);
1832
+ if (user) await User.update(user.id, { refreshToken: newRefreshToken, refreshTokenExpiry });
1833
+ else if (admin) await Admin.update(admin.id, { refreshToken: newRefreshToken, refreshTokenExpiry });
1834
+
1835
+ res.cookie('efc_refresh_token', newRefreshToken, {
1836
+ httpOnly: true,
1837
+ secure: process.env.NODE_ENV === 'production',
1838
+ sameSite: 'strict',
1839
+ maxAge: REFRESH_TOKEN_TTL_MS,
1840
+ });
1841
+
1842
+ await issueToken(res, { id: account.id, role: account.role, email: account.email });
1843
+ res.json({ message: 'Token refreshed' });
1844
+ };
1845
+ ` : ts ? `${RA}import type { Request, Response } from 'express';
1717
1846
  ${refreshMeta}export const POST = async (_req: Request, res: Response) => {
1718
1847
  // TODO: validate refresh token, issue new JWT
1719
1848
  res.json({ message: 'Token refreshed' });
@@ -1725,7 +1854,58 @@ ${refreshMeta}export const POST = async (_req: Request, res: Response) => {
1725
1854
  `;
1726
1855
  await fs.outputFile(path.join(dest, "src", "api", "auth", `refresh.${ext}`), refreshContent);
1727
1856
  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';
1857
+ const veContent = mongo ? ts ? `import type { Request, Response } from 'express';
1858
+ import crypto from 'node:crypto';
1859
+ ${mailerTaskImport}import { User } from '../../model/User.js';
1860
+ ${veMeta}export const GET = async (req: Request, res: Response) => {
1861
+ const { token } = req.query;
1862
+ if (!token || typeof token !== 'string') return res.status(400).json({ error: 'token is required' });
1863
+
1864
+ const user = await User.findOne({ verifyToken: token });
1865
+ if (!user) return res.status(400).json({ error: 'Invalid or expired verification token' });
1866
+
1867
+ await User.update(user.id, { isVerified: true, verifyToken: '' });
1868
+ res.json({ message: 'Email verified' });
1869
+ };
1870
+
1871
+ export const POST = async (req: Request, res: Response) => {
1872
+ const { email } = req.body;
1873
+ if (!email) return res.status(400).json({ error: 'email is required' });
1874
+
1875
+ const user = await User.findOne({ email });
1876
+ if (user && !user.isVerified) {
1877
+ const verifyToken = crypto.randomBytes(32).toString('hex');
1878
+ await User.update(user.id, { verifyToken });
1879
+ ${sendVerifyEmailResend} }
1880
+
1881
+ res.json({ message: 'Verification email sent' });
1882
+ };
1883
+ ` : `import crypto from 'node:crypto';
1884
+ ${mailerTaskImport}import { User } from '../../model/User.js';
1885
+ ${veMeta}export const GET = async (req, res) => {
1886
+ const { token } = req.query;
1887
+ if (!token || typeof token !== 'string') return res.status(400).json({ error: 'token is required' });
1888
+
1889
+ const user = await User.findOne({ verifyToken: token });
1890
+ if (!user) return res.status(400).json({ error: 'Invalid or expired verification token' });
1891
+
1892
+ await User.update(user.id, { isVerified: true, verifyToken: '' });
1893
+ res.json({ message: 'Email verified' });
1894
+ };
1895
+
1896
+ export const POST = async (req, res) => {
1897
+ const { email } = req.body;
1898
+ if (!email) return res.status(400).json({ error: 'email is required' });
1899
+
1900
+ const user = await User.findOne({ email });
1901
+ if (user && !user.isVerified) {
1902
+ const verifyToken = crypto.randomBytes(32).toString('hex');
1903
+ await User.update(user.id, { verifyToken });
1904
+ ${sendVerifyEmailResend} }
1905
+
1906
+ res.json({ message: 'Verification email sent' });
1907
+ };
1908
+ ` : ts ? `${RA}import type { Request, Response } from 'express';
1729
1909
  ${veMeta}export const GET = async (req: Request, res: Response) => {
1730
1910
  const { token } = req.query;
1731
1911
  // TODO: verify token and mark user as verified
@@ -1749,7 +1929,54 @@ export const POST = async (_req, res) => {
1749
1929
  `;
1750
1930
  await fs.outputFile(path.join(dest, "src", "api", "auth", `verify-email.${ext}`), veContent);
1751
1931
  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';
1932
+ const fpContent = mongo ? ts ? `import type { Request, Response } from 'express';
1933
+ import crypto from 'node:crypto';
1934
+ ${mailerTaskImport}import { User } from '../../model/User.js';
1935
+ import { Admin } from '../../model/Admin.js';
1936
+ ${fpMeta}const RESET_TOKEN_TTL_MS = 1000 * 60 * 60; // 1 hour
1937
+
1938
+ export const POST = async (req: Request, res: Response) => {
1939
+ const { email } = req.body;
1940
+ if (!email) return res.status(400).json({ error: 'email is required' });
1941
+
1942
+ const user = await User.findOne({ email });
1943
+ const admin = user ? null : await Admin.findOne({ email });
1944
+
1945
+ // Always respond the same way whether or not the account exists, so this
1946
+ // endpoint can't be used to enumerate registered emails.
1947
+ if (user || admin) {
1948
+ const resetToken = crypto.randomBytes(32).toString('hex');
1949
+ const resetTokenExpiry = new Date(Date.now() + RESET_TOKEN_TTL_MS);
1950
+ if (user) await User.update(user.id, { resetToken, resetTokenExpiry });
1951
+ else if (admin) await Admin.update(admin.id, { resetToken, resetTokenExpiry });
1952
+ ${sendResetEmail} }
1953
+
1954
+ res.json({ message: 'Reset email sent' });
1955
+ };
1956
+ ` : `import crypto from 'node:crypto';
1957
+ ${mailerTaskImport}import { User } from '../../model/User.js';
1958
+ import { Admin } from '../../model/Admin.js';
1959
+ ${fpMeta}const RESET_TOKEN_TTL_MS = 1000 * 60 * 60; // 1 hour
1960
+
1961
+ export const POST = async (req, res) => {
1962
+ const { email } = req.body;
1963
+ if (!email) return res.status(400).json({ error: 'email is required' });
1964
+
1965
+ const user = await User.findOne({ email });
1966
+ const admin = user ? null : await Admin.findOne({ email });
1967
+
1968
+ // Always respond the same way whether or not the account exists, so this
1969
+ // endpoint can't be used to enumerate registered emails.
1970
+ if (user || admin) {
1971
+ const resetToken = crypto.randomBytes(32).toString('hex');
1972
+ const resetTokenExpiry = new Date(Date.now() + RESET_TOKEN_TTL_MS);
1973
+ if (user) await User.update(user.id, { resetToken, resetTokenExpiry });
1974
+ else if (admin) await Admin.update(admin.id, { resetToken, resetTokenExpiry });
1975
+ ${sendResetEmail} }
1976
+
1977
+ res.json({ message: 'Reset email sent' });
1978
+ };
1979
+ ` : ts ? `${RA}import type { Request, Response } from 'express';
1753
1980
  ${fpMeta}export const POST = async (req: Request, res: Response) => {
1754
1981
  const { email } = req.body;
1755
1982
  if (!email) return res.status(400).json({ error: 'email is required' });
@@ -1765,7 +1992,50 @@ ${fpMeta}export const POST = async (req: Request, res: Response) => {
1765
1992
  `;
1766
1993
  await fs.outputFile(path.join(dest, "src", "api", "auth", `forgot-password.${ext}`), fpContent);
1767
1994
  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';
1995
+ const rpContent = mongo ? ts ? `import type { Request, Response } from 'express';
1996
+ import bcrypt from 'bcrypt';
1997
+ import { User } from '../../model/User.js';
1998
+ import { Admin } from '../../model/Admin.js';
1999
+ ${rpMeta}export const POST = async (req: Request, res: Response) => {
2000
+ const { token, password } = req.body;
2001
+ if (!token || !password) return res.status(400).json({ error: 'token and password are required' });
2002
+
2003
+ const user = await User.findOne({ resetToken: token });
2004
+ const admin = user ? null : await Admin.findOne({ resetToken: token });
2005
+ const account = user || admin;
2006
+
2007
+ if (!account || !account.resetTokenExpiry || new Date(account.resetTokenExpiry) < new Date()) {
2008
+ return res.status(400).json({ error: 'Invalid or expired reset token' });
2009
+ }
2010
+
2011
+ const hashed = await bcrypt.hash(password, 10);
2012
+ if (user) await User.update(user.id, { password: hashed, resetToken: '' });
2013
+ else if (admin) await Admin.update(admin.id, { password: hashed, resetToken: '' });
2014
+
2015
+ res.json({ message: 'Password reset successfully' });
2016
+ };
2017
+ ` : `import bcrypt from 'bcrypt';
2018
+ import { User } from '../../model/User.js';
2019
+ import { Admin } from '../../model/Admin.js';
2020
+ ${rpMeta}export const POST = async (req, res) => {
2021
+ const { token, password } = req.body;
2022
+ if (!token || !password) return res.status(400).json({ error: 'token and password are required' });
2023
+
2024
+ const user = await User.findOne({ resetToken: token });
2025
+ const admin = user ? null : await Admin.findOne({ resetToken: token });
2026
+ const account = user || admin;
2027
+
2028
+ if (!account || !account.resetTokenExpiry || new Date(account.resetTokenExpiry) < new Date()) {
2029
+ return res.status(400).json({ error: 'Invalid or expired reset token' });
2030
+ }
2031
+
2032
+ const hashed = await bcrypt.hash(password, 10);
2033
+ if (user) await User.update(user.id, { password: hashed, resetToken: '' });
2034
+ else if (admin) await Admin.update(admin.id, { password: hashed, resetToken: '' });
2035
+
2036
+ res.json({ message: 'Password reset successfully' });
2037
+ };
2038
+ ` : ts ? `${RA}import type { Request, Response } from 'express';
1769
2039
  ${rpMeta}export const POST = async (req: Request, res: Response) => {
1770
2040
  const { token, password } = req.body;
1771
2041
  if (!token || !password) return res.status(400).json({ error: 'token and password are required' });
@@ -1781,7 +2051,7 @@ ${rpMeta}export const POST = async (req: Request, res: Response) => {
1781
2051
  `;
1782
2052
  await fs.outputFile(path.join(dest, "src", "api", "auth", `reset-password.${ext}`), rpContent);
1783
2053
  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';
2054
+ const cpContent = ts ? `${RA}import type { Request, Response } from 'express';
1785
2055
  ${cpMeta}${mwUser2}
1786
2056
  export const POST = async (req: Request, res: Response) => {
1787
2057
  const { oldPassword, newPassword } = req.body;
@@ -1789,7 +2059,7 @@ export const POST = async (req: Request, res: Response) => {
1789
2059
  // TODO: verify old password, hash and update new password
1790
2060
  res.json({ message: 'Password changed successfully' });
1791
2061
  };
1792
- ` : `${RA}${rr2}${cpMeta}${mwUser2}
2062
+ ` : `${RA}${cpMeta}${mwUser2}
1793
2063
  export const POST = async (req, res) => {
1794
2064
  const { oldPassword, newPassword } = req.body;
1795
2065
  if (!oldPassword || !newPassword) return res.status(400).json({ error: 'oldPassword and newPassword are required' });
@@ -1799,7 +2069,7 @@ export const POST = async (req, res) => {
1799
2069
  `;
1800
2070
  await fs.outputFile(path.join(dest, "src", "api", "auth", `change-password.${ext}`), cpContent);
1801
2071
  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';
2072
+ const tfaSetupContent = ts ? `${RA}import type { Request, Response } from 'express';
1803
2073
  ${tfaSetupMeta}${mwUser3}
1804
2074
  export const GET = async (req: Request, res: Response) => {
1805
2075
  // TODO: generate TOTP secret and return QR code URL
@@ -1812,7 +2082,7 @@ export const POST = async (req: Request, res: Response) => {
1812
2082
  // TODO: verify TOTP code and enable 2FA
1813
2083
  res.json({ message: '2FA enabled' });
1814
2084
  };
1815
- ` : `${RA}${rr3}${tfaSetupMeta}${mwUser3}
2085
+ ` : `${RA}${tfaSetupMeta}${mwUser3}
1816
2086
  export const GET = async (req, res) => {
1817
2087
  // TODO: generate TOTP secret and return QR code URL
1818
2088
  res.json({ qrCode: 'otpauth://totp/...', secret: 'BASE32SECRET' });
@@ -1843,7 +2113,7 @@ ${tfaVerifyMeta}export const POST = async (req: Request, res: Response) => {
1843
2113
  `;
1844
2114
  await fs.outputFile(path.join(dest, "src", "api", "auth", "2fa", `verify.${ext}`), tfaVerifyContent);
1845
2115
  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';
2116
+ const tfaDisableContent = ts ? `${RA}import type { Request, Response } from 'express';
1847
2117
  ${tfaDisableMeta}${mwUser3}
1848
2118
  export const POST = async (req: Request, res: Response) => {
1849
2119
  const { code } = req.body;
@@ -1851,7 +2121,7 @@ export const POST = async (req: Request, res: Response) => {
1851
2121
  // TODO: verify code and disable 2FA
1852
2122
  res.json({ message: '2FA disabled' });
1853
2123
  };
1854
- ` : `${RA}${rr3}${tfaDisableMeta}${mwUser3}
2124
+ ` : `${RA}${tfaDisableMeta}${mwUser3}
1855
2125
  export const POST = async (req, res) => {
1856
2126
  const { code } = req.body;
1857
2127
  if (!code) return res.status(400).json({ error: 'code is required' });
@@ -1861,27 +2131,27 @@ export const POST = async (req, res) => {
1861
2131
  `;
1862
2132
  await fs.outputFile(path.join(dest, "src", "api", "auth", "2fa", `disable.${ext}`), tfaDisableContent);
1863
2133
  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';
2134
+ const sessListContent = ts ? `${RA}import type { Request, Response } from 'express';
1865
2135
  ${sessListMeta}${mwUser3}
1866
2136
  export const GET = async (req: Request, res: Response) => {
1867
2137
  // TODO: fetch sessions for req.user.id
1868
2138
  res.json({ sessions: [] });
1869
2139
  };
1870
- ` : `${RA}${rr3}${sessListMeta}${mwUser3}
2140
+ ` : `${RA}${sessListMeta}${mwUser3}
1871
2141
  export const GET = async (req, res) => {
1872
2142
  // TODO: fetch sessions for req.user.id
1873
2143
  res.json({ sessions: [] });
1874
2144
  };
1875
2145
  `;
1876
2146
  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';
2147
+ const sessRevokeContent = ts ? `${RA}import type { Request, Response } from 'express';
1878
2148
  ${mwUser3}
1879
2149
  export const DELETE = async (req: Request, res: Response) => {
1880
2150
  const { id } = req.params;
1881
2151
  // TODO: revoke session by id
1882
2152
  res.json({ message: \`Session \${id} revoked\` });
1883
2153
  };
1884
- ` : `${RA}${rr3}${mwUser3}
2154
+ ` : `${RA}${mwUser3}
1885
2155
  export const DELETE = async (req, res) => {
1886
2156
  const { id } = req.params;
1887
2157
  // TODO: revoke session by id
@@ -1893,11 +2163,7 @@ export const DELETE = async (req, res) => {
1893
2163
  async function writeUserExtendedRoutes(dest, opts) {
1894
2164
  const ext = opts.language === "typescript" ? "ts" : "js";
1895
2165
  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')];
2166
+ const mw2 = opts.rbac ? `export const middlewares = [requireAuth('user', 'admin')];
1901
2167
  ` : `export const middlewares = [requireAuth];
1902
2168
  `;
1903
2169
  const mw3 = mw2;
@@ -1905,7 +2171,7 @@ async function writeUserExtendedRoutes(dest, opts) {
1905
2171
  `;
1906
2172
  const user = ts ? `(req as any).user` : `req.user`;
1907
2173
  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';
2174
+ const avatarContent = ts ? `${RA}import type { Request, Response } from 'express';
1909
2175
  ${avatarMeta}${mw2}
1910
2176
  export const POST = async (req: Request, res: Response) => {
1911
2177
  // TODO: handle multipart upload, store file, update user.avatar
@@ -1916,7 +2182,7 @@ export const DELETE = async (req: Request, res: Response) => {
1916
2182
  // TODO: remove avatar and clear user.avatar
1917
2183
  res.json({ message: 'Avatar removed' });
1918
2184
  };
1919
- ` : `${RA}${rr2}${avatarMeta}${mw2}
2185
+ ` : `${RA}${avatarMeta}${mw2}
1920
2186
  export const POST = async (req, res) => {
1921
2187
  // TODO: handle multipart upload, store file, update user.avatar
1922
2188
  res.json({ message: 'Avatar updated', url: 'https://example.com/avatar.jpg' });
@@ -1929,7 +2195,7 @@ export const DELETE = async (req, res) => {
1929
2195
  `;
1930
2196
  await fs.outputFile(path.join(dest, "src", "api", "user", `avatar.${ext}`), avatarContent);
1931
2197
  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';
2198
+ const settingsContent = ts ? `${RA}import type { Request, Response } from 'express';
1933
2199
  ${settingsMeta}${mw2}
1934
2200
  export const GET = async (req: Request, res: Response) => {
1935
2201
  // TODO: fetch user settings from DB
@@ -1941,7 +2207,7 @@ export const PUT = async (req: Request, res: Response) => {
1941
2207
  // TODO: update user settings in DB
1942
2208
  res.json({ message: 'Settings updated', settings: { notifications, language, theme, privacy } });
1943
2209
  };
1944
- ` : `${RA}${rr2}${settingsMeta}${mw2}
2210
+ ` : `${RA}${settingsMeta}${mw2}
1945
2211
  export const GET = async (req, res) => {
1946
2212
  // TODO: fetch user settings from DB
1947
2213
  res.json({ settings: { notifications: true, language: 'en', theme: 'system', privacy: 'public' } });
@@ -1955,7 +2221,7 @@ export const PUT = async (req, res) => {
1955
2221
  `;
1956
2222
  await fs.outputFile(path.join(dest, "src", "api", "user", `settings.${ext}`), settingsContent);
1957
2223
  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';
2224
+ const accountContent = ts ? `${RA}import type { Request, Response } from 'express';
1959
2225
  ${accountMeta}${mw2}
1960
2226
  export const GET = async (req: Request, res: Response) => {
1961
2227
  // TODO: compile and return personal data export
@@ -1968,7 +2234,7 @@ export const DELETE = async (req: Request, res: Response) => {
1968
2234
  // TODO: verify password, schedule account deletion
1969
2235
  res.json({ message: 'Account scheduled for deletion' });
1970
2236
  };
1971
- ` : `${RA}${rr2}${accountMeta}${mw2}
2237
+ ` : `${RA}${accountMeta}${mw2}
1972
2238
  export const GET = async (req, res) => {
1973
2239
  // TODO: compile and return personal data export
1974
2240
  res.json({ data: { user: ${user}, exportedAt: new Date().toISOString() } });
@@ -1983,13 +2249,13 @@ export const DELETE = async (req, res) => {
1983
2249
  `;
1984
2250
  await fs.outputFile(path.join(dest, "src", "api", "user", `account.${ext}`), accountContent);
1985
2251
  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';
2252
+ const dashContent = ts ? `${RA}import type { Request, Response } from 'express';
1987
2253
  ${dashMeta}${mw2}
1988
2254
  export const GET = async (req: Request, res: Response) => {
1989
2255
  // TODO: aggregate personal stats and activity
1990
2256
  res.json({ stats: {}, recentActivity: [], quickActions: [] });
1991
2257
  };
1992
- ` : `${RA}${rr2}${dashMeta}${mw2}
2258
+ ` : `${RA}${dashMeta}${mw2}
1993
2259
  export const GET = async (req, res) => {
1994
2260
  // TODO: aggregate personal stats and activity
1995
2261
  res.json({ stats: {}, recentActivity: [], quickActions: [] });
@@ -1997,14 +2263,14 @@ export const GET = async (req, res) => {
1997
2263
  `;
1998
2264
  await fs.outputFile(path.join(dest, "src", "api", "user", `dashboard.${ext}`), dashContent);
1999
2265
  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';
2266
+ const actContent = ts ? `${RA}import type { Request, Response } from 'express';
2001
2267
  ${actMeta}${mw2}
2002
2268
  export const GET = async (req: Request, res: Response) => {
2003
2269
  const { page = 1, limit = 20 } = req.query;
2004
2270
  // TODO: fetch paginated activity log
2005
2271
  res.json({ activities: [], total: 0, page: Number(page), limit: Number(limit) });
2006
2272
  };
2007
- ` : `${RA}${rr2}${actMeta}${mw2}
2273
+ ` : `${RA}${actMeta}${mw2}
2008
2274
  export const GET = async (req, res) => {
2009
2275
  const { page = 1, limit = 20 } = req.query;
2010
2276
  // TODO: fetch paginated activity log
@@ -2013,7 +2279,7 @@ export const GET = async (req, res) => {
2013
2279
  `;
2014
2280
  await fs.outputFile(path.join(dest, "src", "api", "user", `activity.${ext}`), actContent);
2015
2281
  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';
2282
+ const notifListContent = ts ? `${RA}import type { Request, Response } from 'express';
2017
2283
  ${notifListMeta}${mw3}
2018
2284
  export const GET = async (req: Request, res: Response) => {
2019
2285
  // TODO: fetch notifications for user
@@ -2024,7 +2290,7 @@ export const POST = async (req: Request, res: Response) => {
2024
2290
  // TODO: mark all notifications as read
2025
2291
  res.json({ message: 'All notifications marked as read' });
2026
2292
  };
2027
- ` : `${RA}${rr3}${notifListMeta}${mw3}
2293
+ ` : `${RA}${notifListMeta}${mw3}
2028
2294
  export const GET = async (req, res) => {
2029
2295
  // TODO: fetch notifications for user
2030
2296
  res.json({ notifications: [], total: 0, unread: 0 });
@@ -2036,7 +2302,7 @@ export const POST = async (req, res) => {
2036
2302
  };
2037
2303
  `;
2038
2304
  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';
2305
+ const notifByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2040
2306
  ${mw3}
2041
2307
  export const GET = async (req: Request, res: Response) => {
2042
2308
  const { id } = req.params;
@@ -2055,7 +2321,7 @@ export const DELETE = async (req: Request, res: Response) => {
2055
2321
  // TODO: delete notification
2056
2322
  res.json({ message: \`Notification \${id} deleted\` });
2057
2323
  };
2058
- ` : `${RA}${rr3}${mw3}
2324
+ ` : `${RA}${mw3}
2059
2325
  export const GET = async (req, res) => {
2060
2326
  const { id } = req.params;
2061
2327
  // TODO: fetch notification by id
@@ -2076,7 +2342,7 @@ export const DELETE = async (req, res) => {
2076
2342
  `;
2077
2343
  await fs.outputFile(path.join(dest, "src", "api", "user", "notifications", `[id].${ext}`), notifByIdContent);
2078
2344
  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';
2345
+ const filesListContent = ts ? `${RA}import type { Request, Response } from 'express';
2080
2346
  ${filesListMeta}${mw3}
2081
2347
  export const GET = async (req: Request, res: Response) => {
2082
2348
  // TODO: fetch user files and compute storage usage
@@ -2087,7 +2353,7 @@ export const POST = async (req: Request, res: Response) => {
2087
2353
  // TODO: handle multipart upload, persist file record
2088
2354
  res.status(201).json({ message: 'File uploaded', file: { id: 'new-id' } });
2089
2355
  };
2090
- ` : `${RA}${rr3}${filesListMeta}${mw3}
2356
+ ` : `${RA}${filesListMeta}${mw3}
2091
2357
  export const GET = async (req, res) => {
2092
2358
  // TODO: fetch user files and compute storage usage
2093
2359
  res.json({ files: [], total: 0, storageUsed: 0 });
@@ -2099,7 +2365,7 @@ export const POST = async (req, res) => {
2099
2365
  };
2100
2366
  `;
2101
2367
  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';
2368
+ const fileByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2103
2369
  ${mw3}
2104
2370
  export const GET = async (req: Request, res: Response) => {
2105
2371
  const { id } = req.params;
@@ -2112,7 +2378,7 @@ export const DELETE = async (req: Request, res: Response) => {
2112
2378
  // TODO: delete file from storage and DB
2113
2379
  res.json({ message: \`File \${id} deleted\` });
2114
2380
  };
2115
- ` : `${RA}${rr3}${mw3}
2381
+ ` : `${RA}${mw3}
2116
2382
  export const GET = async (req, res) => {
2117
2383
  const { id } = req.params;
2118
2384
  // TODO: stream or redirect to file download/preview URL
@@ -2126,7 +2392,7 @@ export const DELETE = async (req, res) => {
2126
2392
  };
2127
2393
  `;
2128
2394
  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';
2395
+ const favListContent = ts ? `${RA}import type { Request, Response } from 'express';
2130
2396
  ${mw3}
2131
2397
  export const GET = async (req: Request, res: Response) => {
2132
2398
  // TODO: fetch user favorites
@@ -2139,7 +2405,7 @@ export const POST = async (req: Request, res: Response) => {
2139
2405
  // TODO: add to favorites
2140
2406
  res.status(201).json({ message: 'Added to favorites' });
2141
2407
  };
2142
- ` : `${RA}${rr3}${mw3}
2408
+ ` : `${RA}${mw3}
2143
2409
  export const GET = async (req, res) => {
2144
2410
  // TODO: fetch user favorites
2145
2411
  res.json({ favorites: [] });
@@ -2153,14 +2419,14 @@ export const POST = async (req, res) => {
2153
2419
  };
2154
2420
  `;
2155
2421
  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';
2422
+ const favByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2157
2423
  ${mw3}
2158
2424
  export const DELETE = async (req: Request, res: Response) => {
2159
2425
  const { id } = req.params;
2160
2426
  // TODO: remove from favorites
2161
2427
  res.json({ message: \`Removed favorite \${id}\` });
2162
2428
  };
2163
- ` : `${RA}${rr3}${mw3}
2429
+ ` : `${RA}${mw3}
2164
2430
  export const DELETE = async (req, res) => {
2165
2431
  const { id } = req.params;
2166
2432
  // TODO: remove from favorites
@@ -2168,7 +2434,7 @@ export const DELETE = async (req, res) => {
2168
2434
  };
2169
2435
  `;
2170
2436
  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';
2437
+ const bkListContent = ts ? `${RA}import type { Request, Response } from 'express';
2172
2438
  ${mw3}
2173
2439
  export const GET = async (req: Request, res: Response) => {
2174
2440
  // TODO: fetch user bookmarks
@@ -2181,7 +2447,7 @@ export const POST = async (req: Request, res: Response) => {
2181
2447
  // TODO: save bookmark
2182
2448
  res.status(201).json({ message: 'Bookmark saved' });
2183
2449
  };
2184
- ` : `${RA}${rr3}${mw3}
2450
+ ` : `${RA}${mw3}
2185
2451
  export const GET = async (req, res) => {
2186
2452
  // TODO: fetch user bookmarks
2187
2453
  res.json({ bookmarks: [] });
@@ -2195,14 +2461,14 @@ export const POST = async (req, res) => {
2195
2461
  };
2196
2462
  `;
2197
2463
  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';
2464
+ const bkByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2199
2465
  ${mw3}
2200
2466
  export const DELETE = async (req: Request, res: Response) => {
2201
2467
  const { id } = req.params;
2202
2468
  // TODO: delete bookmark
2203
2469
  res.json({ message: \`Bookmark \${id} removed\` });
2204
2470
  };
2205
- ` : `${RA}${rr3}${mw3}
2471
+ ` : `${RA}${mw3}
2206
2472
  export const DELETE = async (req, res) => {
2207
2473
  const { id } = req.params;
2208
2474
  // TODO: delete bookmark
@@ -2211,14 +2477,14 @@ export const DELETE = async (req, res) => {
2211
2477
  `;
2212
2478
  await fs.outputFile(path.join(dest, "src", "api", "user", "bookmarks", `[id].${ext}`), bkByIdContent);
2213
2479
  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';
2480
+ const searchContent = ts ? `${RA}import type { Request, Response } from 'express';
2215
2481
  ${searchMeta}${mw2}
2216
2482
  export const GET = async (req: Request, res: Response) => {
2217
2483
  const { q, filter, sort, page = 1, limit = 20 } = req.query;
2218
2484
  // TODO: implement search
2219
2485
  res.json({ results: [], total: 0, page: Number(page), limit: Number(limit) });
2220
2486
  };
2221
- ` : `${RA}${rr2}${searchMeta}${mw2}
2487
+ ` : `${RA}${searchMeta}${mw2}
2222
2488
  export const GET = async (req, res) => {
2223
2489
  const { q, filter, sort, page = 1, limit = 20 } = req.query;
2224
2490
  // TODO: implement search
@@ -2227,7 +2493,7 @@ export const GET = async (req, res) => {
2227
2493
  `;
2228
2494
  await fs.outputFile(path.join(dest, "src", "api", "user", `search.${ext}`), searchContent);
2229
2495
  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';
2496
+ const akListContent = ts ? `${RA}import type { Request, Response } from 'express';
2231
2497
  ${akListMeta}${mw3}
2232
2498
  export const GET = async (req: Request, res: Response) => {
2233
2499
  // TODO: fetch api keys for user
@@ -2240,7 +2506,7 @@ export const POST = async (req: Request, res: Response) => {
2240
2506
  // TODO: generate and store API key
2241
2507
  res.status(201).json({ message: 'API key created', key: 'efc_...' });
2242
2508
  };
2243
- ` : `${RA}${rr3}${akListMeta}${mw3}
2509
+ ` : `${RA}${akListMeta}${mw3}
2244
2510
  export const GET = async (req, res) => {
2245
2511
  // TODO: fetch api keys for user
2246
2512
  res.json({ apiKeys: [] });
@@ -2254,14 +2520,14 @@ export const POST = async (req, res) => {
2254
2520
  };
2255
2521
  `;
2256
2522
  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';
2523
+ const akByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2258
2524
  ${mw3}
2259
2525
  export const DELETE = async (req: Request, res: Response) => {
2260
2526
  const { id } = req.params;
2261
2527
  // TODO: revoke and delete API key
2262
2528
  res.json({ message: \`API key \${id} revoked\` });
2263
2529
  };
2264
- ` : `${RA}${rr3}${mw3}
2530
+ ` : `${RA}${mw3}
2265
2531
  export const DELETE = async (req, res) => {
2266
2532
  const { id } = req.params;
2267
2533
  // TODO: revoke and delete API key
@@ -2273,24 +2539,20 @@ export const DELETE = async (req, res) => {
2273
2539
  async function writeUserBillingRoutes(dest, opts) {
2274
2540
  const ext = opts.language === "typescript" ? "ts" : "js";
2275
2541
  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')];
2542
+ const mw3 = opts.rbac ? `export const middlewares = [requireAuth('user', 'admin')];
2281
2543
  ` : `export const middlewares = [requireAuth];
2282
2544
  `;
2283
2545
  const mw4 = mw3;
2284
2546
  const RA = `import { requireAuth } from 'express-file-cluster/auth';
2285
2547
  `;
2286
2548
  const plansMeta = mkMeta(opts, "List all available subscription plans.", `{ plans: [] }`);
2287
- const plansContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2549
+ const plansContent = ts ? `${RA}import type { Request, Response } from 'express';
2288
2550
  ${plansMeta}${mw3}
2289
2551
  export const GET = async (_req: Request, res: Response) => {
2290
2552
  // TODO: fetch active plans from DB
2291
2553
  res.json({ plans: [] });
2292
2554
  };
2293
- ` : `${RA}${rr3}${plansMeta}${mw3}
2555
+ ` : `${RA}${plansMeta}${mw3}
2294
2556
  export const GET = async (_req, res) => {
2295
2557
  // TODO: fetch active plans from DB
2296
2558
  res.json({ plans: [] });
@@ -2298,7 +2560,7 @@ export const GET = async (_req, res) => {
2298
2560
  `;
2299
2561
  await fs.outputFile(path.join(dest, "src", "api", "user", "billing", `plans.${ext}`), plansContent);
2300
2562
  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';
2563
+ const subContent = ts ? `${RA}import type { Request, Response } from 'express';
2302
2564
  ${subMeta}${mw3}
2303
2565
  export const GET = async (req: Request, res: Response) => {
2304
2566
  // TODO: fetch current subscription for user
@@ -2316,7 +2578,7 @@ export const DELETE = async (req: Request, res: Response) => {
2316
2578
  // TODO: cancel subscription at period end
2317
2579
  res.json({ message: 'Subscription cancelled' });
2318
2580
  };
2319
- ` : `${RA}${rr3}${subMeta}${mw3}
2581
+ ` : `${RA}${subMeta}${mw3}
2320
2582
  export const GET = async (req, res) => {
2321
2583
  // TODO: fetch current subscription for user
2322
2584
  res.json({ subscription: null });
@@ -2335,7 +2597,7 @@ export const DELETE = async (req, res) => {
2335
2597
  };
2336
2598
  `;
2337
2599
  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';
2600
+ const pmListContent = ts ? `${RA}import type { Request, Response } from 'express';
2339
2601
  ${mw4}
2340
2602
  export const GET = async (req: Request, res: Response) => {
2341
2603
  // TODO: fetch payment methods for user
@@ -2348,7 +2610,7 @@ export const POST = async (req: Request, res: Response) => {
2348
2610
  // TODO: attach payment method via payment gateway
2349
2611
  res.status(201).json({ message: 'Payment method added' });
2350
2612
  };
2351
- ` : `${RA}${rr4}${mw4}
2613
+ ` : `${RA}${mw4}
2352
2614
  export const GET = async (req, res) => {
2353
2615
  // TODO: fetch payment methods for user
2354
2616
  res.json({ paymentMethods: [] });
@@ -2362,14 +2624,14 @@ export const POST = async (req, res) => {
2362
2624
  };
2363
2625
  `;
2364
2626
  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';
2627
+ const pmByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2366
2628
  ${mw4}
2367
2629
  export const DELETE = async (req: Request, res: Response) => {
2368
2630
  const { id } = req.params;
2369
2631
  // TODO: detach payment method from payment gateway
2370
2632
  res.json({ message: \`Payment method \${id} removed\` });
2371
2633
  };
2372
- ` : `${RA}${rr4}${mw4}
2634
+ ` : `${RA}${mw4}
2373
2635
  export const DELETE = async (req, res) => {
2374
2636
  const { id } = req.params;
2375
2637
  // TODO: detach payment method from payment gateway
@@ -2378,27 +2640,27 @@ export const DELETE = async (req, res) => {
2378
2640
  `;
2379
2641
  await fs.outputFile(path.join(dest, "src", "api", "user", "billing", "payment-methods", `[id].${ext}`), pmByIdContent);
2380
2642
  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';
2643
+ const invListContent = ts ? `${RA}import type { Request, Response } from 'express';
2382
2644
  ${invListMeta}${mw4}
2383
2645
  export const GET = async (req: Request, res: Response) => {
2384
2646
  // TODO: fetch invoices for user
2385
2647
  res.json({ invoices: [], total: 0 });
2386
2648
  };
2387
- ` : `${RA}${rr4}${invListMeta}${mw4}
2649
+ ` : `${RA}${invListMeta}${mw4}
2388
2650
  export const GET = async (req, res) => {
2389
2651
  // TODO: fetch invoices for user
2390
2652
  res.json({ invoices: [], total: 0 });
2391
2653
  };
2392
2654
  `;
2393
2655
  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';
2656
+ const invByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2395
2657
  ${mw4}
2396
2658
  export const GET = async (req: Request, res: Response) => {
2397
2659
  const { id } = req.params;
2398
2660
  // TODO: return invoice PDF URL or inline data
2399
2661
  res.json({ invoice: { id, downloadUrl: 'https://...' } });
2400
2662
  };
2401
- ` : `${RA}${rr4}${mw4}
2663
+ ` : `${RA}${mw4}
2402
2664
  export const GET = async (req, res) => {
2403
2665
  const { id } = req.params;
2404
2666
  // TODO: return invoice PDF URL or inline data
@@ -2410,15 +2672,13 @@ export const GET = async (req, res) => {
2410
2672
  async function writeSupportRoutes(dest, opts) {
2411
2673
  const ext = opts.language === "typescript" ? "ts" : "js";
2412
2674
  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')];
2675
+ const mw3 = opts.rbac ? `export const middlewares = [requireAuth('user', 'admin')];
2416
2676
  ` : `export const middlewares = [requireAuth];
2417
2677
  `;
2418
2678
  const RA = `import { requireAuth } from 'express-file-cluster/auth';
2419
2679
  `;
2420
2680
  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';
2681
+ const ticketListContent = ts ? `${RA}import type { Request, Response } from 'express';
2422
2682
  ${ticketListMeta}${mw3}
2423
2683
  export const GET = async (req: Request, res: Response) => {
2424
2684
  // TODO: fetch tickets for current user
@@ -2431,7 +2691,7 @@ export const POST = async (req: Request, res: Response) => {
2431
2691
  // TODO: create support ticket in DB
2432
2692
  res.status(201).json({ message: 'Ticket created', ticket: { id: 'new-id', subject, status: 'open' } });
2433
2693
  };
2434
- ` : `${RA}${rr3}${ticketListMeta}${mw3}
2694
+ ` : `${RA}${ticketListMeta}${mw3}
2435
2695
  export const GET = async (req, res) => {
2436
2696
  // TODO: fetch tickets for current user
2437
2697
  res.json({ tickets: [], total: 0 });
@@ -2446,7 +2706,7 @@ export const POST = async (req, res) => {
2446
2706
  `;
2447
2707
  await fs.outputFile(path.join(dest, "src", "api", "support", "tickets", `index.${ext}`), ticketListContent);
2448
2708
  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';
2709
+ const ticketByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2450
2710
  ${ticketByIdMeta}${mw3}
2451
2711
  export const GET = async (req: Request, res: Response) => {
2452
2712
  const { id } = req.params;
@@ -2460,7 +2720,7 @@ export const PUT = async (req: Request, res: Response) => {
2460
2720
  // TODO: add reply or update status
2461
2721
  res.json({ message: 'Ticket updated', ticket: { id, status: status ?? 'open' } });
2462
2722
  };
2463
- ` : `${RA}${rr3}${ticketByIdMeta}${mw3}
2723
+ ` : `${RA}${ticketByIdMeta}${mw3}
2464
2724
  export const GET = async (req, res) => {
2465
2725
  const { id } = req.params;
2466
2726
  // TODO: fetch ticket by id, verify ownership
@@ -2479,11 +2739,7 @@ export const PUT = async (req, res) => {
2479
2739
  async function writeAdminExtendedRoutes(dest, opts) {
2480
2740
  const ext = opts.language === "typescript" ? "ts" : "js";
2481
2741
  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')];
2742
+ const mwAdmin3 = opts.rbac ? `export const middlewares = [requireAuth('admin')];
2487
2743
  ` : `export const middlewares = [requireAuth];
2488
2744
  `;
2489
2745
  const mwAdmin4 = mwAdmin3;
@@ -2495,13 +2751,13 @@ async function writeAdminExtendedRoutes(dest, opts) {
2495
2751
  const RA = `import { requireAuth } from 'express-file-cluster/auth';
2496
2752
  `;
2497
2753
  const analyticsOverviewMeta = mkMeta(opts, "Analytics overview: users, revenue, traffic.", `{ users: {}, revenue: {}, traffic: {} }`);
2498
- const analyticsOverviewContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2754
+ const analyticsOverviewContent = ts ? `${RA}import type { Request, Response } from 'express';
2499
2755
  ${analyticsOverviewMeta}${mwAdmin3}
2500
2756
  export const GET = async (req: Request, res: Response) => {
2501
2757
  ${roleGuard} // TODO: aggregate analytics overview
2502
2758
  res.json({ users: {}, revenue: {}, traffic: {} });
2503
2759
  };
2504
- ` : `${RA}${rr3}${analyticsOverviewMeta}${mwAdmin3}
2760
+ ` : `${RA}${analyticsOverviewMeta}${mwAdmin3}
2505
2761
  export const GET = async (req, res) => {
2506
2762
  ${roleGuard} // TODO: aggregate analytics overview
2507
2763
  res.json({ users: {}, revenue: {}, traffic: {} });
@@ -2509,14 +2765,14 @@ ${roleGuard} // TODO: aggregate analytics overview
2509
2765
  `;
2510
2766
  await fs.outputFile(path.join(dest, "src", "api", "admin", "analytics", `index.${ext}`), analyticsOverviewContent);
2511
2767
  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';
2768
+ const analyticsUsersContent = ts ? `${RA}import type { Request, Response } from 'express';
2513
2769
  ${analyticsUsersMeta}${mwAdmin3}
2514
2770
  export const GET = async (req: Request, res: Response) => {
2515
2771
  ${roleGuard} const { period = '30d' } = req.query;
2516
2772
  // TODO: fetch user analytics for period
2517
2773
  res.json({ registrations: [], activeUsers: 0, churn: 0, period });
2518
2774
  };
2519
- ` : `${RA}${rr3}${analyticsUsersMeta}${mwAdmin3}
2775
+ ` : `${RA}${analyticsUsersMeta}${mwAdmin3}
2520
2776
  export const GET = async (req, res) => {
2521
2777
  ${roleGuard} const { period = '30d' } = req.query;
2522
2778
  // TODO: fetch user analytics for period
@@ -2525,14 +2781,14 @@ ${roleGuard} const { period = '30d' } = req.query;
2525
2781
  `;
2526
2782
  await fs.outputFile(path.join(dest, "src", "api", "admin", "analytics", `users.${ext}`), analyticsUsersContent);
2527
2783
  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';
2784
+ const analyticsRevenueContent = ts ? `${RA}import type { Request, Response } from 'express';
2529
2785
  ${analyticsRevenueMeta}${mwAdmin3}
2530
2786
  export const GET = async (req: Request, res: Response) => {
2531
2787
  ${roleGuard} const { period = '30d' } = req.query;
2532
2788
  // TODO: fetch revenue analytics for period
2533
2789
  res.json({ mrr: 0, arr: 0, history: [], period });
2534
2790
  };
2535
- ` : `${RA}${rr3}${analyticsRevenueMeta}${mwAdmin3}
2791
+ ` : `${RA}${analyticsRevenueMeta}${mwAdmin3}
2536
2792
  export const GET = async (req, res) => {
2537
2793
  ${roleGuard} const { period = '30d' } = req.query;
2538
2794
  // TODO: fetch revenue analytics for period
@@ -2541,14 +2797,14 @@ ${roleGuard} const { period = '30d' } = req.query;
2541
2797
  `;
2542
2798
  await fs.outputFile(path.join(dest, "src", "api", "admin", "analytics", `revenue.${ext}`), analyticsRevenueContent);
2543
2799
  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';
2800
+ const analyticsTrafficContent = ts ? `${RA}import type { Request, Response } from 'express';
2545
2801
  ${analyticsTrafficMeta}${mwAdmin3}
2546
2802
  export const GET = async (req: Request, res: Response) => {
2547
2803
  ${roleGuard} const { period = '30d' } = req.query;
2548
2804
  // TODO: fetch traffic analytics for period
2549
2805
  res.json({ pageViews: 0, devices: {}, countries: {}, period });
2550
2806
  };
2551
- ` : `${RA}${rr3}${analyticsTrafficMeta}${mwAdmin3}
2807
+ ` : `${RA}${analyticsTrafficMeta}${mwAdmin3}
2552
2808
  export const GET = async (req, res) => {
2553
2809
  ${roleGuard} const { period = '30d' } = req.query;
2554
2810
  // TODO: fetch traffic analytics for period
@@ -2556,7 +2812,7 @@ ${roleGuard} const { period = '30d' } = req.query;
2556
2812
  };
2557
2813
  `;
2558
2814
  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';
2815
+ const suspendContent = ts ? `${RA}import type { Request, Response } from 'express';
2560
2816
  ${mwAdmin4}
2561
2817
  export const POST = async (req: Request, res: Response) => {
2562
2818
  ${roleGuard} const { id } = req.params;
@@ -2564,7 +2820,7 @@ ${roleGuard} const { id } = req.params;
2564
2820
  // TODO: set user.isActive = false, log audit event
2565
2821
  res.json({ message: \`User \${id} suspended\`, reason });
2566
2822
  };
2567
- ` : `${RA}${rr4}${mwAdmin4}
2823
+ ` : `${RA}${mwAdmin4}
2568
2824
  export const POST = async (req, res) => {
2569
2825
  ${roleGuard} const { id } = req.params;
2570
2826
  const { reason } = req.body;
@@ -2573,14 +2829,14 @@ ${roleGuard} const { id } = req.params;
2573
2829
  };
2574
2830
  `;
2575
2831
  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';
2832
+ const activateContent = ts ? `${RA}import type { Request, Response } from 'express';
2577
2833
  ${mwAdmin4}
2578
2834
  export const POST = async (req: Request, res: Response) => {
2579
2835
  ${roleGuard} const { id } = req.params;
2580
2836
  // TODO: set user.isActive = true, log audit event
2581
2837
  res.json({ message: \`User \${id} activated\` });
2582
2838
  };
2583
- ` : `${RA}${rr4}${mwAdmin4}
2839
+ ` : `${RA}${mwAdmin4}
2584
2840
  export const POST = async (req, res) => {
2585
2841
  ${roleGuard} const { id } = req.params;
2586
2842
  // TODO: set user.isActive = true, log audit event
@@ -2588,14 +2844,14 @@ ${roleGuard} const { id } = req.params;
2588
2844
  };
2589
2845
  `;
2590
2846
  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';
2847
+ const verifyUserContent = ts ? `${RA}import type { Request, Response } from 'express';
2592
2848
  ${mwAdmin4}
2593
2849
  export const POST = async (req: Request, res: Response) => {
2594
2850
  ${roleGuard} const { id } = req.params;
2595
2851
  // TODO: set user.isVerified = true
2596
2852
  res.json({ message: \`User \${id} verified\` });
2597
2853
  };
2598
- ` : `${RA}${rr4}${mwAdmin4}
2854
+ ` : `${RA}${mwAdmin4}
2599
2855
  export const POST = async (req, res) => {
2600
2856
  ${roleGuard} const { id } = req.params;
2601
2857
  // TODO: set user.isVerified = true
@@ -2604,7 +2860,7 @@ ${roleGuard} const { id } = req.params;
2604
2860
  `;
2605
2861
  await fs.outputFile(path.join(dest, "src", "api", "admin", "users", "[id]", `verify.${ext}`), verifyUserContent);
2606
2862
  const exportMeta = mkMeta(opts, "Export all users as CSV.", `{ csv: '...' }`);
2607
- const exportContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2863
+ const exportContent = ts ? `${RA}import type { Request, Response } from 'express';
2608
2864
  ${exportMeta}${mwAdmin3}
2609
2865
  export const GET = async (_req: Request, res: Response) => {
2610
2866
  ${roleGuard} // TODO: generate CSV of all users and stream response
@@ -2612,7 +2868,7 @@ ${roleGuard} // TODO: generate CSV of all users and stream response
2612
2868
  res.setHeader('Content-Disposition', 'attachment; filename="users.csv"');
2613
2869
  res.send('id,name,email,role,createdAt\\n');
2614
2870
  };
2615
- ` : `${RA}${rr3}${exportMeta}${mwAdmin3}
2871
+ ` : `${RA}${exportMeta}${mwAdmin3}
2616
2872
  export const GET = async (_req, res) => {
2617
2873
  ${roleGuard} // TODO: generate CSV of all users and stream response
2618
2874
  res.setHeader('Content-Type', 'text/csv');
@@ -2622,7 +2878,7 @@ ${roleGuard} // TODO: generate CSV of all users and stream response
2622
2878
  `;
2623
2879
  await fs.outputFile(path.join(dest, "src", "api", "admin", "users", `export.${ext}`), exportContent);
2624
2880
  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';
2881
+ const adminsListContent = ts ? `${RA}import type { Request, Response } from 'express';
2626
2882
  ${adminsListMeta}${mwAdmin3}
2627
2883
  export const GET = async (_req: Request, res: Response) => {
2628
2884
  ${roleGuard} // TODO: fetch admins from DB
@@ -2635,7 +2891,7 @@ ${roleGuard} const { name, email, role } = req.body;
2635
2891
  // TODO: create admin account
2636
2892
  res.status(201).json({ message: 'Admin created', admin: { id: 'new-id', name, email, role: role ?? 'admin' } });
2637
2893
  };
2638
- ` : `${RA}${rr3}${adminsListMeta}${mwAdmin3}
2894
+ ` : `${RA}${adminsListMeta}${mwAdmin3}
2639
2895
  export const GET = async (_req, res) => {
2640
2896
  ${roleGuard} // TODO: fetch admins from DB
2641
2897
  res.json({ admins: [], total: 0 });
@@ -2649,7 +2905,7 @@ ${roleGuard} const { name, email, role } = req.body;
2649
2905
  };
2650
2906
  `;
2651
2907
  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';
2908
+ const adminByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2653
2909
  ${mwAdmin3}
2654
2910
  export const GET = async (req: Request, res: Response) => {
2655
2911
  ${roleGuard} const { id } = req.params;
@@ -2668,7 +2924,7 @@ ${roleGuard} const { id } = req.params;
2668
2924
  // TODO: delete admin
2669
2925
  res.json({ message: \`Admin \${id} deleted\` });
2670
2926
  };
2671
- ` : `${RA}${rr3}${mwAdmin3}
2927
+ ` : `${RA}${mwAdmin3}
2672
2928
  export const GET = async (req, res) => {
2673
2929
  ${roleGuard} const { id } = req.params;
2674
2930
  // TODO: fetch admin by id
@@ -2689,7 +2945,7 @@ ${roleGuard} const { id } = req.params;
2689
2945
  `;
2690
2946
  await fs.outputFile(path.join(dest, "src", "api", "admin", "admins", `[id].${ext}`), adminByIdContent);
2691
2947
  if (opts.rbac) {
2692
- const rolesListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2948
+ const rolesListContent = ts ? `${RA}import type { Request, Response } from 'express';
2693
2949
  ${mwAdmin3}
2694
2950
  export const GET = async (_req: Request, res: Response) => {
2695
2951
  // TODO: fetch all roles
@@ -2702,7 +2958,7 @@ export const POST = async (req: Request, res: Response) => {
2702
2958
  // TODO: create role
2703
2959
  res.status(201).json({ message: 'Role created', role: { id: 'new-id', name, permissions: permissions ?? [] } });
2704
2960
  };
2705
- ` : `${RA}${rr3}${mwAdmin3}
2961
+ ` : `${RA}${mwAdmin3}
2706
2962
  export const GET = async (_req, res) => {
2707
2963
  // TODO: fetch all roles
2708
2964
  res.json({ roles: [] });
@@ -2716,7 +2972,7 @@ export const POST = async (req, res) => {
2716
2972
  };
2717
2973
  `;
2718
2974
  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';
2975
+ const roleByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2720
2976
  ${mwAdmin3}
2721
2977
  export const GET = async (req: Request, res: Response) => {
2722
2978
  const { id } = req.params;
@@ -2735,7 +2991,7 @@ export const DELETE = async (req: Request, res: Response) => {
2735
2991
  // TODO: delete role
2736
2992
  res.json({ message: \`Role \${id} deleted\` });
2737
2993
  };
2738
- ` : `${RA}${rr3}${mwAdmin3}
2994
+ ` : `${RA}${mwAdmin3}
2739
2995
  export const GET = async (req, res) => {
2740
2996
  const { id } = req.params;
2741
2997
  // TODO: fetch role by id
@@ -2756,7 +3012,7 @@ export const DELETE = async (req, res) => {
2756
3012
  `;
2757
3013
  await fs.outputFile(path.join(dest, "src", "api", "admin", "roles", `[id].${ext}`), roleByIdContent);
2758
3014
  }
2759
- const adminNotifContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
3015
+ const adminNotifContent = ts ? `${RA}import type { Request, Response } from 'express';
2760
3016
  ${mwAdmin3}
2761
3017
  export const GET = async (_req: Request, res: Response) => {
2762
3018
  ${roleGuard} // TODO: fetch sent notifications
@@ -2769,7 +3025,7 @@ ${roleGuard} const { userId, title, message, type } = req.body;
2769
3025
  // TODO: create and send notification to user
2770
3026
  res.status(201).json({ message: 'Notification sent' });
2771
3027
  };
2772
- ` : `${RA}${rr3}${mwAdmin3}
3028
+ ` : `${RA}${mwAdmin3}
2773
3029
  export const GET = async (_req, res) => {
2774
3030
  ${roleGuard} // TODO: fetch sent notifications
2775
3031
  res.json({ notifications: [], total: 0 });
@@ -2784,7 +3040,7 @@ ${roleGuard} const { userId, title, message, type } = req.body;
2784
3040
  `;
2785
3041
  await fs.outputFile(path.join(dest, "src", "api", "admin", "notifications", `index.${ext}`), adminNotifContent);
2786
3042
  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';
3043
+ const broadcastContent = ts ? `${RA}import type { Request, Response } from 'express';
2788
3044
  ${broadcastMeta}${mwAdmin3}
2789
3045
  export const POST = async (req: Request, res: Response) => {
2790
3046
  ${roleGuard} const { title, message, type } = req.body;
@@ -2792,7 +3048,7 @@ ${roleGuard} const { title, message, type } = req.body;
2792
3048
  // TODO: send notification to all users
2793
3049
  res.json({ message: 'Broadcast sent', count: 0 });
2794
3050
  };
2795
- ` : `${RA}${rr3}${broadcastMeta}${mwAdmin3}
3051
+ ` : `${RA}${broadcastMeta}${mwAdmin3}
2796
3052
  export const POST = async (req, res) => {
2797
3053
  ${roleGuard} const { title, message, type } = req.body;
2798
3054
  if (!title || !message) return res.status(400).json({ error: 'title and message are required' });
@@ -2803,14 +3059,14 @@ ${roleGuard} const { title, message, type } = req.body;
2803
3059
  await fs.outputFile(path.join(dest, "src", "api", "admin", "notifications", `broadcast.${ext}`), broadcastContent);
2804
3060
  const mkLogContent = (label) => {
2805
3061
  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';
3062
+ return ts ? `${RA}import type { Request, Response } from 'express';
2807
3063
  ${meta}${mwAdmin3}
2808
3064
  export const GET = async (req: Request, res: Response) => {
2809
3065
  ${roleGuard} const { page = 1, limit = 50 } = req.query;
2810
3066
  // TODO: fetch ${label} logs with pagination
2811
3067
  res.json({ logs: [], total: 0, page: Number(page), limit: Number(limit) });
2812
3068
  };
2813
- ` : `${RA}${rr3}${meta}${mwAdmin3}
3069
+ ` : `${RA}${meta}${mwAdmin3}
2814
3070
  export const GET = async (req, res) => {
2815
3071
  ${roleGuard} const { page = 1, limit = 50 } = req.query;
2816
3072
  // TODO: fetch ${label} logs with pagination
@@ -2822,7 +3078,7 @@ ${roleGuard} const { page = 1, limit = 50 } = req.query;
2822
3078
  await fs.outputFile(path.join(dest, "src", "api", "admin", "logs", `activity.${ext}`), mkLogContent("activity"));
2823
3079
  await fs.outputFile(path.join(dest, "src", "api", "admin", "logs", `security.${ext}`), mkLogContent("security"));
2824
3080
  const settingsMeta = mkMeta(opts, "Get or update system-wide settings.", `{ settings: {} }`);
2825
- const settingsContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
3081
+ const settingsContent = ts ? `${RA}import type { Request, Response } from 'express';
2826
3082
  ${settingsMeta}${mwAdmin3}
2827
3083
  export const GET = async (_req: Request, res: Response) => {
2828
3084
  ${roleGuard} // TODO: fetch system settings from DB
@@ -2833,7 +3089,7 @@ export const PUT = async (req: Request, res: Response) => {
2833
3089
  ${roleGuard} // TODO: update system settings
2834
3090
  res.json({ message: 'Settings updated', settings: req.body });
2835
3091
  };
2836
- ` : `${RA}${rr3}${settingsMeta}${mwAdmin3}
3092
+ ` : `${RA}${settingsMeta}${mwAdmin3}
2837
3093
  export const GET = async (_req, res) => {
2838
3094
  ${roleGuard} // TODO: fetch system settings from DB
2839
3095
  res.json({ settings: {} });
@@ -2846,13 +3102,13 @@ ${roleGuard} // TODO: update system settings
2846
3102
  `;
2847
3103
  await fs.outputFile(path.join(dest, "src", "api", "admin", "settings", `index.${ext}`), settingsContent);
2848
3104
  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';
3105
+ const healthContent = ts ? `${RA}import type { Request, Response } from 'express';
2850
3106
  ${healthMeta}${mwAdmin3}
2851
3107
  export const GET = async (_req: Request, res: Response) => {
2852
3108
  ${roleGuard} // TODO: check DB connection, queue, cache health
2853
3109
  res.json({ status: 'ok', db: 'ok', queue: 'ok', uptime: process.uptime() });
2854
3110
  };
2855
- ` : `${RA}${rr3}${healthMeta}${mwAdmin3}
3111
+ ` : `${RA}${healthMeta}${mwAdmin3}
2856
3112
  export const GET = async (_req, res) => {
2857
3113
  ${roleGuard} // TODO: check DB connection, queue, cache health
2858
3114
  res.json({ status: 'ok', db: 'ok', queue: 'ok', uptime: process.uptime() });
@@ -2860,13 +3116,13 @@ ${roleGuard} // TODO: check DB connection, queue, cache health
2860
3116
  `;
2861
3117
  await fs.outputFile(path.join(dest, "src", "api", "admin", "system", `health.${ext}`), healthContent);
2862
3118
  const cacheMeta = mkMeta(opts, "Flush the application cache.", `{ message: 'Cache cleared' }`);
2863
- const cacheContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
3119
+ const cacheContent = ts ? `${RA}import type { Request, Response } from 'express';
2864
3120
  ${cacheMeta}${mwAdmin3}
2865
3121
  export const DELETE = async (_req: Request, res: Response) => {
2866
3122
  ${roleGuard} // TODO: flush Redis or in-memory cache
2867
3123
  res.json({ message: 'Cache cleared' });
2868
3124
  };
2869
- ` : `${RA}${rr3}${cacheMeta}${mwAdmin3}
3125
+ ` : `${RA}${cacheMeta}${mwAdmin3}
2870
3126
  export const DELETE = async (_req, res) => {
2871
3127
  ${roleGuard} // TODO: flush Redis or in-memory cache
2872
3128
  res.json({ message: 'Cache cleared' });
@@ -2874,14 +3130,14 @@ ${roleGuard} // TODO: flush Redis or in-memory cache
2874
3130
  `;
2875
3131
  await fs.outputFile(path.join(dest, "src", "api", "admin", "system", `cache.${ext}`), cacheContent);
2876
3132
  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';
3133
+ const adminTicketsListContent = ts ? `${RA}import type { Request, Response } from 'express';
2878
3134
  ${adminTicketsListMeta}${mwAdmin3}
2879
3135
  export const GET = async (req: Request, res: Response) => {
2880
3136
  ${roleGuard} const { status, priority, page = 1, limit = 20 } = req.query;
2881
3137
  // TODO: fetch all tickets with filters
2882
3138
  res.json({ tickets: [], total: 0, page: Number(page), limit: Number(limit) });
2883
3139
  };
2884
- ` : `${RA}${rr3}${adminTicketsListMeta}${mwAdmin3}
3140
+ ` : `${RA}${adminTicketsListMeta}${mwAdmin3}
2885
3141
  export const GET = async (req, res) => {
2886
3142
  ${roleGuard} const { status, priority, page = 1, limit = 20 } = req.query;
2887
3143
  // TODO: fetch all tickets with filters
@@ -2889,7 +3145,7 @@ ${roleGuard} const { status, priority, page = 1, limit = 20 } = req.query;
2889
3145
  };
2890
3146
  `;
2891
3147
  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';
3148
+ const adminTicketByIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2893
3149
  ${mwAdmin3}
2894
3150
  export const GET = async (req: Request, res: Response) => {
2895
3151
  ${roleGuard} const { id } = req.params;
@@ -2903,7 +3159,7 @@ ${roleGuard} const { id } = req.params;
2903
3159
  // TODO: update ticket (assign, change status, add reply)
2904
3160
  res.json({ message: 'Ticket updated', ticket: { id, status, assignedTo } });
2905
3161
  };
2906
- ` : `${RA}${rr3}${mwAdmin3}
3162
+ ` : `${RA}${mwAdmin3}
2907
3163
  export const GET = async (req, res) => {
2908
3164
  ${roleGuard} const { id } = req.params;
2909
3165
  // TODO: fetch ticket by id
@@ -2920,7 +3176,7 @@ ${roleGuard} const { id } = req.params;
2920
3176
  await fs.outputFile(path.join(dest, "src", "api", "admin", "tickets", `[id].${ext}`), adminTicketByIdContent);
2921
3177
  const mkContentCrud = (name, namePlural, dir, createFields) => {
2922
3178
  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';
3179
+ const listContent = ts ? `${RA}import type { Request, Response } from 'express';
2924
3180
  ${listMeta}${mwAdmin4}
2925
3181
  export const GET = async (_req: Request, res: Response) => {
2926
3182
  ${roleGuard} // TODO: fetch ${namePlural}
@@ -2932,7 +3188,7 @@ ${roleGuard} const { ${createFields} } = req.body;
2932
3188
  // TODO: create ${name}
2933
3189
  res.status(201).json({ message: '${name} created', ${name.toLowerCase()}: { id: 'new-id', ${createFields.split(", ")[0]} } });
2934
3190
  };
2935
- ` : `${RA}${rr4}${listMeta}${mwAdmin4}
3191
+ ` : `${RA}${listMeta}${mwAdmin4}
2936
3192
  export const GET = async (_req, res) => {
2937
3193
  ${roleGuard} // TODO: fetch ${namePlural}
2938
3194
  res.json({ ${namePlural}: [], total: 0 });
@@ -2944,7 +3200,7 @@ ${roleGuard} const { ${createFields} } = req.body;
2944
3200
  res.status(201).json({ message: '${name} created', ${name.toLowerCase()}: { id: 'new-id' } });
2945
3201
  };
2946
3202
  `;
2947
- const byIdContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
3203
+ const byIdContent = ts ? `${RA}import type { Request, Response } from 'express';
2948
3204
  ${mwAdmin4}
2949
3205
  export const GET = async (req: Request, res: Response) => {
2950
3206
  ${roleGuard} const { id } = req.params;
@@ -2963,7 +3219,7 @@ ${roleGuard} const { id } = req.params;
2963
3219
  // TODO: delete ${name}
2964
3220
  res.json({ message: \`${name} \${id} deleted\` });
2965
3221
  };
2966
- ` : `${RA}${rr4}${mwAdmin4}
3222
+ ` : `${RA}${mwAdmin4}
2967
3223
  export const GET = async (req, res) => {
2968
3224
  ${roleGuard} const { id } = req.params;
2969
3225
  // TODO: fetch ${name} by id
@@ -3000,14 +3256,14 @@ ${roleGuard} const { id } = req.params;
3000
3256
  await fs.outputFile(path.join(dest, "src", "api", "admin", "billing", "coupons", `index.${ext}`), couponList);
3001
3257
  await fs.outputFile(path.join(dest, "src", "api", "admin", "billing", "coupons", `[id].${ext}`), couponById);
3002
3258
  const adminSubsMeta = mkMeta(opts, "List all subscriptions with filters.", `{ subscriptions: [], total: 0 }`);
3003
- const adminSubsContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
3259
+ const adminSubsContent = ts ? `${RA}import type { Request, Response } from 'express';
3004
3260
  ${adminSubsMeta}${mwAdmin4}
3005
3261
  export const GET = async (req: Request, res: Response) => {
3006
3262
  ${roleGuard} const { status, page = 1, limit = 20 } = req.query;
3007
3263
  // TODO: fetch all subscriptions with filters
3008
3264
  res.json({ subscriptions: [], total: 0, page: Number(page), limit: Number(limit) });
3009
3265
  };
3010
- ` : `${RA}${rr4}${adminSubsMeta}${mwAdmin4}
3266
+ ` : `${RA}${adminSubsMeta}${mwAdmin4}
3011
3267
  export const GET = async (req, res) => {
3012
3268
  ${roleGuard} const { status, page = 1, limit = 20 } = req.query;
3013
3269
  // TODO: fetch all subscriptions with filters
@@ -3064,7 +3320,7 @@ async function main() {
3064
3320
  { value: "routeDocs", label: "API route documentation", hint: "meta exports + dashboard" },
3065
3321
  { value: "userPortal", label: "User portal", hint: "auth, profile, billing routes" },
3066
3322
  { value: "adminPortal", label: "Admin portal", hint: "dashboard, user mgmt, analytics" },
3067
- { value: "rbac", label: "Role-based access control", hint: "requireRole middleware" },
3323
+ { value: "rbac", label: "Role-based access control", hint: "requireAuth('role') middleware" },
3068
3324
  { value: "mailer", label: "Mailer", hint: "nodemailer + SMTP" }
3069
3325
  ],
3070
3326
  initialValues: ["cluster", "tasks", "routeDocs", "userPortal", "adminPortal", "rbac"],
@@ -3177,6 +3433,13 @@ async function main() {
3177
3433
  await npmInstallGlobal().catch(() => {
3178
3434
  });
3179
3435
  spinner2.stop("efc CLI ready");
3436
+ if (opts.mailer) {
3437
+ p.note(
3438
+ `SMTP_USER and SMTP_PASS were written to ${projectName}/.env \u2014 that file is gitignored, never commit it.
3439
+ ` + (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."),
3440
+ "Mailer credentials"
3441
+ );
3442
+ }
3180
3443
  p.outro(
3181
3444
  pc.green(`
3182
3445
  Your project is ready!