create-efc-app 0.7.0 → 0.7.1

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
@@ -30,12 +30,70 @@ var NO_USER_FEATURES = {
30
30
  apiAndBilling: false,
31
31
  support: false
32
32
  };
33
+ function usesTables(opts) {
34
+ return opts.database === "postgresql" && opts.modelApi !== "defineModel";
35
+ }
36
+ function orm(opts) {
37
+ const t = usesTables(opts);
38
+ const where = (filter) => filter.replace(/\s/g, "") === "{}" ? "" : `{ where: ${filter} }`;
39
+ const byId = (id) => id === "id" ? "{ id }" : `{ id: ${id} }`;
40
+ return {
41
+ findOne: (m, filter) => t ? `${m}.findFirst({ where: ${filter} })` : `${m}.findOne(${filter})`,
42
+ findById: (m, id) => t ? `${m}.findUnique({ where: ${byId(id)} })` : `${m}.findById(${id})`,
43
+ find: (m, filter) => t ? `${m}.findMany(${where(filter)})` : `${m}.find(${filter})`,
44
+ count: (m, filter) => t ? `${m}.count(${where(filter)})` : `${m}.count(${filter})`,
45
+ create: (m, data) => t ? `${m}.create({ data: ${data} })` : `${m}.create(${data})`,
46
+ update: (m, id, data) => t ? `${m}.update({ where: ${byId(id)}, data: ${data} })` : `${m}.update(${id}, ${data})`,
47
+ delete: (m, id) => t ? `${m}.delete({ where: ${byId(id)} })` : `${m}.delete(${id})`
48
+ };
49
+ }
50
+ function generatedModels(opts) {
51
+ const uf = opts.userFeatures;
52
+ const af = opts.adminFeatures;
53
+ const user = opts.userPortal;
54
+ const admin = opts.adminPortal;
55
+ const m = /* @__PURE__ */ new Set();
56
+ if (user || admin && af.userManagement) m.add("User");
57
+ if (admin) m.add("Admin");
58
+ if (user && uf.accountSecurity) m.add("Session");
59
+ if (user && uf.notifications || admin && af.notificationsAndLogs) m.add("Notification");
60
+ if (user && uf.filesAndMedia) m.add("File");
61
+ if (user && uf.support || admin && af.supportManagement) m.add("SupportTicket");
62
+ if (admin && af.notificationsAndLogs) m.add("AuditLog");
63
+ if (user && uf.apiAndBilling) m.add("Subscription").add("Invoice").add("ApiKey");
64
+ if (admin && af.billingManagement) m.add("Plan").add("Coupon");
65
+ if (opts.rbac && admin && af.adminManagement) m.add("Role");
66
+ if (admin && af.contentManagement) m.add("FAQ").add("Blog").add("Category");
67
+ return m;
68
+ }
69
+ function modelApi(opts) {
70
+ const name = usesTables(opts) ? "defineTable" : "defineModel";
71
+ return { name, import: `import { ${name} } from 'express-file-cluster';` };
72
+ }
73
+ function refField(opts, r) {
74
+ if (!usesTables(opts) || !generatedModels(opts).has(r.target)) return r.legacy;
75
+ const opts2 = `${r.required ? ", required: true" : ""}${r.onDelete === "restrict" ? "" : `, onDelete: '${r.onDelete}'`}`;
76
+ return ` ${(r.field + ":").padEnd(13)}{ type: 'uuid', ref: '${r.target}'${opts2} },`;
77
+ }
78
+ function relationsOption(opts, rels) {
79
+ if (!usesTables(opts)) return "";
80
+ const models = generatedModels(opts);
81
+ const live = rels.filter((r) => models.has(r.target));
82
+ if (live.length === 0) return "";
83
+ const lines = live.map(
84
+ (r) => ` ${r.name}: { ${r.kind}: '${r.target}', foreignKey: '${r.foreignKey}' },`
85
+ );
86
+ return `, {
87
+ relations: {
88
+ ${lines.join("\n")}
89
+ },
90
+ }`;
91
+ }
33
92
  async function scaffold(opts) {
34
93
  const dest = path.resolve(process.cwd(), opts.projectName);
35
94
  await fs.ensureDir(dest);
36
95
  const uf = opts.userFeatures;
37
- const af = opts.adminFeatures;
38
- const needsUserModel = opts.userPortal || opts.adminPortal && af.userManagement;
96
+ const models = generatedModels(opts);
39
97
  await writePackageJson(dest, opts);
40
98
  await writeTsConfig(dest, opts);
41
99
  await writeEfcConfig(dest, opts);
@@ -44,28 +102,30 @@ async function scaffold(opts) {
44
102
  await writeAgentsMd(dest, opts);
45
103
  await writeEnvFiles(dest, opts);
46
104
  await writeExampleRoute(dest, opts);
47
- if (needsUserModel) await writeUserModel(dest, opts);
48
- if (opts.adminPortal) await writeAdminModel(dest, opts);
105
+ if (models.has("User")) await writeUserModel(dest, opts);
106
+ if (models.has("Admin")) await writeAdminModel(dest, opts);
49
107
  await writeAuthRoutes(dest, opts);
50
108
  if (opts.adminPortal) await writeAdminRoutes(dest, opts);
51
109
  if (opts.userPortal && uf.profileViewing) await writeUserRoutes(dest, opts);
52
110
  if (opts.tasks) await writeExampleTask(dest, opts);
53
- if (opts.userPortal && uf.accountSecurity) await writeSessionModel(dest, opts);
54
- if (opts.userPortal && uf.notifications || opts.adminPortal && af.notificationsAndLogs)
55
- await writeNotificationModel(dest, opts);
56
- if (opts.userPortal && uf.filesAndMedia) await writeFileModel(dest, opts);
57
- if (opts.userPortal && uf.support || opts.adminPortal && af.supportManagement)
58
- await writeSupportTicketModel(dest, opts);
59
- if (opts.adminPortal && af.notificationsAndLogs) await writeAuditLogModel(dest, opts);
60
- if (opts.userPortal && uf.apiAndBilling) await writeSubscriptionModel(dest, opts);
61
- if (opts.adminPortal && af.billingManagement) await writePlanModel(dest, opts);
62
- if (opts.userPortal && uf.apiAndBilling) await writeInvoiceModel(dest, opts);
63
- if (opts.userPortal && uf.apiAndBilling) await writeApiKeyModel(dest, opts);
64
- if (opts.rbac && opts.adminPortal && af.adminManagement) await writeRoleModel(dest, opts);
65
- if (opts.adminPortal && af.contentManagement) await writeFAQModel(dest, opts);
66
- if (opts.adminPortal && af.contentManagement) await writeBlogModel(dest, opts);
67
- if (opts.adminPortal && af.contentManagement) await writeCategoryModel(dest, opts);
68
- if (opts.adminPortal && af.billingManagement) await writeCouponModel(dest, opts);
111
+ const extended = [
112
+ ["Session", writeSessionModel],
113
+ ["Notification", writeNotificationModel],
114
+ ["File", writeFileModel],
115
+ ["SupportTicket", writeSupportTicketModel],
116
+ ["AuditLog", writeAuditLogModel],
117
+ ["Subscription", writeSubscriptionModel],
118
+ ["Plan", writePlanModel],
119
+ ["Invoice", writeInvoiceModel],
120
+ ["ApiKey", writeApiKeyModel],
121
+ ["Role", writeRoleModel],
122
+ ["FAQ", writeFAQModel],
123
+ ["Blog", writeBlogModel],
124
+ ["Category", writeCategoryModel],
125
+ ["Coupon", writeCouponModel]
126
+ ];
127
+ for (const [name, write] of extended) if (models.has(name)) await write(dest, opts);
128
+ if (usesTables(opts)) await writeModelIndex(dest, opts);
69
129
  if (opts.userPortal) await writeAuthExtendedRoutes(dest, opts);
70
130
  if (opts.userPortal) await writeUserExtendedRoutes(dest, opts);
71
131
  if (opts.userPortal && uf.apiAndBilling) await writeUserBillingRoutes(dest, opts);
@@ -75,7 +135,7 @@ async function scaffold(opts) {
75
135
  }
76
136
  async function writePackageJson(dest, opts) {
77
137
  const deps = {
78
- "express-file-cluster": "^0.7.0"
138
+ "express-file-cluster": "^0.7.1"
79
139
  };
80
140
  if (opts.database === "mongodb") deps["mongoose"] = "^9.7.2";
81
141
  if (opts.database === "postgresql") {
@@ -229,7 +289,10 @@ Optional \u2014 the same docs over MCP, with search and scaffolding tools:
229
289
  - \`src/api/**\` \u2014 every file is a route (\`users/[id].${ext}\` \u2192 \`/v1/api/users/:id\`); export
230
290
  uppercase \`GET\`/\`POST\`/\u2026 handlers.
231
291
  - \`src/tasks/*.${ext}\` \u2014 background tasks; the file name is the task name (not recursive).
232
- - \`src/model/*.${ext}\` \u2014 \`defineModel\` models.
292
+ ${usesTables(opts) ? `- \`src/model/*.${ext}\` \u2014 \`defineTable\` models (PostgreSQL-native, Prisma-style queries:
293
+ \`findMany({ where, include })\`, \`create({ data })\`, \`update({ where, data })\`). A \`ref\` is a
294
+ foreign key; back-relations are in each model's \`relations\`. \`src/model/index.${ext}\` imports
295
+ them all${opts.language === "typescript" ? " and declares `Tables` (typed `include`)" : ""} \u2014 add new models there.` : `- \`src/model/*.${ext}\` \u2014 \`defineModel\` models.`}
233
296
  - \`src/index.${ext}\` is the entry point; it imports \`efc.config.${ext}\` and passes it to \`ignite()\`.
234
297
  - Only three import paths exist: \`express-file-cluster\`, \`express-file-cluster/auth\`,
235
298
  \`express-file-cluster/tasks\`.
@@ -391,9 +454,9 @@ export default defineTask(async (payload) => {
391
454
  }
392
455
  async function writeUserModel(dest, opts) {
393
456
  const ext = opts.language === "typescript" ? "ts" : "js";
394
- const content = `import { defineModel } from 'express-file-cluster';
457
+ const content = `${modelApi(opts).import}
395
458
 
396
- export const User = defineModel('User', {
459
+ export const User = ${modelApi(opts).name}('User', {
397
460
  name: { type: 'string', required: true },
398
461
  email: { type: 'string', required: true, unique: true },
399
462
  password: { type: 'string', required: true },
@@ -406,15 +469,23 @@ export const User = defineModel('User', {
406
469
  resetTokenExpiry: { type: 'date' },
407
470
  refreshToken: { type: 'string' },
408
471
  refreshTokenExpiry: { type: 'date' },
409
- });
472
+ }${relationsOption(opts, [
473
+ { name: "sessions", kind: "hasMany", target: "Session", foreignKey: "user" },
474
+ { name: "notifications", kind: "hasMany", target: "Notification", foreignKey: "user" },
475
+ { name: "files", kind: "hasMany", target: "File", foreignKey: "user" },
476
+ { name: "tickets", kind: "hasMany", target: "SupportTicket", foreignKey: "user" },
477
+ { name: "subscriptions", kind: "hasMany", target: "Subscription", foreignKey: "user" },
478
+ { name: "invoices", kind: "hasMany", target: "Invoice", foreignKey: "user" },
479
+ { name: "apiKeys", kind: "hasMany", target: "ApiKey", foreignKey: "user" }
480
+ ])});
410
481
  `;
411
482
  await fs.outputFile(path.join(dest, "src", "model", `User.${ext}`), content);
412
483
  }
413
484
  async function writeAdminModel(dest, opts) {
414
485
  const ext = opts.language === "typescript" ? "ts" : "js";
415
- const content = `import { defineModel } from 'express-file-cluster';
486
+ const content = `${modelApi(opts).import}
416
487
 
417
- export const Admin = defineModel('Admin', {
488
+ export const Admin = ${modelApi(opts).name}('Admin', {
418
489
  name: { type: 'string', required: true },
419
490
  email: { type: 'string', required: true, unique: true },
420
491
  password: { type: 'string', required: true },
@@ -425,11 +496,16 @@ export const Admin = defineModel('Admin', {
425
496
  resetTokenExpiry: { type: 'date' },
426
497
  refreshToken: { type: 'string' },
427
498
  refreshTokenExpiry: { type: 'date' },
428
- });
499
+ }${relationsOption(opts, [
500
+ { name: "assignedTickets", kind: "hasMany", target: "SupportTicket", foreignKey: "assignedTo" },
501
+ { name: "auditLogs", kind: "hasMany", target: "AuditLog", foreignKey: "admin" },
502
+ { name: "posts", kind: "hasMany", target: "Blog", foreignKey: "author" }
503
+ ])});
429
504
  `;
430
505
  await fs.outputFile(path.join(dest, "src", "model", `Admin.${ext}`), content);
431
506
  }
432
507
  async function writeAuthRoutes(dest, opts) {
508
+ const db = orm(opts);
433
509
  const ext = opts.language === "typescript" ? "ts" : "js";
434
510
  const ts = opts.language === "typescript";
435
511
  const loginMeta = opts.routeDocs ? ts ? `import type { RouteMeta } from 'express-file-cluster';
@@ -459,7 +535,7 @@ import crypto from 'node:crypto';
459
535
  ${hasUser ? `import { User } from '../../model/User.js';
460
536
  ` : ""}${hasAdmin ? `import { Admin } from '../../model/Admin.js';
461
537
  ` : ""}` : "";
462
- const loginBody = hasAdmin && hasUser ? ` const admin = await Admin.findOne({ email });
538
+ const loginBody = hasAdmin && hasUser ? ` const admin = await ${db.findOne("Admin", `{ email }`)};
463
539
  if (admin) {
464
540
  const match = await bcrypt.compare(password, admin.password);
465
541
  if (!match) return res.status(401).json({ error: 'Invalid credentials' });
@@ -469,21 +545,21 @@ ${hasUser ? `import { User } from '../../model/User.js';
469
545
  return res.json({ message: 'Logged in as admin' });
470
546
  }
471
547
 
472
- const user = await User.findOne({ email });
548
+ const user = await ${db.findOne("User", `{ email }`)};
473
549
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });
474
550
  const match = await bcrypt.compare(password, user.password);
475
551
  if (!match) return res.status(401).json({ error: 'Invalid credentials' });
476
552
  if (!user.isActive) return res.status(403).json({ error: 'Account suspended' });
477
553
  await issueToken(res, { id: user.id, role: user.role, email: user.email });
478
554
  await issueRefreshToken(res, User, user.id);
479
- res.json({ message: 'Logged in' });` : hasAdmin ? ` const admin = await Admin.findOne({ email });
555
+ res.json({ message: 'Logged in' });` : hasAdmin ? ` const admin = await ${db.findOne("Admin", `{ email }`)};
480
556
  if (!admin) return res.status(401).json({ error: 'Invalid credentials' });
481
557
  const match = await bcrypt.compare(password, admin.password);
482
558
  if (!match) return res.status(401).json({ error: 'Invalid credentials' });
483
559
  if (!admin.isActive) return res.status(403).json({ error: 'Account suspended' });
484
560
  await issueToken(res, { id: admin.id, role: admin.role, email: admin.email });
485
561
  await issueRefreshToken(res, Admin, admin.id);
486
- res.json({ message: 'Logged in as admin' });` : ` const user = await User.findOne({ email });
562
+ res.json({ message: 'Logged in as admin' });` : ` const user = await ${db.findOne("User", `{ email }`)};
487
563
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });
488
564
  const match = await bcrypt.compare(password, user.password);
489
565
  if (!match) return res.status(401).json({ error: 'Invalid credentials' });
@@ -497,11 +573,11 @@ ${loginDbImports}${loginMeta}const REFRESH_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 *
497
573
 
498
574
  async function issueRefreshToken(
499
575
  res: Response,
500
- model: { update: (id: string, data: Record<string, unknown>) => Promise<unknown> },
576
+ model: ${usesTables(opts) ? "{ update: (args: { where: { id: string }; data: Record<string, unknown> }) => Promise<unknown> }" : "{ update: (id: string, data: Record<string, unknown>) => Promise<unknown> }"},
501
577
  id: string,
502
578
  ): Promise<void> {
503
579
  const refreshToken = crypto.randomBytes(40).toString('hex');
504
- await model.update(id, { refreshToken, refreshTokenExpiry: new Date(Date.now() + REFRESH_TOKEN_TTL_MS) });
580
+ await ${db.update("model", "id", "{ refreshToken, refreshTokenExpiry: new Date(Date.now() + REFRESH_TOKEN_TTL_MS) }")};
505
581
  res.cookie('efc_refresh_token', refreshToken, {
506
582
  httpOnly: true,
507
583
  secure: process.env.NODE_ENV === 'production',
@@ -521,7 +597,7 @@ ${loginDbImports}${loginMeta}const REFRESH_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 *
521
597
 
522
598
  async function issueRefreshToken(res, model, id) {
523
599
  const refreshToken = crypto.randomBytes(40).toString('hex');
524
- await model.update(id, { refreshToken, refreshTokenExpiry: new Date(Date.now() + REFRESH_TOKEN_TTL_MS) });
600
+ await ${db.update("model", "id", "{ refreshToken, refreshTokenExpiry: new Date(Date.now() + REFRESH_TOKEN_TTL_MS) }")};
525
601
  res.cookie('efc_refresh_token', refreshToken, {
526
602
  httpOnly: true,
527
603
  secure: process.env.NODE_ENV === 'production',
@@ -625,11 +701,11 @@ ${registerDbImports}${registerMeta}export const POST = async (req: Request, res:
625
701
  if (!name || !email || !password) {
626
702
  return res.status(400).json({ error: 'name, email and password are required' });
627
703
  }
628
- const existing = await User.findOne({ email });
704
+ const existing = await ${db.findOne("User", `{ email }`)};
629
705
  if (existing) return res.status(409).json({ error: 'Email already in use' });
630
706
  const hashed = await bcrypt.hash(password, 10);
631
707
  const verifyToken = crypto.randomBytes(32).toString('hex');
632
- const user = await User.create({ name, email, password: hashed, verifyToken });
708
+ const user = await ${db.create("User", `{ name, email, password: hashed, verifyToken }`)};
633
709
  ${sendVerifyEmail}
634
710
  const { password: _, verifyToken: __, ...safe } = user;
635
711
  res.status(201).json({ message: 'Account created successfully', user: safe });
@@ -639,11 +715,11 @@ ${sendVerifyEmail}
639
715
  if (!name || !email || !password) {
640
716
  return res.status(400).json({ error: 'name, email and password are required' });
641
717
  }
642
- const existing = await User.findOne({ email });
718
+ const existing = await ${db.findOne("User", `{ email }`)};
643
719
  if (existing) return res.status(409).json({ error: 'Email already in use' });
644
720
  const hashed = await bcrypt.hash(password, 10);
645
721
  const verifyToken = crypto.randomBytes(32).toString('hex');
646
- const user = await User.create({ name, email, password: hashed, verifyToken });
722
+ const user = await ${db.create("User", `{ name, email, password: hashed, verifyToken }`)};
647
723
  ${sendVerifyEmail}
648
724
  const { password: _, verifyToken: __, ...safe } = user;
649
725
  res.status(201).json({ message: 'Account created successfully', user: safe });
@@ -685,6 +761,7 @@ ${meMeta}${meMiddlewares}export const GET = async (req, res) => {
685
761
  await fs.outputFile(path.join(dest, "src", "api", "auth", `me.${ext}`), meContent);
686
762
  }
687
763
  async function writeAdminRoutes(dest, opts) {
764
+ const db = orm(opts);
688
765
  const ext = opts.language === "typescript" ? "ts" : "js";
689
766
  const ts = opts.language === "typescript";
690
767
  const middlewares = opts.rbac ? `export const middlewares = [requireAuth('admin')];
@@ -726,9 +803,9 @@ import type { Request, Response } from 'express';
726
803
  ${dashboardDbImport}${dashboardMeta}${middlewares}
727
804
  export const GET = async (_req: Request, res: Response) => {
728
805
  ${roleGuard} const [totalUsers, activeUsers, verifiedUsers] = await Promise.all([
729
- User.count({}),
730
- User.count({ isActive: true }),
731
- User.count({ isVerified: true }),
806
+ ${db.count("User", `{}`)},
807
+ ${db.count("User", `{ isActive: true }`)},
808
+ ${db.count("User", `{ isVerified: true }`)},
732
809
  ]);
733
810
  res.json({ stats: { totalUsers, activeUsers, verifiedUsers } });
734
811
  };
@@ -736,9 +813,9 @@ ${roleGuard} const [totalUsers, activeUsers, verifiedUsers] = await Promise.all
736
813
  ${dashboardDbImport}${dashboardMeta}${middlewares}
737
814
  export const GET = async (_req, res) => {
738
815
  ${roleGuard} const [totalUsers, activeUsers, verifiedUsers] = await Promise.all([
739
- User.count({}),
740
- User.count({ isActive: true }),
741
- User.count({ isVerified: true }),
816
+ ${db.count("User", `{}`)},
817
+ ${db.count("User", `{ isActive: true }`)},
818
+ ${db.count("User", `{ isVerified: true }`)},
742
819
  ]);
743
820
  res.json({ stats: { totalUsers, activeUsers, verifiedUsers } });
744
821
  };
@@ -798,7 +875,7 @@ ${adminUsersDbImport}${usersListMeta}${middlewares}
798
875
  export const GET = async (req: Request, res: Response) => {
799
876
  ${roleGuard} const page = Math.max(1, Number(req.query.page) || 1);
800
877
  const limit = Math.min(100, Number(req.query.limit) || 20);
801
- const [all, total] = await Promise.all([User.find({}), User.count({})]);
878
+ const [all, total] = await Promise.all([${db.find("User", `{}`)}, ${db.count("User", `{}`)}]);
802
879
  const users = all.slice((page - 1) * limit, page * limit).map(({ password: _, ...u }) => u);
803
880
  res.json({ users, total, page, limit });
804
881
  };
@@ -806,10 +883,10 @@ ${roleGuard} const page = Math.max(1, Number(req.query.page) || 1);
806
883
  export const POST = async (req: Request, res: Response) => {
807
884
  ${roleGuard} const { name, email, password, role } = req.body;
808
885
  if (!name || !email || !password) return res.status(400).json({ error: 'name, email and password are required' });
809
- const existing = await User.findOne({ email });
886
+ const existing = await ${db.findOne("User", `{ email }`)};
810
887
  if (existing) return res.status(409).json({ error: 'Email already in use' });
811
888
  const hashed = await bcrypt.hash(password, 10);
812
- const user = await User.create({ name, email, password: hashed, role: role ?? 'user' });
889
+ const user = await ${db.create("User", `{ name, email, password: hashed, role: role ?? 'user' }`)};
813
890
  const { password: _, ...safe } = user;
814
891
  res.status(201).json({ message: 'User created', user: safe });
815
892
  };
@@ -818,7 +895,7 @@ ${adminUsersDbImport}${usersListMeta}${middlewares}
818
895
  export const GET = async (req, res) => {
819
896
  ${roleGuard} const page = Math.max(1, Number(req.query.page) || 1);
820
897
  const limit = Math.min(100, Number(req.query.limit) || 20);
821
- const [all, total] = await Promise.all([User.find({}), User.count({})]);
898
+ const [all, total] = await Promise.all([${db.find("User", `{}`)}, ${db.count("User", `{}`)}]);
822
899
  const users = all.slice((page - 1) * limit, page * limit).map(({ password: _, ...u }) => u);
823
900
  res.json({ users, total, page, limit });
824
901
  };
@@ -826,10 +903,10 @@ ${roleGuard} const page = Math.max(1, Number(req.query.page) || 1);
826
903
  export const POST = async (req, res) => {
827
904
  ${roleGuard} const { name, email, password, role } = req.body;
828
905
  if (!name || !email || !password) return res.status(400).json({ error: 'name, email and password are required' });
829
- const existing = await User.findOne({ email });
906
+ const existing = await ${db.findOne("User", `{ email }`)};
830
907
  if (existing) return res.status(409).json({ error: 'Email already in use' });
831
908
  const hashed = await bcrypt.hash(password, 10);
832
- const user = await User.create({ name, email, password: hashed, role: role ?? 'user' });
909
+ const user = await ${db.create("User", `{ name, email, password: hashed, role: role ?? 'user' }`)};
833
910
  const { password: _, ...safe } = user;
834
911
  res.status(201).json({ message: 'User created', user: safe });
835
912
  };
@@ -884,7 +961,7 @@ import type { Request, Response } from 'express';
884
961
  ${adminUserByIdDbImport}${userByIdMeta}${middlewares}
885
962
  export const GET = async (req: Request, res: Response) => {
886
963
  ${roleGuard} const { id } = req.params;
887
- const user = await User.findById(id);
964
+ const user = await ${db.findById("User", `id`)};
888
965
  if (!user) return res.status(404).json({ error: 'User not found' });
889
966
  const { password: _, ...safe } = user;
890
967
  res.json({ user: safe });
@@ -893,7 +970,7 @@ ${roleGuard} const { id } = req.params;
893
970
  export const PUT = async (req: Request, res: Response) => {
894
971
  ${roleGuard} const { id } = req.params;
895
972
  const { name, email, role, isActive } = req.body;
896
- const updated = await User.update(id, { name, email, role, isActive });
973
+ const updated = await ${db.update("User", `id`, `{ name, email, role, isActive }`)};
897
974
  if (!updated) return res.status(404).json({ error: 'User not found' });
898
975
  const { password: _, ...safe } = updated;
899
976
  res.json({ message: 'User updated', user: safe });
@@ -901,16 +978,16 @@ ${roleGuard} const { id } = req.params;
901
978
 
902
979
  export const DELETE = async (req: Request, res: Response) => {
903
980
  ${roleGuard} const { id } = req.params;
904
- const user = await User.findById(id);
981
+ const user = await ${db.findById("User", `id`)};
905
982
  if (!user) return res.status(404).json({ error: 'User not found' });
906
- await User.delete(id);
983
+ await ${db.delete("User", `id`)};
907
984
  res.json({ message: \`User \${id} deleted\` });
908
985
  };
909
986
  ` : `import { requireAuth } from 'express-file-cluster/auth';
910
987
  ${adminUserByIdDbImport}${userByIdMeta}${middlewares}
911
988
  export const GET = async (req, res) => {
912
989
  ${roleGuard} const { id } = req.params;
913
- const user = await User.findById(id);
990
+ const user = await ${db.findById("User", `id`)};
914
991
  if (!user) return res.status(404).json({ error: 'User not found' });
915
992
  const { password: _, ...safe } = user;
916
993
  res.json({ user: safe });
@@ -919,7 +996,7 @@ ${roleGuard} const { id } = req.params;
919
996
  export const PUT = async (req, res) => {
920
997
  ${roleGuard} const { id } = req.params;
921
998
  const { name, email, role, isActive } = req.body;
922
- const updated = await User.update(id, { name, email, role, isActive });
999
+ const updated = await ${db.update("User", `id`, `{ name, email, role, isActive }`)};
923
1000
  if (!updated) return res.status(404).json({ error: 'User not found' });
924
1001
  const { password: _, ...safe } = updated;
925
1002
  res.json({ message: 'User updated', user: safe });
@@ -927,9 +1004,9 @@ ${roleGuard} const { id } = req.params;
927
1004
 
928
1005
  export const DELETE = async (req, res) => {
929
1006
  ${roleGuard} const { id } = req.params;
930
- const user = await User.findById(id);
1007
+ const user = await ${db.findById("User", `id`)};
931
1008
  if (!user) return res.status(404).json({ error: 'User not found' });
932
- await User.delete(id);
1009
+ await ${db.delete("User", `id`)};
933
1010
  res.json({ message: \`User \${id} deleted\` });
934
1011
  };
935
1012
  `;
@@ -939,6 +1016,7 @@ ${roleGuard} const { id } = req.params;
939
1016
  );
940
1017
  }
941
1018
  async function writeUserRoutes(dest, opts) {
1019
+ const db = orm(opts);
942
1020
  const ext = opts.language === "typescript" ? "ts" : "js";
943
1021
  const ts = opts.language === "typescript";
944
1022
  const middlewares = opts.rbac ? `export const middlewares = [requireAuth('user', 'admin')];
@@ -978,7 +1056,7 @@ import type { Request, Response } from 'express';
978
1056
  ${profileDbImport}${profileMeta}${middlewares}
979
1057
  export const GET = async (req: Request, res: Response) => {
980
1058
  const { id } = (req as any).user;
981
- const user = await User.findById(id);
1059
+ const user = await ${db.findById("User", `id`)};
982
1060
  if (!user) return res.status(404).json({ error: 'User not found' });
983
1061
  const { password: _, ...safe } = user;
984
1062
  res.json({ user: safe });
@@ -987,7 +1065,7 @@ export const GET = async (req: Request, res: Response) => {
987
1065
  export const PUT = async (req: Request, res: Response) => {
988
1066
  const { id } = (req as any).user;
989
1067
  const { name, email } = req.body;
990
- const updated = await User.update(id, { name, email });
1068
+ const updated = await ${db.update("User", `id`, `{ name, email }`)};
991
1069
  if (!updated) return res.status(404).json({ error: 'User not found' });
992
1070
  const { password: _, ...safe } = updated;
993
1071
  res.json({ message: 'Profile updated', user: safe });
@@ -996,7 +1074,7 @@ export const PUT = async (req: Request, res: Response) => {
996
1074
  ${profileDbImport}${profileMeta}${middlewares}
997
1075
  export const GET = async (req, res) => {
998
1076
  const { id } = req.user;
999
- const user = await User.findById(id);
1077
+ const user = await ${db.findById("User", `id`)};
1000
1078
  if (!user) return res.status(404).json({ error: 'User not found' });
1001
1079
  const { password: _, ...safe } = user;
1002
1080
  res.json({ user: safe });
@@ -1005,7 +1083,7 @@ export const GET = async (req, res) => {
1005
1083
  export const PUT = async (req, res) => {
1006
1084
  const { id } = req.user;
1007
1085
  const { name, email } = req.body;
1008
- const updated = await User.update(id, { name, email });
1086
+ const updated = await ${db.update("User", `id`, `{ name, email }`)};
1009
1087
  if (!updated) return res.status(404).json({ error: 'User not found' });
1010
1088
  const { password: _, ...safe } = updated;
1011
1089
  res.json({ message: 'Profile updated', user: safe });
@@ -1045,10 +1123,10 @@ ${blocks}
1045
1123
  }
1046
1124
  async function writeSessionModel(dest, opts) {
1047
1125
  const ext = opts.language === "typescript" ? "ts" : "js";
1048
- const content = `import { defineModel } from 'express-file-cluster';
1126
+ const content = `${modelApi(opts).import}
1049
1127
 
1050
- export const Session = defineModel('Session', {
1051
- userId: { type: 'string', required: true },
1128
+ export const Session = ${modelApi(opts).name}('Session', {
1129
+ ${refField(opts, { field: "user", target: "User", legacy: ` userId: { type: 'string', required: true },`, required: true, onDelete: "cascade" })}
1052
1130
  token: { type: 'string', required: true, unique: true },
1053
1131
  ip: { type: 'string', required: true },
1054
1132
  userAgent: { type: 'string', required: true },
@@ -1060,10 +1138,10 @@ export const Session = defineModel('Session', {
1060
1138
  }
1061
1139
  async function writeNotificationModel(dest, opts) {
1062
1140
  const ext = opts.language === "typescript" ? "ts" : "js";
1063
- const content = `import { defineModel } from 'express-file-cluster';
1141
+ const content = `${modelApi(opts).import}
1064
1142
 
1065
- export const Notification = defineModel('Notification', {
1066
- userId: { type: 'string', required: true },
1143
+ export const Notification = ${modelApi(opts).name}('Notification', {
1144
+ ${refField(opts, { field: "user", target: "User", legacy: ` userId: { type: 'string', required: true },`, required: true, onDelete: "cascade" })}
1067
1145
  title: { type: 'string', required: true },
1068
1146
  message: { type: 'string', required: true },
1069
1147
  type: { type: 'string', required: true, default: 'info' },
@@ -1075,10 +1153,10 @@ export const Notification = defineModel('Notification', {
1075
1153
  }
1076
1154
  async function writeFileModel(dest, opts) {
1077
1155
  const ext = opts.language === "typescript" ? "ts" : "js";
1078
- const content = `import { defineModel } from 'express-file-cluster';
1156
+ const content = `${modelApi(opts).import}
1079
1157
 
1080
- export const File = defineModel('File', {
1081
- userId: { type: 'string', required: true },
1158
+ export const File = ${modelApi(opts).name}('File', {
1159
+ ${refField(opts, { field: "user", target: "User", legacy: ` userId: { type: 'string', required: true },`, required: true, onDelete: "cascade" })}
1082
1160
  filename: { type: 'string', required: true },
1083
1161
  originalName: { type: 'string', required: true },
1084
1162
  mimeType: { type: 'string', required: true },
@@ -1091,25 +1169,25 @@ export const File = defineModel('File', {
1091
1169
  }
1092
1170
  async function writeSupportTicketModel(dest, opts) {
1093
1171
  const ext = opts.language === "typescript" ? "ts" : "js";
1094
- const content = `import { defineModel } from 'express-file-cluster';
1172
+ const content = `${modelApi(opts).import}
1095
1173
 
1096
- export const SupportTicket = defineModel('SupportTicket', {
1097
- userId: { type: 'string', required: true },
1174
+ export const SupportTicket = ${modelApi(opts).name}('SupportTicket', {
1175
+ ${refField(opts, { field: "user", target: "User", legacy: ` userId: { type: 'string', required: true },`, required: true, onDelete: "cascade" })}
1098
1176
  subject: { type: 'string', required: true },
1099
1177
  message: { type: 'string', required: true },
1100
1178
  status: { type: 'string', required: true, default: 'open' },
1101
1179
  priority: { type: 'string', required: true, default: 'normal' },
1102
- assignedTo: { type: 'string' },
1180
+ ${refField(opts, { field: "assignedTo", target: "Admin", legacy: ` assignedTo: { type: 'string' },`, onDelete: "setNull" })}
1103
1181
  });
1104
1182
  `;
1105
1183
  await fs.outputFile(path.join(dest, "src", "model", `SupportTicket.${ext}`), content);
1106
1184
  }
1107
1185
  async function writeAuditLogModel(dest, opts) {
1108
1186
  const ext = opts.language === "typescript" ? "ts" : "js";
1109
- const content = `import { defineModel } from 'express-file-cluster';
1187
+ const content = `${modelApi(opts).import}
1110
1188
 
1111
- export const AuditLog = defineModel('AuditLog', {
1112
- adminId: { type: 'string', required: true },
1189
+ export const AuditLog = ${modelApi(opts).name}('AuditLog', {
1190
+ ${refField(opts, { field: "admin", target: "Admin", legacy: ` adminId: { type: 'string', required: true },`, onDelete: "setNull" })}
1113
1191
  action: { type: 'string', required: true },
1114
1192
  entity: { type: 'string', required: true },
1115
1193
  entityId: { type: 'string', required: true },
@@ -1121,41 +1199,41 @@ export const AuditLog = defineModel('AuditLog', {
1121
1199
  }
1122
1200
  async function writeSubscriptionModel(dest, opts) {
1123
1201
  const ext = opts.language === "typescript" ? "ts" : "js";
1124
- const content = `import { defineModel } from 'express-file-cluster';
1202
+ const content = `${modelApi(opts).import}
1125
1203
 
1126
- export const Subscription = defineModel('Subscription', {
1127
- userId: { type: 'string', required: true },
1128
- planId: { type: 'string', required: true },
1204
+ export const Subscription = ${modelApi(opts).name}('Subscription', {
1205
+ ${refField(opts, { field: "user", target: "User", legacy: ` userId: { type: 'string', required: true },`, required: true, onDelete: "cascade" })}
1206
+ ${refField(opts, { field: "plan", target: "Plan", legacy: ` planId: { type: 'string', required: true },`, required: true, onDelete: "restrict" })}
1129
1207
  status: { type: 'string', required: true, default: 'active' },
1130
1208
  startDate: { type: 'date', required: true },
1131
1209
  endDate: { type: 'date', required: true },
1132
1210
  cancelledAt: { type: 'date' },
1133
- });
1211
+ }${relationsOption(opts, [{ name: "invoices", kind: "hasMany", target: "Invoice", foreignKey: "subscription" }])});
1134
1212
  `;
1135
1213
  await fs.outputFile(path.join(dest, "src", "model", `Subscription.${ext}`), content);
1136
1214
  }
1137
1215
  async function writePlanModel(dest, opts) {
1138
1216
  const ext = opts.language === "typescript" ? "ts" : "js";
1139
- const content = `import { defineModel } from 'express-file-cluster';
1217
+ const content = `${modelApi(opts).import}
1140
1218
 
1141
- export const Plan = defineModel('Plan', {
1219
+ export const Plan = ${modelApi(opts).name}('Plan', {
1142
1220
  name: { type: 'string', required: true },
1143
1221
  description: { type: 'string', required: true },
1144
1222
  price: { type: 'number', required: true },
1145
1223
  interval: { type: 'string', required: true, default: 'monthly' },
1146
1224
  features: { type: 'array', of: 'string', default: [] },
1147
1225
  isActive: { type: 'boolean', default: true },
1148
- });
1226
+ }${relationsOption(opts, [{ name: "subscriptions", kind: "hasMany", target: "Subscription", foreignKey: "plan" }])});
1149
1227
  `;
1150
1228
  await fs.outputFile(path.join(dest, "src", "model", `Plan.${ext}`), content);
1151
1229
  }
1152
1230
  async function writeInvoiceModel(dest, opts) {
1153
1231
  const ext = opts.language === "typescript" ? "ts" : "js";
1154
- const content = `import { defineModel } from 'express-file-cluster';
1232
+ const content = `${modelApi(opts).import}
1155
1233
 
1156
- export const Invoice = defineModel('Invoice', {
1157
- userId: { type: 'string', required: true },
1158
- subscriptionId: { type: 'string', required: true },
1234
+ export const Invoice = ${modelApi(opts).name}('Invoice', {
1235
+ ${refField(opts, { field: "user", target: "User", legacy: ` userId: { type: 'string', required: true },`, required: true, onDelete: "cascade" })}
1236
+ ${refField(opts, { field: "subscription", target: "Subscription", legacy: ` subscriptionId: { type: 'string', required: true },`, onDelete: "setNull" })}
1159
1237
  amount: { type: 'number', required: true },
1160
1238
  currency: { type: 'string', required: true, default: 'USD' },
1161
1239
  status: { type: 'string', required: true, default: 'pending' },
@@ -1166,10 +1244,10 @@ export const Invoice = defineModel('Invoice', {
1166
1244
  }
1167
1245
  async function writeApiKeyModel(dest, opts) {
1168
1246
  const ext = opts.language === "typescript" ? "ts" : "js";
1169
- const content = `import { defineModel } from 'express-file-cluster';
1247
+ const content = `${modelApi(opts).import}
1170
1248
 
1171
- export const ApiKey = defineModel('ApiKey', {
1172
- userId: { type: 'string', required: true },
1249
+ export const ApiKey = ${modelApi(opts).name}('ApiKey', {
1250
+ ${refField(opts, { field: "user", target: "User", legacy: ` userId: { type: 'string', required: true },`, required: true, onDelete: "cascade" })}
1173
1251
  name: { type: 'string', required: true },
1174
1252
  key: { type: 'string', required: true, unique: true },
1175
1253
  lastUsed: { type: 'date' },
@@ -1181,9 +1259,9 @@ export const ApiKey = defineModel('ApiKey', {
1181
1259
  }
1182
1260
  async function writeRoleModel(dest, opts) {
1183
1261
  const ext = opts.language === "typescript" ? "ts" : "js";
1184
- const content = `import { defineModel } from 'express-file-cluster';
1262
+ const content = `${modelApi(opts).import}
1185
1263
 
1186
- export const Role = defineModel('Role', {
1264
+ export const Role = ${modelApi(opts).name}('Role', {
1187
1265
  name: { type: 'string', required: true, unique: true },
1188
1266
  description: { type: 'string', required: true },
1189
1267
  permissions: { type: 'array', of: 'string', default: [] },
@@ -1193,9 +1271,9 @@ export const Role = defineModel('Role', {
1193
1271
  }
1194
1272
  async function writeFAQModel(dest, opts) {
1195
1273
  const ext = opts.language === "typescript" ? "ts" : "js";
1196
- const content = `import { defineModel } from 'express-file-cluster';
1274
+ const content = `${modelApi(opts).import}
1197
1275
 
1198
- export const FAQ = defineModel('FAQ', {
1276
+ export const FAQ = ${modelApi(opts).name}('FAQ', {
1199
1277
  question: { type: 'string', required: true },
1200
1278
  answer: { type: 'string', required: true },
1201
1279
  category: { type: 'string', required: true, default: 'general' },
@@ -1207,13 +1285,13 @@ export const FAQ = defineModel('FAQ', {
1207
1285
  }
1208
1286
  async function writeBlogModel(dest, opts) {
1209
1287
  const ext = opts.language === "typescript" ? "ts" : "js";
1210
- const content = `import { defineModel } from 'express-file-cluster';
1288
+ const content = `${modelApi(opts).import}
1211
1289
 
1212
- export const Blog = defineModel('Blog', {
1290
+ export const Blog = ${modelApi(opts).name}('Blog', {
1213
1291
  title: { type: 'string', required: true },
1214
1292
  slug: { type: 'string', required: true, unique: true },
1215
1293
  content: { type: 'string', required: true },
1216
- authorId: { type: 'string', required: true },
1294
+ ${refField(opts, { field: "author", target: "Admin", legacy: ` authorId: { type: 'string', required: true },`, onDelete: "setNull" })}
1217
1295
  category: { type: 'string', required: true, default: 'general' },
1218
1296
  tags: { type: 'array', of: 'string', default: [] },
1219
1297
  status: { type: 'string', required: true, default: 'draft' },
@@ -1224,22 +1302,22 @@ export const Blog = defineModel('Blog', {
1224
1302
  }
1225
1303
  async function writeCategoryModel(dest, opts) {
1226
1304
  const ext = opts.language === "typescript" ? "ts" : "js";
1227
- const content = `import { defineModel } from 'express-file-cluster';
1305
+ const content = `${modelApi(opts).import}
1228
1306
 
1229
- export const Category = defineModel('Category', {
1307
+ export const Category = ${modelApi(opts).name}('Category', {
1230
1308
  name: { type: 'string', required: true },
1231
1309
  slug: { type: 'string', required: true, unique: true },
1232
1310
  description: { type: 'string' },
1233
- parentId: { type: 'string' },
1234
- });
1311
+ ${refField(opts, { field: "parent", target: "Category", legacy: ` parentId: { type: 'string' },`, onDelete: "setNull" })}
1312
+ }${relationsOption(opts, [{ name: "children", kind: "hasMany", target: "Category", foreignKey: "parent" }])});
1235
1313
  `;
1236
1314
  await fs.outputFile(path.join(dest, "src", "model", `Category.${ext}`), content);
1237
1315
  }
1238
1316
  async function writeCouponModel(dest, opts) {
1239
1317
  const ext = opts.language === "typescript" ? "ts" : "js";
1240
- const content = `import { defineModel } from 'express-file-cluster';
1318
+ const content = `${modelApi(opts).import}
1241
1319
 
1242
- export const Coupon = defineModel('Coupon', {
1320
+ export const Coupon = ${modelApi(opts).name}('Coupon', {
1243
1321
  code: { type: 'string', required: true, unique: true },
1244
1322
  type: { type: 'string', required: true, default: 'percent' },
1245
1323
  value: { type: 'number', required: true },
@@ -1252,6 +1330,7 @@ export const Coupon = defineModel('Coupon', {
1252
1330
  await fs.outputFile(path.join(dest, "src", "model", `Coupon.${ext}`), content);
1253
1331
  }
1254
1332
  async function writeAuthExtendedRoutes(dest, opts) {
1333
+ const db = orm(opts);
1255
1334
  const ext = opts.language === "typescript" ? "ts" : "js";
1256
1335
  const ts = opts.language === "typescript";
1257
1336
  const mwUser2 = opts.rbac ? `export const middlewares = [requireAuth('user', 'admin')];
@@ -1290,12 +1369,12 @@ async function writeAuthExtendedRoutes(dest, opts) {
1290
1369
  const refreshModelImports = `import { User } from '../../model/User.js';
1291
1370
  ${hasAdmin ? `import { Admin } from '../../model/Admin.js';
1292
1371
  ` : ""}`;
1293
- const refreshLookup = hasAdmin ? ` const user = await User.findOne({ refreshToken: token });
1294
- const admin = user ? null : await Admin.findOne({ refreshToken: token });
1295
- const account = user || admin;` : ` const user = await User.findOne({ refreshToken: token });
1372
+ const refreshLookup = hasAdmin ? ` const user = await ${db.findOne("User", `{ refreshToken: token }`)};
1373
+ const admin = user ? null : await ${db.findOne("Admin", `{ refreshToken: token }`)};
1374
+ const account = user || admin;` : ` const user = await ${db.findOne("User", `{ refreshToken: token }`)};
1296
1375
  const account = user;`;
1297
- const refreshPersist = hasAdmin ? ` if (user) await User.update(user.id, { refreshToken: newRefreshToken, refreshTokenExpiry });
1298
- else if (admin) await Admin.update(admin.id, { refreshToken: newRefreshToken, refreshTokenExpiry });` : ` await User.update(user.id, { refreshToken: newRefreshToken, refreshTokenExpiry });`;
1376
+ const refreshPersist = hasAdmin ? ` if (user) await ${db.update("User", `user.id`, `{ refreshToken: newRefreshToken, refreshTokenExpiry }`)};
1377
+ else if (admin) await ${db.update("Admin", `admin.id`, `{ refreshToken: newRefreshToken, refreshTokenExpiry }`)};` : ` await ${db.update("User", `user.id`, `{ refreshToken: newRefreshToken, refreshTokenExpiry }`)};`;
1299
1378
  const refreshContent = ts ? `import { issueToken } from 'express-file-cluster/auth';
1300
1379
  import type { Request, Response } from 'express';
1301
1380
  import crypto from 'node:crypto';
@@ -1376,10 +1455,10 @@ ${veMeta}export const GET = async (req: Request, res: Response) => {
1376
1455
  const { token } = req.query;
1377
1456
  if (!token || typeof token !== 'string') return res.status(400).json({ error: 'token is required' });
1378
1457
 
1379
- const user = await User.findOne({ verifyToken: token });
1458
+ const user = await ${db.findOne("User", `{ verifyToken: token }`)};
1380
1459
  if (!user) return res.status(400).json({ error: 'Invalid or expired verification token' });
1381
1460
 
1382
- await User.update(user.id, { isVerified: true, verifyToken: '' });
1461
+ await ${db.update("User", `user.id`, `{ isVerified: true, verifyToken: '' }`)};
1383
1462
  res.json({ message: 'Email verified' });
1384
1463
  };
1385
1464
 
@@ -1387,10 +1466,10 @@ export const POST = async (req: Request, res: Response) => {
1387
1466
  const { email } = req.body;
1388
1467
  if (!email) return res.status(400).json({ error: 'email is required' });
1389
1468
 
1390
- const user = await User.findOne({ email });
1469
+ const user = await ${db.findOne("User", `{ email }`)};
1391
1470
  if (user && !user.isVerified) {
1392
1471
  const verifyToken = crypto.randomBytes(32).toString('hex');
1393
- await User.update(user.id, { verifyToken });
1472
+ await ${db.update("User", `user.id`, `{ verifyToken }`)};
1394
1473
  ${sendVerifyEmailResend} }
1395
1474
 
1396
1475
  res.json({ message: 'Verification email sent' });
@@ -1401,10 +1480,10 @@ ${veMeta}export const GET = async (req, res) => {
1401
1480
  const { token } = req.query;
1402
1481
  if (!token || typeof token !== 'string') return res.status(400).json({ error: 'token is required' });
1403
1482
 
1404
- const user = await User.findOne({ verifyToken: token });
1483
+ const user = await ${db.findOne("User", `{ verifyToken: token }`)};
1405
1484
  if (!user) return res.status(400).json({ error: 'Invalid or expired verification token' });
1406
1485
 
1407
- await User.update(user.id, { isVerified: true, verifyToken: '' });
1486
+ await ${db.update("User", `user.id`, `{ isVerified: true, verifyToken: '' }`)};
1408
1487
  res.json({ message: 'Email verified' });
1409
1488
  };
1410
1489
 
@@ -1412,10 +1491,10 @@ export const POST = async (req, res) => {
1412
1491
  const { email } = req.body;
1413
1492
  if (!email) return res.status(400).json({ error: 'email is required' });
1414
1493
 
1415
- const user = await User.findOne({ email });
1494
+ const user = await ${db.findOne("User", `{ email }`)};
1416
1495
  if (user && !user.isVerified) {
1417
1496
  const verifyToken = crypto.randomBytes(32).toString('hex');
1418
- await User.update(user.id, { verifyToken });
1497
+ await ${db.update("User", `user.id`, `{ verifyToken }`)};
1419
1498
  ${sendVerifyEmailResend} }
1420
1499
 
1421
1500
  res.json({ message: 'Verification email sent' });
@@ -1434,11 +1513,11 @@ ${sendVerifyEmailResend} }
1434
1513
  const fpModelImports = `import { User } from '../../model/User.js';
1435
1514
  ${hasAdmin ? `import { Admin } from '../../model/Admin.js';
1436
1515
  ` : ""}`;
1437
- const fpLookup = hasAdmin ? ` const user = await User.findOne({ email });
1438
- const admin = user ? null : await Admin.findOne({ email });` : ` const user = await User.findOne({ email });`;
1516
+ const fpLookup = hasAdmin ? ` const user = await ${db.findOne("User", `{ email }`)};
1517
+ const admin = user ? null : await ${db.findOne("Admin", `{ email }`)};` : ` const user = await ${db.findOne("User", `{ email }`)};`;
1439
1518
  const fpGuard = hasAdmin ? "user || admin" : "user";
1440
- const fpPersist = hasAdmin ? ` if (user) await User.update(user.id, { resetToken, resetTokenExpiry });
1441
- else if (admin) await Admin.update(admin.id, { resetToken, resetTokenExpiry });` : ` await User.update(user.id, { resetToken, resetTokenExpiry });`;
1519
+ const fpPersist = hasAdmin ? ` if (user) await ${db.update("User", `user.id`, `{ resetToken, resetTokenExpiry }`)};
1520
+ else if (admin) await ${db.update("Admin", `admin.id`, `{ resetToken, resetTokenExpiry }`)};` : ` await ${db.update("User", `user.id`, `{ resetToken, resetTokenExpiry }`)};`;
1442
1521
  const fpContent = ts ? `import type { Request, Response } from 'express';
1443
1522
  import crypto from 'node:crypto';
1444
1523
  ${mailerTaskImport}${fpModelImports}${fpMeta}const RESET_TOKEN_TTL_MS = 1000 * 60 * 60; // 1 hour
@@ -1490,12 +1569,12 @@ ${sendResetEmail} }
1490
1569
  const rpModelImports = `import { User } from '../../model/User.js';
1491
1570
  ${hasAdmin ? `import { Admin } from '../../model/Admin.js';
1492
1571
  ` : ""}`;
1493
- const rpLookup = hasAdmin ? ` const user = await User.findOne({ resetToken: token });
1494
- const admin = user ? null : await Admin.findOne({ resetToken: token });
1495
- const account = user || admin;` : ` const user = await User.findOne({ resetToken: token });
1572
+ const rpLookup = hasAdmin ? ` const user = await ${db.findOne("User", `{ resetToken: token }`)};
1573
+ const admin = user ? null : await ${db.findOne("Admin", `{ resetToken: token }`)};
1574
+ const account = user || admin;` : ` const user = await ${db.findOne("User", `{ resetToken: token }`)};
1496
1575
  const account = user;`;
1497
- const rpPersist = hasAdmin ? ` if (user) await User.update(user.id, { password: hashed, resetToken: '' });
1498
- else if (admin) await Admin.update(admin.id, { password: hashed, resetToken: '' });` : ` await User.update(user.id, { password: hashed, resetToken: '' });`;
1576
+ const rpPersist = hasAdmin ? ` if (user) await ${db.update("User", `user.id`, `{ password: hashed, resetToken: '' }`)};
1577
+ else if (admin) await ${db.update("Admin", `admin.id`, `{ password: hashed, resetToken: '' }`)};` : ` await ${db.update("User", `user.id`, `{ password: hashed, resetToken: '' }`)};`;
1499
1578
  const rpContent = ts ? `import type { Request, Response } from 'express';
1500
1579
  import bcrypt from 'bcrypt';
1501
1580
  import { revokeAllSessions } from 'express-file-cluster/auth';
@@ -2204,7 +2283,10 @@ export const GET = async (req, res) => {
2204
2283
  }
2205
2284
  if (uf.apiAndBilling) {
2206
2285
  const akListMeta = mkMeta(opts, {
2207
- GET: { description: "List API keys for the authenticated user.", response: `{ apiKeys: [] }` },
2286
+ GET: {
2287
+ description: "List API keys for the authenticated user.",
2288
+ response: `{ apiKeys: [] }`
2289
+ },
2208
2290
  POST: {
2209
2291
  description: "Generate a new API key for the authenticated user.",
2210
2292
  request: `{ name: 'CI key' }`,
@@ -3447,12 +3529,53 @@ ${roleGuard} const { status, page = 1, limit = 20 } = req.query;
3447
3529
  );
3448
3530
  }
3449
3531
  }
3532
+ async function writeModelIndex(dest, opts) {
3533
+ const ts = opts.language === "typescript";
3534
+ const ext = ts ? "ts" : "js";
3535
+ const names = [...generatedModels(opts)].sort();
3536
+ if (names.length === 0) return;
3537
+ const exports = names.map((n) => `export { ${n} } from './${n}.js';`).join("\n");
3538
+ const tables = ts ? `
3539
+ ${names.map((n) => `import type { ${n} } from './${n}.js';`).join("\n")}
3540
+
3541
+ // Types \`include\` results: relations name models by string, this maps the names to models.
3542
+ declare module 'express-file-cluster' {
3543
+ interface Tables {
3544
+ ${names.map((n) => ` ${n}: typeof ${n};`).join("\n")}
3545
+ }
3546
+ }
3547
+ ` : "";
3548
+ const content = `// Every model, in one import: \`import { User } from '../model/index.js'\` also registers the
3549
+ // models its relations reach, which \`include\` needs.
3550
+ ${exports}
3551
+ ${tables}`;
3552
+ await fs.outputFile(path.join(dest, "src", "model", `index.${ext}`), content);
3553
+ }
3554
+ var MODEL_ORDER = [
3555
+ "User",
3556
+ "Admin",
3557
+ "Plan",
3558
+ "Category",
3559
+ "Role",
3560
+ "FAQ",
3561
+ "Coupon",
3562
+ "Session",
3563
+ "Notification",
3564
+ "File",
3565
+ "SupportTicket",
3566
+ "AuditLog",
3567
+ "Subscription",
3568
+ "Invoice",
3569
+ "ApiKey",
3570
+ "Blog"
3571
+ ];
3450
3572
  async function writeInitMigration(dest, opts) {
3451
3573
  const modelDir = path.join(dest, "src", "model");
3452
3574
  if (!await fs.pathExists(modelDir)) return;
3453
3575
  const ts = opts.language === "typescript";
3454
3576
  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();
3577
+ const rank = (n) => MODEL_ORDER.includes(n) ? MODEL_ORDER.indexOf(n) : MODEL_ORDER.length;
3578
+ const names = (await fs.readdir(modelDir)).filter((f) => f.endsWith(`.${ext}`) && f !== `index.${ext}`).map((f) => f.slice(0, -(ext.length + 1))).sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
3456
3579
  if (names.length === 0) return;
3457
3580
  const imports = names.map((n) => `import { ${n} } from '../model/${n}.js';`).join("\n");
3458
3581
  const typeImport = ts ? "import type { MigrationContext } from 'express-file-cluster';\n" : "";
@@ -3503,6 +3626,22 @@ async function main() {
3503
3626
  ]
3504
3627
  });
3505
3628
  if (p.isCancel(database)) cancel2();
3629
+ let modelApi2;
3630
+ if (database === "postgresql") {
3631
+ const api = await p.select({
3632
+ message: "Models:",
3633
+ options: [
3634
+ {
3635
+ value: "defineTable",
3636
+ label: "defineTable",
3637
+ hint: "PostgreSQL-native \u2014 foreign keys, relations, include, Prisma-style queries"
3638
+ },
3639
+ { value: "defineModel", label: "defineModel", hint: "portable \u2014 the same API on MongoDB" }
3640
+ ]
3641
+ });
3642
+ if (p.isCancel(api)) cancel2();
3643
+ modelApi2 = api;
3644
+ }
3506
3645
  const authStrategy = await p.select({
3507
3646
  message: "Authentication strategy:",
3508
3647
  options: [
@@ -3532,17 +3671,55 @@ async function main() {
3532
3671
  const chosen = await p.multiselect({
3533
3672
  message: "User portal features: (space to toggle, enter to confirm)",
3534
3673
  options: [
3535
- { value: "profileViewing", label: "Profile viewing & editing", hint: "GET/PUT /user/profile" },
3536
- { value: "forgotPassword", label: "Forgot / reset password", hint: "forgot-password, reset-password" },
3537
- { value: "accountSecurity", label: "Change password, 2FA & sessions", hint: "change-password, 2fa/*, sessions/*, token refresh" },
3538
- { value: "emailVerification", label: "Email verification", hint: "verify-email route + registration flow" },
3539
- { value: "accountSettings", label: "Account settings", hint: "avatar, settings, account, dashboard, activity" },
3674
+ {
3675
+ value: "profileViewing",
3676
+ label: "Profile viewing & editing",
3677
+ hint: "GET/PUT /user/profile"
3678
+ },
3679
+ {
3680
+ value: "forgotPassword",
3681
+ label: "Forgot / reset password",
3682
+ hint: "forgot-password, reset-password"
3683
+ },
3684
+ {
3685
+ value: "accountSecurity",
3686
+ label: "Change password, 2FA & sessions",
3687
+ hint: "change-password, 2fa/*, sessions/*, token refresh"
3688
+ },
3689
+ {
3690
+ value: "emailVerification",
3691
+ label: "Email verification",
3692
+ hint: "verify-email route + registration flow"
3693
+ },
3694
+ {
3695
+ value: "accountSettings",
3696
+ label: "Account settings",
3697
+ hint: "avatar, settings, account, dashboard, activity"
3698
+ },
3540
3699
  { value: "notifications", label: "Notifications", hint: "user/notifications" },
3541
- { value: "filesAndMedia", label: "Files, favorites & bookmarks", hint: "files, favorites, bookmarks, search" },
3542
- { value: "apiAndBilling", label: "API keys & billing", hint: "api-keys, plans, subscription, invoices" },
3700
+ {
3701
+ value: "filesAndMedia",
3702
+ label: "Files, favorites & bookmarks",
3703
+ hint: "files, favorites, bookmarks, search"
3704
+ },
3705
+ {
3706
+ value: "apiAndBilling",
3707
+ label: "API keys & billing",
3708
+ hint: "api-keys, plans, subscription, invoices"
3709
+ },
3543
3710
  { value: "support", label: "Support tickets", hint: "create & view own tickets" }
3544
3711
  ],
3545
- initialValues: ["profileViewing", "forgotPassword", "accountSecurity", "emailVerification", "accountSettings", "notifications", "filesAndMedia", "apiAndBilling", "support"],
3712
+ initialValues: [
3713
+ "profileViewing",
3714
+ "forgotPassword",
3715
+ "accountSecurity",
3716
+ "emailVerification",
3717
+ "accountSettings",
3718
+ "notifications",
3719
+ "filesAndMedia",
3720
+ "apiAndBilling",
3721
+ "support"
3722
+ ],
3546
3723
  required: false
3547
3724
  });
3548
3725
  if (p.isCancel(chosen)) cancel2();
@@ -3564,16 +3741,49 @@ async function main() {
3564
3741
  const chosen = await p.multiselect({
3565
3742
  message: "Admin panel features: (space to toggle, enter to confirm)",
3566
3743
  options: [
3567
- { value: "userManagement", label: "User management", hint: "list/create/suspend/verify/export users + dashboard stats" },
3568
- { value: "adminManagement", label: "Admin & role management", hint: "manage other admins" + (selected.has("rbac") ? " + roles" : "") },
3744
+ {
3745
+ value: "userManagement",
3746
+ label: "User management",
3747
+ hint: "list/create/suspend/verify/export users + dashboard stats"
3748
+ },
3749
+ {
3750
+ value: "adminManagement",
3751
+ label: "Admin & role management",
3752
+ hint: "manage other admins" + (selected.has("rbac") ? " + roles" : "")
3753
+ },
3569
3754
  { value: "analytics", label: "Analytics", hint: "users, revenue, traffic dashboards" },
3570
- { value: "contentManagement", label: "Content management", hint: "FAQs, blog posts, categories" },
3571
- { value: "billingManagement", label: "Billing management", hint: "plans, coupons, subscriptions" },
3755
+ {
3756
+ value: "contentManagement",
3757
+ label: "Content management",
3758
+ hint: "FAQs, blog posts, categories"
3759
+ },
3760
+ {
3761
+ value: "billingManagement",
3762
+ label: "Billing management",
3763
+ hint: "plans, coupons, subscriptions"
3764
+ },
3572
3765
  { value: "supportManagement", label: "Support tickets", hint: "view & respond to tickets" },
3573
- { value: "notificationsAndLogs", label: "Notifications & audit logs", hint: "broadcast + audit/activity/security logs" },
3574
- { value: "systemSettings", label: "System settings & health", hint: "app settings, health check, cache" }
3766
+ {
3767
+ value: "notificationsAndLogs",
3768
+ label: "Notifications & audit logs",
3769
+ hint: "broadcast + audit/activity/security logs"
3770
+ },
3771
+ {
3772
+ value: "systemSettings",
3773
+ label: "System settings & health",
3774
+ hint: "app settings, health check, cache"
3775
+ }
3776
+ ],
3777
+ initialValues: [
3778
+ "userManagement",
3779
+ "adminManagement",
3780
+ "analytics",
3781
+ "contentManagement",
3782
+ "billingManagement",
3783
+ "supportManagement",
3784
+ "notificationsAndLogs",
3785
+ "systemSettings"
3575
3786
  ],
3576
- initialValues: ["userManagement", "adminManagement", "analytics", "contentManagement", "billingManagement", "supportManagement", "notificationsAndLogs", "systemSettings"],
3577
3787
  required: false
3578
3788
  });
3579
3789
  if (p.isCancel(chosen)) cancel2();
@@ -3676,6 +3886,7 @@ async function main() {
3676
3886
  adminFeatures,
3677
3887
  rbac: selected.has("rbac"),
3678
3888
  mailer: selected.has("mailer"),
3889
+ ...modelApi2 !== void 0 && { modelApi: modelApi2 },
3679
3890
  ...taskBackend !== void 0 && { taskBackend },
3680
3891
  ...smtpProvider !== void 0 && { smtpProvider },
3681
3892
  ...smtpHost !== void 0 && { smtpHost },
@@ -3727,7 +3938,10 @@ function npmInstall(projectDir) {
3727
3938
  function npmInstallGlobal() {
3728
3939
  return new Promise((resolve, reject) => {
3729
3940
  const child = spawn("npm", ["install", "-g", "express-file-cluster"], { stdio: "ignore" });
3730
- child.on("exit", (code) => code === 0 ? resolve() : reject(new Error("global install failed")));
3941
+ child.on(
3942
+ "exit",
3943
+ (code) => code === 0 ? resolve() : reject(new Error("global install failed"))
3944
+ );
3731
3945
  });
3732
3946
  }
3733
3947
  main().catch((err) => {