create-efc-app 0.3.5 → 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 +485 -222
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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:
|
|
298
|
-
email:
|
|
299
|
-
password:
|
|
300
|
-
role:
|
|
301
|
-
avatar:
|
|
302
|
-
isVerified:
|
|
303
|
-
isActive:
|
|
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:
|
|
309
|
-
email:
|
|
310
|
-
password:
|
|
311
|
-
role:
|
|
312
|
-
avatar:
|
|
313
|
-
isVerified:
|
|
314
|
-
isActive:
|
|
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:
|
|
353
|
-
email:
|
|
354
|
-
password:
|
|
355
|
-
role:
|
|
356
|
-
permissions:
|
|
357
|
-
isActive:
|
|
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:
|
|
363
|
-
email:
|
|
364
|
-
password:
|
|
365
|
-
role:
|
|
366
|
-
permissions:
|
|
367
|
-
isActive:
|
|
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}
|
|
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}
|
|
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" ?
|
|
517
|
-
import
|
|
518
|
-
|
|
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
|
|
531
|
-
const { password:
|
|
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
|
|
543
|
-
const { password:
|
|
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
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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}
|
|
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}${
|
|
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: "
|
|
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!
|