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