create-efc-app 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -19,10 +19,32 @@ 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
+ if (opts.userPortal) await writeUserModel(dest, opts);
24
+ if (opts.adminPortal) await writeAdminModel(dest, opts);
22
25
  await writeAuthRoutes(dest, opts);
23
- await writeAdminRoutes(dest, opts);
24
- await writeUserRoutes(dest, opts);
26
+ if (opts.adminPortal) await writeAdminRoutes(dest, opts);
27
+ if (opts.userPortal) await writeUserRoutes(dest, opts);
25
28
  if (opts.tasks) await writeExampleTask(dest, opts);
29
+ if (opts.userPortal) await writeSessionModel(dest, opts);
30
+ if (opts.userPortal) await writeNotificationModel(dest, opts);
31
+ if (opts.userPortal) await writeFileModel(dest, opts);
32
+ if (opts.userPortal || opts.adminPortal) await writeSupportTicketModel(dest, opts);
33
+ if (opts.adminPortal) await writeAuditLogModel(dest, opts);
34
+ if (opts.userPortal) await writeSubscriptionModel(dest, opts);
35
+ if (opts.adminPortal) await writePlanModel(dest, opts);
36
+ if (opts.userPortal) await writeInvoiceModel(dest, opts);
37
+ if (opts.userPortal) await writeApiKeyModel(dest, opts);
38
+ if (opts.rbac) await writeRoleModel(dest, opts);
39
+ if (opts.adminPortal) await writeFAQModel(dest, opts);
40
+ if (opts.adminPortal) await writeBlogModel(dest, opts);
41
+ if (opts.adminPortal) await writeCategoryModel(dest, opts);
42
+ if (opts.adminPortal) await writeCouponModel(dest, opts);
43
+ if (opts.userPortal) await writeAuthExtendedRoutes(dest, opts);
44
+ if (opts.userPortal) await writeUserExtendedRoutes(dest, opts);
45
+ if (opts.userPortal) await writeUserBillingRoutes(dest, opts);
46
+ if (opts.userPortal) await writeSupportRoutes(dest, opts);
47
+ if (opts.adminPortal) await writeAdminExtendedRoutes(dest, opts);
26
48
  }
27
49
  async function writePackageJson(dest, opts) {
28
50
  const deps = {
@@ -35,6 +57,8 @@ async function writePackageJson(dest, opts) {
35
57
  }
36
58
  if (opts.tasks && opts.taskBackend === "bullmq") deps["bullmq"] = "^5.0.0";
37
59
  if (opts.tasks && opts.taskBackend === "pg-boss") deps["pg-boss"] = "^10.0.0";
60
+ if (opts.mailer) deps["nodemailer"] = "^6.9.0";
61
+ if (opts.database === "mongodb") deps["bcrypt"] = "^5.1.0";
38
62
  const devDeps = {
39
63
  vitest: "^4.1.9"
40
64
  };
@@ -44,6 +68,8 @@ async function writePackageJson(dest, opts) {
44
68
  devDeps["@types/express"] = "^4.17.21";
45
69
  devDeps["tsup"] = "^8.2.0";
46
70
  devDeps["tsx"] = "^4.0.0";
71
+ if (opts.mailer) devDeps["@types/nodemailer"] = "^6.4.0";
72
+ if (opts.database === "mongodb") devDeps["@types/bcrypt"] = "^5.0.0";
47
73
  }
48
74
  const pkg = {
49
75
  name: opts.projectName,
@@ -118,178 +144,2875 @@ async function writeEnvFiles(dest, opts) {
118
144
  const projectName = path.basename(dest);
119
145
  const dbUrl = opts.database === "mongodb" ? `mongodb://localhost:27017/${projectName}` : `postgresql://localhost:5432/${projectName}`;
120
146
  const dbExampleUrl = opts.database === "mongodb" ? `mongodb://localhost:27017/${projectName}` : `postgresql://user:password@localhost:5432/${projectName}`;
147
+ const smtpVars = opts.mailer ? `
148
+ SMTP_HOST=${opts.smtpHost ?? "smtp.gmail.com"}
149
+ SMTP_PORT=${opts.smtpPort ?? "587"}
150
+ SMTP_USER=
151
+ SMTP_PASS=
152
+ SMTP_FROM=noreply@example.com
153
+ ` : "";
154
+ const smtpExample = opts.mailer ? `
155
+ SMTP_HOST=${opts.smtpHost ?? "smtp.gmail.com"}
156
+ SMTP_PORT=${opts.smtpPort ?? "587"}
157
+ SMTP_USER=your@email.com
158
+ SMTP_PASS=your_app_password
159
+ SMTP_FROM=noreply@yourapp.com
160
+ ` : "";
121
161
  const dotenv = `PORT=3000
122
162
  NODE_ENV=development
123
163
  DATABASE_URL=${dbUrl}
124
164
  JWT_SECRET=${secret}
125
165
  REDIS_URL=redis://localhost:6379
126
- CORS_ORIGINS=http://localhost:3000
127
- `;
166
+ CORS_ORIGINS=http://localhost:3000${smtpVars}`;
128
167
  const example = `PORT=3000
129
168
  NODE_ENV=development
130
169
  DATABASE_URL=${dbExampleUrl}
131
170
  JWT_SECRET=<generate with: openssl rand -hex 64>
132
171
  REDIS_URL=redis://localhost:6379
133
- CORS_ORIGINS=http://localhost:3000,https://yourapp.com
172
+ CORS_ORIGINS=http://localhost:3000,https://yourapp.com${smtpExample}`;
173
+ await fs.outputFile(path.join(dest, ".env"), dotenv);
174
+ await fs.outputFile(path.join(dest, ".env.example"), example);
175
+ }
176
+ async function writeExampleRoute(dest, opts) {
177
+ const ext = opts.language === "typescript" ? "ts" : "js";
178
+ const metaTs = opts.routeDocs ? `import type { RouteMeta } from 'express-file-cluster';
179
+
180
+ export const meta: RouteMeta = {
181
+ description: 'Health check \u2014 returns server status and current timestamp.',
182
+ response: { status: 200, body: { status: 'OK', timestamp: '2024-01-01T00:00:00.000Z' } },
183
+ };
184
+
185
+ ` : "";
186
+ const metaJs = opts.routeDocs ? `export const meta = {
187
+ description: 'Health check \u2014 returns server status and current timestamp.',
188
+ response: { status: 200, body: { status: 'OK', timestamp: '2024-01-01T00:00:00.000Z' } },
189
+ };
190
+
191
+ ` : "";
192
+ const content = opts.language === "typescript" ? `import type { Request, Response } from 'express';
193
+ ${metaTs}export const GET = async (_req: Request, res: Response) => {
194
+ res.json({ status: 'OK', timestamp: new Date().toISOString() });
195
+ };
196
+ ` : `${metaJs}export const GET = async (_req, res) => {
197
+ res.json({ status: 'OK', timestamp: new Date().toISOString() });
198
+ };
199
+ `;
200
+ await fs.outputFile(path.join(dest, "src", "api", `health.${ext}`), content);
201
+ }
202
+ async function writeExampleTask(dest, opts) {
203
+ const ext = opts.language === "typescript" ? "ts" : "js";
204
+ const tsMailer = `import { defineTask } from 'express-file-cluster/tasks';
205
+ import nodemailer from 'nodemailer';
206
+
207
+ interface SendEmailPayload {
208
+ to: string;
209
+ subject: string;
210
+ body: string;
211
+ }
212
+
213
+ const transporter = nodemailer.createTransport({
214
+ host: process.env.SMTP_HOST,
215
+ port: Number(process.env.SMTP_PORT ?? 587),
216
+ secure: Number(process.env.SMTP_PORT) === 465,
217
+ auth: {
218
+ user: process.env.SMTP_USER,
219
+ pass: process.env.SMTP_PASS,
220
+ },
221
+ });
222
+
223
+ export default defineTask<SendEmailPayload>(async (payload) => {
224
+ await transporter.sendMail({
225
+ from: process.env.SMTP_FROM ?? process.env.SMTP_USER,
226
+ to: payload.to,
227
+ subject: payload.subject,
228
+ html: payload.body,
229
+ });
230
+ });
231
+ `;
232
+ const jsMailer = `import { defineTask } from 'express-file-cluster/tasks';
233
+ import nodemailer from 'nodemailer';
234
+
235
+ const transporter = nodemailer.createTransport({
236
+ host: process.env.SMTP_HOST,
237
+ port: Number(process.env.SMTP_PORT ?? 587),
238
+ secure: Number(process.env.SMTP_PORT) === 465,
239
+ auth: {
240
+ user: process.env.SMTP_USER,
241
+ pass: process.env.SMTP_PASS,
242
+ },
243
+ });
244
+
245
+ export default defineTask(async (payload) => {
246
+ await transporter.sendMail({
247
+ from: process.env.SMTP_FROM ?? process.env.SMTP_USER,
248
+ to: payload.to,
249
+ subject: payload.subject,
250
+ html: payload.body,
251
+ });
252
+ });
253
+ `;
254
+ const tsStub = `import { defineTask } from 'express-file-cluster/tasks';
255
+
256
+ interface SendEmailPayload {
257
+ to: string;
258
+ subject: string;
259
+ body: string;
260
+ }
261
+
262
+ export default defineTask<SendEmailPayload>(async (payload) => {
263
+ // TODO: wire up your mailer
264
+ console.log('[SendEmail] Sending to', payload.to);
265
+ });
266
+ `;
267
+ const jsStub = `import { defineTask } from 'express-file-cluster/tasks';
268
+
269
+ export default defineTask(async (payload) => {
270
+ // TODO: wire up your mailer
271
+ console.log('[SendEmail] Sending to', payload.to);
272
+ });
273
+ `;
274
+ const content = opts.mailer ? opts.language === "typescript" ? tsMailer : jsMailer : opts.language === "typescript" ? tsStub : jsStub;
275
+ await fs.outputFile(path.join(dest, "src", "tasks", `SendEmail.${ext}`), content);
276
+ }
277
+ async function writeUserModel(dest, opts) {
278
+ const ext = opts.language === "typescript" ? "ts" : "js";
279
+ const ts = opts.language === "typescript";
280
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
281
+
282
+ export interface UserDocument {
283
+ name: string;
284
+ email: string;
285
+ password: string;
286
+ role: string;
287
+ avatar?: string;
288
+ isVerified: boolean;
289
+ isActive: boolean;
290
+ }
291
+
292
+ export const User = defineModel<UserDocument>('User', {
293
+ name: { type: 'string', required: true },
294
+ email: { type: 'string', required: true, unique: true },
295
+ password: { type: 'string', required: true },
296
+ role: { type: 'string', required: true, default: 'user' },
297
+ avatar: { type: 'string' },
298
+ isVerified: { type: 'boolean', default: false },
299
+ isActive: { type: 'boolean', default: true },
300
+ });
301
+ ` : `import { defineModel } from 'express-file-cluster';
302
+
303
+ export const User = defineModel('User', {
304
+ name: { type: 'string', required: true },
305
+ email: { type: 'string', required: true, unique: true },
306
+ password: { type: 'string', required: true },
307
+ role: { type: 'string', required: true, default: 'user' },
308
+ avatar: { type: 'string' },
309
+ isVerified: { type: 'boolean', default: false },
310
+ isActive: { type: 'boolean', default: true },
311
+ });
312
+ ` : ts ? `// TODO: define your Drizzle schema for User
313
+ // import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';
314
+ //
315
+ // export const users = pgTable('users', {
316
+ // id: serial('id').primaryKey(),
317
+ // name: text('name').notNull(),
318
+ // email: text('email').notNull().unique(),
319
+ // password: text('password').notNull(),
320
+ // role: text('role').notNull().default('user'),
321
+ // avatar: text('avatar'),
322
+ // isVerified: boolean('is_verified').notNull().default(false),
323
+ // isActive: boolean('is_active').notNull().default(true),
324
+ // createdAt: timestamp('created_at').defaultNow(),
325
+ // updatedAt: timestamp('updated_at').defaultNow(),
326
+ // });
327
+ export {};
328
+ ` : `// TODO: define your Drizzle schema for User
329
+ export {};
330
+ `;
331
+ await fs.outputFile(path.join(dest, "src", "model", `User.${ext}`), content);
332
+ }
333
+ async function writeAdminModel(dest, opts) {
334
+ const ext = opts.language === "typescript" ? "ts" : "js";
335
+ const ts = opts.language === "typescript";
336
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
337
+
338
+ export interface AdminDocument {
339
+ name: string;
340
+ email: string;
341
+ password: string;
342
+ role: string;
343
+ permissions: string[];
344
+ isActive: boolean;
345
+ }
346
+
347
+ export const Admin = defineModel<AdminDocument>('Admin', {
348
+ name: { type: 'string', required: true },
349
+ email: { type: 'string', required: true, unique: true },
350
+ password: { type: 'string', required: true },
351
+ role: { type: 'string', required: true, default: 'admin' },
352
+ permissions: { type: 'array', default: [] },
353
+ isActive: { type: 'boolean', default: true },
354
+ });
355
+ ` : `import { defineModel } from 'express-file-cluster';
356
+
357
+ export const Admin = defineModel('Admin', {
358
+ name: { type: 'string', required: true },
359
+ email: { type: 'string', required: true, unique: true },
360
+ password: { type: 'string', required: true },
361
+ role: { type: 'string', required: true, default: 'admin' },
362
+ permissions: { type: 'array', default: [] },
363
+ isActive: { type: 'boolean', default: true },
364
+ });
365
+ ` : ts ? `// TODO: define your Drizzle schema for Admin
366
+ // import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';
367
+ //
368
+ // export const admins = pgTable('admins', {
369
+ // id: serial('id').primaryKey(),
370
+ // name: text('name').notNull(),
371
+ // email: text('email').notNull().unique(),
372
+ // password: text('password').notNull(),
373
+ // role: text('role').notNull().default('admin'),
374
+ // permissions: text('permissions').array().notNull().default([]),
375
+ // isActive: boolean('is_active').notNull().default(true),
376
+ // createdAt: timestamp('created_at').defaultNow(),
377
+ // updatedAt: timestamp('updated_at').defaultNow(),
378
+ // });
379
+ export {};
380
+ ` : `// TODO: define your Drizzle schema for Admin
381
+ export {};
382
+ `;
383
+ await fs.outputFile(path.join(dest, "src", "model", `Admin.${ext}`), content);
384
+ }
385
+ async function writeAuthRoutes(dest, opts) {
386
+ const ext = opts.language === "typescript" ? "ts" : "js";
387
+ const ts = opts.language === "typescript";
388
+ const loginMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
389
+
390
+ export const meta: RouteMeta = {
391
+ description: 'Authenticate a user and issue a JWT.',
392
+ request: { body: { email: 'user@example.com', password: 'user' } },
393
+ response: { status: 200, body: { message: 'Logged in as user' } },
394
+ };
395
+
396
+ ` : `export const meta = {
397
+ description: 'Authenticate a user and issue a JWT.',
398
+ request: { body: { email: 'user@example.com', password: 'user' } },
399
+ response: { status: 200, body: { message: 'Logged in as user' } },
400
+ };
401
+
402
+ ` : "";
403
+ const loginDbImports = opts.database === "mongodb" ? ts ? `import bcrypt from 'bcrypt';
404
+ import { User } from '../../model/User.js';
405
+ import { Admin } from '../../model/Admin.js';
406
+ ` : `import bcrypt from 'bcrypt';
407
+ import { User } from '../../model/User.js';
408
+ import { Admin } from '../../model/Admin.js';
409
+ ` : "";
410
+ const loginContent = opts.database === "mongodb" ? ts ? `import { issueToken } from 'express-file-cluster/auth';
411
+ import type { Request, Response } from 'express';
412
+ ${loginDbImports}${loginMeta}export const POST = async (req: Request, res: Response) => {
413
+ const { email, password } = req.body;
414
+ if (!email || !password) return res.status(400).json({ error: 'email and password are required' });
415
+
416
+ const admin = await Admin.findOne({ email });
417
+ if (admin) {
418
+ const match = await bcrypt.compare(password, admin.password);
419
+ if (!match) return res.status(401).json({ error: 'Invalid credentials' });
420
+ if (!admin.isActive) return res.status(403).json({ error: 'Account suspended' });
421
+ await issueToken(res, { id: admin.id, role: admin.role, email: admin.email });
422
+ return res.json({ message: 'Logged in as admin' });
423
+ }
424
+
425
+ const user = await User.findOne({ email });
426
+ if (!user) return res.status(401).json({ error: 'Invalid credentials' });
427
+ const match = await bcrypt.compare(password, user.password);
428
+ if (!match) return res.status(401).json({ error: 'Invalid credentials' });
429
+ if (!user.isActive) return res.status(403).json({ error: 'Account suspended' });
430
+ await issueToken(res, { id: user.id, role: user.role, email: user.email });
431
+ res.json({ message: 'Logged in' });
432
+ };
433
+ ` : `import { issueToken } from 'express-file-cluster/auth';
434
+ ${loginDbImports}${loginMeta}export const POST = async (req, res) => {
435
+ const { email, password } = req.body;
436
+ if (!email || !password) return res.status(400).json({ error: 'email and password are required' });
437
+
438
+ const admin = await Admin.findOne({ email });
439
+ if (admin) {
440
+ const match = await bcrypt.compare(password, admin.password);
441
+ if (!match) return res.status(401).json({ error: 'Invalid credentials' });
442
+ if (!admin.isActive) return res.status(403).json({ error: 'Account suspended' });
443
+ await issueToken(res, { id: admin.id, role: admin.role, email: admin.email });
444
+ return res.json({ message: 'Logged in as admin' });
445
+ }
446
+
447
+ const user = await User.findOne({ email });
448
+ if (!user) return res.status(401).json({ error: 'Invalid credentials' });
449
+ const match = await bcrypt.compare(password, user.password);
450
+ if (!match) return res.status(401).json({ error: 'Invalid credentials' });
451
+ if (!user.isActive) return res.status(403).json({ error: 'Account suspended' });
452
+ await issueToken(res, { id: user.id, role: user.role, email: user.email });
453
+ res.json({ message: 'Logged in' });
454
+ };
455
+ ` : ts ? `import { issueToken } from 'express-file-cluster/auth';
456
+ import type { Request, Response } from 'express';
457
+ ${loginMeta}export const POST = async (req: Request, res: Response) => {
458
+ const { email, password } = req.body;
459
+ // TODO: look up user in DB and compare password
460
+ res.status(401).json({ error: 'Invalid credentials' });
461
+ };
462
+ ` : `import { issueToken } from 'express-file-cluster/auth';
463
+ ${loginMeta}export const POST = async (req, res) => {
464
+ const { email, password } = req.body;
465
+ // TODO: look up user in DB and compare password
466
+ res.status(401).json({ error: 'Invalid credentials' });
467
+ };
468
+ `;
469
+ const logoutMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
470
+
471
+ export const meta: RouteMeta = {
472
+ description: 'Clear the auth cookie and log the user out.',
473
+ response: { status: 200, body: { message: 'Logged out successfully' } },
474
+ };
475
+
476
+ ` : `export const meta = {
477
+ description: 'Clear the auth cookie and log the user out.',
478
+ response: { status: 200, body: { message: 'Logged out successfully' } },
479
+ };
480
+
481
+ ` : "";
482
+ const logoutContent = ts ? `import { revokeToken } from 'express-file-cluster/auth';
483
+ import type { Request, Response } from 'express';
484
+ ${logoutMeta}export const POST = async (_req: Request, res: Response) => {
485
+ revokeToken(res);
486
+ res.json({ message: 'Logged out successfully' });
487
+ };
488
+ ` : `import { revokeToken } from 'express-file-cluster/auth';
489
+ ${logoutMeta}export const POST = async (_req, res) => {
490
+ revokeToken(res);
491
+ res.json({ message: 'Logged out successfully' });
492
+ };
493
+ `;
494
+ await fs.outputFile(path.join(dest, "src", "api", "auth", `login.${ext}`), loginContent);
495
+ await fs.outputFile(path.join(dest, "src", "api", "auth", `logout.${ext}`), logoutContent);
496
+ if (!opts.userPortal) return;
497
+ const registerMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
498
+
499
+ export const meta: RouteMeta = {
500
+ description: 'Register a new user account.',
501
+ request: { body: { name: 'Jane Doe', email: 'jane@example.com', password: 'secret' } },
502
+ response: { status: 201, body: { message: 'Account created successfully' } },
503
+ };
504
+
505
+ ` : `export const meta = {
506
+ description: 'Register a new user account.',
507
+ request: { body: { name: 'Jane Doe', email: 'jane@example.com', password: 'secret' } },
508
+ response: { status: 201, body: { message: 'Account created successfully' } },
509
+ };
510
+
511
+ ` : "";
512
+ const registerDbImports = opts.database === "mongodb" ? ts ? `import bcrypt from 'bcrypt';
513
+ import { User } from '../../model/User.js';
514
+ ` : `import bcrypt from 'bcrypt';
515
+ import { User } from '../../model/User.js';
516
+ ` : "";
517
+ const registerContent = opts.database === "mongodb" ? ts ? `import type { Request, Response } from 'express';
518
+ ${registerDbImports}${registerMeta}export const POST = async (req: Request, res: Response) => {
519
+ const { name, email, password } = req.body;
520
+ if (!name || !email || !password) {
521
+ return res.status(400).json({ error: 'name, email and password are required' });
522
+ }
523
+ const existing = await User.findOne({ email });
524
+ if (existing) return res.status(409).json({ error: 'Email already in use' });
525
+ const hashed = await bcrypt.hash(password, 10);
526
+ const user = await User.create({ name, email, password: hashed });
527
+ const { password: _, ...safe } = user;
528
+ res.status(201).json({ message: 'Account created successfully', user: safe });
529
+ };
530
+ ` : `${registerDbImports}${registerMeta}export const POST = async (req, res) => {
531
+ const { name, email, password } = req.body;
532
+ if (!name || !email || !password) {
533
+ return res.status(400).json({ error: 'name, email and password are required' });
534
+ }
535
+ const existing = await User.findOne({ email });
536
+ if (existing) return res.status(409).json({ error: 'Email already in use' });
537
+ const hashed = await bcrypt.hash(password, 10);
538
+ const user = await User.create({ name, email, password: hashed });
539
+ const { password: _, ...safe } = user;
540
+ res.status(201).json({ message: 'Account created successfully', user: safe });
541
+ };
542
+ ` : ts ? `import type { Request, Response } from 'express';
543
+ ${registerMeta}export const POST = async (req: Request, res: Response) => {
544
+ const { name, email, password } = req.body;
545
+ if (!name || !email || !password) {
546
+ return res.status(400).json({ error: 'name, email and password are required' });
547
+ }
548
+ // TODO: hash password and persist to DB
549
+ res.status(201).json({ message: 'Account created successfully' });
550
+ };
551
+ ` : `${registerMeta}export const POST = async (req, res) => {
552
+ const { name, email, password } = req.body;
553
+ if (!name || !email || !password) {
554
+ return res.status(400).json({ error: 'name, email and password are required' });
555
+ }
556
+ // TODO: hash password and persist to DB
557
+ res.status(201).json({ message: 'Account created successfully' });
558
+ };
559
+ `;
560
+ const meMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
561
+
562
+ export const meta: RouteMeta = {
563
+ description: 'Return the currently authenticated user.',
564
+ response: { status: 200, body: { user: { id: '1', role: 'user', email: 'user@example.com' } } },
565
+ };
566
+
567
+ ` : `export const meta = {
568
+ description: 'Return the currently authenticated user.',
569
+ response: { status: 200, body: { user: { id: '1', role: 'user', email: 'user@example.com' } } },
570
+ };
571
+
572
+ ` : "";
573
+ const requireRoleImport = opts.rbac ? ts ? `import { requireRole } from '../../middleware/requireRole.js';
574
+ ` : `import { requireRole } from '../../middleware/requireRole.js';
575
+ ` : "";
576
+ const meMiddlewares = opts.rbac ? `export const middlewares = [requireAuth, requireRole('user', 'admin')];
577
+
578
+ ` : `export const middlewares = [requireAuth];
579
+
580
+ `;
581
+ const meContent = ts ? `import { requireAuth } from 'express-file-cluster/auth';
582
+ ${requireRoleImport}import type { Request, Response } from 'express';
583
+ ${meMeta}${meMiddlewares}export const GET = async (req: Request, res: Response) => {
584
+ res.json({ user: (req as any).user });
585
+ };
586
+ ` : `import { requireAuth } from 'express-file-cluster/auth';
587
+ ${requireRoleImport}${meMeta}${meMiddlewares}export const GET = async (req, res) => {
588
+ res.json({ user: req.user });
589
+ };
590
+ `;
591
+ await fs.outputFile(path.join(dest, "src", "api", "auth", `register.${ext}`), registerContent);
592
+ await fs.outputFile(path.join(dest, "src", "api", "auth", `me.${ext}`), meContent);
593
+ }
594
+ async function writeRequireRoleMiddleware(dest, opts) {
595
+ const ext = opts.language === "typescript" ? "ts" : "js";
596
+ const content = opts.language === "typescript" ? `import type { Request, Response, NextFunction } from 'express';
597
+
598
+ export function requireRole(...roles: string[]) {
599
+ return (req: Request, res: Response, next: NextFunction) => {
600
+ const user = (req as any).user;
601
+ if (!user) return res.status(401).json({ error: 'Unauthorized' });
602
+ if (!roles.includes(user.role)) return res.status(403).json({ error: 'Forbidden' });
603
+ next();
604
+ };
605
+ }
606
+ ` : `export function requireRole(...roles) {
607
+ return (req, res, next) => {
608
+ const user = req.user;
609
+ if (!user) return res.status(401).json({ error: 'Unauthorized' });
610
+ if (!roles.includes(user.role)) return res.status(403).json({ error: 'Forbidden' });
611
+ next();
612
+ };
613
+ }
614
+ `;
615
+ await fs.outputFile(path.join(dest, "src", "middleware", `requireRole.${ext}`), content);
616
+ }
617
+ async function writeAdminRoutes(dest, opts) {
618
+ const ext = opts.language === "typescript" ? "ts" : "js";
619
+ const ts = opts.language === "typescript";
620
+ const requireRoleImport = opts.rbac ? `import { requireRole } from '../../middleware/requireRole.js';
621
+ ` : "";
622
+ const usersRequireRoleImport = opts.rbac ? `import { requireRole } from '../../../middleware/requireRole.js';
623
+ ` : "";
624
+ const middlewares = opts.rbac ? `export const middlewares = [requireAuth, requireRole('admin')];
625
+ ` : `export const middlewares = [requireAuth];
626
+ `;
627
+ const roleGuard = opts.rbac ? "" : ts ? ` const user = (req as any).user;
628
+ if (user?.role !== 'admin') {
629
+ return res.status(403).json({ error: 'Forbidden: Admin access required' });
630
+ }
631
+
632
+ ` : ` const user = req.user;
633
+ if (user?.role !== 'admin') {
634
+ return res.status(403).json({ error: 'Forbidden: Admin access required' });
635
+ }
636
+
637
+ `;
638
+ const dashboardMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
639
+
640
+ export const meta: RouteMeta = {
641
+ description: 'Admin dashboard stats. Requires admin role.',
642
+ response: { status: 200, body: { stats: { users: 120, revenue: 5000 } } },
643
+ };
644
+
645
+ ` : `export const meta = {
646
+ description: 'Admin dashboard stats. Requires admin role.',
647
+ response: { status: 200, body: { stats: { users: 120, revenue: 5000 } } },
648
+ };
649
+
650
+ ` : "";
651
+ const dashboardDbImport = opts.database === "mongodb" ? `import { User } from '../../model/User.js';
652
+ ` : "";
653
+ const dashboardContent = opts.database === "mongodb" ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
654
+ ${requireRoleImport}import type { Request, Response } from 'express';
655
+ ${dashboardDbImport}${dashboardMeta}${middlewares}
656
+ export const GET = async (_req: Request, res: Response) => {
657
+ ${roleGuard} const [totalUsers, activeUsers, verifiedUsers] = await Promise.all([
658
+ User.count({}),
659
+ User.count({ isActive: true }),
660
+ User.count({ isVerified: true }),
661
+ ]);
662
+ res.json({ stats: { totalUsers, activeUsers, verifiedUsers } });
663
+ };
664
+ ` : `import { requireAuth } from 'express-file-cluster/auth';
665
+ ${requireRoleImport}${dashboardDbImport}${dashboardMeta}${middlewares}
666
+ export const GET = async (_req, res) => {
667
+ ${roleGuard} const [totalUsers, activeUsers, verifiedUsers] = await Promise.all([
668
+ User.count({}),
669
+ User.count({ isActive: true }),
670
+ User.count({ isVerified: true }),
671
+ ]);
672
+ res.json({ stats: { totalUsers, activeUsers, verifiedUsers } });
673
+ };
674
+ ` : ts ? `import { requireAuth } from 'express-file-cluster/auth';
675
+ ${requireRoleImport}import type { Request, Response } from 'express';
676
+ ${dashboardMeta}${middlewares}
677
+ export const GET = async (_req: Request, res: Response) => {
678
+ ${roleGuard} // TODO: aggregate stats from DB
679
+ res.json({ stats: { users: 0 } });
680
+ };
681
+ ` : `import { requireAuth } from 'express-file-cluster/auth';
682
+ ${requireRoleImport}${dashboardMeta}${middlewares}
683
+ export const GET = async (_req, res) => {
684
+ ${roleGuard} // TODO: aggregate stats from DB
685
+ res.json({ stats: { users: 0 } });
686
+ };
687
+ `;
688
+ await fs.outputFile(path.join(dest, "src", "api", "admin", `dashboard.${ext}`), dashboardContent);
689
+ const usersListMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
690
+
691
+ export const meta: RouteMeta = {
692
+ description: 'List all users (admin only).',
693
+ response: { status: 200, body: { users: [], total: 0 } },
694
+ };
695
+
696
+ ` : `export const meta = {
697
+ description: 'List all users (admin only).',
698
+ response: { status: 200, body: { users: [], total: 0 } },
699
+ };
700
+
701
+ ` : "";
702
+ const adminUsersDbImport = opts.database === "mongodb" ? ts ? `import bcrypt from 'bcrypt';
703
+ import { User } from '../../../model/User.js';
704
+ ` : `import bcrypt from 'bcrypt';
705
+ import { User } from '../../../model/User.js';
706
+ ` : "";
707
+ const usersListContent = opts.database === "mongodb" ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
708
+ ${usersRequireRoleImport}import type { Request, Response } from 'express';
709
+ ${adminUsersDbImport}${usersListMeta}${middlewares}
710
+ export const GET = async (req: Request, res: Response) => {
711
+ ${roleGuard} const page = Math.max(1, Number(req.query.page) || 1);
712
+ const limit = Math.min(100, Number(req.query.limit) || 20);
713
+ const [all, total] = await Promise.all([User.find({}), User.count({})]);
714
+ const users = all.slice((page - 1) * limit, page * limit).map(({ password: _, ...u }) => u);
715
+ res.json({ users, total, page, limit });
716
+ };
717
+
718
+ export const POST = async (req: Request, res: Response) => {
719
+ ${roleGuard} const { name, email, password, role } = req.body;
720
+ if (!name || !email || !password) return res.status(400).json({ error: 'name, email and password are required' });
721
+ const existing = await User.findOne({ email });
722
+ if (existing) return res.status(409).json({ error: 'Email already in use' });
723
+ const hashed = await bcrypt.hash(password, 10);
724
+ const user = await User.create({ name, email, password: hashed, role: role ?? 'user' });
725
+ const { password: _, ...safe } = user;
726
+ res.status(201).json({ message: 'User created', user: safe });
727
+ };
728
+ ` : `import { requireAuth } from 'express-file-cluster/auth';
729
+ ${usersRequireRoleImport}${adminUsersDbImport}${usersListMeta}${middlewares}
730
+ export const GET = async (req, res) => {
731
+ ${roleGuard} const page = Math.max(1, Number(req.query.page) || 1);
732
+ const limit = Math.min(100, Number(req.query.limit) || 20);
733
+ const [all, total] = await Promise.all([User.find({}), User.count({})]);
734
+ const users = all.slice((page - 1) * limit, page * limit).map(({ password: _, ...u }) => u);
735
+ res.json({ users, total, page, limit });
736
+ };
737
+
738
+ export const POST = async (req, res) => {
739
+ ${roleGuard} const { name, email, password, role } = req.body;
740
+ if (!name || !email || !password) return res.status(400).json({ error: 'name, email and password are required' });
741
+ const existing = await User.findOne({ email });
742
+ if (existing) return res.status(409).json({ error: 'Email already in use' });
743
+ const hashed = await bcrypt.hash(password, 10);
744
+ const user = await User.create({ name, email, password: hashed, role: role ?? 'user' });
745
+ const { password: _, ...safe } = user;
746
+ res.status(201).json({ message: 'User created', user: safe });
747
+ };
748
+ ` : ts ? `import { requireAuth } from 'express-file-cluster/auth';
749
+ ${usersRequireRoleImport}import type { Request, Response } from 'express';
750
+ ${usersListMeta}${middlewares}
751
+ export const GET = async (_req: Request, res: Response) => {
752
+ ${roleGuard} // TODO: fetch users from DB with pagination
753
+ res.json({ users: [], total: 0 });
754
+ };
755
+
756
+ export const POST = async (req: Request, res: Response) => {
757
+ ${roleGuard} const { name, email, role } = req.body;
758
+ if (!name || !email) return res.status(400).json({ error: 'name and email are required' });
759
+ // TODO: create user in DB
760
+ res.status(201).json({ message: 'User created', user: { id: 'new-id', name, email, role: role ?? 'user' } });
761
+ };
762
+ ` : `import { requireAuth } from 'express-file-cluster/auth';
763
+ ${usersRequireRoleImport}${usersListMeta}${middlewares}
764
+ export const GET = async (_req, res) => {
765
+ ${roleGuard} // TODO: fetch users from DB with pagination
766
+ res.json({ users: [], total: 0 });
767
+ };
768
+
769
+ export const POST = async (req, res) => {
770
+ ${roleGuard} const { name, email, role } = req.body;
771
+ if (!name || !email) return res.status(400).json({ error: 'name and email are required' });
772
+ // TODO: create user in DB
773
+ res.status(201).json({ message: 'User created', user: { id: 'new-id', name, email, role: role ?? 'user' } });
774
+ };
775
+ `;
776
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "users", `index.${ext}`), usersListContent);
777
+ const userByIdMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
778
+
779
+ export const meta: RouteMeta = {
780
+ description: 'Get, update, or delete a single user by ID (admin only).',
781
+ };
782
+
783
+ ` : `export const meta = {
784
+ description: 'Get, update, or delete a single user by ID (admin only).',
785
+ };
786
+
787
+ ` : "";
788
+ const adminUserByIdDbImport = opts.database === "mongodb" ? `import { User } from '../../../model/User.js';
789
+ ` : "";
790
+ const userByIdContent = opts.database === "mongodb" ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
791
+ ${usersRequireRoleImport}import type { Request, Response } from 'express';
792
+ ${adminUserByIdDbImport}${userByIdMeta}${middlewares}
793
+ export const GET = async (req: Request, res: Response) => {
794
+ ${roleGuard} const { id } = req.params;
795
+ const user = await User.findById(id);
796
+ if (!user) return res.status(404).json({ error: 'User not found' });
797
+ const { password: _, ...safe } = user;
798
+ res.json({ user: safe });
799
+ };
800
+
801
+ export const PUT = async (req: Request, res: Response) => {
802
+ ${roleGuard} const { id } = req.params;
803
+ const { name, email, role, isActive } = req.body;
804
+ const updated = await User.update(id, { name, email, role, isActive });
805
+ if (!updated) return res.status(404).json({ error: 'User not found' });
806
+ const { password: _, ...safe } = updated;
807
+ res.json({ message: 'User updated', user: safe });
808
+ };
809
+
810
+ export const DELETE = async (req: Request, res: Response) => {
811
+ ${roleGuard} const { id } = req.params;
812
+ const user = await User.findById(id);
813
+ if (!user) return res.status(404).json({ error: 'User not found' });
814
+ await User.delete(id);
815
+ res.json({ message: \`User \${id} deleted\` });
816
+ };
817
+ ` : `import { requireAuth } from 'express-file-cluster/auth';
818
+ ${usersRequireRoleImport}${adminUserByIdDbImport}${userByIdMeta}${middlewares}
819
+ export const GET = async (req, res) => {
820
+ ${roleGuard} const { id } = req.params;
821
+ const user = await User.findById(id);
822
+ if (!user) return res.status(404).json({ error: 'User not found' });
823
+ const { password: _, ...safe } = user;
824
+ res.json({ user: safe });
825
+ };
826
+
827
+ export const PUT = async (req, res) => {
828
+ ${roleGuard} const { id } = req.params;
829
+ const { name, email, role, isActive } = req.body;
830
+ const updated = await User.update(id, { name, email, role, isActive });
831
+ if (!updated) return res.status(404).json({ error: 'User not found' });
832
+ const { password: _, ...safe } = updated;
833
+ res.json({ message: 'User updated', user: safe });
834
+ };
835
+
836
+ export const DELETE = async (req, res) => {
837
+ ${roleGuard} const { id } = req.params;
838
+ const user = await User.findById(id);
839
+ if (!user) return res.status(404).json({ error: 'User not found' });
840
+ await User.delete(id);
841
+ res.json({ message: \`User \${id} deleted\` });
842
+ };
843
+ ` : ts ? `import { requireAuth } from 'express-file-cluster/auth';
844
+ ${usersRequireRoleImport}import type { Request, Response } from 'express';
845
+ ${userByIdMeta}${middlewares}
846
+ export const GET = async (req: Request, res: Response) => {
847
+ ${roleGuard} const { id } = req.params;
848
+ // TODO: fetch user from DB
849
+ res.json({ user: { id } });
850
+ };
851
+
852
+ export const PUT = async (req: Request, res: Response) => {
853
+ ${roleGuard} const { id } = req.params;
854
+ // TODO: update user in DB
855
+ res.json({ message: 'User updated', user: { id, ...req.body } });
856
+ };
857
+
858
+ export const DELETE = async (req: Request, res: Response) => {
859
+ ${roleGuard} const { id } = req.params;
860
+ // TODO: delete user from DB
861
+ res.json({ message: \`User \${id} deleted\` });
862
+ };
863
+ ` : `import { requireAuth } from 'express-file-cluster/auth';
864
+ ${usersRequireRoleImport}${userByIdMeta}${middlewares}
865
+ export const GET = async (req, res) => {
866
+ ${roleGuard} const { id } = req.params;
867
+ // TODO: fetch user from DB
868
+ res.json({ user: { id } });
869
+ };
870
+
871
+ export const PUT = async (req, res) => {
872
+ ${roleGuard} const { id } = req.params;
873
+ // TODO: update user in DB
874
+ res.json({ message: 'User updated', user: { id, ...req.body } });
875
+ };
876
+
877
+ export const DELETE = async (req, res) => {
878
+ ${roleGuard} const { id } = req.params;
879
+ // TODO: delete user from DB
880
+ res.json({ message: \`User \${id} deleted\` });
881
+ };
882
+ `;
883
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "users", `[id].${ext}`), userByIdContent);
884
+ }
885
+ async function writeUserRoutes(dest, opts) {
886
+ const ext = opts.language === "typescript" ? "ts" : "js";
887
+ const ts = opts.language === "typescript";
888
+ const requireRoleImport = opts.rbac ? `import { requireRole } from '../../middleware/requireRole.js';
889
+ ` : "";
890
+ const middlewares = opts.rbac ? `export const middlewares = [requireAuth, requireRole('user', 'admin')];
891
+ ` : `export const middlewares = [requireAuth];
892
+ `;
893
+ const profileMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
894
+
895
+ export const meta: RouteMeta = {
896
+ description: "View or update the authenticated user's profile.",
897
+ response: { status: 200, body: { user: { id: '1', role: 'user', email: 'user@example.com' } } },
898
+ };
899
+
900
+ ` : `export const meta = {
901
+ description: "View or update the authenticated user's profile.",
902
+ response: { status: 200, body: { user: { id: '1', role: 'user', email: 'user@example.com' } } },
903
+ };
904
+
905
+ ` : "";
906
+ const profileDbImport = opts.database === "mongodb" ? `import { User } from '../../model/User.js';
907
+ ` : "";
908
+ const profileContent = opts.database === "mongodb" ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
909
+ ${requireRoleImport}import type { Request, Response } from 'express';
910
+ ${profileDbImport}${profileMeta}${middlewares}
911
+ export const GET = async (req: Request, res: Response) => {
912
+ const { id } = (req as any).user;
913
+ const user = await User.findById(id);
914
+ if (!user) return res.status(404).json({ error: 'User not found' });
915
+ const { password: _, ...safe } = user;
916
+ res.json({ user: safe });
917
+ };
918
+
919
+ export const PUT = async (req: Request, res: Response) => {
920
+ const { id } = (req as any).user;
921
+ const { name, email } = req.body;
922
+ const updated = await User.update(id, { name, email });
923
+ if (!updated) return res.status(404).json({ error: 'User not found' });
924
+ const { password: _, ...safe } = updated;
925
+ res.json({ message: 'Profile updated', user: safe });
926
+ };
927
+ ` : `import { requireAuth } from 'express-file-cluster/auth';
928
+ ${requireRoleImport}${profileDbImport}${profileMeta}${middlewares}
929
+ export const GET = async (req, res) => {
930
+ const { id } = req.user;
931
+ const user = await User.findById(id);
932
+ if (!user) return res.status(404).json({ error: 'User not found' });
933
+ const { password: _, ...safe } = user;
934
+ res.json({ user: safe });
935
+ };
936
+
937
+ export const PUT = async (req, res) => {
938
+ const { id } = req.user;
939
+ const { name, email } = req.body;
940
+ const updated = await User.update(id, { name, email });
941
+ if (!updated) return res.status(404).json({ error: 'User not found' });
942
+ const { password: _, ...safe } = updated;
943
+ res.json({ message: 'Profile updated', user: safe });
944
+ };
945
+ ` : ts ? `import { requireAuth } from 'express-file-cluster/auth';
946
+ ${requireRoleImport}import type { Request, Response } from 'express';
947
+ ${profileMeta}${middlewares}
948
+ export const GET = async (req: Request, res: Response) => {
949
+ res.json({ user: (req as any).user });
950
+ };
951
+
952
+ export const PUT = async (req: Request, res: Response) => {
953
+ const { name, email } = req.body;
954
+ // TODO: update user in DB
955
+ res.json({ message: 'Profile updated', user: { ...(req as any).user, name, email } });
956
+ };
957
+ ` : `import { requireAuth } from 'express-file-cluster/auth';
958
+ ${requireRoleImport}${profileMeta}${middlewares}
959
+ export const GET = async (req, res) => {
960
+ res.json({ user: req.user });
961
+ };
962
+
963
+ export const PUT = async (req, res) => {
964
+ const { name, email } = req.body;
965
+ // TODO: update user in DB
966
+ res.json({ message: 'Profile updated', user: { ...req.user, name, email } });
967
+ };
968
+ `;
969
+ await fs.outputFile(path.join(dest, "src", "api", "user", `profile.${ext}`), profileContent);
970
+ }
971
+ function mkMeta(opts, description, body, req) {
972
+ if (!opts.routeDocs) return "";
973
+ const reqLine = req ? ` request: { body: ${req} },
974
+ ` : "";
975
+ return opts.language === "typescript" ? `import type { RouteMeta } from 'express-file-cluster';
976
+
977
+ export const meta: RouteMeta = {
978
+ description: '${description}',
979
+ ${reqLine} response: { status: 200, body: ${body} },
980
+ };
981
+
982
+ ` : `export const meta = {
983
+ description: '${description}',
984
+ ${reqLine} response: { status: 200, body: ${body} },
985
+ };
986
+
987
+ `;
988
+ }
989
+ async function writeSessionModel(dest, opts) {
990
+ const ext = opts.language === "typescript" ? "ts" : "js";
991
+ const ts = opts.language === "typescript";
992
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
993
+
994
+ export interface SessionDocument {
995
+ userId: string;
996
+ token: string;
997
+ ip: string;
998
+ userAgent: string;
999
+ expiresAt: Date;
1000
+ isActive: boolean;
1001
+ }
1002
+
1003
+ export const Session = defineModel<SessionDocument>('Session', {
1004
+ userId: { type: 'string', required: true },
1005
+ token: { type: 'string', required: true, unique: true },
1006
+ ip: { type: 'string', required: true },
1007
+ userAgent: { type: 'string', required: true },
1008
+ expiresAt: { type: 'date', required: true },
1009
+ isActive: { type: 'boolean', default: true },
1010
+ });
1011
+ ` : `import { defineModel } from 'express-file-cluster';
1012
+
1013
+ export const Session = defineModel('Session', {
1014
+ userId: { type: 'string', required: true },
1015
+ token: { type: 'string', required: true, unique: true },
1016
+ ip: { type: 'string', required: true },
1017
+ userAgent: { type: 'string', required: true },
1018
+ expiresAt: { type: 'date', required: true },
1019
+ isActive: { type: 'boolean', default: true },
1020
+ });
1021
+ ` : ts ? `// TODO: define your Drizzle schema for Session
1022
+ // import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';
1023
+ //
1024
+ // export const sessions = pgTable('sessions', {
1025
+ // id: serial('id').primaryKey(),
1026
+ // userId: text('user_id').notNull(),
1027
+ // token: text('token').notNull().unique(),
1028
+ // ip: text('ip').notNull(),
1029
+ // userAgent: text('user_agent').notNull(),
1030
+ // expiresAt: timestamp('expires_at').notNull(),
1031
+ // isActive: boolean('is_active').notNull().default(true),
1032
+ // });
1033
+ export {};
1034
+ ` : `// TODO: define your Drizzle schema for Session
1035
+ export {};
1036
+ `;
1037
+ await fs.outputFile(path.join(dest, "src", "model", `Session.${ext}`), content);
1038
+ }
1039
+ async function writeNotificationModel(dest, opts) {
1040
+ const ext = opts.language === "typescript" ? "ts" : "js";
1041
+ const ts = opts.language === "typescript";
1042
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1043
+
1044
+ export interface NotificationDocument {
1045
+ userId: string;
1046
+ title: string;
1047
+ message: string;
1048
+ type: string;
1049
+ isRead: boolean;
1050
+ link?: string;
1051
+ }
1052
+
1053
+ export const Notification = defineModel<NotificationDocument>('Notification', {
1054
+ userId: { type: 'string', required: true },
1055
+ title: { type: 'string', required: true },
1056
+ message: { type: 'string', required: true },
1057
+ type: { type: 'string', required: true, default: 'info' },
1058
+ isRead: { type: 'boolean', default: false },
1059
+ link: { type: 'string' },
1060
+ });
1061
+ ` : `import { defineModel } from 'express-file-cluster';
1062
+
1063
+ export const Notification = defineModel('Notification', {
1064
+ userId: { type: 'string', required: true },
1065
+ title: { type: 'string', required: true },
1066
+ message: { type: 'string', required: true },
1067
+ type: { type: 'string', required: true, default: 'info' },
1068
+ isRead: { type: 'boolean', default: false },
1069
+ link: { type: 'string' },
1070
+ });
1071
+ ` : ts ? `// TODO: define your Drizzle schema for Notification
1072
+ // import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';
1073
+ //
1074
+ // export const notifications = pgTable('notifications', {
1075
+ // id: serial('id').primaryKey(),
1076
+ // userId: text('user_id').notNull(),
1077
+ // title: text('title').notNull(),
1078
+ // message: text('message').notNull(),
1079
+ // type: text('type').notNull().default('info'),
1080
+ // isRead: boolean('is_read').notNull().default(false),
1081
+ // link: text('link'),
1082
+ // createdAt: timestamp('created_at').defaultNow(),
1083
+ // });
1084
+ export {};
1085
+ ` : `// TODO: define your Drizzle schema for Notification
1086
+ export {};
1087
+ `;
1088
+ await fs.outputFile(path.join(dest, "src", "model", `Notification.${ext}`), content);
1089
+ }
1090
+ async function writeFileModel(dest, opts) {
1091
+ const ext = opts.language === "typescript" ? "ts" : "js";
1092
+ const ts = opts.language === "typescript";
1093
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1094
+
1095
+ export interface FileDocument {
1096
+ userId: string;
1097
+ filename: string;
1098
+ originalName: string;
1099
+ mimeType: string;
1100
+ size: number;
1101
+ url: string;
1102
+ isPublic: boolean;
1103
+ }
1104
+
1105
+ export const File = defineModel<FileDocument>('File', {
1106
+ userId: { type: 'string', required: true },
1107
+ filename: { type: 'string', required: true },
1108
+ originalName: { type: 'string', required: true },
1109
+ mimeType: { type: 'string', required: true },
1110
+ size: { type: 'number', required: true },
1111
+ url: { type: 'string', required: true },
1112
+ isPublic: { type: 'boolean', default: false },
1113
+ });
1114
+ ` : `import { defineModel } from 'express-file-cluster';
1115
+
1116
+ export const File = defineModel('File', {
1117
+ userId: { type: 'string', required: true },
1118
+ filename: { type: 'string', required: true },
1119
+ originalName: { type: 'string', required: true },
1120
+ mimeType: { type: 'string', required: true },
1121
+ size: { type: 'number', required: true },
1122
+ url: { type: 'string', required: true },
1123
+ isPublic: { type: 'boolean', default: false },
1124
+ });
1125
+ ` : ts ? `// TODO: define your Drizzle schema for File
1126
+ // import { pgTable, serial, text, boolean, integer, timestamp } from 'drizzle-orm/pg-core';
1127
+ //
1128
+ // export const files = pgTable('files', {
1129
+ // id: serial('id').primaryKey(),
1130
+ // userId: text('user_id').notNull(),
1131
+ // filename: text('filename').notNull(),
1132
+ // originalName: text('original_name').notNull(),
1133
+ // mimeType: text('mime_type').notNull(),
1134
+ // size: integer('size').notNull(),
1135
+ // url: text('url').notNull(),
1136
+ // isPublic: boolean('is_public').notNull().default(false),
1137
+ // createdAt: timestamp('created_at').defaultNow(),
1138
+ // });
1139
+ export {};
1140
+ ` : `// TODO: define your Drizzle schema for File
1141
+ export {};
1142
+ `;
1143
+ await fs.outputFile(path.join(dest, "src", "model", `File.${ext}`), content);
1144
+ }
1145
+ async function writeSupportTicketModel(dest, opts) {
1146
+ const ext = opts.language === "typescript" ? "ts" : "js";
1147
+ const ts = opts.language === "typescript";
1148
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1149
+
1150
+ export interface SupportTicketDocument {
1151
+ userId: string;
1152
+ subject: string;
1153
+ message: string;
1154
+ status: string;
1155
+ priority: string;
1156
+ assignedTo?: string;
1157
+ }
1158
+
1159
+ export const SupportTicket = defineModel<SupportTicketDocument>('SupportTicket', {
1160
+ userId: { type: 'string', required: true },
1161
+ subject: { type: 'string', required: true },
1162
+ message: { type: 'string', required: true },
1163
+ status: { type: 'string', required: true, default: 'open' },
1164
+ priority: { type: 'string', required: true, default: 'normal' },
1165
+ assignedTo: { type: 'string' },
1166
+ });
1167
+ ` : `import { defineModel } from 'express-file-cluster';
1168
+
1169
+ export const SupportTicket = defineModel('SupportTicket', {
1170
+ userId: { type: 'string', required: true },
1171
+ subject: { type: 'string', required: true },
1172
+ message: { type: 'string', required: true },
1173
+ status: { type: 'string', required: true, default: 'open' },
1174
+ priority: { type: 'string', required: true, default: 'normal' },
1175
+ assignedTo: { type: 'string' },
1176
+ });
1177
+ ` : ts ? `// TODO: define your Drizzle schema for SupportTicket
1178
+ // import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
1179
+ //
1180
+ // export const supportTickets = pgTable('support_tickets', {
1181
+ // id: serial('id').primaryKey(),
1182
+ // userId: text('user_id').notNull(),
1183
+ // subject: text('subject').notNull(),
1184
+ // message: text('message').notNull(),
1185
+ // status: text('status').notNull().default('open'),
1186
+ // priority: text('priority').notNull().default('normal'),
1187
+ // assignedTo: text('assigned_to'),
1188
+ // createdAt: timestamp('created_at').defaultNow(),
1189
+ // updatedAt: timestamp('updated_at').defaultNow(),
1190
+ // });
1191
+ export {};
1192
+ ` : `// TODO: define your Drizzle schema for SupportTicket
1193
+ export {};
1194
+ `;
1195
+ await fs.outputFile(path.join(dest, "src", "model", `SupportTicket.${ext}`), content);
1196
+ }
1197
+ async function writeAuditLogModel(dest, opts) {
1198
+ const ext = opts.language === "typescript" ? "ts" : "js";
1199
+ const ts = opts.language === "typescript";
1200
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1201
+
1202
+ export interface AuditLogDocument {
1203
+ adminId: string;
1204
+ action: string;
1205
+ entity: string;
1206
+ entityId: string;
1207
+ metadata?: Record<string, unknown>;
1208
+ ip: string;
1209
+ }
1210
+
1211
+ export const AuditLog = defineModel<AuditLogDocument>('AuditLog', {
1212
+ adminId: { type: 'string', required: true },
1213
+ action: { type: 'string', required: true },
1214
+ entity: { type: 'string', required: true },
1215
+ entityId: { type: 'string', required: true },
1216
+ metadata: { type: 'object' },
1217
+ ip: { type: 'string', required: true },
1218
+ });
1219
+ ` : `import { defineModel } from 'express-file-cluster';
1220
+
1221
+ export const AuditLog = defineModel('AuditLog', {
1222
+ adminId: { type: 'string', required: true },
1223
+ action: { type: 'string', required: true },
1224
+ entity: { type: 'string', required: true },
1225
+ entityId: { type: 'string', required: true },
1226
+ metadata: { type: 'object' },
1227
+ ip: { type: 'string', required: true },
1228
+ });
1229
+ ` : ts ? `// TODO: define your Drizzle schema for AuditLog
1230
+ // import { pgTable, serial, text, jsonb, timestamp } from 'drizzle-orm/pg-core';
1231
+ //
1232
+ // export const auditLogs = pgTable('audit_logs', {
1233
+ // id: serial('id').primaryKey(),
1234
+ // adminId: text('admin_id').notNull(),
1235
+ // action: text('action').notNull(),
1236
+ // entity: text('entity').notNull(),
1237
+ // entityId: text('entity_id').notNull(),
1238
+ // metadata: jsonb('metadata'),
1239
+ // ip: text('ip').notNull(),
1240
+ // createdAt: timestamp('created_at').defaultNow(),
1241
+ // });
1242
+ export {};
1243
+ ` : `// TODO: define your Drizzle schema for AuditLog
1244
+ export {};
1245
+ `;
1246
+ await fs.outputFile(path.join(dest, "src", "model", `AuditLog.${ext}`), content);
1247
+ }
1248
+ async function writeSubscriptionModel(dest, opts) {
1249
+ const ext = opts.language === "typescript" ? "ts" : "js";
1250
+ const ts = opts.language === "typescript";
1251
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1252
+
1253
+ export interface SubscriptionDocument {
1254
+ userId: string;
1255
+ planId: string;
1256
+ status: string;
1257
+ startDate: Date;
1258
+ endDate: Date;
1259
+ cancelledAt?: Date;
1260
+ }
1261
+
1262
+ export const Subscription = defineModel<SubscriptionDocument>('Subscription', {
1263
+ userId: { type: 'string', required: true },
1264
+ planId: { type: 'string', required: true },
1265
+ status: { type: 'string', required: true, default: 'active' },
1266
+ startDate: { type: 'date', required: true },
1267
+ endDate: { type: 'date', required: true },
1268
+ cancelledAt: { type: 'date' },
1269
+ });
1270
+ ` : `import { defineModel } from 'express-file-cluster';
1271
+
1272
+ export const Subscription = defineModel('Subscription', {
1273
+ userId: { type: 'string', required: true },
1274
+ planId: { type: 'string', required: true },
1275
+ status: { type: 'string', required: true, default: 'active' },
1276
+ startDate: { type: 'date', required: true },
1277
+ endDate: { type: 'date', required: true },
1278
+ cancelledAt: { type: 'date' },
1279
+ });
1280
+ ` : ts ? `// TODO: define your Drizzle schema for Subscription
1281
+ // import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
1282
+ //
1283
+ // export const subscriptions = pgTable('subscriptions', {
1284
+ // id: serial('id').primaryKey(),
1285
+ // userId: text('user_id').notNull(),
1286
+ // planId: text('plan_id').notNull(),
1287
+ // status: text('status').notNull().default('active'),
1288
+ // startDate: timestamp('start_date').notNull(),
1289
+ // endDate: timestamp('end_date').notNull(),
1290
+ // cancelledAt: timestamp('cancelled_at'),
1291
+ // createdAt: timestamp('created_at').defaultNow(),
1292
+ // });
1293
+ export {};
1294
+ ` : `// TODO: define your Drizzle schema for Subscription
1295
+ export {};
1296
+ `;
1297
+ await fs.outputFile(path.join(dest, "src", "model", `Subscription.${ext}`), content);
1298
+ }
1299
+ async function writePlanModel(dest, opts) {
1300
+ const ext = opts.language === "typescript" ? "ts" : "js";
1301
+ const ts = opts.language === "typescript";
1302
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1303
+
1304
+ export interface PlanDocument {
1305
+ name: string;
1306
+ description: string;
1307
+ price: number;
1308
+ interval: string;
1309
+ features: string[];
1310
+ isActive: boolean;
1311
+ }
1312
+
1313
+ export const Plan = defineModel<PlanDocument>('Plan', {
1314
+ name: { type: 'string', required: true },
1315
+ description: { type: 'string', required: true },
1316
+ price: { type: 'number', required: true },
1317
+ interval: { type: 'string', required: true, default: 'monthly' },
1318
+ features: { type: 'array', default: [] },
1319
+ isActive: { type: 'boolean', default: true },
1320
+ });
1321
+ ` : `import { defineModel } from 'express-file-cluster';
1322
+
1323
+ export const Plan = defineModel('Plan', {
1324
+ name: { type: 'string', required: true },
1325
+ description: { type: 'string', required: true },
1326
+ price: { type: 'number', required: true },
1327
+ interval: { type: 'string', required: true, default: 'monthly' },
1328
+ features: { type: 'array', default: [] },
1329
+ isActive: { type: 'boolean', default: true },
1330
+ });
1331
+ ` : ts ? `// TODO: define your Drizzle schema for Plan
1332
+ // import { pgTable, serial, text, numeric, boolean, timestamp } from 'drizzle-orm/pg-core';
1333
+ //
1334
+ // export const plans = pgTable('plans', {
1335
+ // id: serial('id').primaryKey(),
1336
+ // name: text('name').notNull(),
1337
+ // description: text('description').notNull(),
1338
+ // price: numeric('price').notNull(),
1339
+ // interval: text('interval').notNull().default('monthly'),
1340
+ // features: text('features').array().notNull().default([]),
1341
+ // isActive: boolean('is_active').notNull().default(true),
1342
+ // createdAt: timestamp('created_at').defaultNow(),
1343
+ // });
1344
+ export {};
1345
+ ` : `// TODO: define your Drizzle schema for Plan
1346
+ export {};
1347
+ `;
1348
+ await fs.outputFile(path.join(dest, "src", "model", `Plan.${ext}`), content);
1349
+ }
1350
+ async function writeInvoiceModel(dest, opts) {
1351
+ const ext = opts.language === "typescript" ? "ts" : "js";
1352
+ const ts = opts.language === "typescript";
1353
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1354
+
1355
+ export interface InvoiceDocument {
1356
+ userId: string;
1357
+ subscriptionId: string;
1358
+ amount: number;
1359
+ currency: string;
1360
+ status: string;
1361
+ paidAt?: Date;
1362
+ }
1363
+
1364
+ export const Invoice = defineModel<InvoiceDocument>('Invoice', {
1365
+ userId: { type: 'string', required: true },
1366
+ subscriptionId: { type: 'string', required: true },
1367
+ amount: { type: 'number', required: true },
1368
+ currency: { type: 'string', required: true, default: 'USD' },
1369
+ status: { type: 'string', required: true, default: 'pending' },
1370
+ paidAt: { type: 'date' },
1371
+ });
1372
+ ` : `import { defineModel } from 'express-file-cluster';
1373
+
1374
+ export const Invoice = defineModel('Invoice', {
1375
+ userId: { type: 'string', required: true },
1376
+ subscriptionId: { type: 'string', required: true },
1377
+ amount: { type: 'number', required: true },
1378
+ currency: { type: 'string', required: true, default: 'USD' },
1379
+ status: { type: 'string', required: true, default: 'pending' },
1380
+ paidAt: { type: 'date' },
1381
+ });
1382
+ ` : ts ? `// TODO: define your Drizzle schema for Invoice
1383
+ // import { pgTable, serial, text, numeric, timestamp } from 'drizzle-orm/pg-core';
1384
+ //
1385
+ // export const invoices = pgTable('invoices', {
1386
+ // id: serial('id').primaryKey(),
1387
+ // userId: text('user_id').notNull(),
1388
+ // subscriptionId: text('subscription_id').notNull(),
1389
+ // amount: numeric('amount').notNull(),
1390
+ // currency: text('currency').notNull().default('USD'),
1391
+ // status: text('status').notNull().default('pending'),
1392
+ // paidAt: timestamp('paid_at'),
1393
+ // createdAt: timestamp('created_at').defaultNow(),
1394
+ // });
1395
+ export {};
1396
+ ` : `// TODO: define your Drizzle schema for Invoice
1397
+ export {};
1398
+ `;
1399
+ await fs.outputFile(path.join(dest, "src", "model", `Invoice.${ext}`), content);
1400
+ }
1401
+ async function writeApiKeyModel(dest, opts) {
1402
+ const ext = opts.language === "typescript" ? "ts" : "js";
1403
+ const ts = opts.language === "typescript";
1404
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1405
+
1406
+ export interface ApiKeyDocument {
1407
+ userId: string;
1408
+ name: string;
1409
+ key: string;
1410
+ lastUsed?: Date;
1411
+ expiresAt?: Date;
1412
+ isActive: boolean;
1413
+ }
1414
+
1415
+ export const ApiKey = defineModel<ApiKeyDocument>('ApiKey', {
1416
+ userId: { type: 'string', required: true },
1417
+ name: { type: 'string', required: true },
1418
+ key: { type: 'string', required: true, unique: true },
1419
+ lastUsed: { type: 'date' },
1420
+ expiresAt:{ type: 'date' },
1421
+ isActive: { type: 'boolean', default: true },
1422
+ });
1423
+ ` : `import { defineModel } from 'express-file-cluster';
1424
+
1425
+ export const ApiKey = defineModel('ApiKey', {
1426
+ userId: { type: 'string', required: true },
1427
+ name: { type: 'string', required: true },
1428
+ key: { type: 'string', required: true, unique: true },
1429
+ lastUsed: { type: 'date' },
1430
+ expiresAt: { type: 'date' },
1431
+ isActive: { type: 'boolean', default: true },
1432
+ });
1433
+ ` : ts ? `// TODO: define your Drizzle schema for ApiKey
1434
+ // import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';
1435
+ //
1436
+ // export const apiKeys = pgTable('api_keys', {
1437
+ // id: serial('id').primaryKey(),
1438
+ // userId: text('user_id').notNull(),
1439
+ // name: text('name').notNull(),
1440
+ // key: text('key').notNull().unique(),
1441
+ // lastUsed: timestamp('last_used'),
1442
+ // expiresAt: timestamp('expires_at'),
1443
+ // isActive: boolean('is_active').notNull().default(true),
1444
+ // createdAt: timestamp('created_at').defaultNow(),
1445
+ // });
1446
+ export {};
1447
+ ` : `// TODO: define your Drizzle schema for ApiKey
1448
+ export {};
1449
+ `;
1450
+ await fs.outputFile(path.join(dest, "src", "model", `ApiKey.${ext}`), content);
1451
+ }
1452
+ async function writeRoleModel(dest, opts) {
1453
+ const ext = opts.language === "typescript" ? "ts" : "js";
1454
+ const ts = opts.language === "typescript";
1455
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1456
+
1457
+ export interface RoleDocument {
1458
+ name: string;
1459
+ description: string;
1460
+ permissions: string[];
1461
+ }
1462
+
1463
+ export const Role = defineModel<RoleDocument>('Role', {
1464
+ name: { type: 'string', required: true, unique: true },
1465
+ description: { type: 'string', required: true },
1466
+ permissions: { type: 'array', default: [] },
1467
+ });
1468
+ ` : `import { defineModel } from 'express-file-cluster';
1469
+
1470
+ export const Role = defineModel('Role', {
1471
+ name: { type: 'string', required: true, unique: true },
1472
+ description: { type: 'string', required: true },
1473
+ permissions: { type: 'array', default: [] },
1474
+ });
1475
+ ` : ts ? `// TODO: define your Drizzle schema for Role
1476
+ // import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
1477
+ //
1478
+ // export const roles = pgTable('roles', {
1479
+ // id: serial('id').primaryKey(),
1480
+ // name: text('name').notNull().unique(),
1481
+ // description: text('description').notNull(),
1482
+ // permissions: text('permissions').array().notNull().default([]),
1483
+ // createdAt: timestamp('created_at').defaultNow(),
1484
+ // });
1485
+ export {};
1486
+ ` : `// TODO: define your Drizzle schema for Role
1487
+ export {};
1488
+ `;
1489
+ await fs.outputFile(path.join(dest, "src", "model", `Role.${ext}`), content);
1490
+ }
1491
+ async function writeFAQModel(dest, opts) {
1492
+ const ext = opts.language === "typescript" ? "ts" : "js";
1493
+ const ts = opts.language === "typescript";
1494
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1495
+
1496
+ export interface FAQDocument {
1497
+ question: string;
1498
+ answer: string;
1499
+ category: string;
1500
+ order: number;
1501
+ isPublished: boolean;
1502
+ }
1503
+
1504
+ export const FAQ = defineModel<FAQDocument>('FAQ', {
1505
+ question: { type: 'string', required: true },
1506
+ answer: { type: 'string', required: true },
1507
+ category: { type: 'string', required: true, default: 'general' },
1508
+ order: { type: 'number', default: 0 },
1509
+ isPublished: { type: 'boolean', default: false },
1510
+ });
1511
+ ` : `import { defineModel } from 'express-file-cluster';
1512
+
1513
+ export const FAQ = defineModel('FAQ', {
1514
+ question: { type: 'string', required: true },
1515
+ answer: { type: 'string', required: true },
1516
+ category: { type: 'string', required: true, default: 'general' },
1517
+ order: { type: 'number', default: 0 },
1518
+ isPublished: { type: 'boolean', default: false },
1519
+ });
1520
+ ` : ts ? `// TODO: define your Drizzle schema for FAQ
1521
+ // import { pgTable, serial, text, boolean, integer, timestamp } from 'drizzle-orm/pg-core';
1522
+ //
1523
+ // export const faqs = pgTable('faqs', {
1524
+ // id: serial('id').primaryKey(),
1525
+ // question: text('question').notNull(),
1526
+ // answer: text('answer').notNull(),
1527
+ // category: text('category').notNull().default('general'),
1528
+ // order: integer('order').notNull().default(0),
1529
+ // isPublished: boolean('is_published').notNull().default(false),
1530
+ // createdAt: timestamp('created_at').defaultNow(),
1531
+ // });
1532
+ export {};
1533
+ ` : `// TODO: define your Drizzle schema for FAQ
1534
+ export {};
1535
+ `;
1536
+ await fs.outputFile(path.join(dest, "src", "model", `FAQ.${ext}`), content);
1537
+ }
1538
+ async function writeBlogModel(dest, opts) {
1539
+ const ext = opts.language === "typescript" ? "ts" : "js";
1540
+ const ts = opts.language === "typescript";
1541
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1542
+
1543
+ export interface BlogDocument {
1544
+ title: string;
1545
+ slug: string;
1546
+ content: string;
1547
+ authorId: string;
1548
+ category: string;
1549
+ tags: string[];
1550
+ status: string;
1551
+ publishedAt?: Date;
1552
+ }
1553
+
1554
+ export const Blog = defineModel<BlogDocument>('Blog', {
1555
+ title: { type: 'string', required: true },
1556
+ slug: { type: 'string', required: true, unique: true },
1557
+ content: { type: 'string', required: true },
1558
+ authorId: { type: 'string', required: true },
1559
+ category: { type: 'string', required: true, default: 'general' },
1560
+ tags: { type: 'array', default: [] },
1561
+ status: { type: 'string', required: true, default: 'draft' },
1562
+ publishedAt: { type: 'date' },
1563
+ });
1564
+ ` : `import { defineModel } from 'express-file-cluster';
1565
+
1566
+ export const Blog = defineModel('Blog', {
1567
+ title: { type: 'string', required: true },
1568
+ slug: { type: 'string', required: true, unique: true },
1569
+ content: { type: 'string', required: true },
1570
+ authorId: { type: 'string', required: true },
1571
+ category: { type: 'string', required: true, default: 'general' },
1572
+ tags: { type: 'array', default: [] },
1573
+ status: { type: 'string', required: true, default: 'draft' },
1574
+ publishedAt: { type: 'date' },
1575
+ });
1576
+ ` : ts ? `// TODO: define your Drizzle schema for Blog
1577
+ // import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
1578
+ //
1579
+ // export const blogs = pgTable('blogs', {
1580
+ // id: serial('id').primaryKey(),
1581
+ // title: text('title').notNull(),
1582
+ // slug: text('slug').notNull().unique(),
1583
+ // content: text('content').notNull(),
1584
+ // authorId: text('author_id').notNull(),
1585
+ // category: text('category').notNull().default('general'),
1586
+ // tags: text('tags').array().notNull().default([]),
1587
+ // status: text('status').notNull().default('draft'),
1588
+ // publishedAt: timestamp('published_at'),
1589
+ // createdAt: timestamp('created_at').defaultNow(),
1590
+ // updatedAt: timestamp('updated_at').defaultNow(),
1591
+ // });
1592
+ export {};
1593
+ ` : `// TODO: define your Drizzle schema for Blog
1594
+ export {};
1595
+ `;
1596
+ await fs.outputFile(path.join(dest, "src", "model", `Blog.${ext}`), content);
1597
+ }
1598
+ async function writeCategoryModel(dest, opts) {
1599
+ const ext = opts.language === "typescript" ? "ts" : "js";
1600
+ const ts = opts.language === "typescript";
1601
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1602
+
1603
+ export interface CategoryDocument {
1604
+ name: string;
1605
+ slug: string;
1606
+ description?: string;
1607
+ parentId?: string;
1608
+ }
1609
+
1610
+ export const Category = defineModel<CategoryDocument>('Category', {
1611
+ name: { type: 'string', required: true },
1612
+ slug: { type: 'string', required: true, unique: true },
1613
+ description: { type: 'string' },
1614
+ parentId: { type: 'string' },
1615
+ });
1616
+ ` : `import { defineModel } from 'express-file-cluster';
1617
+
1618
+ export const Category = defineModel('Category', {
1619
+ name: { type: 'string', required: true },
1620
+ slug: { type: 'string', required: true, unique: true },
1621
+ description: { type: 'string' },
1622
+ parentId: { type: 'string' },
1623
+ });
1624
+ ` : ts ? `// TODO: define your Drizzle schema for Category
1625
+ // import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
1626
+ //
1627
+ // export const categories = pgTable('categories', {
1628
+ // id: serial('id').primaryKey(),
1629
+ // name: text('name').notNull(),
1630
+ // slug: text('slug').notNull().unique(),
1631
+ // description: text('description'),
1632
+ // parentId: text('parent_id'),
1633
+ // createdAt: timestamp('created_at').defaultNow(),
1634
+ // });
1635
+ export {};
1636
+ ` : `// TODO: define your Drizzle schema for Category
1637
+ export {};
1638
+ `;
1639
+ await fs.outputFile(path.join(dest, "src", "model", `Category.${ext}`), content);
1640
+ }
1641
+ async function writeCouponModel(dest, opts) {
1642
+ const ext = opts.language === "typescript" ? "ts" : "js";
1643
+ const ts = opts.language === "typescript";
1644
+ const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1645
+
1646
+ export interface CouponDocument {
1647
+ code: string;
1648
+ type: string;
1649
+ value: number;
1650
+ maxUses: number;
1651
+ usedCount: number;
1652
+ expiresAt?: Date;
1653
+ isActive: boolean;
1654
+ }
1655
+
1656
+ export const Coupon = defineModel<CouponDocument>('Coupon', {
1657
+ code: { type: 'string', required: true, unique: true },
1658
+ type: { type: 'string', required: true, default: 'percent' },
1659
+ value: { type: 'number', required: true },
1660
+ maxUses: { type: 'number', default: 0 },
1661
+ usedCount: { type: 'number', default: 0 },
1662
+ expiresAt: { type: 'date' },
1663
+ isActive: { type: 'boolean', default: true },
1664
+ });
1665
+ ` : `import { defineModel } from 'express-file-cluster';
1666
+
1667
+ export const Coupon = defineModel('Coupon', {
1668
+ code: { type: 'string', required: true, unique: true },
1669
+ type: { type: 'string', required: true, default: 'percent' },
1670
+ value: { type: 'number', required: true },
1671
+ maxUses: { type: 'number', default: 0 },
1672
+ usedCount: { type: 'number', default: 0 },
1673
+ expiresAt: { type: 'date' },
1674
+ isActive: { type: 'boolean', default: true },
1675
+ });
1676
+ ` : ts ? `// TODO: define your Drizzle schema for Coupon
1677
+ // import { pgTable, serial, text, numeric, integer, boolean, timestamp } from 'drizzle-orm/pg-core';
1678
+ //
1679
+ // export const coupons = pgTable('coupons', {
1680
+ // id: serial('id').primaryKey(),
1681
+ // code: text('code').notNull().unique(),
1682
+ // type: text('type').notNull().default('percent'),
1683
+ // value: numeric('value').notNull(),
1684
+ // maxUses: integer('max_uses').notNull().default(0),
1685
+ // usedCount: integer('used_count').notNull().default(0),
1686
+ // expiresAt: timestamp('expires_at'),
1687
+ // isActive: boolean('is_active').notNull().default(true),
1688
+ // createdAt: timestamp('created_at').defaultNow(),
1689
+ // });
1690
+ export {};
1691
+ ` : `// TODO: define your Drizzle schema for Coupon
1692
+ export {};
1693
+ `;
1694
+ await fs.outputFile(path.join(dest, "src", "model", `Coupon.${ext}`), content);
1695
+ }
1696
+ async function writeAuthExtendedRoutes(dest, opts) {
1697
+ const ext = opts.language === "typescript" ? "ts" : "js";
1698
+ const ts = opts.language === "typescript";
1699
+ const rr2 = opts.rbac ? `import { requireRole } from '../../middleware/requireRole.js';
1700
+ ` : "";
1701
+ const rr3 = opts.rbac ? `import { requireRole } from '../../../middleware/requireRole.js';
1702
+ ` : "";
1703
+ const mwUser2 = opts.rbac ? `export const middlewares = [requireAuth, requireRole('user', 'admin')];
1704
+ ` : `export const middlewares = [requireAuth];
1705
+ `;
1706
+ const mwUser3 = mwUser2;
1707
+ const reqT = ts ? `import type { Request, Response } from 'express';
1708
+ ` : "";
1709
+ const RA = `import { requireAuth } from 'express-file-cluster/auth';
1710
+ `;
1711
+ const refreshMeta = mkMeta(opts, "Refresh the JWT and issue a new token.", `{ message: 'Token refreshed' }`);
1712
+ const refreshContent = ts ? `${RA}import type { Request, Response } from 'express';
1713
+ ${refreshMeta}export const POST = async (_req: Request, res: Response) => {
1714
+ // TODO: validate refresh token, issue new JWT
1715
+ res.json({ message: 'Token refreshed' });
1716
+ };
1717
+ ` : `${RA}${refreshMeta}export const POST = async (_req, res) => {
1718
+ // TODO: validate refresh token, issue new JWT
1719
+ res.json({ message: 'Token refreshed' });
1720
+ };
1721
+ `;
1722
+ await fs.outputFile(path.join(dest, "src", "api", "auth", `refresh.${ext}`), refreshContent);
1723
+ const veMeta = mkMeta(opts, "Verify email address via token or resend verification email.", `{ message: 'Email verified' }`);
1724
+ const veContent = ts ? `${RA}import type { Request, Response } from 'express';
1725
+ ${veMeta}export const GET = async (req: Request, res: Response) => {
1726
+ const { token } = req.query;
1727
+ // TODO: verify token and mark user as verified
1728
+ res.json({ message: 'Email verified' });
1729
+ };
1730
+
1731
+ export const POST = async (_req: Request, res: Response) => {
1732
+ // TODO: resend verification email
1733
+ res.json({ message: 'Verification email sent' });
1734
+ };
1735
+ ` : `${RA}${veMeta}export const GET = async (req, res) => {
1736
+ const { token } = req.query;
1737
+ // TODO: verify token and mark user as verified
1738
+ res.json({ message: 'Email verified' });
1739
+ };
1740
+
1741
+ export const POST = async (_req, res) => {
1742
+ // TODO: resend verification email
1743
+ res.json({ message: 'Verification email sent' });
1744
+ };
1745
+ `;
1746
+ await fs.outputFile(path.join(dest, "src", "api", "auth", `verify-email.${ext}`), veContent);
1747
+ const fpMeta = mkMeta(opts, "Send a password reset email to the given address.", `{ message: 'Reset email sent' }`, `{ email: 'user@example.com' }`);
1748
+ const fpContent = ts ? `${RA}import type { Request, Response } from 'express';
1749
+ ${fpMeta}export const POST = async (req: Request, res: Response) => {
1750
+ const { email } = req.body;
1751
+ if (!email) return res.status(400).json({ error: 'email is required' });
1752
+ // TODO: generate reset token and send email
1753
+ res.json({ message: 'Reset email sent' });
1754
+ };
1755
+ ` : `${RA}${fpMeta}export const POST = async (req, res) => {
1756
+ const { email } = req.body;
1757
+ if (!email) return res.status(400).json({ error: 'email is required' });
1758
+ // TODO: generate reset token and send email
1759
+ res.json({ message: 'Reset email sent' });
1760
+ };
1761
+ `;
1762
+ await fs.outputFile(path.join(dest, "src", "api", "auth", `forgot-password.${ext}`), fpContent);
1763
+ const rpMeta = mkMeta(opts, "Reset password using a valid reset token.", `{ message: 'Password reset successfully' }`, `{ token: 'reset-token', password: 'newpassword' }`);
1764
+ const rpContent = ts ? `${RA}import type { Request, Response } from 'express';
1765
+ ${rpMeta}export const POST = async (req: Request, res: Response) => {
1766
+ const { token, password } = req.body;
1767
+ if (!token || !password) return res.status(400).json({ error: 'token and password are required' });
1768
+ // TODO: validate token, hash and update password
1769
+ res.json({ message: 'Password reset successfully' });
1770
+ };
1771
+ ` : `${RA}${rpMeta}export const POST = async (req, res) => {
1772
+ const { token, password } = req.body;
1773
+ if (!token || !password) return res.status(400).json({ error: 'token and password are required' });
1774
+ // TODO: validate token, hash and update password
1775
+ res.json({ message: 'Password reset successfully' });
1776
+ };
1777
+ `;
1778
+ await fs.outputFile(path.join(dest, "src", "api", "auth", `reset-password.${ext}`), rpContent);
1779
+ const cpMeta = mkMeta(opts, "Change password for the authenticated user.", `{ message: 'Password changed successfully' }`, `{ oldPassword: 'current', newPassword: 'newpassword' }`);
1780
+ const cpContent = ts ? `${RA}${rr2}import type { Request, Response } from 'express';
1781
+ ${cpMeta}${mwUser2}
1782
+ export const POST = async (req: Request, res: Response) => {
1783
+ const { oldPassword, newPassword } = req.body;
1784
+ if (!oldPassword || !newPassword) return res.status(400).json({ error: 'oldPassword and newPassword are required' });
1785
+ // TODO: verify old password, hash and update new password
1786
+ res.json({ message: 'Password changed successfully' });
1787
+ };
1788
+ ` : `${RA}${rr2}${cpMeta}${mwUser2}
1789
+ export const POST = async (req, res) => {
1790
+ const { oldPassword, newPassword } = req.body;
1791
+ if (!oldPassword || !newPassword) return res.status(400).json({ error: 'oldPassword and newPassword are required' });
1792
+ // TODO: verify old password, hash and update new password
1793
+ res.json({ message: 'Password changed successfully' });
1794
+ };
1795
+ `;
1796
+ await fs.outputFile(path.join(dest, "src", "api", "auth", `change-password.${ext}`), cpContent);
1797
+ const tfaSetupMeta = mkMeta(opts, "Get 2FA QR code (GET) or enable 2FA with a verified TOTP code (POST).", `{ message: '2FA enabled' }`);
1798
+ const tfaSetupContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
1799
+ ${tfaSetupMeta}${mwUser3}
1800
+ export const GET = async (req: Request, res: Response) => {
1801
+ // TODO: generate TOTP secret and return QR code URL
1802
+ res.json({ qrCode: 'otpauth://totp/...', secret: 'BASE32SECRET' });
1803
+ };
1804
+
1805
+ export const POST = async (req: Request, res: Response) => {
1806
+ const { code } = req.body;
1807
+ if (!code) return res.status(400).json({ error: 'code is required' });
1808
+ // TODO: verify TOTP code and enable 2FA
1809
+ res.json({ message: '2FA enabled' });
1810
+ };
1811
+ ` : `${RA}${rr3}${tfaSetupMeta}${mwUser3}
1812
+ export const GET = async (req, res) => {
1813
+ // TODO: generate TOTP secret and return QR code URL
1814
+ res.json({ qrCode: 'otpauth://totp/...', secret: 'BASE32SECRET' });
1815
+ };
1816
+
1817
+ export const POST = async (req, res) => {
1818
+ const { code } = req.body;
1819
+ if (!code) return res.status(400).json({ error: 'code is required' });
1820
+ // TODO: verify TOTP code and enable 2FA
1821
+ res.json({ message: '2FA enabled' });
1822
+ };
1823
+ `;
1824
+ await fs.outputFile(path.join(dest, "src", "api", "auth", "2fa", `setup.${ext}`), tfaSetupContent);
1825
+ const tfaVerifyMeta = mkMeta(opts, "Verify a TOTP code during login.", `{ message: '2FA verified' }`);
1826
+ const tfaVerifyContent = ts ? `${RA}import type { Request, Response } from 'express';
1827
+ ${tfaVerifyMeta}export const POST = async (req: Request, res: Response) => {
1828
+ const { code } = req.body;
1829
+ if (!code) return res.status(400).json({ error: 'code is required' });
1830
+ // TODO: verify TOTP code
1831
+ res.json({ message: '2FA verified' });
1832
+ };
1833
+ ` : `${RA}${tfaVerifyMeta}export const POST = async (req, res) => {
1834
+ const { code } = req.body;
1835
+ if (!code) return res.status(400).json({ error: 'code is required' });
1836
+ // TODO: verify TOTP code
1837
+ res.json({ message: '2FA verified' });
1838
+ };
1839
+ `;
1840
+ await fs.outputFile(path.join(dest, "src", "api", "auth", "2fa", `verify.${ext}`), tfaVerifyContent);
1841
+ const tfaDisableMeta = mkMeta(opts, "Disable 2FA for the authenticated user.", `{ message: '2FA disabled' }`);
1842
+ const tfaDisableContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
1843
+ ${tfaDisableMeta}${mwUser3}
1844
+ export const POST = async (req: Request, res: Response) => {
1845
+ const { code } = req.body;
1846
+ if (!code) return res.status(400).json({ error: 'code is required' });
1847
+ // TODO: verify code and disable 2FA
1848
+ res.json({ message: '2FA disabled' });
1849
+ };
1850
+ ` : `${RA}${rr3}${tfaDisableMeta}${mwUser3}
1851
+ export const POST = async (req, res) => {
1852
+ const { code } = req.body;
1853
+ if (!code) return res.status(400).json({ error: 'code is required' });
1854
+ // TODO: verify code and disable 2FA
1855
+ res.json({ message: '2FA disabled' });
1856
+ };
1857
+ `;
1858
+ await fs.outputFile(path.join(dest, "src", "api", "auth", "2fa", `disable.${ext}`), tfaDisableContent);
1859
+ const sessListMeta = mkMeta(opts, "List all active sessions for the authenticated user.", `{ sessions: [] }`);
1860
+ const sessListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
1861
+ ${sessListMeta}${mwUser3}
1862
+ export const GET = async (req: Request, res: Response) => {
1863
+ // TODO: fetch sessions for req.user.id
1864
+ res.json({ sessions: [] });
1865
+ };
1866
+ ` : `${RA}${rr3}${sessListMeta}${mwUser3}
1867
+ export const GET = async (req, res) => {
1868
+ // TODO: fetch sessions for req.user.id
1869
+ res.json({ sessions: [] });
1870
+ };
1871
+ `;
1872
+ await fs.outputFile(path.join(dest, "src", "api", "auth", "sessions", `index.${ext}`), sessListContent);
1873
+ const sessRevokeContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
1874
+ ${mwUser3}
1875
+ export const DELETE = async (req: Request, res: Response) => {
1876
+ const { id } = req.params;
1877
+ // TODO: revoke session by id
1878
+ res.json({ message: \`Session \${id} revoked\` });
1879
+ };
1880
+ ` : `${RA}${rr3}${mwUser3}
1881
+ export const DELETE = async (req, res) => {
1882
+ const { id } = req.params;
1883
+ // TODO: revoke session by id
1884
+ res.json({ message: \`Session \${id} revoked\` });
1885
+ };
1886
+ `;
1887
+ await fs.outputFile(path.join(dest, "src", "api", "auth", "sessions", `[id].${ext}`), sessRevokeContent);
1888
+ }
1889
+ async function writeUserExtendedRoutes(dest, opts) {
1890
+ const ext = opts.language === "typescript" ? "ts" : "js";
1891
+ const ts = opts.language === "typescript";
1892
+ const rr2 = opts.rbac ? `import { requireRole } from '../../middleware/requireRole.js';
1893
+ ` : "";
1894
+ const rr3 = opts.rbac ? `import { requireRole } from '../../../middleware/requireRole.js';
1895
+ ` : "";
1896
+ const mw2 = opts.rbac ? `export const middlewares = [requireAuth, requireRole('user', 'admin')];
1897
+ ` : `export const middlewares = [requireAuth];
1898
+ `;
1899
+ const mw3 = mw2;
1900
+ const RA = `import { requireAuth } from 'express-file-cluster/auth';
1901
+ `;
1902
+ const user = ts ? `(req as any).user` : `req.user`;
1903
+ const avatarMeta = mkMeta(opts, "Upload (POST) or remove (DELETE) the authenticated user avatar.", `{ message: 'Avatar updated', url: 'https://...' }`);
1904
+ const avatarContent = ts ? `${RA}${rr2}import type { Request, Response } from 'express';
1905
+ ${avatarMeta}${mw2}
1906
+ export const POST = async (req: Request, res: Response) => {
1907
+ // TODO: handle multipart upload, store file, update user.avatar
1908
+ res.json({ message: 'Avatar updated', url: 'https://example.com/avatar.jpg' });
1909
+ };
1910
+
1911
+ export const DELETE = async (req: Request, res: Response) => {
1912
+ // TODO: remove avatar and clear user.avatar
1913
+ res.json({ message: 'Avatar removed' });
1914
+ };
1915
+ ` : `${RA}${rr2}${avatarMeta}${mw2}
1916
+ export const POST = async (req, res) => {
1917
+ // TODO: handle multipart upload, store file, update user.avatar
1918
+ res.json({ message: 'Avatar updated', url: 'https://example.com/avatar.jpg' });
1919
+ };
1920
+
1921
+ export const DELETE = async (req, res) => {
1922
+ // TODO: remove avatar and clear user.avatar
1923
+ res.json({ message: 'Avatar removed' });
1924
+ };
1925
+ `;
1926
+ await fs.outputFile(path.join(dest, "src", "api", "user", `avatar.${ext}`), avatarContent);
1927
+ const settingsMeta = mkMeta(opts, "Get or update account settings (notifications, language, theme, privacy).", `{ settings: { notifications: true, language: 'en', theme: 'system' } }`);
1928
+ const settingsContent = ts ? `${RA}${rr2}import type { Request, Response } from 'express';
1929
+ ${settingsMeta}${mw2}
1930
+ export const GET = async (req: Request, res: Response) => {
1931
+ // TODO: fetch user settings from DB
1932
+ res.json({ settings: { notifications: true, language: 'en', theme: 'system', privacy: 'public' } });
1933
+ };
1934
+
1935
+ export const PUT = async (req: Request, res: Response) => {
1936
+ const { notifications, language, theme, privacy } = req.body;
1937
+ // TODO: update user settings in DB
1938
+ res.json({ message: 'Settings updated', settings: { notifications, language, theme, privacy } });
1939
+ };
1940
+ ` : `${RA}${rr2}${settingsMeta}${mw2}
1941
+ export const GET = async (req, res) => {
1942
+ // TODO: fetch user settings from DB
1943
+ res.json({ settings: { notifications: true, language: 'en', theme: 'system', privacy: 'public' } });
1944
+ };
1945
+
1946
+ export const PUT = async (req, res) => {
1947
+ const { notifications, language, theme, privacy } = req.body;
1948
+ // TODO: update user settings in DB
1949
+ res.json({ message: 'Settings updated', settings: { notifications, language, theme, privacy } });
1950
+ };
1951
+ `;
1952
+ await fs.outputFile(path.join(dest, "src", "api", "user", `settings.${ext}`), settingsContent);
1953
+ const accountMeta = mkMeta(opts, "Delete account (DELETE) or download personal data (GET).", `{ message: 'Account deleted' }`);
1954
+ const accountContent = ts ? `${RA}${rr2}import type { Request, Response } from 'express';
1955
+ ${accountMeta}${mw2}
1956
+ export const GET = async (req: Request, res: Response) => {
1957
+ // TODO: compile and return personal data export
1958
+ res.json({ data: { user: ${user}, exportedAt: new Date().toISOString() } });
1959
+ };
1960
+
1961
+ export const DELETE = async (req: Request, res: Response) => {
1962
+ const { password } = req.body;
1963
+ if (!password) return res.status(400).json({ error: 'password confirmation required' });
1964
+ // TODO: verify password, schedule account deletion
1965
+ res.json({ message: 'Account scheduled for deletion' });
1966
+ };
1967
+ ` : `${RA}${rr2}${accountMeta}${mw2}
1968
+ export const GET = async (req, res) => {
1969
+ // TODO: compile and return personal data export
1970
+ res.json({ data: { user: ${user}, exportedAt: new Date().toISOString() } });
1971
+ };
1972
+
1973
+ export const DELETE = async (req, res) => {
1974
+ const { password } = req.body;
1975
+ if (!password) return res.status(400).json({ error: 'password confirmation required' });
1976
+ // TODO: verify password, schedule account deletion
1977
+ res.json({ message: 'Account scheduled for deletion' });
1978
+ };
1979
+ `;
1980
+ await fs.outputFile(path.join(dest, "src", "api", "user", `account.${ext}`), accountContent);
1981
+ const dashMeta = mkMeta(opts, "Personal dashboard: stats, recent activity, and quick actions.", `{ stats: {}, recentActivity: [], quickActions: [] }`);
1982
+ const dashContent = ts ? `${RA}${rr2}import type { Request, Response } from 'express';
1983
+ ${dashMeta}${mw2}
1984
+ export const GET = async (req: Request, res: Response) => {
1985
+ // TODO: aggregate personal stats and activity
1986
+ res.json({ stats: {}, recentActivity: [], quickActions: [] });
1987
+ };
1988
+ ` : `${RA}${rr2}${dashMeta}${mw2}
1989
+ export const GET = async (req, res) => {
1990
+ // TODO: aggregate personal stats and activity
1991
+ res.json({ stats: {}, recentActivity: [], quickActions: [] });
1992
+ };
1993
+ `;
1994
+ await fs.outputFile(path.join(dest, "src", "api", "user", `dashboard.${ext}`), dashContent);
1995
+ const actMeta = mkMeta(opts, "Paginated activity history for the authenticated user.", `{ activities: [], total: 0, page: 1, limit: 20 }`);
1996
+ const actContent = ts ? `${RA}${rr2}import type { Request, Response } from 'express';
1997
+ ${actMeta}${mw2}
1998
+ export const GET = async (req: Request, res: Response) => {
1999
+ const { page = 1, limit = 20 } = req.query;
2000
+ // TODO: fetch paginated activity log
2001
+ res.json({ activities: [], total: 0, page: Number(page), limit: Number(limit) });
2002
+ };
2003
+ ` : `${RA}${rr2}${actMeta}${mw2}
2004
+ export const GET = async (req, res) => {
2005
+ const { page = 1, limit = 20 } = req.query;
2006
+ // TODO: fetch paginated activity log
2007
+ res.json({ activities: [], total: 0, page: Number(page), limit: Number(limit) });
2008
+ };
2009
+ `;
2010
+ await fs.outputFile(path.join(dest, "src", "api", "user", `activity.${ext}`), actContent);
2011
+ const notifListMeta = mkMeta(opts, "List notifications (GET) or mark all as read (POST).", `{ notifications: [], total: 0, unread: 0 }`);
2012
+ const notifListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2013
+ ${notifListMeta}${mw3}
2014
+ export const GET = async (req: Request, res: Response) => {
2015
+ // TODO: fetch notifications for user
2016
+ res.json({ notifications: [], total: 0, unread: 0 });
2017
+ };
2018
+
2019
+ export const POST = async (req: Request, res: Response) => {
2020
+ // TODO: mark all notifications as read
2021
+ res.json({ message: 'All notifications marked as read' });
2022
+ };
2023
+ ` : `${RA}${rr3}${notifListMeta}${mw3}
2024
+ export const GET = async (req, res) => {
2025
+ // TODO: fetch notifications for user
2026
+ res.json({ notifications: [], total: 0, unread: 0 });
2027
+ };
2028
+
2029
+ export const POST = async (req, res) => {
2030
+ // TODO: mark all notifications as read
2031
+ res.json({ message: 'All notifications marked as read' });
2032
+ };
2033
+ `;
2034
+ await fs.outputFile(path.join(dest, "src", "api", "user", "notifications", `index.${ext}`), notifListContent);
2035
+ const notifByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2036
+ ${mw3}
2037
+ export const GET = async (req: Request, res: Response) => {
2038
+ const { id } = req.params;
2039
+ // TODO: fetch notification by id
2040
+ res.json({ notification: { id } });
2041
+ };
2042
+
2043
+ export const PATCH = async (req: Request, res: Response) => {
2044
+ const { id } = req.params;
2045
+ // TODO: mark notification as read
2046
+ res.json({ message: 'Notification marked as read', id });
2047
+ };
2048
+
2049
+ export const DELETE = async (req: Request, res: Response) => {
2050
+ const { id } = req.params;
2051
+ // TODO: delete notification
2052
+ res.json({ message: \`Notification \${id} deleted\` });
2053
+ };
2054
+ ` : `${RA}${rr3}${mw3}
2055
+ export const GET = async (req, res) => {
2056
+ const { id } = req.params;
2057
+ // TODO: fetch notification by id
2058
+ res.json({ notification: { id } });
2059
+ };
2060
+
2061
+ export const PATCH = async (req, res) => {
2062
+ const { id } = req.params;
2063
+ // TODO: mark notification as read
2064
+ res.json({ message: 'Notification marked as read', id });
2065
+ };
2066
+
2067
+ export const DELETE = async (req, res) => {
2068
+ const { id } = req.params;
2069
+ // TODO: delete notification
2070
+ res.json({ message: \`Notification \${id} deleted\` });
2071
+ };
2072
+ `;
2073
+ await fs.outputFile(path.join(dest, "src", "api", "user", "notifications", `[id].${ext}`), notifByIdContent);
2074
+ const filesListMeta = mkMeta(opts, "List uploaded files with storage usage (GET) or upload a new file (POST).", `{ files: [], total: 0, storageUsed: 0 }`);
2075
+ const filesListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2076
+ ${filesListMeta}${mw3}
2077
+ export const GET = async (req: Request, res: Response) => {
2078
+ // TODO: fetch user files and compute storage usage
2079
+ res.json({ files: [], total: 0, storageUsed: 0 });
2080
+ };
2081
+
2082
+ export const POST = async (req: Request, res: Response) => {
2083
+ // TODO: handle multipart upload, persist file record
2084
+ res.status(201).json({ message: 'File uploaded', file: { id: 'new-id' } });
2085
+ };
2086
+ ` : `${RA}${rr3}${filesListMeta}${mw3}
2087
+ export const GET = async (req, res) => {
2088
+ // TODO: fetch user files and compute storage usage
2089
+ res.json({ files: [], total: 0, storageUsed: 0 });
2090
+ };
2091
+
2092
+ export const POST = async (req, res) => {
2093
+ // TODO: handle multipart upload, persist file record
2094
+ res.status(201).json({ message: 'File uploaded', file: { id: 'new-id' } });
2095
+ };
2096
+ `;
2097
+ await fs.outputFile(path.join(dest, "src", "api", "user", "files", `index.${ext}`), filesListContent);
2098
+ const fileByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2099
+ ${mw3}
2100
+ export const GET = async (req: Request, res: Response) => {
2101
+ const { id } = req.params;
2102
+ // TODO: stream or redirect to file download/preview URL
2103
+ res.json({ file: { id, url: 'https://...' } });
2104
+ };
2105
+
2106
+ export const DELETE = async (req: Request, res: Response) => {
2107
+ const { id } = req.params;
2108
+ // TODO: delete file from storage and DB
2109
+ res.json({ message: \`File \${id} deleted\` });
2110
+ };
2111
+ ` : `${RA}${rr3}${mw3}
2112
+ export const GET = async (req, res) => {
2113
+ const { id } = req.params;
2114
+ // TODO: stream or redirect to file download/preview URL
2115
+ res.json({ file: { id, url: 'https://...' } });
2116
+ };
2117
+
2118
+ export const DELETE = async (req, res) => {
2119
+ const { id } = req.params;
2120
+ // TODO: delete file from storage and DB
2121
+ res.json({ message: \`File \${id} deleted\` });
2122
+ };
2123
+ `;
2124
+ await fs.outputFile(path.join(dest, "src", "api", "user", "files", `[id].${ext}`), fileByIdContent);
2125
+ const favListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2126
+ ${mw3}
2127
+ export const GET = async (req: Request, res: Response) => {
2128
+ // TODO: fetch user favorites
2129
+ res.json({ favorites: [] });
2130
+ };
2131
+
2132
+ export const POST = async (req: Request, res: Response) => {
2133
+ const { entityId, entityType } = req.body;
2134
+ if (!entityId || !entityType) return res.status(400).json({ error: 'entityId and entityType are required' });
2135
+ // TODO: add to favorites
2136
+ res.status(201).json({ message: 'Added to favorites' });
2137
+ };
2138
+ ` : `${RA}${rr3}${mw3}
2139
+ export const GET = async (req, res) => {
2140
+ // TODO: fetch user favorites
2141
+ res.json({ favorites: [] });
2142
+ };
2143
+
2144
+ export const POST = async (req, res) => {
2145
+ const { entityId, entityType } = req.body;
2146
+ if (!entityId || !entityType) return res.status(400).json({ error: 'entityId and entityType are required' });
2147
+ // TODO: add to favorites
2148
+ res.status(201).json({ message: 'Added to favorites' });
2149
+ };
2150
+ `;
2151
+ await fs.outputFile(path.join(dest, "src", "api", "user", "favorites", `index.${ext}`), favListContent);
2152
+ const favByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2153
+ ${mw3}
2154
+ export const DELETE = async (req: Request, res: Response) => {
2155
+ const { id } = req.params;
2156
+ // TODO: remove from favorites
2157
+ res.json({ message: \`Removed favorite \${id}\` });
2158
+ };
2159
+ ` : `${RA}${rr3}${mw3}
2160
+ export const DELETE = async (req, res) => {
2161
+ const { id } = req.params;
2162
+ // TODO: remove from favorites
2163
+ res.json({ message: \`Removed favorite \${id}\` });
2164
+ };
2165
+ `;
2166
+ await fs.outputFile(path.join(dest, "src", "api", "user", "favorites", `[id].${ext}`), favByIdContent);
2167
+ const bkListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2168
+ ${mw3}
2169
+ export const GET = async (req: Request, res: Response) => {
2170
+ // TODO: fetch user bookmarks
2171
+ res.json({ bookmarks: [] });
2172
+ };
2173
+
2174
+ export const POST = async (req: Request, res: Response) => {
2175
+ const { url, title } = req.body;
2176
+ if (!url) return res.status(400).json({ error: 'url is required' });
2177
+ // TODO: save bookmark
2178
+ res.status(201).json({ message: 'Bookmark saved' });
2179
+ };
2180
+ ` : `${RA}${rr3}${mw3}
2181
+ export const GET = async (req, res) => {
2182
+ // TODO: fetch user bookmarks
2183
+ res.json({ bookmarks: [] });
2184
+ };
2185
+
2186
+ export const POST = async (req, res) => {
2187
+ const { url, title } = req.body;
2188
+ if (!url) return res.status(400).json({ error: 'url is required' });
2189
+ // TODO: save bookmark
2190
+ res.status(201).json({ message: 'Bookmark saved' });
2191
+ };
2192
+ `;
2193
+ await fs.outputFile(path.join(dest, "src", "api", "user", "bookmarks", `index.${ext}`), bkListContent);
2194
+ const bkByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2195
+ ${mw3}
2196
+ export const DELETE = async (req: Request, res: Response) => {
2197
+ const { id } = req.params;
2198
+ // TODO: delete bookmark
2199
+ res.json({ message: \`Bookmark \${id} removed\` });
2200
+ };
2201
+ ` : `${RA}${rr3}${mw3}
2202
+ export const DELETE = async (req, res) => {
2203
+ const { id } = req.params;
2204
+ // TODO: delete bookmark
2205
+ res.json({ message: \`Bookmark \${id} removed\` });
2206
+ };
2207
+ `;
2208
+ await fs.outputFile(path.join(dest, "src", "api", "user", "bookmarks", `[id].${ext}`), bkByIdContent);
2209
+ const searchMeta = mkMeta(opts, "Search with filters, sort, and pagination.", `{ results: [], total: 0, page: 1, limit: 20 }`);
2210
+ const searchContent = ts ? `${RA}${rr2}import type { Request, Response } from 'express';
2211
+ ${searchMeta}${mw2}
2212
+ export const GET = async (req: Request, res: Response) => {
2213
+ const { q, filter, sort, page = 1, limit = 20 } = req.query;
2214
+ // TODO: implement search
2215
+ res.json({ results: [], total: 0, page: Number(page), limit: Number(limit) });
2216
+ };
2217
+ ` : `${RA}${rr2}${searchMeta}${mw2}
2218
+ export const GET = async (req, res) => {
2219
+ const { q, filter, sort, page = 1, limit = 20 } = req.query;
2220
+ // TODO: implement search
2221
+ res.json({ results: [], total: 0, page: Number(page), limit: Number(limit) });
2222
+ };
2223
+ `;
2224
+ await fs.outputFile(path.join(dest, "src", "api", "user", `search.${ext}`), searchContent);
2225
+ const akListMeta = mkMeta(opts, "List API keys (GET) or create a new one (POST).", `{ apiKeys: [] }`);
2226
+ const akListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2227
+ ${akListMeta}${mw3}
2228
+ export const GET = async (req: Request, res: Response) => {
2229
+ // TODO: fetch api keys for user
2230
+ res.json({ apiKeys: [] });
2231
+ };
2232
+
2233
+ export const POST = async (req: Request, res: Response) => {
2234
+ const { name } = req.body;
2235
+ if (!name) return res.status(400).json({ error: 'name is required' });
2236
+ // TODO: generate and store API key
2237
+ res.status(201).json({ message: 'API key created', key: 'efc_...' });
2238
+ };
2239
+ ` : `${RA}${rr3}${akListMeta}${mw3}
2240
+ export const GET = async (req, res) => {
2241
+ // TODO: fetch api keys for user
2242
+ res.json({ apiKeys: [] });
2243
+ };
2244
+
2245
+ export const POST = async (req, res) => {
2246
+ const { name } = req.body;
2247
+ if (!name) return res.status(400).json({ error: 'name is required' });
2248
+ // TODO: generate and store API key
2249
+ res.status(201).json({ message: 'API key created', key: 'efc_...' });
2250
+ };
2251
+ `;
2252
+ await fs.outputFile(path.join(dest, "src", "api", "user", "api-keys", `index.${ext}`), akListContent);
2253
+ const akByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2254
+ ${mw3}
2255
+ export const DELETE = async (req: Request, res: Response) => {
2256
+ const { id } = req.params;
2257
+ // TODO: revoke and delete API key
2258
+ res.json({ message: \`API key \${id} revoked\` });
2259
+ };
2260
+ ` : `${RA}${rr3}${mw3}
2261
+ export const DELETE = async (req, res) => {
2262
+ const { id } = req.params;
2263
+ // TODO: revoke and delete API key
2264
+ res.json({ message: \`API key \${id} revoked\` });
2265
+ };
2266
+ `;
2267
+ await fs.outputFile(path.join(dest, "src", "api", "user", "api-keys", `[id].${ext}`), akByIdContent);
2268
+ }
2269
+ async function writeUserBillingRoutes(dest, opts) {
2270
+ const ext = opts.language === "typescript" ? "ts" : "js";
2271
+ const ts = opts.language === "typescript";
2272
+ const rr3 = opts.rbac ? `import { requireRole } from '../../../middleware/requireRole.js';
2273
+ ` : "";
2274
+ const rr4 = opts.rbac ? `import { requireRole } from '../../../../middleware/requireRole.js';
2275
+ ` : "";
2276
+ const mw3 = opts.rbac ? `export const middlewares = [requireAuth, requireRole('user', 'admin')];
2277
+ ` : `export const middlewares = [requireAuth];
2278
+ `;
2279
+ const mw4 = mw3;
2280
+ const RA = `import { requireAuth } from 'express-file-cluster/auth';
2281
+ `;
2282
+ const plansMeta = mkMeta(opts, "List all available subscription plans.", `{ plans: [] }`);
2283
+ const plansContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2284
+ ${plansMeta}${mw3}
2285
+ export const GET = async (_req: Request, res: Response) => {
2286
+ // TODO: fetch active plans from DB
2287
+ res.json({ plans: [] });
2288
+ };
2289
+ ` : `${RA}${rr3}${plansMeta}${mw3}
2290
+ export const GET = async (_req, res) => {
2291
+ // TODO: fetch active plans from DB
2292
+ res.json({ plans: [] });
2293
+ };
2294
+ `;
2295
+ await fs.outputFile(path.join(dest, "src", "api", "user", "billing", `plans.${ext}`), plansContent);
2296
+ const subMeta = mkMeta(opts, "Get current subscription (GET), subscribe to a plan (POST), or cancel (DELETE).", `{ subscription: null }`);
2297
+ const subContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2298
+ ${subMeta}${mw3}
2299
+ export const GET = async (req: Request, res: Response) => {
2300
+ // TODO: fetch current subscription for user
2301
+ res.json({ subscription: null });
2302
+ };
2303
+
2304
+ export const POST = async (req: Request, res: Response) => {
2305
+ const { planId } = req.body;
2306
+ if (!planId) return res.status(400).json({ error: 'planId is required' });
2307
+ // TODO: create subscription via payment gateway
2308
+ res.status(201).json({ message: 'Subscribed', subscription: { planId } });
2309
+ };
2310
+
2311
+ export const DELETE = async (req: Request, res: Response) => {
2312
+ // TODO: cancel subscription at period end
2313
+ res.json({ message: 'Subscription cancelled' });
2314
+ };
2315
+ ` : `${RA}${rr3}${subMeta}${mw3}
2316
+ export const GET = async (req, res) => {
2317
+ // TODO: fetch current subscription for user
2318
+ res.json({ subscription: null });
2319
+ };
2320
+
2321
+ export const POST = async (req, res) => {
2322
+ const { planId } = req.body;
2323
+ if (!planId) return res.status(400).json({ error: 'planId is required' });
2324
+ // TODO: create subscription via payment gateway
2325
+ res.status(201).json({ message: 'Subscribed', subscription: { planId } });
2326
+ };
2327
+
2328
+ export const DELETE = async (req, res) => {
2329
+ // TODO: cancel subscription at period end
2330
+ res.json({ message: 'Subscription cancelled' });
2331
+ };
2332
+ `;
2333
+ await fs.outputFile(path.join(dest, "src", "api", "user", "billing", `subscription.${ext}`), subContent);
2334
+ const pmListContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2335
+ ${mw4}
2336
+ export const GET = async (req: Request, res: Response) => {
2337
+ // TODO: fetch payment methods for user
2338
+ res.json({ paymentMethods: [] });
2339
+ };
2340
+
2341
+ export const POST = async (req: Request, res: Response) => {
2342
+ const { token } = req.body;
2343
+ if (!token) return res.status(400).json({ error: 'payment token is required' });
2344
+ // TODO: attach payment method via payment gateway
2345
+ res.status(201).json({ message: 'Payment method added' });
2346
+ };
2347
+ ` : `${RA}${rr4}${mw4}
2348
+ export const GET = async (req, res) => {
2349
+ // TODO: fetch payment methods for user
2350
+ res.json({ paymentMethods: [] });
2351
+ };
2352
+
2353
+ export const POST = async (req, res) => {
2354
+ const { token } = req.body;
2355
+ if (!token) return res.status(400).json({ error: 'payment token is required' });
2356
+ // TODO: attach payment method via payment gateway
2357
+ res.status(201).json({ message: 'Payment method added' });
2358
+ };
2359
+ `;
2360
+ await fs.outputFile(path.join(dest, "src", "api", "user", "billing", "payment-methods", `index.${ext}`), pmListContent);
2361
+ const pmByIdContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2362
+ ${mw4}
2363
+ export const DELETE = async (req: Request, res: Response) => {
2364
+ const { id } = req.params;
2365
+ // TODO: detach payment method from payment gateway
2366
+ res.json({ message: \`Payment method \${id} removed\` });
2367
+ };
2368
+ ` : `${RA}${rr4}${mw4}
2369
+ export const DELETE = async (req, res) => {
2370
+ const { id } = req.params;
2371
+ // TODO: detach payment method from payment gateway
2372
+ res.json({ message: \`Payment method \${id} removed\` });
2373
+ };
2374
+ `;
2375
+ await fs.outputFile(path.join(dest, "src", "api", "user", "billing", "payment-methods", `[id].${ext}`), pmByIdContent);
2376
+ const invListMeta = mkMeta(opts, "List all invoices for the authenticated user.", `{ invoices: [], total: 0 }`);
2377
+ const invListContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2378
+ ${invListMeta}${mw4}
2379
+ export const GET = async (req: Request, res: Response) => {
2380
+ // TODO: fetch invoices for user
2381
+ res.json({ invoices: [], total: 0 });
2382
+ };
2383
+ ` : `${RA}${rr4}${invListMeta}${mw4}
2384
+ export const GET = async (req, res) => {
2385
+ // TODO: fetch invoices for user
2386
+ res.json({ invoices: [], total: 0 });
2387
+ };
2388
+ `;
2389
+ await fs.outputFile(path.join(dest, "src", "api", "user", "billing", "invoices", `index.${ext}`), invListContent);
2390
+ const invByIdContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2391
+ ${mw4}
2392
+ export const GET = async (req: Request, res: Response) => {
2393
+ const { id } = req.params;
2394
+ // TODO: return invoice PDF URL or inline data
2395
+ res.json({ invoice: { id, downloadUrl: 'https://...' } });
2396
+ };
2397
+ ` : `${RA}${rr4}${mw4}
2398
+ export const GET = async (req, res) => {
2399
+ const { id } = req.params;
2400
+ // TODO: return invoice PDF URL or inline data
2401
+ res.json({ invoice: { id, downloadUrl: 'https://...' } });
2402
+ };
134
2403
  `;
135
- await fs.outputFile(path.join(dest, ".env"), dotenv);
136
- await fs.outputFile(path.join(dest, ".env.example"), example);
2404
+ await fs.outputFile(path.join(dest, "src", "api", "user", "billing", "invoices", `[id].${ext}`), invByIdContent);
137
2405
  }
138
- async function writeExampleRoute(dest, opts) {
2406
+ async function writeSupportRoutes(dest, opts) {
139
2407
  const ext = opts.language === "typescript" ? "ts" : "js";
140
- const content = opts.language === "typescript" ? `import type { Request, Response } from 'express';
2408
+ const ts = opts.language === "typescript";
2409
+ const rr3 = opts.rbac ? `import { requireRole } from '../../../middleware/requireRole.js';
2410
+ ` : "";
2411
+ const mw3 = opts.rbac ? `export const middlewares = [requireAuth, requireRole('user', 'admin')];
2412
+ ` : `export const middlewares = [requireAuth];
2413
+ `;
2414
+ const RA = `import { requireAuth } from 'express-file-cluster/auth';
2415
+ `;
2416
+ 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' }`);
2417
+ const ticketListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2418
+ ${ticketListMeta}${mw3}
2419
+ export const GET = async (req: Request, res: Response) => {
2420
+ // TODO: fetch tickets for current user
2421
+ res.json({ tickets: [], total: 0 });
2422
+ };
141
2423
 
142
- export const GET = async (_req: Request, res: Response) => {
143
- res.json({ status: 'OK', timestamp: new Date().toISOString() });
2424
+ export const POST = async (req: Request, res: Response) => {
2425
+ const { subject, message, priority } = req.body;
2426
+ if (!subject || !message) return res.status(400).json({ error: 'subject and message are required' });
2427
+ // TODO: create support ticket in DB
2428
+ res.status(201).json({ message: 'Ticket created', ticket: { id: 'new-id', subject, status: 'open' } });
144
2429
  };
145
- ` : `export const GET = async (_req, res) => {
146
- res.json({ status: 'OK', timestamp: new Date().toISOString() });
2430
+ ` : `${RA}${rr3}${ticketListMeta}${mw3}
2431
+ export const GET = async (req, res) => {
2432
+ // TODO: fetch tickets for current user
2433
+ res.json({ tickets: [], total: 0 });
147
2434
  };
148
- `;
149
- await fs.outputFile(path.join(dest, "src", "api", `health.${ext}`), content);
150
- }
151
- async function writeExampleTask(dest, opts) {
152
- const ext = opts.language === "typescript" ? "ts" : "js";
153
- const content = opts.language === "typescript" ? `import { defineTask } from 'express-file-cluster/tasks';
154
2435
 
155
- interface SendEmailPayload {
156
- to: string;
157
- subject: string;
158
- body: string;
159
- }
2436
+ export const POST = async (req, res) => {
2437
+ const { subject, message, priority } = req.body;
2438
+ if (!subject || !message) return res.status(400).json({ error: 'subject and message are required' });
2439
+ // TODO: create support ticket in DB
2440
+ res.status(201).json({ message: 'Ticket created', ticket: { id: 'new-id', subject, status: 'open' } });
2441
+ };
2442
+ `;
2443
+ await fs.outputFile(path.join(dest, "src", "api", "support", "tickets", `index.${ext}`), ticketListContent);
2444
+ const ticketByIdMeta = mkMeta(opts, "View a support ticket (GET) or add a reply / close it (PUT).", `{ ticket: { id: '1', subject: 'Issue', status: 'open', replies: [] } }`);
2445
+ const ticketByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2446
+ ${ticketByIdMeta}${mw3}
2447
+ export const GET = async (req: Request, res: Response) => {
2448
+ const { id } = req.params;
2449
+ // TODO: fetch ticket by id, verify ownership
2450
+ res.json({ ticket: { id, subject: '', status: 'open', replies: [] } });
2451
+ };
160
2452
 
161
- export default defineTask<SendEmailPayload>(async (payload) => {
162
- // TODO: wire up your mailer
163
- console.log('[SendEmail] Sending to', payload.to);
164
- });
165
- ` : `import { defineTask } from 'express-file-cluster/tasks';
2453
+ export const PUT = async (req: Request, res: Response) => {
2454
+ const { id } = req.params;
2455
+ const { reply, status } = req.body;
2456
+ // TODO: add reply or update status
2457
+ res.json({ message: 'Ticket updated', ticket: { id, status: status ?? 'open' } });
2458
+ };
2459
+ ` : `${RA}${rr3}${ticketByIdMeta}${mw3}
2460
+ export const GET = async (req, res) => {
2461
+ const { id } = req.params;
2462
+ // TODO: fetch ticket by id, verify ownership
2463
+ res.json({ ticket: { id, subject: '', status: 'open', replies: [] } });
2464
+ };
166
2465
 
167
- export default defineTask(async (payload) => {
168
- console.log('[SendEmail] Sending to', payload.to);
169
- });
2466
+ export const PUT = async (req, res) => {
2467
+ const { id } = req.params;
2468
+ const { reply, status } = req.body;
2469
+ // TODO: add reply or update status
2470
+ res.json({ message: 'Ticket updated', ticket: { id, status: status ?? 'open' } });
2471
+ };
170
2472
  `;
171
- await fs.outputFile(path.join(dest, "src", "tasks", `SendEmail.${ext}`), content);
2473
+ await fs.outputFile(path.join(dest, "src", "api", "support", "tickets", `[id].${ext}`), ticketByIdContent);
172
2474
  }
173
- async function writeAuthRoutes(dest, opts) {
2475
+ async function writeAdminExtendedRoutes(dest, opts) {
174
2476
  const ext = opts.language === "typescript" ? "ts" : "js";
175
- const loginContent = opts.language === "typescript" ? `import { issueToken } from 'express-file-cluster/auth';
176
- import type { Request, Response } from 'express';
2477
+ const ts = opts.language === "typescript";
2478
+ const rr3 = opts.rbac ? `import { requireRole } from '../../../middleware/requireRole.js';
2479
+ ` : "";
2480
+ const rr4 = opts.rbac ? `import { requireRole } from '../../../../middleware/requireRole.js';
2481
+ ` : "";
2482
+ const mwAdmin3 = opts.rbac ? `export const middlewares = [requireAuth, requireRole('admin')];
2483
+ ` : `export const middlewares = [requireAuth];
2484
+ `;
2485
+ const mwAdmin4 = mwAdmin3;
2486
+ const roleGuard = opts.rbac ? "" : ts ? ` const user = (req as any).user;
2487
+ if (user?.role !== 'admin') return res.status(403).json({ error: 'Forbidden' });
2488
+ ` : ` const user = req.user;
2489
+ if (user?.role !== 'admin') return res.status(403).json({ error: 'Forbidden' });
2490
+ `;
2491
+ const RA = `import { requireAuth } from 'express-file-cluster/auth';
2492
+ `;
2493
+ const analyticsOverviewMeta = mkMeta(opts, "Analytics overview: users, revenue, traffic.", `{ users: {}, revenue: {}, traffic: {} }`);
2494
+ const analyticsOverviewContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2495
+ ${analyticsOverviewMeta}${mwAdmin3}
2496
+ export const GET = async (req: Request, res: Response) => {
2497
+ ${roleGuard} // TODO: aggregate analytics overview
2498
+ res.json({ users: {}, revenue: {}, traffic: {} });
2499
+ };
2500
+ ` : `${RA}${rr3}${analyticsOverviewMeta}${mwAdmin3}
2501
+ export const GET = async (req, res) => {
2502
+ ${roleGuard} // TODO: aggregate analytics overview
2503
+ res.json({ users: {}, revenue: {}, traffic: {} });
2504
+ };
2505
+ `;
2506
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "analytics", `index.${ext}`), analyticsOverviewContent);
2507
+ const analyticsUsersMeta = mkMeta(opts, "User analytics: registrations, active users, churn.", `{ registrations: [], activeUsers: 0, churn: 0 }`);
2508
+ const analyticsUsersContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2509
+ ${analyticsUsersMeta}${mwAdmin3}
2510
+ export const GET = async (req: Request, res: Response) => {
2511
+ ${roleGuard} const { period = '30d' } = req.query;
2512
+ // TODO: fetch user analytics for period
2513
+ res.json({ registrations: [], activeUsers: 0, churn: 0, period });
2514
+ };
2515
+ ` : `${RA}${rr3}${analyticsUsersMeta}${mwAdmin3}
2516
+ export const GET = async (req, res) => {
2517
+ ${roleGuard} const { period = '30d' } = req.query;
2518
+ // TODO: fetch user analytics for period
2519
+ res.json({ registrations: [], activeUsers: 0, churn: 0, period });
2520
+ };
2521
+ `;
2522
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "analytics", `users.${ext}`), analyticsUsersContent);
2523
+ const analyticsRevenueMeta = mkMeta(opts, "Revenue analytics: MRR, ARR, payment history.", `{ mrr: 0, arr: 0, history: [] }`);
2524
+ const analyticsRevenueContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2525
+ ${analyticsRevenueMeta}${mwAdmin3}
2526
+ export const GET = async (req: Request, res: Response) => {
2527
+ ${roleGuard} const { period = '30d' } = req.query;
2528
+ // TODO: fetch revenue analytics for period
2529
+ res.json({ mrr: 0, arr: 0, history: [], period });
2530
+ };
2531
+ ` : `${RA}${rr3}${analyticsRevenueMeta}${mwAdmin3}
2532
+ export const GET = async (req, res) => {
2533
+ ${roleGuard} const { period = '30d' } = req.query;
2534
+ // TODO: fetch revenue analytics for period
2535
+ res.json({ mrr: 0, arr: 0, history: [], period });
2536
+ };
2537
+ `;
2538
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "analytics", `revenue.${ext}`), analyticsRevenueContent);
2539
+ const analyticsTrafficMeta = mkMeta(opts, "Traffic analytics: page views, devices, countries.", `{ pageViews: 0, devices: {}, countries: {} }`);
2540
+ const analyticsTrafficContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2541
+ ${analyticsTrafficMeta}${mwAdmin3}
2542
+ export const GET = async (req: Request, res: Response) => {
2543
+ ${roleGuard} const { period = '30d' } = req.query;
2544
+ // TODO: fetch traffic analytics for period
2545
+ res.json({ pageViews: 0, devices: {}, countries: {}, period });
2546
+ };
2547
+ ` : `${RA}${rr3}${analyticsTrafficMeta}${mwAdmin3}
2548
+ export const GET = async (req, res) => {
2549
+ ${roleGuard} const { period = '30d' } = req.query;
2550
+ // TODO: fetch traffic analytics for period
2551
+ res.json({ pageViews: 0, devices: {}, countries: {}, period });
2552
+ };
2553
+ `;
2554
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "analytics", `traffic.${ext}`), analyticsTrafficContent);
2555
+ const suspendContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2556
+ ${mwAdmin4}
2557
+ export const POST = async (req: Request, res: Response) => {
2558
+ ${roleGuard} const { id } = req.params;
2559
+ const { reason } = req.body;
2560
+ // TODO: set user.isActive = false, log audit event
2561
+ res.json({ message: \`User \${id} suspended\`, reason });
2562
+ };
2563
+ ` : `${RA}${rr4}${mwAdmin4}
2564
+ export const POST = async (req, res) => {
2565
+ ${roleGuard} const { id } = req.params;
2566
+ const { reason } = req.body;
2567
+ // TODO: set user.isActive = false, log audit event
2568
+ res.json({ message: \`User \${id} suspended\`, reason });
2569
+ };
2570
+ `;
2571
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "users", "[id]", `suspend.${ext}`), suspendContent);
2572
+ const activateContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2573
+ ${mwAdmin4}
2574
+ export const POST = async (req: Request, res: Response) => {
2575
+ ${roleGuard} const { id } = req.params;
2576
+ // TODO: set user.isActive = true, log audit event
2577
+ res.json({ message: \`User \${id} activated\` });
2578
+ };
2579
+ ` : `${RA}${rr4}${mwAdmin4}
2580
+ export const POST = async (req, res) => {
2581
+ ${roleGuard} const { id } = req.params;
2582
+ // TODO: set user.isActive = true, log audit event
2583
+ res.json({ message: \`User \${id} activated\` });
2584
+ };
2585
+ `;
2586
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "users", "[id]", `activate.${ext}`), activateContent);
2587
+ const verifyUserContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2588
+ ${mwAdmin4}
2589
+ export const POST = async (req: Request, res: Response) => {
2590
+ ${roleGuard} const { id } = req.params;
2591
+ // TODO: set user.isVerified = true
2592
+ res.json({ message: \`User \${id} verified\` });
2593
+ };
2594
+ ` : `${RA}${rr4}${mwAdmin4}
2595
+ export const POST = async (req, res) => {
2596
+ ${roleGuard} const { id } = req.params;
2597
+ // TODO: set user.isVerified = true
2598
+ res.json({ message: \`User \${id} verified\` });
2599
+ };
2600
+ `;
2601
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "users", "[id]", `verify.${ext}`), verifyUserContent);
2602
+ const exportMeta = mkMeta(opts, "Export all users as CSV.", `{ csv: '...' }`);
2603
+ const exportContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2604
+ ${exportMeta}${mwAdmin3}
2605
+ export const GET = async (_req: Request, res: Response) => {
2606
+ ${roleGuard} // TODO: generate CSV of all users and stream response
2607
+ res.setHeader('Content-Type', 'text/csv');
2608
+ res.setHeader('Content-Disposition', 'attachment; filename="users.csv"');
2609
+ res.send('id,name,email,role,createdAt
2610
+ ');
2611
+ };
2612
+ ` : `${RA}${rr3}${exportMeta}${mwAdmin3}
2613
+ export const GET = async (_req, res) => {
2614
+ ${roleGuard} // TODO: generate CSV of all users and stream response
2615
+ res.setHeader('Content-Type', 'text/csv');
2616
+ res.setHeader('Content-Disposition', 'attachment; filename="users.csv"');
2617
+ res.send('id,name,email,role,createdAt
2618
+ ');
2619
+ };
2620
+ `;
2621
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "users", `export.${ext}`), exportContent);
2622
+ const adminsListMeta = mkMeta(opts, "List all admins (GET) or create a new admin (POST).", `{ admins: [], total: 0 }`);
2623
+ const adminsListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2624
+ ${adminsListMeta}${mwAdmin3}
2625
+ export const GET = async (_req: Request, res: Response) => {
2626
+ ${roleGuard} // TODO: fetch admins from DB
2627
+ res.json({ admins: [], total: 0 });
2628
+ };
177
2629
 
178
2630
  export const POST = async (req: Request, res: Response) => {
179
- const { email, password } = req.body;
180
-
181
- if (email === 'admin@example.com' && password === 'admin') {
182
- await issueToken(res, { id: '1', role: 'admin', email });
183
- return res.json({ message: 'Logged in as admin' });
184
- }
185
-
186
- if (email === 'user@example.com' && password === 'user') {
187
- await issueToken(res, { id: '2', role: 'user', email });
188
- return res.json({ message: 'Logged in as user' });
189
- }
190
-
191
- res.status(401).json({ error: 'Invalid credentials' });
2631
+ ${roleGuard} const { name, email, role } = req.body;
2632
+ if (!name || !email) return res.status(400).json({ error: 'name and email are required' });
2633
+ // TODO: create admin account
2634
+ res.status(201).json({ message: 'Admin created', admin: { id: 'new-id', name, email, role: role ?? 'admin' } });
2635
+ };
2636
+ ` : `${RA}${rr3}${adminsListMeta}${mwAdmin3}
2637
+ export const GET = async (_req, res) => {
2638
+ ${roleGuard} // TODO: fetch admins from DB
2639
+ res.json({ admins: [], total: 0 });
192
2640
  };
193
- ` : `import { issueToken } from 'express-file-cluster/auth';
194
2641
 
195
2642
  export const POST = async (req, res) => {
196
- const { email, password } = req.body;
197
-
198
- if (email === 'admin@example.com' && password === 'admin') {
199
- await issueToken(res, { id: '1', role: 'admin', email });
200
- return res.json({ message: 'Logged in as admin' });
201
- }
202
-
203
- if (email === 'user@example.com' && password === 'user') {
204
- await issueToken(res, { id: '2', role: 'user', email });
205
- return res.json({ message: 'Logged in as user' });
206
- }
207
-
208
- res.status(401).json({ error: 'Invalid credentials' });
2643
+ ${roleGuard} const { name, email, role } = req.body;
2644
+ if (!name || !email) return res.status(400).json({ error: 'name and email are required' });
2645
+ // TODO: create admin account
2646
+ res.status(201).json({ message: 'Admin created', admin: { id: 'new-id', name, email, role: role ?? 'admin' } });
209
2647
  };
210
2648
  `;
211
- const logoutContent = opts.language === "typescript" ? `import { revokeToken } from 'express-file-cluster/auth';
212
- import type { Request, Response } from 'express';
2649
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "admins", `index.${ext}`), adminsListContent);
2650
+ const adminByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2651
+ ${mwAdmin3}
2652
+ export const GET = async (req: Request, res: Response) => {
2653
+ ${roleGuard} const { id } = req.params;
2654
+ // TODO: fetch admin by id
2655
+ res.json({ admin: { id } });
2656
+ };
213
2657
 
214
- export const POST = async (_req: Request, res: Response) => {
215
- revokeToken(res);
216
- res.json({ message: 'Logged out successfully' });
2658
+ export const PUT = async (req: Request, res: Response) => {
2659
+ ${roleGuard} const { id } = req.params;
2660
+ // TODO: update admin record
2661
+ res.json({ message: 'Admin updated', admin: { id, ...req.body } });
217
2662
  };
218
- ` : `import { revokeToken } from 'express-file-cluster/auth';
219
2663
 
220
- export const POST = async (_req, res) => {
221
- revokeToken(res);
222
- res.json({ message: 'Logged out successfully' });
2664
+ export const DELETE = async (req: Request, res: Response) => {
2665
+ ${roleGuard} const { id } = req.params;
2666
+ // TODO: delete admin
2667
+ res.json({ message: \`Admin \${id} deleted\` });
2668
+ };
2669
+ ` : `${RA}${rr3}${mwAdmin3}
2670
+ export const GET = async (req, res) => {
2671
+ ${roleGuard} const { id } = req.params;
2672
+ // TODO: fetch admin by id
2673
+ res.json({ admin: { id } });
2674
+ };
2675
+
2676
+ export const PUT = async (req, res) => {
2677
+ ${roleGuard} const { id } = req.params;
2678
+ // TODO: update admin record
2679
+ res.json({ message: 'Admin updated', admin: { id, ...req.body } });
2680
+ };
2681
+
2682
+ export const DELETE = async (req, res) => {
2683
+ ${roleGuard} const { id } = req.params;
2684
+ // TODO: delete admin
2685
+ res.json({ message: \`Admin \${id} deleted\` });
223
2686
  };
224
2687
  `;
225
- await fs.outputFile(path.join(dest, "src", "api", "auth", `login.${ext}`), loginContent);
226
- await fs.outputFile(path.join(dest, "src", "api", "auth", `logout.${ext}`), logoutContent);
227
- }
228
- async function writeAdminRoutes(dest, opts) {
229
- const ext = opts.language === "typescript" ? "ts" : "js";
230
- const content = opts.language === "typescript" ? `import { requireAuth } from 'express-file-cluster/auth';
231
- import type { Request, Response } from 'express';
2688
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "admins", `[id].${ext}`), adminByIdContent);
2689
+ if (opts.rbac) {
2690
+ const rolesListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2691
+ ${mwAdmin3}
2692
+ export const GET = async (_req: Request, res: Response) => {
2693
+ // TODO: fetch all roles
2694
+ res.json({ roles: [] });
2695
+ };
232
2696
 
233
- export const middlewares = [requireAuth];
2697
+ export const POST = async (req: Request, res: Response) => {
2698
+ const { name, description, permissions } = req.body;
2699
+ if (!name) return res.status(400).json({ error: 'name is required' });
2700
+ // TODO: create role
2701
+ res.status(201).json({ message: 'Role created', role: { id: 'new-id', name, permissions: permissions ?? [] } });
2702
+ };
2703
+ ` : `${RA}${rr3}${mwAdmin3}
2704
+ export const GET = async (_req, res) => {
2705
+ // TODO: fetch all roles
2706
+ res.json({ roles: [] });
2707
+ };
234
2708
 
2709
+ export const POST = async (req, res) => {
2710
+ const { name, description, permissions } = req.body;
2711
+ if (!name) return res.status(400).json({ error: 'name is required' });
2712
+ // TODO: create role
2713
+ res.status(201).json({ message: 'Role created', role: { id: 'new-id', name, permissions: permissions ?? [] } });
2714
+ };
2715
+ `;
2716
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "roles", `index.${ext}`), rolesListContent);
2717
+ const roleByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2718
+ ${mwAdmin3}
235
2719
  export const GET = async (req: Request, res: Response) => {
236
- const user = (req as any).user;
237
- if (user?.role !== 'admin') {
238
- return res.status(403).json({ error: 'Forbidden: Admin access required' });
239
- }
240
-
241
- res.json({
242
- message: 'Welcome to the Admin Panel',
243
- stats: { users: 120, revenue: 5000 }
244
- });
2720
+ const { id } = req.params;
2721
+ // TODO: fetch role by id
2722
+ res.json({ role: { id } });
245
2723
  };
246
- ` : `import { requireAuth } from 'express-file-cluster/auth';
247
2724
 
248
- export const middlewares = [requireAuth];
2725
+ export const PUT = async (req: Request, res: Response) => {
2726
+ const { id } = req.params;
2727
+ // TODO: update role
2728
+ res.json({ message: 'Role updated', role: { id, ...req.body } });
2729
+ };
249
2730
 
2731
+ export const DELETE = async (req: Request, res: Response) => {
2732
+ const { id } = req.params;
2733
+ // TODO: delete role
2734
+ res.json({ message: \`Role \${id} deleted\` });
2735
+ };
2736
+ ` : `${RA}${rr3}${mwAdmin3}
250
2737
  export const GET = async (req, res) => {
251
- const user = req.user;
252
- if (user?.role !== 'admin') {
253
- return res.status(403).json({ error: 'Forbidden: Admin access required' });
2738
+ const { id } = req.params;
2739
+ // TODO: fetch role by id
2740
+ res.json({ role: { id } });
2741
+ };
2742
+
2743
+ export const PUT = async (req, res) => {
2744
+ const { id } = req.params;
2745
+ // TODO: update role
2746
+ res.json({ message: 'Role updated', role: { id, ...req.body } });
2747
+ };
2748
+
2749
+ export const DELETE = async (req, res) => {
2750
+ const { id } = req.params;
2751
+ // TODO: delete role
2752
+ res.json({ message: \`Role \${id} deleted\` });
2753
+ };
2754
+ `;
2755
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "roles", `[id].${ext}`), roleByIdContent);
254
2756
  }
255
-
256
- res.json({
257
- message: 'Welcome to the Admin Panel',
258
- stats: { users: 120, revenue: 5000 }
259
- });
2757
+ const adminNotifContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2758
+ ${mwAdmin3}
2759
+ export const GET = async (_req: Request, res: Response) => {
2760
+ ${roleGuard} // TODO: fetch sent notifications
2761
+ res.json({ notifications: [], total: 0 });
2762
+ };
2763
+
2764
+ export const POST = async (req: Request, res: Response) => {
2765
+ ${roleGuard} const { userId, title, message, type } = req.body;
2766
+ if (!userId || !title || !message) return res.status(400).json({ error: 'userId, title and message are required' });
2767
+ // TODO: create and send notification to user
2768
+ res.status(201).json({ message: 'Notification sent' });
2769
+ };
2770
+ ` : `${RA}${rr3}${mwAdmin3}
2771
+ export const GET = async (_req, res) => {
2772
+ ${roleGuard} // TODO: fetch sent notifications
2773
+ res.json({ notifications: [], total: 0 });
2774
+ };
2775
+
2776
+ export const POST = async (req, res) => {
2777
+ ${roleGuard} const { userId, title, message, type } = req.body;
2778
+ if (!userId || !title || !message) return res.status(400).json({ error: 'userId, title and message are required' });
2779
+ // TODO: create and send notification to user
2780
+ res.status(201).json({ message: 'Notification sent' });
260
2781
  };
261
2782
  `;
262
- await fs.outputFile(path.join(dest, "src", "api", "admin", `dashboard.${ext}`), content);
263
- }
264
- async function writeUserRoutes(dest, opts) {
265
- const ext = opts.language === "typescript" ? "ts" : "js";
266
- const content = opts.language === "typescript" ? `import { requireAuth } from 'express-file-cluster/auth';
267
- import type { Request, Response } from 'express';
2783
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "notifications", `index.${ext}`), adminNotifContent);
2784
+ const broadcastMeta = mkMeta(opts, "Broadcast a notification to all users.", `{ message: 'Broadcast sent', count: 0 }`, `{ title: 'Announcement', message: 'Hello everyone!', type: 'info' }`);
2785
+ const broadcastContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2786
+ ${broadcastMeta}${mwAdmin3}
2787
+ export const POST = async (req: Request, res: Response) => {
2788
+ ${roleGuard} const { title, message, type } = req.body;
2789
+ if (!title || !message) return res.status(400).json({ error: 'title and message are required' });
2790
+ // TODO: send notification to all users
2791
+ res.json({ message: 'Broadcast sent', count: 0 });
2792
+ };
2793
+ ` : `${RA}${rr3}${broadcastMeta}${mwAdmin3}
2794
+ export const POST = async (req, res) => {
2795
+ ${roleGuard} const { title, message, type } = req.body;
2796
+ if (!title || !message) return res.status(400).json({ error: 'title and message are required' });
2797
+ // TODO: send notification to all users
2798
+ res.json({ message: 'Broadcast sent', count: 0 });
2799
+ };
2800
+ `;
2801
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "notifications", `broadcast.${ext}`), broadcastContent);
2802
+ const mkLogContent = (label) => {
2803
+ const meta = mkMeta(opts, `Paginated ${label} log entries.`, `{ logs: [], total: 0, page: 1, limit: 50 }`);
2804
+ return ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2805
+ ${meta}${mwAdmin3}
2806
+ export const GET = async (req: Request, res: Response) => {
2807
+ ${roleGuard} const { page = 1, limit = 50 } = req.query;
2808
+ // TODO: fetch ${label} logs with pagination
2809
+ res.json({ logs: [], total: 0, page: Number(page), limit: Number(limit) });
2810
+ };
2811
+ ` : `${RA}${rr3}${meta}${mwAdmin3}
2812
+ export const GET = async (req, res) => {
2813
+ ${roleGuard} const { page = 1, limit = 50 } = req.query;
2814
+ // TODO: fetch ${label} logs with pagination
2815
+ res.json({ logs: [], total: 0, page: Number(page), limit: Number(limit) });
2816
+ };
2817
+ `;
2818
+ };
2819
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "logs", `audit.${ext}`), mkLogContent("audit"));
2820
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "logs", `activity.${ext}`), mkLogContent("activity"));
2821
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "logs", `security.${ext}`), mkLogContent("security"));
2822
+ const settingsMeta = mkMeta(opts, "Get or update system-wide settings.", `{ settings: {} }`);
2823
+ const settingsContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2824
+ ${settingsMeta}${mwAdmin3}
2825
+ export const GET = async (_req: Request, res: Response) => {
2826
+ ${roleGuard} // TODO: fetch system settings from DB
2827
+ res.json({ settings: {} });
2828
+ };
268
2829
 
269
- export const middlewares = [requireAuth];
2830
+ export const PUT = async (req: Request, res: Response) => {
2831
+ ${roleGuard} // TODO: update system settings
2832
+ res.json({ message: 'Settings updated', settings: req.body });
2833
+ };
2834
+ ` : `${RA}${rr3}${settingsMeta}${mwAdmin3}
2835
+ export const GET = async (_req, res) => {
2836
+ ${roleGuard} // TODO: fetch system settings from DB
2837
+ res.json({ settings: {} });
2838
+ };
270
2839
 
2840
+ export const PUT = async (req, res) => {
2841
+ ${roleGuard} // TODO: update system settings
2842
+ res.json({ message: 'Settings updated', settings: req.body });
2843
+ };
2844
+ `;
2845
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "settings", `index.${ext}`), settingsContent);
2846
+ const healthMeta = mkMeta(opts, "System health check: DB, queue, cache status.", `{ status: 'ok', db: 'ok', queue: 'ok' }`);
2847
+ const healthContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2848
+ ${healthMeta}${mwAdmin3}
2849
+ export const GET = async (_req: Request, res: Response) => {
2850
+ ${roleGuard} // TODO: check DB connection, queue, cache health
2851
+ res.json({ status: 'ok', db: 'ok', queue: 'ok', uptime: process.uptime() });
2852
+ };
2853
+ ` : `${RA}${rr3}${healthMeta}${mwAdmin3}
2854
+ export const GET = async (_req, res) => {
2855
+ ${roleGuard} // TODO: check DB connection, queue, cache health
2856
+ res.json({ status: 'ok', db: 'ok', queue: 'ok', uptime: process.uptime() });
2857
+ };
2858
+ `;
2859
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "system", `health.${ext}`), healthContent);
2860
+ const cacheMeta = mkMeta(opts, "Flush the application cache.", `{ message: 'Cache cleared' }`);
2861
+ const cacheContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2862
+ ${cacheMeta}${mwAdmin3}
2863
+ export const DELETE = async (_req: Request, res: Response) => {
2864
+ ${roleGuard} // TODO: flush Redis or in-memory cache
2865
+ res.json({ message: 'Cache cleared' });
2866
+ };
2867
+ ` : `${RA}${rr3}${cacheMeta}${mwAdmin3}
2868
+ export const DELETE = async (_req, res) => {
2869
+ ${roleGuard} // TODO: flush Redis or in-memory cache
2870
+ res.json({ message: 'Cache cleared' });
2871
+ };
2872
+ `;
2873
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "system", `cache.${ext}`), cacheContent);
2874
+ const adminTicketsListMeta = mkMeta(opts, "List all support tickets with filters.", `{ tickets: [], total: 0 }`);
2875
+ const adminTicketsListContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2876
+ ${adminTicketsListMeta}${mwAdmin3}
271
2877
  export const GET = async (req: Request, res: Response) => {
272
- const user = (req as any).user;
273
-
274
- res.json({
275
- message: 'User Profile Panel',
276
- user
277
- });
2878
+ ${roleGuard} const { status, priority, page = 1, limit = 20 } = req.query;
2879
+ // TODO: fetch all tickets with filters
2880
+ res.json({ tickets: [], total: 0, page: Number(page), limit: Number(limit) });
2881
+ };
2882
+ ` : `${RA}${rr3}${adminTicketsListMeta}${mwAdmin3}
2883
+ export const GET = async (req, res) => {
2884
+ ${roleGuard} const { status, priority, page = 1, limit = 20 } = req.query;
2885
+ // TODO: fetch all tickets with filters
2886
+ res.json({ tickets: [], total: 0, page: Number(page), limit: Number(limit) });
2887
+ };
2888
+ `;
2889
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "tickets", `index.${ext}`), adminTicketsListContent);
2890
+ const adminTicketByIdContent = ts ? `${RA}${rr3}import type { Request, Response } from 'express';
2891
+ ${mwAdmin3}
2892
+ export const GET = async (req: Request, res: Response) => {
2893
+ ${roleGuard} const { id } = req.params;
2894
+ // TODO: fetch ticket by id
2895
+ res.json({ ticket: { id } });
2896
+ };
2897
+
2898
+ export const PUT = async (req: Request, res: Response) => {
2899
+ ${roleGuard} const { id } = req.params;
2900
+ const { reply, status, assignedTo } = req.body;
2901
+ // TODO: update ticket (assign, change status, add reply)
2902
+ res.json({ message: 'Ticket updated', ticket: { id, status, assignedTo } });
2903
+ };
2904
+ ` : `${RA}${rr3}${mwAdmin3}
2905
+ export const GET = async (req, res) => {
2906
+ ${roleGuard} const { id } = req.params;
2907
+ // TODO: fetch ticket by id
2908
+ res.json({ ticket: { id } });
2909
+ };
2910
+
2911
+ export const PUT = async (req, res) => {
2912
+ ${roleGuard} const { id } = req.params;
2913
+ const { reply, status, assignedTo } = req.body;
2914
+ // TODO: update ticket (assign, change status, add reply)
2915
+ res.json({ message: 'Ticket updated', ticket: { id, status, assignedTo } });
2916
+ };
2917
+ `;
2918
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "tickets", `[id].${ext}`), adminTicketByIdContent);
2919
+ const mkContentCrud = (name, namePlural, dir, createFields) => {
2920
+ const listMeta = mkMeta(opts, `List ${namePlural} (GET) or create a new ${name} (POST).`, `{ ${namePlural}: [], total: 0 }`);
2921
+ const listContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2922
+ ${listMeta}${mwAdmin4}
2923
+ export const GET = async (_req: Request, res: Response) => {
2924
+ ${roleGuard} // TODO: fetch ${namePlural}
2925
+ res.json({ ${namePlural}: [], total: 0 });
2926
+ };
2927
+
2928
+ export const POST = async (req: Request, res: Response) => {
2929
+ ${roleGuard} const { ${createFields} } = req.body;
2930
+ // TODO: create ${name}
2931
+ res.status(201).json({ message: '${name} created', ${name.toLowerCase()}: { id: 'new-id', ${createFields.split(", ")[0]} } });
2932
+ };
2933
+ ` : `${RA}${rr4}${listMeta}${mwAdmin4}
2934
+ export const GET = async (_req, res) => {
2935
+ ${roleGuard} // TODO: fetch ${namePlural}
2936
+ res.json({ ${namePlural}: [], total: 0 });
2937
+ };
2938
+
2939
+ export const POST = async (req, res) => {
2940
+ ${roleGuard} const { ${createFields} } = req.body;
2941
+ // TODO: create ${name}
2942
+ res.status(201).json({ message: '${name} created', ${name.toLowerCase()}: { id: 'new-id' } });
2943
+ };
2944
+ `;
2945
+ const byIdContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
2946
+ ${mwAdmin4}
2947
+ export const GET = async (req: Request, res: Response) => {
2948
+ ${roleGuard} const { id } = req.params;
2949
+ // TODO: fetch ${name} by id
2950
+ res.json({ ${name.toLowerCase()}: { id } });
278
2951
  };
279
- ` : `import { requireAuth } from 'express-file-cluster/auth';
280
2952
 
281
- export const middlewares = [requireAuth];
2953
+ export const PUT = async (req: Request, res: Response) => {
2954
+ ${roleGuard} const { id } = req.params;
2955
+ // TODO: update ${name}
2956
+ res.json({ message: '${name} updated', ${name.toLowerCase()}: { id, ...req.body } });
2957
+ };
282
2958
 
2959
+ export const DELETE = async (req: Request, res: Response) => {
2960
+ ${roleGuard} const { id } = req.params;
2961
+ // TODO: delete ${name}
2962
+ res.json({ message: \`${name} \${id} deleted\` });
2963
+ };
2964
+ ` : `${RA}${rr4}${mwAdmin4}
283
2965
  export const GET = async (req, res) => {
284
- const user = req.user;
285
-
286
- res.json({
287
- message: 'User Profile Panel',
288
- user
289
- });
2966
+ ${roleGuard} const { id } = req.params;
2967
+ // TODO: fetch ${name} by id
2968
+ res.json({ ${name.toLowerCase()}: { id } });
2969
+ };
2970
+
2971
+ export const PUT = async (req, res) => {
2972
+ ${roleGuard} const { id } = req.params;
2973
+ // TODO: update ${name}
2974
+ res.json({ message: '${name} updated', ${name.toLowerCase()}: { id, ...req.body } });
2975
+ };
2976
+
2977
+ export const DELETE = async (req, res) => {
2978
+ ${roleGuard} const { id } = req.params;
2979
+ // TODO: delete ${name}
2980
+ res.json({ message: \`${name} \${id} deleted\` });
2981
+ };
2982
+ `;
2983
+ return { listContent, byIdContent };
2984
+ };
2985
+ const { listContent: faqList, byIdContent: faqById } = mkContentCrud("FAQ", "faqs", [], "question, answer, category");
2986
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "content", "faqs", `index.${ext}`), faqList);
2987
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "content", "faqs", `[id].${ext}`), faqById);
2988
+ const { listContent: blogList, byIdContent: blogById } = mkContentCrud("Blog", "blogs", [], "title, slug, content");
2989
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "content", "blogs", `index.${ext}`), blogList);
2990
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "content", "blogs", `[id].${ext}`), blogById);
2991
+ const { listContent: catList, byIdContent: catById } = mkContentCrud("Category", "categories", [], "name, slug");
2992
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "content", "categories", `index.${ext}`), catList);
2993
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "content", "categories", `[id].${ext}`), catById);
2994
+ const { listContent: planList, byIdContent: planById } = mkContentCrud("Plan", "plans", [], "name, price, interval");
2995
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "billing", "plans", `index.${ext}`), planList);
2996
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "billing", "plans", `[id].${ext}`), planById);
2997
+ const { listContent: couponList, byIdContent: couponById } = mkContentCrud("Coupon", "coupons", [], "code, type, value");
2998
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "billing", "coupons", `index.${ext}`), couponList);
2999
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "billing", "coupons", `[id].${ext}`), couponById);
3000
+ const adminSubsMeta = mkMeta(opts, "List all subscriptions with filters.", `{ subscriptions: [], total: 0 }`);
3001
+ const adminSubsContent = ts ? `${RA}${rr4}import type { Request, Response } from 'express';
3002
+ ${adminSubsMeta}${mwAdmin4}
3003
+ export const GET = async (req: Request, res: Response) => {
3004
+ ${roleGuard} const { status, page = 1, limit = 20 } = req.query;
3005
+ // TODO: fetch all subscriptions with filters
3006
+ res.json({ subscriptions: [], total: 0, page: Number(page), limit: Number(limit) });
3007
+ };
3008
+ ` : `${RA}${rr4}${adminSubsMeta}${mwAdmin4}
3009
+ export const GET = async (req, res) => {
3010
+ ${roleGuard} const { status, page = 1, limit = 20 } = req.query;
3011
+ // TODO: fetch all subscriptions with filters
3012
+ res.json({ subscriptions: [], total: 0, page: Number(page), limit: Number(limit) });
290
3013
  };
291
3014
  `;
292
- await fs.outputFile(path.join(dest, "src", "api", "user", `profile.${ext}`), content);
3015
+ await fs.outputFile(path.join(dest, "src", "api", "admin", "billing", "subscriptions", `index.${ext}`), adminSubsContent);
293
3016
  }
294
3017
 
295
3018
  // src/index.ts
@@ -370,6 +3093,75 @@ async function main() {
370
3093
  }
371
3094
  taskBackend = backend;
372
3095
  }
3096
+ const routeDocs = await p.confirm({
3097
+ message: "Include route documentation (meta exports for the API dashboard)?",
3098
+ initialValue: true
3099
+ });
3100
+ if (p.isCancel(routeDocs)) {
3101
+ p.cancel("Cancelled");
3102
+ process.exit(0);
3103
+ }
3104
+ const userPortal = await p.confirm({
3105
+ message: "Build user portal?",
3106
+ initialValue: true
3107
+ });
3108
+ if (p.isCancel(userPortal)) {
3109
+ p.cancel("Cancelled");
3110
+ process.exit(0);
3111
+ }
3112
+ const adminPortal = await p.confirm({
3113
+ message: "Build admin portal?",
3114
+ initialValue: true
3115
+ });
3116
+ if (p.isCancel(adminPortal)) {
3117
+ p.cancel("Cancelled");
3118
+ process.exit(0);
3119
+ }
3120
+ let rbac = false;
3121
+ if (userPortal || adminPortal) {
3122
+ const rbacAnswer = await p.confirm({
3123
+ message: "Enable role-based access control (RBAC)?",
3124
+ initialValue: true
3125
+ });
3126
+ if (p.isCancel(rbacAnswer)) {
3127
+ p.cancel("Cancelled");
3128
+ process.exit(0);
3129
+ }
3130
+ rbac = rbacAnswer;
3131
+ }
3132
+ const mailerAnswer = await p.confirm({
3133
+ message: "Enable mailer (nodemailer)?",
3134
+ initialValue: false
3135
+ });
3136
+ if (p.isCancel(mailerAnswer)) {
3137
+ p.cancel("Cancelled");
3138
+ process.exit(0);
3139
+ }
3140
+ const mailer = mailerAnswer;
3141
+ let smtpHost;
3142
+ let smtpPort;
3143
+ if (mailer) {
3144
+ const host = await p.text({
3145
+ message: "SMTP host:",
3146
+ placeholder: "smtp.gmail.com",
3147
+ defaultValue: "smtp.gmail.com"
3148
+ });
3149
+ if (p.isCancel(host)) {
3150
+ p.cancel("Cancelled");
3151
+ process.exit(0);
3152
+ }
3153
+ smtpHost = host;
3154
+ const port = await p.text({
3155
+ message: "SMTP port:",
3156
+ placeholder: "587",
3157
+ defaultValue: "587"
3158
+ });
3159
+ if (p.isCancel(port)) {
3160
+ p.cancel("Cancelled");
3161
+ process.exit(0);
3162
+ }
3163
+ smtpPort = port;
3164
+ }
373
3165
  const opts = {
374
3166
  projectName,
375
3167
  language,
@@ -377,7 +3169,14 @@ async function main() {
377
3169
  authStrategy,
378
3170
  cluster,
379
3171
  tasks,
380
- ...taskBackend !== void 0 && { taskBackend }
3172
+ routeDocs,
3173
+ userPortal,
3174
+ adminPortal,
3175
+ rbac,
3176
+ mailer,
3177
+ ...taskBackend !== void 0 && { taskBackend },
3178
+ ...smtpHost !== void 0 && { smtpHost },
3179
+ ...smtpPort !== void 0 && { smtpPort }
381
3180
  };
382
3181
  const spinner2 = p.spinner();
383
3182
  spinner2.start("Scaffolding project\u2026");