create-efc-app 0.4.13 → 0.5.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
@@ -41,6 +41,7 @@ async function scaffold(opts) {
41
41
  await writeEfcConfig(dest, opts);
42
42
  await writeEntryPoint(dest, opts);
43
43
  await writeGitignore(dest);
44
+ await writeAgentsMd(dest, opts);
44
45
  await writeEnvFiles(dest, opts);
45
46
  await writeExampleRoute(dest, opts);
46
47
  if (needsUserModel) await writeUserModel(dest, opts);
@@ -70,31 +71,33 @@ async function scaffold(opts) {
70
71
  if (opts.userPortal && uf.apiAndBilling) await writeUserBillingRoutes(dest, opts);
71
72
  if (opts.userPortal && uf.support) await writeSupportRoutes(dest, opts);
72
73
  if (opts.adminPortal) await writeAdminExtendedRoutes(dest, opts);
74
+ await writeInitMigration(dest, opts);
73
75
  }
74
76
  async function writePackageJson(dest, opts) {
75
77
  const deps = {
76
- "express-file-cluster": "^0.3.0"
78
+ "express-file-cluster": "^0.5.0"
77
79
  };
78
- if (opts.database === "mongodb") deps["mongoose"] = "^8.0.0";
80
+ if (opts.database === "mongodb") deps["mongoose"] = "^9.7.2";
79
81
  if (opts.database === "postgresql") {
80
- deps["pg"] = "^8.0.0";
81
- deps["drizzle-orm"] = "^0.33.0";
82
+ deps["pg"] = "^8.11.0";
83
+ deps["drizzle-orm"] = "^0.45.0";
82
84
  }
83
85
  if (opts.tasks && opts.taskBackend === "bullmq") deps["bullmq"] = "^5.0.0";
84
- if (opts.tasks && opts.taskBackend === "pg-boss") deps["pg-boss"] = "^10.0.0";
86
+ if (opts.tasks && opts.taskBackend === "pg-boss") deps["pg-boss"] = "^12.0.0";
85
87
  if (opts.mailer) deps["nodemailer"] = "^6.9.0";
86
- if (opts.database === "mongodb") deps["bcrypt"] = "^5.1.0";
88
+ deps["bcrypt"] = "^5.1.0";
87
89
  const devDeps = {
88
- vitest: "^4.1.9"
90
+ vitest: "^5.0.3"
89
91
  };
90
92
  if (opts.language === "typescript") {
93
+ devDeps["tsup"] = "^8.2.0";
91
94
  devDeps["typescript"] = "^5.5.0";
92
95
  devDeps["@types/node"] = "^22.0.0";
93
96
  devDeps["@types/express"] = "^4.17.21";
94
- devDeps["tsup"] = "^8.2.0";
95
97
  devDeps["tsx"] = "^4.0.0";
96
98
  if (opts.mailer) devDeps["@types/nodemailer"] = "^6.4.0";
97
- if (opts.database === "mongodb") devDeps["@types/bcrypt"] = "^5.0.0";
99
+ devDeps["@types/bcrypt"] = "^5.0.0";
100
+ if (opts.database === "postgresql") devDeps["@types/pg"] = "^8.11.0";
98
101
  }
99
102
  const pkg = {
100
103
  name: opts.projectName,
@@ -104,7 +107,11 @@ async function writePackageJson(dest, opts) {
104
107
  dev: "efc start dev",
105
108
  build: "efc build prod",
106
109
  start: "efc start prod",
107
- test: "efc run tests"
110
+ test: "efc run tests",
111
+ "db:migrate": "efc db migrate",
112
+ "db:rollback": "efc db rollback",
113
+ "db:seed": "efc db seed",
114
+ "db:studio": "efc db studio"
108
115
  },
109
116
  dependencies: deps,
110
117
  devDependencies: devDeps
@@ -125,7 +132,7 @@ async function writeTsConfig(dest, opts) {
125
132
  },
126
133
  // No explicit rootDir: efc.config.ts lives at the project root, outside src/, and
127
134
  // src/index.ts imports it. `efc build prod` only uses this config for `tsc --noEmit`
128
- // typechecking — the actual dist/index.js output path is decided by tsup's entry arg,
135
+ // typechecking — tsup writes the actual output (dist/src/**, dist/efc.config.js),
129
136
  // so leaving rootDir implicit here has no effect on the production build's output layout.
130
137
  include: ["src/**/*", "efc.config.ts"],
131
138
  exclude: ["node_modules", "dist"]
@@ -134,17 +141,18 @@ async function writeTsConfig(dest, opts) {
134
141
  }
135
142
  async function writeEfcConfig(dest, opts) {
136
143
  const ext = opts.language === "typescript" ? "ts" : "js";
137
- const tasks = opts.tasks ? `{ backend: '${opts.taskBackend ?? "bullmq"}', concurrency: 5, redisUrl: process.env.REDIS_URL }` : "false";
138
- const content = `import type { EFCConfig } from 'express-file-cluster';
139
-
140
- // The framework never reads process.env itself \u2014 every runtime value it needs is read
144
+ const tasks = !opts.tasks ? "false" : opts.taskBackend === "pg-boss" ? `{ backend: 'pg-boss', concurrency: 5 }` : `{ backend: 'bullmq', concurrency: 5, redisUrl: process.env.REDIS_URL }`;
145
+ const ts = opts.language === "typescript";
146
+ const typeImport = ts ? "import type { EFCConfig } from 'express-file-cluster';\n\n" : "";
147
+ const declaration = ts ? "const config: EFCConfig = {" : "/** @type {import('express-file-cluster').EFCConfig} */\nconst config = {";
148
+ const content = `${typeImport}// The framework never reads process.env itself \u2014 every runtime value it needs is read
141
149
  // here, explicitly, and passed in. Edit .env to change values; edit this file to change
142
150
  // which env vars are wired up or add new ones.
143
151
  const corsOrigins = process.env.CORS_ORIGINS
144
152
  ? process.env.CORS_ORIGINS.split(',').map((o) => o.trim()).filter(Boolean)
145
153
  : undefined;
146
154
 
147
- const config: EFCConfig = {
155
+ ${declaration}
148
156
  port: process.env.PORT ? Number(process.env.PORT) : undefined,
149
157
  databaseUrl: process.env.DATABASE_URL,
150
158
  jwtSecret: process.env.JWT_SECRET,
@@ -153,6 +161,10 @@ const config: EFCConfig = {
153
161
  cors: corsOrigins ? { origin: corsOrigins } : true,
154
162
  authStrategy: '${opts.authStrategy}',
155
163
  tasks: ${tasks},
164
+ // Cache + session-revocation store (logout really invalidates tokens). Redis when
165
+ // REDIS_URL is set \u2014 shared by every instance; otherwise an in-memory store shared by
166
+ // this machine's workers, emptied on restart.
167
+ cache: process.env.REDIS_URL ? { backend: 'redis', url: process.env.REDIS_URL } : { backend: 'memory' },
156
168
  globalMiddlewares: [],
157
169
  };
158
170
 
@@ -162,6 +174,7 @@ export default config;
162
174
  }
163
175
  async function writeEntryPoint(dest, opts) {
164
176
  const ext = opts.language === "typescript" ? "ts" : "js";
177
+ const clusterLine = opts.cluster ? "" : "\n cluster: false,";
165
178
  const content = `import { ignite, gracefulShutdown } from 'express-file-cluster';
166
179
  import config from '../efc.config.js';
167
180
 
@@ -169,8 +182,7 @@ import config from '../efc.config.js';
169
182
  // .env in efc.config.${ext} and spread in below \u2014 ignite() itself never touches process.env
170
183
  // for these, so this object is the single source of truth for what's actually applied.
171
184
  ignite({
172
- ...config,
173
- cluster: ${opts.cluster},
185
+ ...config,${clusterLine}
174
186
  }).then(gracefulShutdown).catch(console.error);
175
187
  `;
176
188
  await fs.outputFile(path.join(dest, "src", `index.${ext}`), content);
@@ -181,6 +193,58 @@ async function writeGitignore(dest) {
181
193
  "node_modules/\ndist/\n.env\n.env.local\n*.log\n"
182
194
  );
183
195
  }
196
+ async function writeAgentsMd(dest, opts) {
197
+ const ext = opts.language === "typescript" ? "ts" : "js";
198
+ const D = "node_modules/express-file-cluster/dist/docs";
199
+ const engine = opts.database === "postgresql" ? "PostgreSQL" : "MongoDB";
200
+ const content = `# AGENTS.md
201
+
202
+ This is an **express-file-cluster (EFC)** app (${opts.language === "typescript" ? "TypeScript" : "JavaScript"}, ${engine}).
203
+ EFC's API is probably not in your training data \u2014 **read its docs before writing code**, and
204
+ don't use any EFC import, option or method you can't find in them.
205
+
206
+ ## Read the docs first
207
+
208
+ The docs for the exact installed version ship inside the package, under \`${D}/\`:
209
+
210
+ 1. \`${D}/skills/api-surface.md\` \u2014 every public export and signature
211
+ 2. \`${D}/skills/what-not-to-invent.md\` \u2014 things that do NOT exist
212
+ 3. \`${D}/index.md\`, then the page for your task:
213
+ - routes & URL params \u2014 \`core-concepts/file-based-routing.md\`, \`skills/routing-rules.md\`
214
+ - request validation \u2014 \`guides/validation.md\`
215
+ - models & queries \u2014 \`guides/database.md\`, \`api-reference/db.md\`${opts.database === "postgresql" ? ", `api-reference/table.md` (PostgreSQL-native `defineTable`)" : ""}
216
+ - migrations \u2014 \`guides/migrations.md\`
217
+ - auth & roles \u2014 \`guides/authentication.md\`, \`guides/rbac.md\`
218
+ - background tasks \u2014 \`core-concepts/background-tasks.md\`
219
+ - config & boot \u2014 \`api-reference/ignite.md\`
220
+
221
+ Search them: \`grep -rn "<term>" ${D}\`
222
+
223
+ Optional \u2014 the same docs over MCP, with search and scaffolding tools:
224
+ \`claude mcp add efc -- npx -y express-file-cluster-mcp\`
225
+ (other clients: command \`npx\`, args \`["-y", "express-file-cluster-mcp"]\`).
226
+
227
+ ## Project conventions
228
+
229
+ - \`src/api/**\` \u2014 every file is a route (\`users/[id].${ext}\` \u2192 \`/v1/api/users/:id\`); export
230
+ uppercase \`GET\`/\`POST\`/\u2026 handlers.
231
+ - \`src/tasks/*.${ext}\` \u2014 background tasks; the file name is the task name (not recursive).
232
+ - \`src/model/*.${ext}\` \u2014 \`defineModel\` models.
233
+ - \`src/index.${ext}\` is the entry point; it imports \`efc.config.${ext}\` and passes it to \`ignite()\`.
234
+ - Only three import paths exist: \`express-file-cluster\`, \`express-file-cluster/auth\`,
235
+ \`express-file-cluster/tasks\`.
236
+
237
+ ## Commands
238
+
239
+ \`\`\`bash
240
+ npx efc start dev # dev server with reload
241
+ npx efc db migrate # run migrations
242
+ npx efc build prod # type-check + production build
243
+ npx efc doctor # diagnose the setup
244
+ \`\`\`
245
+ `;
246
+ await fs.outputFile(path.join(dest, "AGENTS.md"), content);
247
+ }
184
248
  async function writeEnvFiles(dest, opts) {
185
249
  const secret = crypto.randomBytes(64).toString("hex");
186
250
  const projectName = path.basename(dest);
@@ -206,17 +270,16 @@ SMTP_USER=your@email.com
206
270
  SMTP_PASS=${isGmail ? "your_16_char_app_password" : "your_smtp_password"}${passComment}
207
271
  SMTP_FROM=noreply@yourapp.com
208
272
  ` : "";
273
+ const redis = opts.tasks && opts.taskBackend !== "pg-boss" ? "\nREDIS_URL=redis://localhost:6379" : "";
209
274
  const dotenv = `PORT=3000
210
275
  NODE_ENV=development
211
276
  DATABASE_URL=${dbUrl}
212
- JWT_SECRET=${secret}
213
- REDIS_URL=redis://localhost:6379
277
+ JWT_SECRET=${secret}${redis}
214
278
  CORS_ORIGINS=http://localhost:3000${smtpVars}`;
215
279
  const example = `PORT=3000
216
280
  NODE_ENV=development
217
281
  DATABASE_URL=${dbExampleUrl}
218
- JWT_SECRET=<generate with: openssl rand -hex 64>
219
- REDIS_URL=redis://localhost:6379
282
+ JWT_SECRET=<generate with: openssl rand -hex 64>${redis}
220
283
  CORS_ORIGINS=http://localhost:3000,https://yourapp.com${smtpExample}`;
221
284
  await fs.outputFile(path.join(dest, ".env"), dotenv);
222
285
  await fs.outputFile(path.join(dest, ".env.example"), example);
@@ -328,39 +391,7 @@ export default defineTask(async (payload) => {
328
391
  }
329
392
  async function writeUserModel(dest, opts) {
330
393
  const ext = opts.language === "typescript" ? "ts" : "js";
331
- const ts = opts.language === "typescript";
332
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
333
-
334
- export interface UserDocument {
335
- name: string;
336
- email: string;
337
- password: string;
338
- role: string;
339
- avatar?: string;
340
- isVerified: boolean;
341
- isActive: boolean;
342
- verifyToken?: string;
343
- resetToken?: string;
344
- resetTokenExpiry?: Date;
345
- refreshToken?: string;
346
- refreshTokenExpiry?: Date;
347
- }
348
-
349
- export const User = defineModel<UserDocument>('User', {
350
- name: { type: 'string', required: true },
351
- email: { type: 'string', required: true, unique: true },
352
- password: { type: 'string', required: true },
353
- role: { type: 'string', required: true, default: 'user' },
354
- avatar: { type: 'string' },
355
- isVerified: { type: 'boolean', default: false },
356
- isActive: { type: 'boolean', default: true },
357
- verifyToken: { type: 'string' },
358
- resetToken: { type: 'string' },
359
- resetTokenExpiry: { type: 'date' },
360
- refreshToken: { type: 'string' },
361
- refreshTokenExpiry: { type: 'date' },
362
- });
363
- ` : `import { defineModel } from 'express-file-cluster';
394
+ const content = `import { defineModel } from 'express-file-cluster';
364
395
 
365
396
  export const User = defineModel('User', {
366
397
  name: { type: 'string', required: true },
@@ -376,88 +407,25 @@ export const User = defineModel('User', {
376
407
  refreshToken: { type: 'string' },
377
408
  refreshTokenExpiry: { type: 'date' },
378
409
  });
379
- ` : ts ? `// TODO: define your Drizzle schema for User
380
- // import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';
381
- //
382
- // export const users = pgTable('users', {
383
- // id: serial('id').primaryKey(),
384
- // name: text('name').notNull(),
385
- // email: text('email').notNull().unique(),
386
- // password: text('password').notNull(),
387
- // role: text('role').notNull().default('user'),
388
- // avatar: text('avatar'),
389
- // isVerified: boolean('is_verified').notNull().default(false),
390
- // isActive: boolean('is_active').notNull().default(true),
391
- // createdAt: timestamp('created_at').defaultNow(),
392
- // updatedAt: timestamp('updated_at').defaultNow(),
393
- // });
394
- export {};
395
- ` : `// TODO: define your Drizzle schema for User
396
- export {};
397
410
  `;
398
411
  await fs.outputFile(path.join(dest, "src", "model", `User.${ext}`), content);
399
412
  }
400
413
  async function writeAdminModel(dest, opts) {
401
414
  const ext = opts.language === "typescript" ? "ts" : "js";
402
- const ts = opts.language === "typescript";
403
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
404
-
405
- export interface AdminDocument {
406
- name: string;
407
- email: string;
408
- password: string;
409
- role: string;
410
- permissions: string[];
411
- isActive: boolean;
412
- resetToken?: string;
413
- resetTokenExpiry?: Date;
414
- refreshToken?: string;
415
- refreshTokenExpiry?: Date;
416
- }
417
-
418
- export const Admin = defineModel<AdminDocument>('Admin', {
419
- name: { type: 'string', required: true },
420
- email: { type: 'string', required: true, unique: true },
421
- password: { type: 'string', required: true },
422
- role: { type: 'string', required: true, default: 'admin' },
423
- permissions: { type: 'array', default: [] },
424
- isActive: { type: 'boolean', default: true },
425
- resetToken: { type: 'string' },
426
- resetTokenExpiry: { type: 'date' },
427
- refreshToken: { type: 'string' },
428
- refreshTokenExpiry: { type: 'date' },
429
- });
430
- ` : `import { defineModel } from 'express-file-cluster';
415
+ const content = `import { defineModel } from 'express-file-cluster';
431
416
 
432
417
  export const Admin = defineModel('Admin', {
433
418
  name: { type: 'string', required: true },
434
419
  email: { type: 'string', required: true, unique: true },
435
420
  password: { type: 'string', required: true },
436
421
  role: { type: 'string', required: true, default: 'admin' },
437
- permissions: { type: 'array', default: [] },
422
+ permissions: { type: 'array', of: 'string', default: [] },
438
423
  isActive: { type: 'boolean', default: true },
439
424
  resetToken: { type: 'string' },
440
425
  resetTokenExpiry: { type: 'date' },
441
426
  refreshToken: { type: 'string' },
442
427
  refreshTokenExpiry: { type: 'date' },
443
428
  });
444
- ` : ts ? `// TODO: define your Drizzle schema for Admin
445
- // import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';
446
- //
447
- // export const admins = pgTable('admins', {
448
- // id: serial('id').primaryKey(),
449
- // name: text('name').notNull(),
450
- // email: text('email').notNull().unique(),
451
- // password: text('password').notNull(),
452
- // role: text('role').notNull().default('admin'),
453
- // permissions: text('permissions').array().notNull().default([]),
454
- // isActive: boolean('is_active').notNull().default(true),
455
- // createdAt: timestamp('created_at').defaultNow(),
456
- // updatedAt: timestamp('updated_at').defaultNow(),
457
- // });
458
- export {};
459
- ` : `// TODO: define your Drizzle schema for Admin
460
- export {};
461
429
  `;
462
430
  await fs.outputFile(path.join(dest, "src", "model", `Admin.${ext}`), content);
463
431
  }
@@ -485,8 +453,8 @@ export const meta: RouteMeta = {
485
453
  ` : "";
486
454
  const hasUser = opts.userPortal;
487
455
  const hasAdmin = opts.adminPortal;
488
- const mongo = opts.database === "mongodb" && (hasUser || hasAdmin);
489
- const loginDbImports = mongo ? `import bcrypt from 'bcrypt';
456
+ const hasModels = hasUser || hasAdmin;
457
+ const loginDbImports = hasModels ? `import bcrypt from 'bcrypt';
490
458
  import crypto from 'node:crypto';
491
459
  ${hasUser ? `import { User } from '../../model/User.js';
492
460
  ` : ""}${hasAdmin ? `import { Admin } from '../../model/Admin.js';
@@ -523,7 +491,7 @@ ${hasUser ? `import { User } from '../../model/User.js';
523
491
  await issueToken(res, { id: user.id, role: user.role, email: user.email });
524
492
  await issueRefreshToken(res, User, user.id);
525
493
  res.json({ message: 'Logged in' });`;
526
- const loginContent = mongo ? ts ? `import { issueToken } from 'express-file-cluster/auth';
494
+ const loginContent = hasModels ? ts ? `import { issueToken } from 'express-file-cluster/auth';
527
495
  import type { Request, Response } from 'express';
528
496
  ${loginDbImports}${loginMeta}const REFRESH_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
529
497
 
@@ -586,29 +554,31 @@ ${loginMeta}export const POST = async (req, res) => {
586
554
 
587
555
  export const meta: RouteMeta = {
588
556
  POST: {
589
- description: 'Clear the auth cookie and log the user out.',
557
+ description: 'Revoke the current session and clear the auth cookie.',
590
558
  response: { status: 200, body: { message: 'Logged out successfully' } },
591
559
  },
592
560
  };
593
561
 
594
562
  ` : `export const meta = {
595
563
  POST: {
596
- description: 'Clear the auth cookie and log the user out.',
564
+ description: 'Revoke the current session and clear the auth cookie.',
597
565
  response: { status: 200, body: { message: 'Logged out successfully' } },
598
566
  },
599
567
  };
600
568
 
601
569
  ` : "";
602
- const logoutContent = ts ? `import { revokeToken } from 'express-file-cluster/auth';
570
+ const logoutContent = ts ? `import { revokeSession } from 'express-file-cluster/auth';
603
571
  import type { Request, Response } from 'express';
604
- ${logoutMeta}export const POST = async (_req: Request, res: Response) => {
605
- revokeToken(res);
572
+ ${logoutMeta}export const POST = async (req: Request, res: Response) => {
573
+ // Rejects this token from now on (not just the cookie) \u2014 see \`cache\` in efc.config.
574
+ await revokeSession(req, res);
606
575
  res.clearCookie('efc_refresh_token');
607
576
  res.json({ message: 'Logged out successfully' });
608
577
  };
609
- ` : `import { revokeToken } from 'express-file-cluster/auth';
610
- ${logoutMeta}export const POST = async (_req, res) => {
611
- revokeToken(res);
578
+ ` : `import { revokeSession } from 'express-file-cluster/auth';
579
+ ${logoutMeta}export const POST = async (req, res) => {
580
+ // Rejects this token from now on (not just the cookie) \u2014 see \`cache\` in efc.config.
581
+ await revokeSession(req, res);
612
582
  res.clearCookie('efc_refresh_token');
613
583
  res.json({ message: 'Logged out successfully' });
614
584
  };
@@ -635,10 +605,10 @@ export const meta: RouteMeta = {
635
605
  };
636
606
 
637
607
  ` : "";
638
- const registerDbImports = opts.database === "mongodb" ? `import bcrypt from 'bcrypt';
608
+ const registerDbImports = `import bcrypt from 'bcrypt';
639
609
  import crypto from 'node:crypto';
640
610
  ${opts.mailer ? "import { enqueue } from 'express-file-cluster/tasks';\n" : ""}import { User } from '../../model/User.js';
641
- ` : "";
611
+ `;
642
612
  const sendVerifyEmail = opts.mailer ? `
643
613
  var appUrl = process.env.APP_URL || 'http://localhost:3000';
644
614
  await enqueue('SendEmail', {
@@ -649,7 +619,7 @@ ${opts.mailer ? "import { enqueue } from 'express-file-cluster/tasks';\n" : ""}i
649
619
  ` : `
650
620
  // TODO: email this token to the user \u2014 enable the Mailer feature to auto-wire SendEmail
651
621
  `;
652
- const registerContent = opts.database === "mongodb" ? ts ? `import type { Request, Response } from 'express';
622
+ const registerContent = ts ? `import type { Request, Response } from 'express';
653
623
  ${registerDbImports}${registerMeta}export const POST = async (req: Request, res: Response) => {
654
624
  const { name, email, password } = req.body;
655
625
  if (!name || !email || !password) {
@@ -678,23 +648,6 @@ ${sendVerifyEmail}
678
648
  const { password: _, verifyToken: __, ...safe } = user;
679
649
  res.status(201).json({ message: 'Account created successfully', user: safe });
680
650
  };
681
- ` : ts ? `import type { Request, Response } from 'express';
682
- ${registerMeta}export const POST = async (req: Request, res: Response) => {
683
- const { name, email, password } = req.body;
684
- if (!name || !email || !password) {
685
- return res.status(400).json({ error: 'name, email and password are required' });
686
- }
687
- // TODO: hash password and persist to DB
688
- res.status(201).json({ message: 'Account created successfully' });
689
- };
690
- ` : `${registerMeta}export const POST = async (req, res) => {
691
- const { name, email, password } = req.body;
692
- if (!name || !email || !password) {
693
- return res.status(400).json({ error: 'name, email and password are required' });
694
- }
695
- // TODO: hash password and persist to DB
696
- res.status(201).json({ message: 'Account created successfully' });
697
- };
698
651
  `;
699
652
  const meMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
700
653
 
@@ -765,7 +718,7 @@ export const meta: RouteMeta = {
765
718
  };
766
719
 
767
720
  ` : "";
768
- const dashboardHasUserModel = opts.database === "mongodb" && opts.adminFeatures.userManagement;
721
+ const dashboardHasUserModel = opts.adminFeatures.userManagement;
769
722
  const dashboardDbImport = dashboardHasUserModel ? `import { User } from '../../model/User.js';
770
723
  ` : "";
771
724
  const dashboardContent = dashboardHasUserModel ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
@@ -834,12 +787,12 @@ export const meta: RouteMeta = {
834
787
  };
835
788
 
836
789
  ` : "";
837
- const adminUsersDbImport = opts.database === "mongodb" ? ts ? `import bcrypt from 'bcrypt';
790
+ const adminUsersDbImport = ts ? `import bcrypt from 'bcrypt';
838
791
  import { User } from '../../../model/User.js';
839
792
  ` : `import bcrypt from 'bcrypt';
840
793
  import { User } from '../../../model/User.js';
841
- ` : "";
842
- const usersListContent = opts.database === "mongodb" ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
794
+ `;
795
+ const usersListContent = ts ? `import { requireAuth } from 'express-file-cluster/auth';
843
796
  import type { Request, Response } from 'express';
844
797
  ${adminUsersDbImport}${usersListMeta}${middlewares}
845
798
  export const GET = async (req: Request, res: Response) => {
@@ -880,33 +833,6 @@ ${roleGuard} const { name, email, password, role } = req.body;
880
833
  const { password: _, ...safe } = user;
881
834
  res.status(201).json({ message: 'User created', user: safe });
882
835
  };
883
- ` : ts ? `import { requireAuth } from 'express-file-cluster/auth';
884
- import type { Request, Response } from 'express';
885
- ${usersListMeta}${middlewares}
886
- export const GET = async (_req: Request, res: Response) => {
887
- ${roleGuard} // TODO: fetch users from DB with pagination
888
- res.json({ users: [], total: 0 });
889
- };
890
-
891
- export const POST = async (req: Request, res: Response) => {
892
- ${roleGuard} const { name, email, role } = req.body;
893
- if (!name || !email) return res.status(400).json({ error: 'name and email are required' });
894
- // TODO: create user in DB
895
- res.status(201).json({ message: 'User created', user: { id: 'new-id', name, email, role: role ?? 'user' } });
896
- };
897
- ` : `import { requireAuth } from 'express-file-cluster/auth';
898
- ${usersListMeta}${middlewares}
899
- export const GET = async (_req, res) => {
900
- ${roleGuard} // TODO: fetch users from DB with pagination
901
- res.json({ users: [], total: 0 });
902
- };
903
-
904
- export const POST = async (req, res) => {
905
- ${roleGuard} const { name, email, role } = req.body;
906
- if (!name || !email) return res.status(400).json({ error: 'name and email are required' });
907
- // TODO: create user in DB
908
- res.status(201).json({ message: 'User created', user: { id: 'new-id', name, email, role: role ?? 'user' } });
909
- };
910
836
  `;
911
837
  await fs.outputFile(
912
838
  path.join(dest, "src", "api", "admin", "users", `index.${ext}`),
@@ -951,9 +877,9 @@ export const meta: RouteMeta = {
951
877
  };
952
878
 
953
879
  ` : "";
954
- const adminUserByIdDbImport = opts.database === "mongodb" ? `import { User } from '../../../model/User.js';
955
- ` : "";
956
- const userByIdContent = opts.database === "mongodb" ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
880
+ const adminUserByIdDbImport = `import { User } from '../../../model/User.js';
881
+ `;
882
+ const userByIdContent = ts ? `import { requireAuth } from 'express-file-cluster/auth';
957
883
  import type { Request, Response } from 'express';
958
884
  ${adminUserByIdDbImport}${userByIdMeta}${middlewares}
959
885
  export const GET = async (req: Request, res: Response) => {
@@ -1006,45 +932,6 @@ ${roleGuard} const { id } = req.params;
1006
932
  await User.delete(id);
1007
933
  res.json({ message: \`User \${id} deleted\` });
1008
934
  };
1009
- ` : ts ? `import { requireAuth } from 'express-file-cluster/auth';
1010
- import type { Request, Response } from 'express';
1011
- ${userByIdMeta}${middlewares}
1012
- export const GET = async (req: Request, res: Response) => {
1013
- ${roleGuard} const { id } = req.params;
1014
- // TODO: fetch user from DB
1015
- res.json({ user: { id } });
1016
- };
1017
-
1018
- export const PUT = async (req: Request, res: Response) => {
1019
- ${roleGuard} const { id } = req.params;
1020
- // TODO: update user in DB
1021
- res.json({ message: 'User updated', user: { id, ...req.body } });
1022
- };
1023
-
1024
- export const DELETE = async (req: Request, res: Response) => {
1025
- ${roleGuard} const { id } = req.params;
1026
- // TODO: delete user from DB
1027
- res.json({ message: \`User \${id} deleted\` });
1028
- };
1029
- ` : `import { requireAuth } from 'express-file-cluster/auth';
1030
- ${userByIdMeta}${middlewares}
1031
- export const GET = async (req, res) => {
1032
- ${roleGuard} const { id } = req.params;
1033
- // TODO: fetch user from DB
1034
- res.json({ user: { id } });
1035
- };
1036
-
1037
- export const PUT = async (req, res) => {
1038
- ${roleGuard} const { id } = req.params;
1039
- // TODO: update user in DB
1040
- res.json({ message: 'User updated', user: { id, ...req.body } });
1041
- };
1042
-
1043
- export const DELETE = async (req, res) => {
1044
- ${roleGuard} const { id } = req.params;
1045
- // TODO: delete user from DB
1046
- res.json({ message: \`User \${id} deleted\` });
1047
- };
1048
935
  `;
1049
936
  await fs.outputFile(
1050
937
  path.join(dest, "src", "api", "admin", "users", `[id].${ext}`),
@@ -1084,9 +971,9 @@ export const meta: RouteMeta = {
1084
971
  };
1085
972
 
1086
973
  ` : "";
1087
- const profileDbImport = opts.database === "mongodb" ? `import { User } from '../../model/User.js';
1088
- ` : "";
1089
- const profileContent = opts.database === "mongodb" ? ts ? `import { requireAuth } from 'express-file-cluster/auth';
974
+ const profileDbImport = `import { User } from '../../model/User.js';
975
+ `;
976
+ const profileContent = ts ? `import { requireAuth } from 'express-file-cluster/auth';
1090
977
  import type { Request, Response } from 'express';
1091
978
  ${profileDbImport}${profileMeta}${middlewares}
1092
979
  export const GET = async (req: Request, res: Response) => {
@@ -1123,29 +1010,6 @@ export const PUT = async (req, res) => {
1123
1010
  const { password: _, ...safe } = updated;
1124
1011
  res.json({ message: 'Profile updated', user: safe });
1125
1012
  };
1126
- ` : ts ? `import { requireAuth } from 'express-file-cluster/auth';
1127
- import type { Request, Response } from 'express';
1128
- ${profileMeta}${middlewares}
1129
- export const GET = async (req: Request, res: Response) => {
1130
- res.json({ user: (req as any).user });
1131
- };
1132
-
1133
- export const PUT = async (req: Request, res: Response) => {
1134
- const { name, email } = req.body;
1135
- // TODO: update user in DB
1136
- res.json({ message: 'Profile updated', user: { ...(req as any).user, name, email } });
1137
- };
1138
- ` : `import { requireAuth } from 'express-file-cluster/auth';
1139
- ${profileMeta}${middlewares}
1140
- export const GET = async (req, res) => {
1141
- res.json({ user: req.user });
1142
- };
1143
-
1144
- export const PUT = async (req, res) => {
1145
- const { name, email } = req.body;
1146
- // TODO: update user in DB
1147
- res.json({ message: 'Profile updated', user: { ...req.user, name, email } });
1148
- };
1149
1013
  `;
1150
1014
  await fs.outputFile(path.join(dest, "src", "api", "user", `profile.${ext}`), profileContent);
1151
1015
  }
@@ -1181,27 +1045,7 @@ ${blocks}
1181
1045
  }
1182
1046
  async function writeSessionModel(dest, opts) {
1183
1047
  const ext = opts.language === "typescript" ? "ts" : "js";
1184
- const ts = opts.language === "typescript";
1185
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1186
-
1187
- export interface SessionDocument {
1188
- userId: string;
1189
- token: string;
1190
- ip: string;
1191
- userAgent: string;
1192
- expiresAt: Date;
1193
- isActive: boolean;
1194
- }
1195
-
1196
- export const Session = defineModel<SessionDocument>('Session', {
1197
- userId: { type: 'string', required: true },
1198
- token: { type: 'string', required: true, unique: true },
1199
- ip: { type: 'string', required: true },
1200
- userAgent: { type: 'string', required: true },
1201
- expiresAt: { type: 'date', required: true },
1202
- isActive: { type: 'boolean', default: true },
1203
- });
1204
- ` : `import { defineModel } from 'express-file-cluster';
1048
+ const content = `import { defineModel } from 'express-file-cluster';
1205
1049
 
1206
1050
  export const Session = defineModel('Session', {
1207
1051
  userId: { type: 'string', required: true },
@@ -1211,47 +1055,12 @@ export const Session = defineModel('Session', {
1211
1055
  expiresAt: { type: 'date', required: true },
1212
1056
  isActive: { type: 'boolean', default: true },
1213
1057
  });
1214
- ` : ts ? `// TODO: define your Drizzle schema for Session
1215
- // import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';
1216
- //
1217
- // export const sessions = pgTable('sessions', {
1218
- // id: serial('id').primaryKey(),
1219
- // userId: text('user_id').notNull(),
1220
- // token: text('token').notNull().unique(),
1221
- // ip: text('ip').notNull(),
1222
- // userAgent: text('user_agent').notNull(),
1223
- // expiresAt: timestamp('expires_at').notNull(),
1224
- // isActive: boolean('is_active').notNull().default(true),
1225
- // });
1226
- export {};
1227
- ` : `// TODO: define your Drizzle schema for Session
1228
- export {};
1229
1058
  `;
1230
1059
  await fs.outputFile(path.join(dest, "src", "model", `Session.${ext}`), content);
1231
1060
  }
1232
1061
  async function writeNotificationModel(dest, opts) {
1233
1062
  const ext = opts.language === "typescript" ? "ts" : "js";
1234
- const ts = opts.language === "typescript";
1235
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1236
-
1237
- export interface NotificationDocument {
1238
- userId: string;
1239
- title: string;
1240
- message: string;
1241
- type: string;
1242
- isRead: boolean;
1243
- link?: string;
1244
- }
1245
-
1246
- export const Notification = defineModel<NotificationDocument>('Notification', {
1247
- userId: { type: 'string', required: true },
1248
- title: { type: 'string', required: true },
1249
- message: { type: 'string', required: true },
1250
- type: { type: 'string', required: true, default: 'info' },
1251
- isRead: { type: 'boolean', default: false },
1252
- link: { type: 'string' },
1253
- });
1254
- ` : `import { defineModel } from 'express-file-cluster';
1063
+ const content = `import { defineModel } from 'express-file-cluster';
1255
1064
 
1256
1065
  export const Notification = defineModel('Notification', {
1257
1066
  userId: { type: 'string', required: true },
@@ -1261,50 +1070,12 @@ export const Notification = defineModel('Notification', {
1261
1070
  isRead: { type: 'boolean', default: false },
1262
1071
  link: { type: 'string' },
1263
1072
  });
1264
- ` : ts ? `// TODO: define your Drizzle schema for Notification
1265
- // import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';
1266
- //
1267
- // export const notifications = pgTable('notifications', {
1268
- // id: serial('id').primaryKey(),
1269
- // userId: text('user_id').notNull(),
1270
- // title: text('title').notNull(),
1271
- // message: text('message').notNull(),
1272
- // type: text('type').notNull().default('info'),
1273
- // isRead: boolean('is_read').notNull().default(false),
1274
- // link: text('link'),
1275
- // createdAt: timestamp('created_at').defaultNow(),
1276
- // });
1277
- export {};
1278
- ` : `// TODO: define your Drizzle schema for Notification
1279
- export {};
1280
1073
  `;
1281
1074
  await fs.outputFile(path.join(dest, "src", "model", `Notification.${ext}`), content);
1282
1075
  }
1283
1076
  async function writeFileModel(dest, opts) {
1284
1077
  const ext = opts.language === "typescript" ? "ts" : "js";
1285
- const ts = opts.language === "typescript";
1286
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1287
-
1288
- export interface FileDocument {
1289
- userId: string;
1290
- filename: string;
1291
- originalName: string;
1292
- mimeType: string;
1293
- size: number;
1294
- url: string;
1295
- isPublic: boolean;
1296
- }
1297
-
1298
- export const File = defineModel<FileDocument>('File', {
1299
- userId: { type: 'string', required: true },
1300
- filename: { type: 'string', required: true },
1301
- originalName: { type: 'string', required: true },
1302
- mimeType: { type: 'string', required: true },
1303
- size: { type: 'number', required: true },
1304
- url: { type: 'string', required: true },
1305
- isPublic: { type: 'boolean', default: false },
1306
- });
1307
- ` : `import { defineModel } from 'express-file-cluster';
1078
+ const content = `import { defineModel } from 'express-file-cluster';
1308
1079
 
1309
1080
  export const File = defineModel('File', {
1310
1081
  userId: { type: 'string', required: true },
@@ -1315,49 +1086,12 @@ export const File = defineModel('File', {
1315
1086
  url: { type: 'string', required: true },
1316
1087
  isPublic: { type: 'boolean', default: false },
1317
1088
  });
1318
- ` : ts ? `// TODO: define your Drizzle schema for File
1319
- // import { pgTable, serial, text, boolean, integer, timestamp } from 'drizzle-orm/pg-core';
1320
- //
1321
- // export const files = pgTable('files', {
1322
- // id: serial('id').primaryKey(),
1323
- // userId: text('user_id').notNull(),
1324
- // filename: text('filename').notNull(),
1325
- // originalName: text('original_name').notNull(),
1326
- // mimeType: text('mime_type').notNull(),
1327
- // size: integer('size').notNull(),
1328
- // url: text('url').notNull(),
1329
- // isPublic: boolean('is_public').notNull().default(false),
1330
- // createdAt: timestamp('created_at').defaultNow(),
1331
- // });
1332
- export {};
1333
- ` : `// TODO: define your Drizzle schema for File
1334
- export {};
1335
1089
  `;
1336
1090
  await fs.outputFile(path.join(dest, "src", "model", `File.${ext}`), content);
1337
1091
  }
1338
1092
  async function writeSupportTicketModel(dest, opts) {
1339
1093
  const ext = opts.language === "typescript" ? "ts" : "js";
1340
- const ts = opts.language === "typescript";
1341
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1342
-
1343
- export interface SupportTicketDocument {
1344
- userId: string;
1345
- subject: string;
1346
- message: string;
1347
- status: string;
1348
- priority: string;
1349
- assignedTo?: string;
1350
- }
1351
-
1352
- export const SupportTicket = defineModel<SupportTicketDocument>('SupportTicket', {
1353
- userId: { type: 'string', required: true },
1354
- subject: { type: 'string', required: true },
1355
- message: { type: 'string', required: true },
1356
- status: { type: 'string', required: true, default: 'open' },
1357
- priority: { type: 'string', required: true, default: 'normal' },
1358
- assignedTo: { type: 'string' },
1359
- });
1360
- ` : `import { defineModel } from 'express-file-cluster';
1094
+ const content = `import { defineModel } from 'express-file-cluster';
1361
1095
 
1362
1096
  export const SupportTicket = defineModel('SupportTicket', {
1363
1097
  userId: { type: 'string', required: true },
@@ -1367,49 +1101,12 @@ export const SupportTicket = defineModel('SupportTicket', {
1367
1101
  priority: { type: 'string', required: true, default: 'normal' },
1368
1102
  assignedTo: { type: 'string' },
1369
1103
  });
1370
- ` : ts ? `// TODO: define your Drizzle schema for SupportTicket
1371
- // import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
1372
- //
1373
- // export const supportTickets = pgTable('support_tickets', {
1374
- // id: serial('id').primaryKey(),
1375
- // userId: text('user_id').notNull(),
1376
- // subject: text('subject').notNull(),
1377
- // message: text('message').notNull(),
1378
- // status: text('status').notNull().default('open'),
1379
- // priority: text('priority').notNull().default('normal'),
1380
- // assignedTo: text('assigned_to'),
1381
- // createdAt: timestamp('created_at').defaultNow(),
1382
- // updatedAt: timestamp('updated_at').defaultNow(),
1383
- // });
1384
- export {};
1385
- ` : `// TODO: define your Drizzle schema for SupportTicket
1386
- export {};
1387
1104
  `;
1388
1105
  await fs.outputFile(path.join(dest, "src", "model", `SupportTicket.${ext}`), content);
1389
1106
  }
1390
1107
  async function writeAuditLogModel(dest, opts) {
1391
1108
  const ext = opts.language === "typescript" ? "ts" : "js";
1392
- const ts = opts.language === "typescript";
1393
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1394
-
1395
- export interface AuditLogDocument {
1396
- adminId: string;
1397
- action: string;
1398
- entity: string;
1399
- entityId: string;
1400
- metadata?: Record<string, unknown>;
1401
- ip: string;
1402
- }
1403
-
1404
- export const AuditLog = defineModel<AuditLogDocument>('AuditLog', {
1405
- adminId: { type: 'string', required: true },
1406
- action: { type: 'string', required: true },
1407
- entity: { type: 'string', required: true },
1408
- entityId: { type: 'string', required: true },
1409
- metadata: { type: 'object' },
1410
- ip: { type: 'string', required: true },
1411
- });
1412
- ` : `import { defineModel } from 'express-file-cluster';
1109
+ const content = `import { defineModel } from 'express-file-cluster';
1413
1110
 
1414
1111
  export const AuditLog = defineModel('AuditLog', {
1415
1112
  adminId: { type: 'string', required: true },
@@ -1419,48 +1116,12 @@ export const AuditLog = defineModel('AuditLog', {
1419
1116
  metadata: { type: 'object' },
1420
1117
  ip: { type: 'string', required: true },
1421
1118
  });
1422
- ` : ts ? `// TODO: define your Drizzle schema for AuditLog
1423
- // import { pgTable, serial, text, jsonb, timestamp } from 'drizzle-orm/pg-core';
1424
- //
1425
- // export const auditLogs = pgTable('audit_logs', {
1426
- // id: serial('id').primaryKey(),
1427
- // adminId: text('admin_id').notNull(),
1428
- // action: text('action').notNull(),
1429
- // entity: text('entity').notNull(),
1430
- // entityId: text('entity_id').notNull(),
1431
- // metadata: jsonb('metadata'),
1432
- // ip: text('ip').notNull(),
1433
- // createdAt: timestamp('created_at').defaultNow(),
1434
- // });
1435
- export {};
1436
- ` : `// TODO: define your Drizzle schema for AuditLog
1437
- export {};
1438
1119
  `;
1439
1120
  await fs.outputFile(path.join(dest, "src", "model", `AuditLog.${ext}`), content);
1440
1121
  }
1441
1122
  async function writeSubscriptionModel(dest, opts) {
1442
1123
  const ext = opts.language === "typescript" ? "ts" : "js";
1443
- const ts = opts.language === "typescript";
1444
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1445
-
1446
- export interface SubscriptionDocument {
1447
- userId: string;
1448
- planId: string;
1449
- status: string;
1450
- startDate: Date;
1451
- endDate: Date;
1452
- cancelledAt?: Date;
1453
- }
1454
-
1455
- export const Subscription = defineModel<SubscriptionDocument>('Subscription', {
1456
- userId: { type: 'string', required: true },
1457
- planId: { type: 'string', required: true },
1458
- status: { type: 'string', required: true, default: 'active' },
1459
- startDate: { type: 'date', required: true },
1460
- endDate: { type: 'date', required: true },
1461
- cancelledAt: { type: 'date' },
1462
- });
1463
- ` : `import { defineModel } from 'express-file-cluster';
1124
+ const content = `import { defineModel } from 'express-file-cluster';
1464
1125
 
1465
1126
  export const Subscription = defineModel('Subscription', {
1466
1127
  userId: { type: 'string', required: true },
@@ -1470,99 +1131,27 @@ export const Subscription = defineModel('Subscription', {
1470
1131
  endDate: { type: 'date', required: true },
1471
1132
  cancelledAt: { type: 'date' },
1472
1133
  });
1473
- ` : ts ? `// TODO: define your Drizzle schema for Subscription
1474
- // import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
1475
- //
1476
- // export const subscriptions = pgTable('subscriptions', {
1477
- // id: serial('id').primaryKey(),
1478
- // userId: text('user_id').notNull(),
1479
- // planId: text('plan_id').notNull(),
1480
- // status: text('status').notNull().default('active'),
1481
- // startDate: timestamp('start_date').notNull(),
1482
- // endDate: timestamp('end_date').notNull(),
1483
- // cancelledAt: timestamp('cancelled_at'),
1484
- // createdAt: timestamp('created_at').defaultNow(),
1485
- // });
1486
- export {};
1487
- ` : `// TODO: define your Drizzle schema for Subscription
1488
- export {};
1489
1134
  `;
1490
1135
  await fs.outputFile(path.join(dest, "src", "model", `Subscription.${ext}`), content);
1491
1136
  }
1492
1137
  async function writePlanModel(dest, opts) {
1493
1138
  const ext = opts.language === "typescript" ? "ts" : "js";
1494
- const ts = opts.language === "typescript";
1495
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1496
-
1497
- export interface PlanDocument {
1498
- name: string;
1499
- description: string;
1500
- price: number;
1501
- interval: string;
1502
- features: string[];
1503
- isActive: boolean;
1504
- }
1505
-
1506
- export const Plan = defineModel<PlanDocument>('Plan', {
1507
- name: { type: 'string', required: true },
1508
- description: { type: 'string', required: true },
1509
- price: { type: 'number', required: true },
1510
- interval: { type: 'string', required: true, default: 'monthly' },
1511
- features: { type: 'array', default: [] },
1512
- isActive: { type: 'boolean', default: true },
1513
- });
1514
- ` : `import { defineModel } from 'express-file-cluster';
1139
+ const content = `import { defineModel } from 'express-file-cluster';
1515
1140
 
1516
1141
  export const Plan = defineModel('Plan', {
1517
1142
  name: { type: 'string', required: true },
1518
1143
  description: { type: 'string', required: true },
1519
1144
  price: { type: 'number', required: true },
1520
1145
  interval: { type: 'string', required: true, default: 'monthly' },
1521
- features: { type: 'array', default: [] },
1146
+ features: { type: 'array', of: 'string', default: [] },
1522
1147
  isActive: { type: 'boolean', default: true },
1523
1148
  });
1524
- ` : ts ? `// TODO: define your Drizzle schema for Plan
1525
- // import { pgTable, serial, text, numeric, boolean, timestamp } from 'drizzle-orm/pg-core';
1526
- //
1527
- // export const plans = pgTable('plans', {
1528
- // id: serial('id').primaryKey(),
1529
- // name: text('name').notNull(),
1530
- // description: text('description').notNull(),
1531
- // price: numeric('price').notNull(),
1532
- // interval: text('interval').notNull().default('monthly'),
1533
- // features: text('features').array().notNull().default([]),
1534
- // isActive: boolean('is_active').notNull().default(true),
1535
- // createdAt: timestamp('created_at').defaultNow(),
1536
- // });
1537
- export {};
1538
- ` : `// TODO: define your Drizzle schema for Plan
1539
- export {};
1540
1149
  `;
1541
1150
  await fs.outputFile(path.join(dest, "src", "model", `Plan.${ext}`), content);
1542
1151
  }
1543
1152
  async function writeInvoiceModel(dest, opts) {
1544
1153
  const ext = opts.language === "typescript" ? "ts" : "js";
1545
- const ts = opts.language === "typescript";
1546
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1547
-
1548
- export interface InvoiceDocument {
1549
- userId: string;
1550
- subscriptionId: string;
1551
- amount: number;
1552
- currency: string;
1553
- status: string;
1554
- paidAt?: Date;
1555
- }
1556
-
1557
- export const Invoice = defineModel<InvoiceDocument>('Invoice', {
1558
- userId: { type: 'string', required: true },
1559
- subscriptionId: { type: 'string', required: true },
1560
- amount: { type: 'number', required: true },
1561
- currency: { type: 'string', required: true, default: 'USD' },
1562
- status: { type: 'string', required: true, default: 'pending' },
1563
- paidAt: { type: 'date' },
1564
- });
1565
- ` : `import { defineModel } from 'express-file-cluster';
1154
+ const content = `import { defineModel } from 'express-file-cluster';
1566
1155
 
1567
1156
  export const Invoice = defineModel('Invoice', {
1568
1157
  userId: { type: 'string', required: true },
@@ -1572,48 +1161,12 @@ export const Invoice = defineModel('Invoice', {
1572
1161
  status: { type: 'string', required: true, default: 'pending' },
1573
1162
  paidAt: { type: 'date' },
1574
1163
  });
1575
- ` : ts ? `// TODO: define your Drizzle schema for Invoice
1576
- // import { pgTable, serial, text, numeric, timestamp } from 'drizzle-orm/pg-core';
1577
- //
1578
- // export const invoices = pgTable('invoices', {
1579
- // id: serial('id').primaryKey(),
1580
- // userId: text('user_id').notNull(),
1581
- // subscriptionId: text('subscription_id').notNull(),
1582
- // amount: numeric('amount').notNull(),
1583
- // currency: text('currency').notNull().default('USD'),
1584
- // status: text('status').notNull().default('pending'),
1585
- // paidAt: timestamp('paid_at'),
1586
- // createdAt: timestamp('created_at').defaultNow(),
1587
- // });
1588
- export {};
1589
- ` : `// TODO: define your Drizzle schema for Invoice
1590
- export {};
1591
1164
  `;
1592
1165
  await fs.outputFile(path.join(dest, "src", "model", `Invoice.${ext}`), content);
1593
1166
  }
1594
1167
  async function writeApiKeyModel(dest, opts) {
1595
1168
  const ext = opts.language === "typescript" ? "ts" : "js";
1596
- const ts = opts.language === "typescript";
1597
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1598
-
1599
- export interface ApiKeyDocument {
1600
- userId: string;
1601
- name: string;
1602
- key: string;
1603
- lastUsed?: Date;
1604
- expiresAt?: Date;
1605
- isActive: boolean;
1606
- }
1607
-
1608
- export const ApiKey = defineModel<ApiKeyDocument>('ApiKey', {
1609
- userId: { type: 'string', required: true },
1610
- name: { type: 'string', required: true },
1611
- key: { type: 'string', required: true, unique: true },
1612
- lastUsed: { type: 'date' },
1613
- expiresAt:{ type: 'date' },
1614
- isActive: { type: 'boolean', default: true },
1615
- });
1616
- ` : `import { defineModel } from 'express-file-cluster';
1169
+ const content = `import { defineModel } from 'express-file-cluster';
1617
1170
 
1618
1171
  export const ApiKey = defineModel('ApiKey', {
1619
1172
  userId: { type: 'string', required: true },
@@ -1623,85 +1176,24 @@ export const ApiKey = defineModel('ApiKey', {
1623
1176
  expiresAt: { type: 'date' },
1624
1177
  isActive: { type: 'boolean', default: true },
1625
1178
  });
1626
- ` : ts ? `// TODO: define your Drizzle schema for ApiKey
1627
- // import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';
1628
- //
1629
- // export const apiKeys = pgTable('api_keys', {
1630
- // id: serial('id').primaryKey(),
1631
- // userId: text('user_id').notNull(),
1632
- // name: text('name').notNull(),
1633
- // key: text('key').notNull().unique(),
1634
- // lastUsed: timestamp('last_used'),
1635
- // expiresAt: timestamp('expires_at'),
1636
- // isActive: boolean('is_active').notNull().default(true),
1637
- // createdAt: timestamp('created_at').defaultNow(),
1638
- // });
1639
- export {};
1640
- ` : `// TODO: define your Drizzle schema for ApiKey
1641
- export {};
1642
1179
  `;
1643
1180
  await fs.outputFile(path.join(dest, "src", "model", `ApiKey.${ext}`), content);
1644
1181
  }
1645
1182
  async function writeRoleModel(dest, opts) {
1646
1183
  const ext = opts.language === "typescript" ? "ts" : "js";
1647
- const ts = opts.language === "typescript";
1648
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1649
-
1650
- export interface RoleDocument {
1651
- name: string;
1652
- description: string;
1653
- permissions: string[];
1654
- }
1655
-
1656
- export const Role = defineModel<RoleDocument>('Role', {
1657
- name: { type: 'string', required: true, unique: true },
1658
- description: { type: 'string', required: true },
1659
- permissions: { type: 'array', default: [] },
1660
- });
1661
- ` : `import { defineModel } from 'express-file-cluster';
1184
+ const content = `import { defineModel } from 'express-file-cluster';
1662
1185
 
1663
1186
  export const Role = defineModel('Role', {
1664
1187
  name: { type: 'string', required: true, unique: true },
1665
1188
  description: { type: 'string', required: true },
1666
- permissions: { type: 'array', default: [] },
1189
+ permissions: { type: 'array', of: 'string', default: [] },
1667
1190
  });
1668
- ` : ts ? `// TODO: define your Drizzle schema for Role
1669
- // import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
1670
- //
1671
- // export const roles = pgTable('roles', {
1672
- // id: serial('id').primaryKey(),
1673
- // name: text('name').notNull().unique(),
1674
- // description: text('description').notNull(),
1675
- // permissions: text('permissions').array().notNull().default([]),
1676
- // createdAt: timestamp('created_at').defaultNow(),
1677
- // });
1678
- export {};
1679
- ` : `// TODO: define your Drizzle schema for Role
1680
- export {};
1681
1191
  `;
1682
1192
  await fs.outputFile(path.join(dest, "src", "model", `Role.${ext}`), content);
1683
1193
  }
1684
1194
  async function writeFAQModel(dest, opts) {
1685
1195
  const ext = opts.language === "typescript" ? "ts" : "js";
1686
- const ts = opts.language === "typescript";
1687
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1688
-
1689
- export interface FAQDocument {
1690
- question: string;
1691
- answer: string;
1692
- category: string;
1693
- order: number;
1694
- isPublished: boolean;
1695
- }
1696
-
1697
- export const FAQ = defineModel<FAQDocument>('FAQ', {
1698
- question: { type: 'string', required: true },
1699
- answer: { type: 'string', required: true },
1700
- category: { type: 'string', required: true, default: 'general' },
1701
- order: { type: 'number', default: 0 },
1702
- isPublished: { type: 'boolean', default: false },
1703
- });
1704
- ` : `import { defineModel } from 'express-file-cluster';
1196
+ const content = `import { defineModel } from 'express-file-cluster';
1705
1197
 
1706
1198
  export const FAQ = defineModel('FAQ', {
1707
1199
  question: { type: 'string', required: true },
@@ -1710,51 +1202,12 @@ export const FAQ = defineModel('FAQ', {
1710
1202
  order: { type: 'number', default: 0 },
1711
1203
  isPublished: { type: 'boolean', default: false },
1712
1204
  });
1713
- ` : ts ? `// TODO: define your Drizzle schema for FAQ
1714
- // import { pgTable, serial, text, boolean, integer, timestamp } from 'drizzle-orm/pg-core';
1715
- //
1716
- // export const faqs = pgTable('faqs', {
1717
- // id: serial('id').primaryKey(),
1718
- // question: text('question').notNull(),
1719
- // answer: text('answer').notNull(),
1720
- // category: text('category').notNull().default('general'),
1721
- // order: integer('order').notNull().default(0),
1722
- // isPublished: boolean('is_published').notNull().default(false),
1723
- // createdAt: timestamp('created_at').defaultNow(),
1724
- // });
1725
- export {};
1726
- ` : `// TODO: define your Drizzle schema for FAQ
1727
- export {};
1728
1205
  `;
1729
1206
  await fs.outputFile(path.join(dest, "src", "model", `FAQ.${ext}`), content);
1730
1207
  }
1731
1208
  async function writeBlogModel(dest, opts) {
1732
1209
  const ext = opts.language === "typescript" ? "ts" : "js";
1733
- const ts = opts.language === "typescript";
1734
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1735
-
1736
- export interface BlogDocument {
1737
- title: string;
1738
- slug: string;
1739
- content: string;
1740
- authorId: string;
1741
- category: string;
1742
- tags: string[];
1743
- status: string;
1744
- publishedAt?: Date;
1745
- }
1746
-
1747
- export const Blog = defineModel<BlogDocument>('Blog', {
1748
- title: { type: 'string', required: true },
1749
- slug: { type: 'string', required: true, unique: true },
1750
- content: { type: 'string', required: true },
1751
- authorId: { type: 'string', required: true },
1752
- category: { type: 'string', required: true, default: 'general' },
1753
- tags: { type: 'array', default: [] },
1754
- status: { type: 'string', required: true, default: 'draft' },
1755
- publishedAt: { type: 'date' },
1756
- });
1757
- ` : `import { defineModel } from 'express-file-cluster';
1210
+ const content = `import { defineModel } from 'express-file-cluster';
1758
1211
 
1759
1212
  export const Blog = defineModel('Blog', {
1760
1213
  title: { type: 'string', required: true },
@@ -1762,51 +1215,16 @@ export const Blog = defineModel('Blog', {
1762
1215
  content: { type: 'string', required: true },
1763
1216
  authorId: { type: 'string', required: true },
1764
1217
  category: { type: 'string', required: true, default: 'general' },
1765
- tags: { type: 'array', default: [] },
1218
+ tags: { type: 'array', of: 'string', default: [] },
1766
1219
  status: { type: 'string', required: true, default: 'draft' },
1767
1220
  publishedAt: { type: 'date' },
1768
1221
  });
1769
- ` : ts ? `// TODO: define your Drizzle schema for Blog
1770
- // import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
1771
- //
1772
- // export const blogs = pgTable('blogs', {
1773
- // id: serial('id').primaryKey(),
1774
- // title: text('title').notNull(),
1775
- // slug: text('slug').notNull().unique(),
1776
- // content: text('content').notNull(),
1777
- // authorId: text('author_id').notNull(),
1778
- // category: text('category').notNull().default('general'),
1779
- // tags: text('tags').array().notNull().default([]),
1780
- // status: text('status').notNull().default('draft'),
1781
- // publishedAt: timestamp('published_at'),
1782
- // createdAt: timestamp('created_at').defaultNow(),
1783
- // updatedAt: timestamp('updated_at').defaultNow(),
1784
- // });
1785
- export {};
1786
- ` : `// TODO: define your Drizzle schema for Blog
1787
- export {};
1788
1222
  `;
1789
1223
  await fs.outputFile(path.join(dest, "src", "model", `Blog.${ext}`), content);
1790
1224
  }
1791
1225
  async function writeCategoryModel(dest, opts) {
1792
1226
  const ext = opts.language === "typescript" ? "ts" : "js";
1793
- const ts = opts.language === "typescript";
1794
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1795
-
1796
- export interface CategoryDocument {
1797
- name: string;
1798
- slug: string;
1799
- description?: string;
1800
- parentId?: string;
1801
- }
1802
-
1803
- export const Category = defineModel<CategoryDocument>('Category', {
1804
- name: { type: 'string', required: true },
1805
- slug: { type: 'string', required: true, unique: true },
1806
- description: { type: 'string' },
1807
- parentId: { type: 'string' },
1808
- });
1809
- ` : `import { defineModel } from 'express-file-cluster';
1227
+ const content = `import { defineModel } from 'express-file-cluster';
1810
1228
 
1811
1229
  export const Category = defineModel('Category', {
1812
1230
  name: { type: 'string', required: true },
@@ -1814,48 +1232,12 @@ export const Category = defineModel('Category', {
1814
1232
  description: { type: 'string' },
1815
1233
  parentId: { type: 'string' },
1816
1234
  });
1817
- ` : ts ? `// TODO: define your Drizzle schema for Category
1818
- // import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
1819
- //
1820
- // export const categories = pgTable('categories', {
1821
- // id: serial('id').primaryKey(),
1822
- // name: text('name').notNull(),
1823
- // slug: text('slug').notNull().unique(),
1824
- // description: text('description'),
1825
- // parentId: text('parent_id'),
1826
- // createdAt: timestamp('created_at').defaultNow(),
1827
- // });
1828
- export {};
1829
- ` : `// TODO: define your Drizzle schema for Category
1830
- export {};
1831
1235
  `;
1832
1236
  await fs.outputFile(path.join(dest, "src", "model", `Category.${ext}`), content);
1833
1237
  }
1834
1238
  async function writeCouponModel(dest, opts) {
1835
1239
  const ext = opts.language === "typescript" ? "ts" : "js";
1836
- const ts = opts.language === "typescript";
1837
- const content = opts.database === "mongodb" ? ts ? `import { defineModel } from 'express-file-cluster';
1838
-
1839
- export interface CouponDocument {
1840
- code: string;
1841
- type: string;
1842
- value: number;
1843
- maxUses: number;
1844
- usedCount: number;
1845
- expiresAt?: Date;
1846
- isActive: boolean;
1847
- }
1848
-
1849
- export const Coupon = defineModel<CouponDocument>('Coupon', {
1850
- code: { type: 'string', required: true, unique: true },
1851
- type: { type: 'string', required: true, default: 'percent' },
1852
- value: { type: 'number', required: true },
1853
- maxUses: { type: 'number', default: 0 },
1854
- usedCount: { type: 'number', default: 0 },
1855
- expiresAt: { type: 'date' },
1856
- isActive: { type: 'boolean', default: true },
1857
- });
1858
- ` : `import { defineModel } from 'express-file-cluster';
1240
+ const content = `import { defineModel } from 'express-file-cluster';
1859
1241
 
1860
1242
  export const Coupon = defineModel('Coupon', {
1861
1243
  code: { type: 'string', required: true, unique: true },
@@ -1866,23 +1248,6 @@ export const Coupon = defineModel('Coupon', {
1866
1248
  expiresAt: { type: 'date' },
1867
1249
  isActive: { type: 'boolean', default: true },
1868
1250
  });
1869
- ` : ts ? `// TODO: define your Drizzle schema for Coupon
1870
- // import { pgTable, serial, text, numeric, integer, boolean, timestamp } from 'drizzle-orm/pg-core';
1871
- //
1872
- // export const coupons = pgTable('coupons', {
1873
- // id: serial('id').primaryKey(),
1874
- // code: text('code').notNull().unique(),
1875
- // type: text('type').notNull().default('percent'),
1876
- // value: numeric('value').notNull(),
1877
- // maxUses: integer('max_uses').notNull().default(0),
1878
- // usedCount: integer('used_count').notNull().default(0),
1879
- // expiresAt: timestamp('expires_at'),
1880
- // isActive: boolean('is_active').notNull().default(true),
1881
- // createdAt: timestamp('created_at').defaultNow(),
1882
- // });
1883
- export {};
1884
- ` : `// TODO: define your Drizzle schema for Coupon
1885
- export {};
1886
1251
  `;
1887
1252
  await fs.outputFile(path.join(dest, "src", "model", `Coupon.${ext}`), content);
1888
1253
  }
@@ -1895,7 +1260,6 @@ async function writeAuthExtendedRoutes(dest, opts) {
1895
1260
  const mwUser3 = mwUser2;
1896
1261
  const RA = `import { requireAuth } from 'express-file-cluster/auth';
1897
1262
  `;
1898
- const mongo = opts.database === "mongodb";
1899
1263
  const hasAdmin = opts.adminPortal;
1900
1264
  const uf = opts.userFeatures;
1901
1265
  const sendResetEmail = opts.mailer ? ` var appUrl = process.env.APP_URL || 'http://localhost:3000';
@@ -1932,7 +1296,7 @@ ${hasAdmin ? `import { Admin } from '../../model/Admin.js';
1932
1296
  const account = user;`;
1933
1297
  const refreshPersist = hasAdmin ? ` if (user) await User.update(user.id, { refreshToken: newRefreshToken, refreshTokenExpiry });
1934
1298
  else if (admin) await Admin.update(admin.id, { refreshToken: newRefreshToken, refreshTokenExpiry });` : ` await User.update(user.id, { refreshToken: newRefreshToken, refreshTokenExpiry });`;
1935
- const refreshContent = mongo ? ts ? `import { issueToken } from 'express-file-cluster/auth';
1299
+ const refreshContent = ts ? `import { issueToken } from 'express-file-cluster/auth';
1936
1300
  import type { Request, Response } from 'express';
1937
1301
  import crypto from 'node:crypto';
1938
1302
  ${refreshModelImports}${refreshMeta}const REFRESH_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
@@ -1989,15 +1353,6 @@ ${refreshPersist}
1989
1353
  await issueToken(res, { id: account.id, role: account.role, email: account.email });
1990
1354
  res.json({ message: 'Token refreshed' });
1991
1355
  };
1992
- ` : ts ? `${RA}import type { Request, Response } from 'express';
1993
- ${refreshMeta}export const POST = async (_req: Request, res: Response) => {
1994
- // TODO: validate refresh token, issue new JWT
1995
- res.json({ message: 'Token refreshed' });
1996
- };
1997
- ` : `${RA}${refreshMeta}export const POST = async (_req, res) => {
1998
- // TODO: validate refresh token, issue new JWT
1999
- res.json({ message: 'Token refreshed' });
2000
- };
2001
1356
  `;
2002
1357
  await fs.outputFile(path.join(dest, "src", "api", "auth", `refresh.${ext}`), refreshContent);
2003
1358
  }
@@ -2014,7 +1369,7 @@ ${refreshMeta}export const POST = async (_req: Request, res: Response) => {
2014
1369
  response: `{ message: 'Verification email sent' }`
2015
1370
  }
2016
1371
  });
2017
- const veContent = mongo ? ts ? `import type { Request, Response } from 'express';
1372
+ const veContent = ts ? `import type { Request, Response } from 'express';
2018
1373
  import crypto from 'node:crypto';
2019
1374
  ${mailerTaskImport}import { User } from '../../model/User.js';
2020
1375
  ${veMeta}export const GET = async (req: Request, res: Response) => {
@@ -2065,27 +1420,6 @@ ${sendVerifyEmailResend} }
2065
1420
 
2066
1421
  res.json({ message: 'Verification email sent' });
2067
1422
  };
2068
- ` : ts ? `${RA}import type { Request, Response } from 'express';
2069
- ${veMeta}export const GET = async (req: Request, res: Response) => {
2070
- const { token } = req.query;
2071
- // TODO: verify token and mark user as verified
2072
- res.json({ message: 'Email verified' });
2073
- };
2074
-
2075
- export const POST = async (_req: Request, res: Response) => {
2076
- // TODO: resend verification email
2077
- res.json({ message: 'Verification email sent' });
2078
- };
2079
- ` : `${RA}${veMeta}export const GET = async (req, res) => {
2080
- const { token } = req.query;
2081
- // TODO: verify token and mark user as verified
2082
- res.json({ message: 'Email verified' });
2083
- };
2084
-
2085
- export const POST = async (_req, res) => {
2086
- // TODO: resend verification email
2087
- res.json({ message: 'Verification email sent' });
2088
- };
2089
1423
  `;
2090
1424
  await fs.outputFile(path.join(dest, "src", "api", "auth", `verify-email.${ext}`), veContent);
2091
1425
  }
@@ -2105,7 +1439,7 @@ ${hasAdmin ? `import { Admin } from '../../model/Admin.js';
2105
1439
  const fpGuard = hasAdmin ? "user || admin" : "user";
2106
1440
  const fpPersist = hasAdmin ? ` if (user) await User.update(user.id, { resetToken, resetTokenExpiry });
2107
1441
  else if (admin) await Admin.update(admin.id, { resetToken, resetTokenExpiry });` : ` await User.update(user.id, { resetToken, resetTokenExpiry });`;
2108
- const fpContent = mongo ? ts ? `import type { Request, Response } from 'express';
1442
+ const fpContent = ts ? `import type { Request, Response } from 'express';
2109
1443
  import crypto from 'node:crypto';
2110
1444
  ${mailerTaskImport}${fpModelImports}${fpMeta}const RESET_TOKEN_TTL_MS = 1000 * 60 * 60; // 1 hour
2111
1445
 
@@ -2144,19 +1478,6 @@ ${sendResetEmail} }
2144
1478
 
2145
1479
  res.json({ message: 'Reset email sent' });
2146
1480
  };
2147
- ` : ts ? `${RA}import type { Request, Response } from 'express';
2148
- ${fpMeta}export const POST = async (req: Request, res: Response) => {
2149
- const { email } = req.body;
2150
- if (!email) return res.status(400).json({ error: 'email is required' });
2151
- // TODO: generate reset token and send email
2152
- res.json({ message: 'Reset email sent' });
2153
- };
2154
- ` : `${RA}${fpMeta}export const POST = async (req, res) => {
2155
- const { email } = req.body;
2156
- if (!email) return res.status(400).json({ error: 'email is required' });
2157
- // TODO: generate reset token and send email
2158
- res.json({ message: 'Reset email sent' });
2159
- };
2160
1481
  `;
2161
1482
  await fs.outputFile(path.join(dest, "src", "api", "auth", `forgot-password.${ext}`), fpContent);
2162
1483
  const rpMeta = mkMeta(opts, {
@@ -2175,8 +1496,9 @@ ${hasAdmin ? `import { Admin } from '../../model/Admin.js';
2175
1496
  const account = user;`;
2176
1497
  const rpPersist = hasAdmin ? ` if (user) await User.update(user.id, { password: hashed, resetToken: '' });
2177
1498
  else if (admin) await Admin.update(admin.id, { password: hashed, resetToken: '' });` : ` await User.update(user.id, { password: hashed, resetToken: '' });`;
2178
- const rpContent = mongo ? ts ? `import type { Request, Response } from 'express';
1499
+ const rpContent = ts ? `import type { Request, Response } from 'express';
2179
1500
  import bcrypt from 'bcrypt';
1501
+ import { revokeAllSessions } from 'express-file-cluster/auth';
2180
1502
  ${rpModelImports}${rpMeta}export const POST = async (req: Request, res: Response) => {
2181
1503
  const { token, password } = req.body;
2182
1504
  if (!token || !password) return res.status(400).json({ error: 'token and password are required' });
@@ -2189,10 +1511,13 @@ ${rpLookup}
2189
1511
 
2190
1512
  const hashed = await bcrypt.hash(password, 10);
2191
1513
  ${rpPersist}
1514
+ // Tokens issued with the old password stop working everywhere.
1515
+ await revokeAllSessions(account.id);
2192
1516
 
2193
1517
  res.json({ message: 'Password reset successfully' });
2194
1518
  };
2195
1519
  ` : `import bcrypt from 'bcrypt';
1520
+ import { revokeAllSessions } from 'express-file-cluster/auth';
2196
1521
  ${rpModelImports}${rpMeta}export const POST = async (req, res) => {
2197
1522
  const { token, password } = req.body;
2198
1523
  if (!token || !password) return res.status(400).json({ error: 'token and password are required' });
@@ -2205,22 +1530,11 @@ ${rpLookup}
2205
1530
 
2206
1531
  const hashed = await bcrypt.hash(password, 10);
2207
1532
  ${rpPersist}
1533
+ // Tokens issued with the old password stop working everywhere.
1534
+ await revokeAllSessions(account.id);
2208
1535
 
2209
1536
  res.json({ message: 'Password reset successfully' });
2210
1537
  };
2211
- ` : ts ? `${RA}import type { Request, Response } from 'express';
2212
- ${rpMeta}export const POST = async (req: Request, res: Response) => {
2213
- const { token, password } = req.body;
2214
- if (!token || !password) return res.status(400).json({ error: 'token and password are required' });
2215
- // TODO: validate token, hash and update password
2216
- res.json({ message: 'Password reset successfully' });
2217
- };
2218
- ` : `${RA}${rpMeta}export const POST = async (req, res) => {
2219
- const { token, password } = req.body;
2220
- if (!token || !password) return res.status(400).json({ error: 'token and password are required' });
2221
- // TODO: validate token, hash and update password
2222
- res.json({ message: 'Password reset successfully' });
2223
- };
2224
1538
  `;
2225
1539
  await fs.outputFile(path.join(dest, "src", "api", "auth", `reset-password.${ext}`), rpContent);
2226
1540
  }
@@ -2237,14 +1551,16 @@ ${cpMeta}${mwUser2}
2237
1551
  export const POST = async (req: Request, res: Response) => {
2238
1552
  const { oldPassword, newPassword } = req.body;
2239
1553
  if (!oldPassword || !newPassword) return res.status(400).json({ error: 'oldPassword and newPassword are required' });
2240
- // TODO: verify old password, hash and update new password
1554
+ // TODO: verify old password, hash and update new password, then sign out other devices:
1555
+ // await revokeAllSessions(req.user.id) (import from 'express-file-cluster/auth')
2241
1556
  res.json({ message: 'Password changed successfully' });
2242
1557
  };
2243
1558
  ` : `${RA}${cpMeta}${mwUser2}
2244
1559
  export const POST = async (req, res) => {
2245
1560
  const { oldPassword, newPassword } = req.body;
2246
1561
  if (!oldPassword || !newPassword) return res.status(400).json({ error: 'oldPassword and newPassword are required' });
2247
- // TODO: verify old password, hash and update new password
1562
+ // TODO: verify old password, hash and update new password, then sign out other devices:
1563
+ // await revokeAllSessions(req.user.id) (import from 'express-file-cluster/auth')
2248
1564
  res.json({ message: 'Password changed successfully' });
2249
1565
  };
2250
1566
  `;
@@ -4131,6 +3447,30 @@ ${roleGuard} const { status, page = 1, limit = 20 } = req.query;
4131
3447
  );
4132
3448
  }
4133
3449
  }
3450
+ async function writeInitMigration(dest, opts) {
3451
+ const modelDir = path.join(dest, "src", "model");
3452
+ if (!await fs.pathExists(modelDir)) return;
3453
+ const ts = opts.language === "typescript";
3454
+ const ext = ts ? "ts" : "js";
3455
+ const names = (await fs.readdir(modelDir)).filter((f) => f.endsWith(`.${ext}`)).map((f) => f.slice(0, -(ext.length + 1))).sort();
3456
+ if (names.length === 0) return;
3457
+ const imports = names.map((n) => `import { ${n} } from '../model/${n}.js';`).join("\n");
3458
+ const typeImport = ts ? "import type { MigrationContext } from 'express-file-cluster';\n" : "";
3459
+ const sig = ts ? "(_ctx: MigrationContext): Promise<void>" : "()";
3460
+ const content = `${typeImport}${imports}
3461
+
3462
+ const models = [${names.join(", ")}];
3463
+
3464
+ export async function up${sig} {
3465
+ for (const model of models) await model.sync();
3466
+ }
3467
+
3468
+ export async function down${sig} {
3469
+ for (const model of [...models].reverse()) await model.drop();
3470
+ }
3471
+ `;
3472
+ await fs.outputFile(path.join(dest, "src", "migrations", `00000000000000_init.${ext}`), content);
3473
+ }
4134
3474
 
4135
3475
  // src/index.ts
4136
3476
  function cancel2(msg = "Cancelled") {
@@ -4255,7 +3595,11 @@ async function main() {
4255
3595
  message: "Task queue backend:",
4256
3596
  options: [
4257
3597
  { value: "bullmq", label: "BullMQ", hint: "Redis" },
4258
- { value: "pg-boss", label: "pg-boss", hint: "PostgreSQL" }
3598
+ {
3599
+ value: "pg-boss",
3600
+ label: "pg-boss",
3601
+ hint: database === "postgresql" ? "uses your PostgreSQL \u2014 no Redis" : "needs PostgreSQL"
3602
+ }
4259
3603
  ]
4260
3604
  });
4261
3605
  if (p.isCancel(backend)) cancel2();