forgestack-os-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/create.d.ts +1 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +78 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/generators/api.d.ts +3 -0
- package/dist/generators/api.d.ts.map +1 -0
- package/dist/generators/api.js +346 -0
- package/dist/generators/api.js.map +1 -0
- package/dist/generators/auth.d.ts +2 -0
- package/dist/generators/auth.d.ts.map +1 -0
- package/dist/generators/auth.js +371 -0
- package/dist/generators/auth.js.map +1 -0
- package/dist/generators/backend.d.ts +2 -0
- package/dist/generators/backend.d.ts.map +1 -0
- package/dist/generators/backend.js +875 -0
- package/dist/generators/backend.js.map +1 -0
- package/dist/generators/common.d.ts +2 -0
- package/dist/generators/common.d.ts.map +1 -0
- package/dist/generators/common.js +354 -0
- package/dist/generators/common.js.map +1 -0
- package/dist/generators/database.d.ts +2 -0
- package/dist/generators/database.d.ts.map +1 -0
- package/dist/generators/database.js +157 -0
- package/dist/generators/database.js.map +1 -0
- package/dist/generators/docker.d.ts +2 -0
- package/dist/generators/docker.d.ts.map +1 -0
- package/dist/generators/docker.js +181 -0
- package/dist/generators/docker.js.map +1 -0
- package/dist/generators/frontend-helpers.d.ts +3 -0
- package/dist/generators/frontend-helpers.d.ts.map +1 -0
- package/dist/generators/frontend-helpers.js +23 -0
- package/dist/generators/frontend-helpers.js.map +1 -0
- package/dist/generators/frontend.d.ts +2 -0
- package/dist/generators/frontend.d.ts.map +1 -0
- package/dist/generators/frontend.js +735 -0
- package/dist/generators/frontend.js.map +1 -0
- package/dist/generators/index.d.ts +2 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +59 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/nextjs-helpers.d.ts +6 -0
- package/dist/generators/nextjs-helpers.d.ts.map +1 -0
- package/dist/generators/nextjs-helpers.js +216 -0
- package/dist/generators/nextjs-helpers.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +32 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/prompts.d.ts +2 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +107 -0
- package/dist/utils/prompts.js.map +1 -0
- package/dist/utils/validators.d.ts +2 -0
- package/dist/utils/validators.d.ts.map +1 -0
- package/dist/utils/validators.js +48 -0
- package/dist/utils/validators.js.map +1 -0
- package/package.json +49 -0
- package/src/commands/create.ts +82 -0
- package/src/generators/api.ts +353 -0
- package/src/generators/auth.ts +406 -0
- package/src/generators/backend.ts +927 -0
- package/src/generators/common.ts +377 -0
- package/src/generators/database.ts +165 -0
- package/src/generators/docker.ts +185 -0
- package/src/generators/frontend.ts +783 -0
- package/src/generators/index.ts +64 -0
- package/src/index.ts +27 -0
- package/src/types.ts +16 -0
- package/src/utils/logger.ts +31 -0
- package/src/utils/prompts.ts +105 -0
- package/src/utils/validators.ts +50 -0
- package/tests/validators.test.ts +69 -0
- package/tsc_output.txt +0 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,875 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateBackend = generateBackend;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
async function generateBackend(config, backendDir) {
|
|
10
|
+
switch (config.backend) {
|
|
11
|
+
case 'express':
|
|
12
|
+
await generateExpress(config, backendDir);
|
|
13
|
+
break;
|
|
14
|
+
case 'fastify':
|
|
15
|
+
await generateFastify(config, backendDir);
|
|
16
|
+
break;
|
|
17
|
+
case 'nestjs':
|
|
18
|
+
await generateNestJS(config, backendDir);
|
|
19
|
+
break;
|
|
20
|
+
case 'bun-elysia':
|
|
21
|
+
await generateBunElysia(config, backendDir);
|
|
22
|
+
break;
|
|
23
|
+
case 'go-fiber':
|
|
24
|
+
await generateGoFiber(config, backendDir);
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
throw new Error(`Unsupported backend: ${config.backend}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function generateExpress(config, backendDir) {
|
|
31
|
+
// Package.json
|
|
32
|
+
const packageJson = {
|
|
33
|
+
name: `${config.projectName}-backend`,
|
|
34
|
+
version: '0.1.0',
|
|
35
|
+
type: 'module',
|
|
36
|
+
scripts: {
|
|
37
|
+
dev: 'tsx watch src/index.ts',
|
|
38
|
+
build: 'tsc',
|
|
39
|
+
start: 'node dist/index.js',
|
|
40
|
+
'db:migrate': config.database.includes('prisma') ? 'prisma migrate dev' : undefined,
|
|
41
|
+
'db:generate': config.database.includes('prisma') ? 'prisma generate' : undefined,
|
|
42
|
+
},
|
|
43
|
+
dependencies: {
|
|
44
|
+
express: '^4.18.2',
|
|
45
|
+
cors: '^2.8.5',
|
|
46
|
+
dotenv: '^16.4.1',
|
|
47
|
+
helmet: '^7.1.0',
|
|
48
|
+
'express-rate-limit': '^7.1.5',
|
|
49
|
+
zod: '^3.22.4',
|
|
50
|
+
...(config.auth === 'jwt' && {
|
|
51
|
+
jsonwebtoken: '^9.0.2',
|
|
52
|
+
bcrypt: '^5.1.1',
|
|
53
|
+
}),
|
|
54
|
+
...(config.database === 'postgresql' && { '@prisma/client': '^5.8.1' }),
|
|
55
|
+
...(config.database === 'mongodb' && { mongoose: '^8.1.0' }),
|
|
56
|
+
...(config.database === 'mysql' && { '@prisma/client': '^5.8.1' }),
|
|
57
|
+
...(config.database === 'sqlite' && { '@prisma/client': '^5.8.1' }),
|
|
58
|
+
},
|
|
59
|
+
devDependencies: {
|
|
60
|
+
'@types/express': '^4.17.21',
|
|
61
|
+
'@types/cors': '^2.8.17',
|
|
62
|
+
'@types/node': '^20.11.5',
|
|
63
|
+
...(config.auth === 'jwt' && {
|
|
64
|
+
'@types/jsonwebtoken': '^9.0.5',
|
|
65
|
+
'@types/bcrypt': '^5.0.2',
|
|
66
|
+
}),
|
|
67
|
+
typescript: '^5.3.3',
|
|
68
|
+
tsx: '^4.7.0',
|
|
69
|
+
...(config.database.includes('prisma') && { prisma: '^5.8.1' }),
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(backendDir, 'package.json'), packageJson, { spaces: 2 });
|
|
73
|
+
// TypeScript config
|
|
74
|
+
const tsConfig = {
|
|
75
|
+
compilerOptions: {
|
|
76
|
+
target: 'ES2020',
|
|
77
|
+
module: 'ESNext',
|
|
78
|
+
lib: ['ES2020'],
|
|
79
|
+
outDir: './dist',
|
|
80
|
+
rootDir: './src',
|
|
81
|
+
strict: true,
|
|
82
|
+
esModuleInterop: true,
|
|
83
|
+
skipLibCheck: true,
|
|
84
|
+
forceConsistentCasingInFileNames: true,
|
|
85
|
+
resolveJsonModule: true,
|
|
86
|
+
moduleResolution: 'bundler',
|
|
87
|
+
allowImportingTsExtensions: true,
|
|
88
|
+
},
|
|
89
|
+
include: ['src/**/*'],
|
|
90
|
+
exclude: ['node_modules', 'dist'],
|
|
91
|
+
};
|
|
92
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(backendDir, 'tsconfig.json'), tsConfig, { spaces: 2 });
|
|
93
|
+
// Create src directory structure
|
|
94
|
+
const srcDir = path_1.default.join(backendDir, 'src');
|
|
95
|
+
await fs_extra_1.default.ensureDir(srcDir);
|
|
96
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'routes'));
|
|
97
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'middleware'));
|
|
98
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'controllers'));
|
|
99
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'lib'));
|
|
100
|
+
// Main server file
|
|
101
|
+
const indexTs = getExpressIndex(config);
|
|
102
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'index.ts'), indexTs);
|
|
103
|
+
// Routes
|
|
104
|
+
const healthRoute = `import { Router } from 'express';
|
|
105
|
+
|
|
106
|
+
const router = Router();
|
|
107
|
+
|
|
108
|
+
router.get('/health', (req, res) => {
|
|
109
|
+
res.json({
|
|
110
|
+
status: 'ok',
|
|
111
|
+
timestamp: new Date().toISOString(),
|
|
112
|
+
service: '${config.projectName}-backend'
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
export default router;
|
|
117
|
+
`;
|
|
118
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'routes', 'health.ts'), healthRoute);
|
|
119
|
+
// Error handler middleware
|
|
120
|
+
const errorHandler = `import { Request, Response, NextFunction } from 'express';
|
|
121
|
+
|
|
122
|
+
export interface AppError extends Error {
|
|
123
|
+
statusCode?: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function errorHandler(
|
|
127
|
+
err: AppError,
|
|
128
|
+
req: Request,
|
|
129
|
+
res: Response,
|
|
130
|
+
next: NextFunction
|
|
131
|
+
) {
|
|
132
|
+
const statusCode = err.statusCode || 500;
|
|
133
|
+
const message = err.message || 'Internal Server Error';
|
|
134
|
+
|
|
135
|
+
console.error('Error:', err);
|
|
136
|
+
|
|
137
|
+
res.status(statusCode).json({
|
|
138
|
+
error: {
|
|
139
|
+
message,
|
|
140
|
+
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
`;
|
|
145
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'middleware', 'errorHandler.ts'), errorHandler);
|
|
146
|
+
// Multi-tenant middleware if enabled
|
|
147
|
+
if (config.multiTenant) {
|
|
148
|
+
const tenantMiddleware = getTenantMiddleware(config);
|
|
149
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'middleware', 'tenant.ts'), tenantMiddleware);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function getExpressIndex(config) {
|
|
153
|
+
return `import express from 'express';
|
|
154
|
+
import cors from 'cors';
|
|
155
|
+
import helmet from 'helmet';
|
|
156
|
+
import rateLimit from 'express-rate-limit';
|
|
157
|
+
import dotenv from 'dotenv';
|
|
158
|
+
import healthRouter from './routes/health';
|
|
159
|
+
import authRouter from './routes/auth';
|
|
160
|
+
import { errorHandler } from './middleware/errorHandler';
|
|
161
|
+
${config.multiTenant ? "import { tenantMiddleware } from './middleware/tenant';" : ''}
|
|
162
|
+
|
|
163
|
+
dotenv.config();
|
|
164
|
+
|
|
165
|
+
const app = express();
|
|
166
|
+
const PORT = process.env.PORT || 3000;
|
|
167
|
+
|
|
168
|
+
// Security middleware
|
|
169
|
+
app.use(helmet());
|
|
170
|
+
app.use(cors({
|
|
171
|
+
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
|
|
172
|
+
credentials: true,
|
|
173
|
+
}));
|
|
174
|
+
|
|
175
|
+
// Rate limiting
|
|
176
|
+
const limiter = rateLimit({
|
|
177
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
178
|
+
max: 100, // limit each IP to 100 requests per windowMs
|
|
179
|
+
});
|
|
180
|
+
app.use('/api', limiter);
|
|
181
|
+
|
|
182
|
+
// Body parsing
|
|
183
|
+
app.use(express.json());
|
|
184
|
+
app.use(express.urlencoded({ extended: true }));
|
|
185
|
+
|
|
186
|
+
${config.multiTenant ? '// Multi-tenant middleware\napp.use(tenantMiddleware);' : ''}
|
|
187
|
+
|
|
188
|
+
// Routes
|
|
189
|
+
app.use('/api', healthRouter);
|
|
190
|
+
app.use('/api/auth', authRouter);
|
|
191
|
+
|
|
192
|
+
// Error handling
|
|
193
|
+
app.use(errorHandler);
|
|
194
|
+
|
|
195
|
+
app.listen(PORT, () => {
|
|
196
|
+
console.log(\`🚀 Server running on http://localhost:\${PORT}\`);
|
|
197
|
+
console.log(\`📊 Health check: http://localhost:\${PORT}/api/health\`);
|
|
198
|
+
});
|
|
199
|
+
`;
|
|
200
|
+
}
|
|
201
|
+
function getTenantMiddleware(config) {
|
|
202
|
+
if (config.auth === 'jwt') {
|
|
203
|
+
return `import { Request, Response, NextFunction } from 'express';
|
|
204
|
+
import jwt from 'jsonwebtoken';
|
|
205
|
+
|
|
206
|
+
export interface TenantRequest extends Request {
|
|
207
|
+
tenantId?: string;
|
|
208
|
+
userId?: string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function tenantMiddleware(
|
|
212
|
+
req: TenantRequest,
|
|
213
|
+
res: Response,
|
|
214
|
+
next: NextFunction
|
|
215
|
+
) {
|
|
216
|
+
try {
|
|
217
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
218
|
+
|
|
219
|
+
if (!token) {
|
|
220
|
+
return next();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any;
|
|
224
|
+
req.tenantId = decoded.tenantId;
|
|
225
|
+
req.userId = decoded.userId;
|
|
226
|
+
|
|
227
|
+
next();
|
|
228
|
+
} catch (error) {
|
|
229
|
+
next();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
`;
|
|
233
|
+
}
|
|
234
|
+
else if (config.auth === 'clerk') {
|
|
235
|
+
return `import { Request, Response, NextFunction } from 'express';
|
|
236
|
+
|
|
237
|
+
export interface TenantRequest extends Request {
|
|
238
|
+
tenantId?: string;
|
|
239
|
+
userId?: string;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function tenantMiddleware(
|
|
243
|
+
req: TenantRequest,
|
|
244
|
+
res: Response,
|
|
245
|
+
next: NextFunction
|
|
246
|
+
) {
|
|
247
|
+
// Clerk organization ID is used as tenant ID
|
|
248
|
+
// Extract from Clerk session token
|
|
249
|
+
const orgId = req.headers['x-clerk-org-id'] as string;
|
|
250
|
+
|
|
251
|
+
if (orgId) {
|
|
252
|
+
req.tenantId = orgId;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
next();
|
|
256
|
+
}
|
|
257
|
+
`;
|
|
258
|
+
}
|
|
259
|
+
return `import { Request, Response, NextFunction } from 'express';
|
|
260
|
+
|
|
261
|
+
export interface TenantRequest extends Request {
|
|
262
|
+
tenantId?: string;
|
|
263
|
+
userId?: string;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function tenantMiddleware(
|
|
267
|
+
req: TenantRequest,
|
|
268
|
+
res: Response,
|
|
269
|
+
next: NextFunction
|
|
270
|
+
) {
|
|
271
|
+
// Extract tenant ID from request
|
|
272
|
+
// Implementation depends on your auth provider
|
|
273
|
+
const tenantId = req.headers['x-tenant-id'] as string;
|
|
274
|
+
|
|
275
|
+
if (tenantId) {
|
|
276
|
+
req.tenantId = tenantId;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
next();
|
|
280
|
+
}
|
|
281
|
+
`;
|
|
282
|
+
}
|
|
283
|
+
async function generateFastify(_config, _backendDir) {
|
|
284
|
+
throw new Error('Fastify support coming in Phase 2');
|
|
285
|
+
}
|
|
286
|
+
async function generateNestJS(config, backendDir) {
|
|
287
|
+
// Package.json
|
|
288
|
+
const packageJson = {
|
|
289
|
+
name: `${config.projectName}-backend`,
|
|
290
|
+
version: '0.1.0',
|
|
291
|
+
scripts: {
|
|
292
|
+
build: 'nest build',
|
|
293
|
+
format: 'prettier --write "src/**/*.ts"',
|
|
294
|
+
start: 'nest start',
|
|
295
|
+
'start:dev': 'nest start --watch',
|
|
296
|
+
'start:debug': 'nest start --debug --watch',
|
|
297
|
+
'start:prod': 'node dist/main',
|
|
298
|
+
lint: 'eslint "{src,apps,libs,test}/**/*.ts" --fix',
|
|
299
|
+
},
|
|
300
|
+
dependencies: {
|
|
301
|
+
'@nestjs/common': '^10.3.0',
|
|
302
|
+
'@nestjs/core': '^10.3.0',
|
|
303
|
+
'@nestjs/platform-express': '^10.3.0',
|
|
304
|
+
'@nestjs/config': '^3.1.1',
|
|
305
|
+
'@nestjs/jwt': '^10.2.0',
|
|
306
|
+
'@nestjs/passport': '^10.0.3',
|
|
307
|
+
'@nestjs/swagger': '^7.1.17',
|
|
308
|
+
'passport': '^0.7.0',
|
|
309
|
+
'passport-jwt': '^4.0.1',
|
|
310
|
+
'bcrypt': '^5.1.1',
|
|
311
|
+
'class-validator': '^0.14.1',
|
|
312
|
+
'class-transformer': '^0.5.1',
|
|
313
|
+
'reflect-metadata': '^0.2.1',
|
|
314
|
+
'rxjs': '^7.8.1',
|
|
315
|
+
...(config.database === 'mongodb' && { '@nestjs/mongoose': '^10.0.2', 'mongoose': '^8.0.4' }),
|
|
316
|
+
...(config.database !== 'mongodb' && { '@prisma/client': '^5.8.1' }),
|
|
317
|
+
},
|
|
318
|
+
devDependencies: {
|
|
319
|
+
'@nestjs/cli': '^10.2.1',
|
|
320
|
+
'@nestjs/schematics': '^10.0.3',
|
|
321
|
+
'@nestjs/testing': '^10.3.0',
|
|
322
|
+
'@types/express': '^4.17.21',
|
|
323
|
+
'@types/node': '^20.11.5',
|
|
324
|
+
'@types/passport-jwt': '^4.0.0',
|
|
325
|
+
'@types/bcrypt': '^5.0.2',
|
|
326
|
+
'@typescript-eslint/eslint-plugin': '^6.19.0',
|
|
327
|
+
'@typescript-eslint/parser': '^6.19.0',
|
|
328
|
+
'eslint': '^8.56.0',
|
|
329
|
+
'prettier': '^3.2.4',
|
|
330
|
+
'typescript': '^5.3.3',
|
|
331
|
+
...(config.database !== 'mongodb' && { 'prisma': '^5.8.1' }),
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(backendDir, 'package.json'), packageJson, { spaces: 2 });
|
|
335
|
+
// NestJS CLI config
|
|
336
|
+
const nestCliJson = {
|
|
337
|
+
'$schema': 'https://json.schemastore.org/nest-cli',
|
|
338
|
+
collection: '@nestjs/schematics',
|
|
339
|
+
sourceRoot: 'src',
|
|
340
|
+
compilerOptions: {
|
|
341
|
+
deleteOutDir: true,
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(backendDir, 'nest-cli.json'), nestCliJson, { spaces: 2 });
|
|
345
|
+
// TypeScript config
|
|
346
|
+
const tsConfig = {
|
|
347
|
+
compilerOptions: {
|
|
348
|
+
module: 'commonjs',
|
|
349
|
+
declaration: true,
|
|
350
|
+
removeComments: true,
|
|
351
|
+
emitDecoratorMetadata: true,
|
|
352
|
+
experimentalDecorators: true,
|
|
353
|
+
allowSyntheticDefaultImports: true,
|
|
354
|
+
target: 'ES2021',
|
|
355
|
+
sourceMap: true,
|
|
356
|
+
outDir: './dist',
|
|
357
|
+
baseUrl: './',
|
|
358
|
+
incremental: true,
|
|
359
|
+
skipLibCheck: true,
|
|
360
|
+
strictNullChecks: false,
|
|
361
|
+
noImplicitAny: false,
|
|
362
|
+
strictBindCallApply: false,
|
|
363
|
+
forceConsistentCasingInFileNames: false,
|
|
364
|
+
noFallthroughCasesInSwitch: false,
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(backendDir, 'tsconfig.json'), tsConfig, { spaces: 2 });
|
|
368
|
+
// Create directory structure
|
|
369
|
+
const srcDir = path_1.default.join(backendDir, 'src');
|
|
370
|
+
await fs_extra_1.default.ensureDir(srcDir);
|
|
371
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'auth'));
|
|
372
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'auth/guards'));
|
|
373
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'auth/strategies'));
|
|
374
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'auth/dto'));
|
|
375
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'users'));
|
|
376
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'users/dto'));
|
|
377
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'common/interceptors'));
|
|
378
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'common/filters'));
|
|
379
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'config'));
|
|
380
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(srcDir, 'database'));
|
|
381
|
+
// Main.ts
|
|
382
|
+
const mainTs = `import { NestFactory } from '@nestjs/core';
|
|
383
|
+
import { ValidationPipe } from '@nestjs/common';
|
|
384
|
+
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
|
385
|
+
import { AppModule } from './app.module';
|
|
386
|
+
|
|
387
|
+
async function bootstrap() {
|
|
388
|
+
const app = await NestFactory.create(AppModule);
|
|
389
|
+
|
|
390
|
+
app.enableCors();
|
|
391
|
+
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
|
|
392
|
+
|
|
393
|
+
const config = new DocumentBuilder()
|
|
394
|
+
.setTitle('${config.projectName} API')
|
|
395
|
+
.setDescription('Generated by ForgeStack OS')
|
|
396
|
+
.setVersion('1.0')
|
|
397
|
+
.addBearerAuth()
|
|
398
|
+
.build();
|
|
399
|
+
const document = SwaggerModule.createDocument(app, config);
|
|
400
|
+
SwaggerModule.setup('api/docs', app, document);
|
|
401
|
+
|
|
402
|
+
await app.listen(process.env.PORT || 3000);
|
|
403
|
+
console.log(\`Application is running on: \${await app.getUrl()}\`);
|
|
404
|
+
}
|
|
405
|
+
bootstrap();
|
|
406
|
+
`;
|
|
407
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'main.ts'), mainTs);
|
|
408
|
+
// App Module
|
|
409
|
+
const appModule = `import { Module } from '@nestjs/common';
|
|
410
|
+
import { ConfigModule } from '@nestjs/config';
|
|
411
|
+
import { AuthModule } from './auth/auth.module';
|
|
412
|
+
import { UsersModule } from './users/users.module';
|
|
413
|
+
import { DatabaseModule } from './database/database.module';
|
|
414
|
+
import configuration from './config/configuration';
|
|
415
|
+
|
|
416
|
+
@Module({
|
|
417
|
+
imports: [
|
|
418
|
+
ConfigModule.forRoot({
|
|
419
|
+
load: [configuration],
|
|
420
|
+
isGlobal: true,
|
|
421
|
+
}),
|
|
422
|
+
DatabaseModule,
|
|
423
|
+
AuthModule,
|
|
424
|
+
UsersModule,
|
|
425
|
+
],
|
|
426
|
+
})
|
|
427
|
+
export class AppModule {}
|
|
428
|
+
`;
|
|
429
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'app.module.ts'), appModule);
|
|
430
|
+
// Configuration
|
|
431
|
+
const configFile = `export default () => ({
|
|
432
|
+
port: parseInt(process.env.PORT, 10) || 3000,
|
|
433
|
+
database: {
|
|
434
|
+
url: process.env.DATABASE_URL,
|
|
435
|
+
},
|
|
436
|
+
jwt: {
|
|
437
|
+
secret: process.env.JWT_SECRET || 'your-secret-key',
|
|
438
|
+
expiresIn: '7d',
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
`;
|
|
442
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'config', 'configuration.ts'), configFile);
|
|
443
|
+
// Database Module
|
|
444
|
+
if (config.database === 'mongodb') {
|
|
445
|
+
const dbModule = `import { Module } from '@nestjs/common';
|
|
446
|
+
import { MongooseModule } from '@nestjs/mongoose';
|
|
447
|
+
import { ConfigService } from '@nestjs/config';
|
|
448
|
+
|
|
449
|
+
@Module({
|
|
450
|
+
imports: [
|
|
451
|
+
MongooseModule.forRootAsync({
|
|
452
|
+
inject: [ConfigService],
|
|
453
|
+
useFactory: (config: ConfigService) => ({
|
|
454
|
+
uri: config.get<string>('database.url'),
|
|
455
|
+
}),
|
|
456
|
+
}),
|
|
457
|
+
],
|
|
458
|
+
})
|
|
459
|
+
export class DatabaseModule {}
|
|
460
|
+
`;
|
|
461
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'database', 'database.module.ts'), dbModule);
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
const prismaService = `import { Injectable, OnModuleInit } from '@nestjs/common';
|
|
465
|
+
import { PrismaClient } from '@prisma/client';
|
|
466
|
+
|
|
467
|
+
@Injectable()
|
|
468
|
+
export class PrismaService extends PrismaClient implements OnModuleInit {
|
|
469
|
+
async onModuleInit() {
|
|
470
|
+
await this.$connect();
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
`;
|
|
474
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'database', 'prisma.service.ts'), prismaService);
|
|
475
|
+
const dbModule = `import { Module, Global } from '@nestjs/common';
|
|
476
|
+
import { PrismaService } from './prisma.service';
|
|
477
|
+
|
|
478
|
+
@Global()
|
|
479
|
+
@Module({
|
|
480
|
+
providers: [PrismaService],
|
|
481
|
+
exports: [PrismaService],
|
|
482
|
+
})
|
|
483
|
+
export class DatabaseModule {}
|
|
484
|
+
`;
|
|
485
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'database', 'database.module.ts'), dbModule);
|
|
486
|
+
}
|
|
487
|
+
// Auth DTOs
|
|
488
|
+
const loginDto = `import { ApiProperty } from '@nestjs/swagger';
|
|
489
|
+
import { IsEmail, IsString, MinLength } from 'class-validator';
|
|
490
|
+
|
|
491
|
+
export class LoginDto {
|
|
492
|
+
@ApiProperty()
|
|
493
|
+
@IsEmail()
|
|
494
|
+
email: string;
|
|
495
|
+
|
|
496
|
+
@ApiProperty()
|
|
497
|
+
@IsString()
|
|
498
|
+
@MinLength(6)
|
|
499
|
+
password: string;
|
|
500
|
+
}
|
|
501
|
+
`;
|
|
502
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'auth/dto', 'login.dto.ts'), loginDto);
|
|
503
|
+
const registerDto = `import { ApiProperty } from '@nestjs/swagger';
|
|
504
|
+
import { IsEmail, IsString, MinLength } from 'class-validator';
|
|
505
|
+
|
|
506
|
+
export class RegisterDto {
|
|
507
|
+
@ApiProperty()
|
|
508
|
+
@IsEmail()
|
|
509
|
+
email: string;
|
|
510
|
+
|
|
511
|
+
@ApiProperty()
|
|
512
|
+
@IsString()
|
|
513
|
+
@MinLength(6)
|
|
514
|
+
password: string;
|
|
515
|
+
|
|
516
|
+
@ApiProperty({ required: false })
|
|
517
|
+
@IsString()
|
|
518
|
+
name?: string;
|
|
519
|
+
}
|
|
520
|
+
`;
|
|
521
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'auth/dto', 'register.dto.ts'), registerDto);
|
|
522
|
+
// JWT Strategy
|
|
523
|
+
const jwtStrategy = `import { Injectable } from '@nestjs/common';
|
|
524
|
+
import { PassportStrategy } from '@nestjs/passport';
|
|
525
|
+
import { ExtractJwt, Strategy } from 'passport-jwt';
|
|
526
|
+
import { ConfigService } from '@nestjs/config';
|
|
527
|
+
|
|
528
|
+
@Injectable()
|
|
529
|
+
export class JwtStrategy extends PassportStrategy(Strategy) {
|
|
530
|
+
constructor(private config: ConfigService) {
|
|
531
|
+
super({
|
|
532
|
+
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
533
|
+
ignoreExpiration: false,
|
|
534
|
+
secretOrKey: config.get<string>('jwt.secret'),
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async validate(payload: any) {
|
|
539
|
+
return { userId: payload.sub, email: payload.email${config.multiTenant ? ', tenantId: payload.tenantId' : ''} };
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
`;
|
|
543
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'auth/strategies', 'jwt.strategy.ts'), jwtStrategy);
|
|
544
|
+
// JWT Auth Guard
|
|
545
|
+
const jwtAuthGuard = `import { Injectable } from '@nestjs/common';
|
|
546
|
+
import { AuthGuard } from '@nestjs/passport';
|
|
547
|
+
|
|
548
|
+
@Injectable()
|
|
549
|
+
export class JwtAuthGuard extends AuthGuard('jwt') {}
|
|
550
|
+
`;
|
|
551
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'auth/guards', 'jwt-auth.guard.ts'), jwtAuthGuard);
|
|
552
|
+
// Auth Service
|
|
553
|
+
const authService = getNestJSAuthService(config);
|
|
554
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'auth', 'auth.service.ts'), authService);
|
|
555
|
+
// Auth Controller
|
|
556
|
+
const authController = `import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
|
|
557
|
+
import { ApiTags, ApiOperation } from '@nestjs/swagger';
|
|
558
|
+
import { AuthService } from './auth.service';
|
|
559
|
+
import { LoginDto } from './dto/login.dto';
|
|
560
|
+
import { RegisterDto } from './dto/register.dto';
|
|
561
|
+
|
|
562
|
+
@ApiTags('auth')
|
|
563
|
+
@Controller('auth')
|
|
564
|
+
export class AuthController {
|
|
565
|
+
constructor(private authService: AuthService) {}
|
|
566
|
+
|
|
567
|
+
@Post('register')
|
|
568
|
+
@ApiOperation({ summary: 'Register a new user' })
|
|
569
|
+
async register(@Body() dto: RegisterDto) {
|
|
570
|
+
return this.authService.register(dto);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
@Post('login')
|
|
574
|
+
@HttpCode(HttpStatus.OK)
|
|
575
|
+
@ApiOperation({ summary: 'Login user' })
|
|
576
|
+
async login(@Body() dto: LoginDto) {
|
|
577
|
+
return this.authService.login(dto);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
`;
|
|
581
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'auth', 'auth.controller.ts'), authController);
|
|
582
|
+
// Auth Module
|
|
583
|
+
const authModule = `import { Module } from '@nestjs/common';
|
|
584
|
+
import { JwtModule } from '@nestjs/jwt';
|
|
585
|
+
import { PassportModule } from '@nestjs/passport';
|
|
586
|
+
import { ConfigService } from '@nestjs/config';
|
|
587
|
+
import { AuthService } from './auth.service';
|
|
588
|
+
import { AuthController } from './auth.controller';
|
|
589
|
+
import { JwtStrategy } from './strategies/jwt.strategy';
|
|
590
|
+
import { UsersModule } from '../users/users.module';
|
|
591
|
+
|
|
592
|
+
@Module({
|
|
593
|
+
imports: [
|
|
594
|
+
UsersModule,
|
|
595
|
+
PassportModule,
|
|
596
|
+
JwtModule.registerAsync({
|
|
597
|
+
inject: [ConfigService],
|
|
598
|
+
useFactory: (config: ConfigService) => ({
|
|
599
|
+
secret: config.get<string>('jwt.secret'),
|
|
600
|
+
signOptions: { expiresIn: config.get<string>('jwt.expiresIn') },
|
|
601
|
+
}),
|
|
602
|
+
}),
|
|
603
|
+
],
|
|
604
|
+
providers: [AuthService, JwtStrategy],
|
|
605
|
+
controllers: [AuthController],
|
|
606
|
+
})
|
|
607
|
+
export class AuthModule {}
|
|
608
|
+
`;
|
|
609
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'auth', 'auth.module.ts'), authModule);
|
|
610
|
+
// Users Module
|
|
611
|
+
const usersService = getNestJSUsersService(config);
|
|
612
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'users', 'users.service.ts'), usersService);
|
|
613
|
+
const usersController = `import { Controller, Get, UseGuards, Request } from '@nestjs/common';
|
|
614
|
+
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
|
615
|
+
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
|
616
|
+
import { UsersService } from './users.service';
|
|
617
|
+
|
|
618
|
+
@ApiTags('users')
|
|
619
|
+
@Controller('users')
|
|
620
|
+
export class UsersController {
|
|
621
|
+
constructor(private usersService: UsersService) {}
|
|
622
|
+
|
|
623
|
+
@Get('me')
|
|
624
|
+
@UseGuards(JwtAuthGuard)
|
|
625
|
+
@ApiBearerAuth()
|
|
626
|
+
async getProfile(@Request() req) {
|
|
627
|
+
return this.usersService.findById(req.user.userId);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
`;
|
|
631
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'users', 'users.controller.ts'), usersController);
|
|
632
|
+
const usersModule = `import { Module } from '@nestjs/common';
|
|
633
|
+
import { UsersService } from './users.service';
|
|
634
|
+
import { UsersController } from './users.controller';
|
|
635
|
+
|
|
636
|
+
@Module({
|
|
637
|
+
providers: [UsersService],
|
|
638
|
+
controllers: [UsersController],
|
|
639
|
+
exports: [UsersService],
|
|
640
|
+
})
|
|
641
|
+
export class UsersModule {}
|
|
642
|
+
`;
|
|
643
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'users', 'users.module.ts'), usersModule);
|
|
644
|
+
// Tenant Interceptor (if multi-tenant)
|
|
645
|
+
if (config.multiTenant) {
|
|
646
|
+
const tenantInterceptor = `import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
647
|
+
import { Observable } from 'rxjs';
|
|
648
|
+
|
|
649
|
+
@Injectable()
|
|
650
|
+
export class TenantInterceptor implements NestInterceptor {
|
|
651
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
|
652
|
+
const request = context.switchToHttp().getRequest();
|
|
653
|
+
const user = request.user;
|
|
654
|
+
|
|
655
|
+
if (user && user.tenantId) {
|
|
656
|
+
request.tenantId = user.tenantId;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
return next.handle();
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
`;
|
|
663
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcDir, 'common/interceptors', 'tenant.interceptor.ts'), tenantInterceptor);
|
|
664
|
+
}
|
|
665
|
+
// .env.example
|
|
666
|
+
const envExample = `PORT=3000
|
|
667
|
+
DATABASE_URL=${config.database === 'mongodb' ? 'mongodb://localhost:27017/myapp' : 'postgresql://user:password@localhost:5432/myapp'}
|
|
668
|
+
JWT_SECRET=your-secret-key-change-in-production
|
|
669
|
+
`;
|
|
670
|
+
await fs_extra_1.default.writeFile(path_1.default.join(backendDir, '.env.example'), envExample);
|
|
671
|
+
// .gitignore
|
|
672
|
+
const gitignore = `# compiled output
|
|
673
|
+
/dist
|
|
674
|
+
/node_modules
|
|
675
|
+
|
|
676
|
+
# Logs
|
|
677
|
+
logs
|
|
678
|
+
*.log
|
|
679
|
+
npm-debug.log*
|
|
680
|
+
pnpm-debug.log*
|
|
681
|
+
yarn-debug.log*
|
|
682
|
+
yarn-error.log*
|
|
683
|
+
lerna-debug.log*
|
|
684
|
+
|
|
685
|
+
# OS
|
|
686
|
+
.DS_Store
|
|
687
|
+
|
|
688
|
+
# Tests
|
|
689
|
+
/coverage
|
|
690
|
+
/.nyc_output
|
|
691
|
+
|
|
692
|
+
# IDEs and editors
|
|
693
|
+
/.idea
|
|
694
|
+
.project
|
|
695
|
+
.classpath
|
|
696
|
+
.c9/
|
|
697
|
+
*.launch
|
|
698
|
+
.settings/
|
|
699
|
+
*.sublime-workspace
|
|
700
|
+
|
|
701
|
+
# IDE - VSCode
|
|
702
|
+
.vscode/*
|
|
703
|
+
!.vscode/settings.json
|
|
704
|
+
!.vscode/tasks.json
|
|
705
|
+
!.vscode/launch.json
|
|
706
|
+
!.vscode/extensions.json
|
|
707
|
+
|
|
708
|
+
# Environment
|
|
709
|
+
.env
|
|
710
|
+
.env.local
|
|
711
|
+
`;
|
|
712
|
+
await fs_extra_1.default.writeFile(path_1.default.join(backendDir, '.gitignore'), gitignore);
|
|
713
|
+
}
|
|
714
|
+
async function generateBunElysia(_config, _backendDir) {
|
|
715
|
+
throw new Error('Bun + Elysia support coming in Phase 2');
|
|
716
|
+
}
|
|
717
|
+
async function generateGoFiber(_config, _backendDir) {
|
|
718
|
+
throw new Error('Go + Fiber support coming in Phase 2');
|
|
719
|
+
}
|
|
720
|
+
// NestJS helper functions
|
|
721
|
+
function getNestJSAuthService(config) {
|
|
722
|
+
if (config.database === 'mongodb') {
|
|
723
|
+
return `import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
724
|
+
import { JwtService } from '@nestjs/jwt';
|
|
725
|
+
import { InjectModel } from '@nestjs/mongoose';
|
|
726
|
+
import { Model } from 'mongoose';
|
|
727
|
+
import * as bcrypt from 'bcrypt';
|
|
728
|
+
import { LoginDto } from './dto/login.dto';
|
|
729
|
+
import { RegisterDto } from './dto/register.dto';
|
|
730
|
+
|
|
731
|
+
@Injectable()
|
|
732
|
+
export class AuthService {
|
|
733
|
+
constructor(
|
|
734
|
+
@InjectModel('User') private userModel: Model<any>,
|
|
735
|
+
private jwtService: JwtService,
|
|
736
|
+
) {}
|
|
737
|
+
|
|
738
|
+
async register(dto: RegisterDto) {
|
|
739
|
+
const hashedPassword = await bcrypt.hash(dto.password, 10);
|
|
740
|
+
const user = new this.userModel({
|
|
741
|
+
email: dto.email,
|
|
742
|
+
password: hashedPassword,
|
|
743
|
+
name: dto.name,
|
|
744
|
+
});
|
|
745
|
+
await user.save();
|
|
746
|
+
|
|
747
|
+
const token = this.generateToken(user);
|
|
748
|
+
return { token, user: { id: user._id, email: user.email, name: user.name } };
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
async login(dto: LoginDto) {
|
|
752
|
+
const user = await this.userModel.findOne({ email: dto.email });
|
|
753
|
+
if (!user) {
|
|
754
|
+
throw new UnauthorizedException('Invalid credentials');
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const isPasswordValid = await bcrypt.compare(dto.password, user.password);
|
|
758
|
+
if (!isPasswordValid) {
|
|
759
|
+
throw new UnauthorizedException('Invalid credentials');
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const token = this.generateToken(user);
|
|
763
|
+
return { token, user: { id: user._id, email: user.email, name: user.name } };
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
private generateToken(user: any) {
|
|
767
|
+
const payload = {
|
|
768
|
+
sub: user._id,
|
|
769
|
+
email: user.email${config.multiTenant ? ',\n tenantId: user.tenantId' : ''}
|
|
770
|
+
};
|
|
771
|
+
return this.jwtService.sign(payload);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
`;
|
|
775
|
+
}
|
|
776
|
+
return `import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
777
|
+
import { JwtService } from '@nestjs/jwt';
|
|
778
|
+
import * as bcrypt from 'bcrypt';
|
|
779
|
+
import { LoginDto } from './dto/login.dto';
|
|
780
|
+
import { RegisterDto } from './dto/register.dto';
|
|
781
|
+
import { PrismaService } from '../database/prisma.service';
|
|
782
|
+
|
|
783
|
+
@Injectable()
|
|
784
|
+
export class AuthService {
|
|
785
|
+
constructor(
|
|
786
|
+
private prisma: PrismaService,
|
|
787
|
+
private jwtService: JwtService,
|
|
788
|
+
) {}
|
|
789
|
+
|
|
790
|
+
async register(dto: RegisterDto) {
|
|
791
|
+
const hashedPassword = await bcrypt.hash(dto.password, 10);
|
|
792
|
+
const user = await this.prisma.user.create({
|
|
793
|
+
data: {
|
|
794
|
+
email: dto.email,
|
|
795
|
+
password: hashedPassword,
|
|
796
|
+
name: dto.name,
|
|
797
|
+
},
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
const token = this.generateToken(user);
|
|
801
|
+
return { token, user: { id: user.id, email: user.email, name: user.name } };
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
async login(dto: LoginDto) {
|
|
805
|
+
const user = await this.prisma.user.findUnique({
|
|
806
|
+
where: { email: dto.email },
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
if (!user) {
|
|
810
|
+
throw new UnauthorizedException('Invalid credentials');
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const isPasswordValid = await bcrypt.compare(dto.password, user.password);
|
|
814
|
+
if (!isPasswordValid) {
|
|
815
|
+
throw new UnauthorizedException('Invalid credentials');
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const token = this.generateToken(user);
|
|
819
|
+
return { token, user: { id: user.id, email: user.email, name: user.name } };
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
private generateToken(user: any) {
|
|
823
|
+
const payload = {
|
|
824
|
+
sub: user.id,
|
|
825
|
+
email: user.email${config.multiTenant ? ',\n tenantId: user.tenantId' : ''}
|
|
826
|
+
};
|
|
827
|
+
return this.jwtService.sign(payload);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
`;
|
|
831
|
+
}
|
|
832
|
+
function getNestJSUsersService(config) {
|
|
833
|
+
if (config.database === 'mongodb') {
|
|
834
|
+
return `import { Injectable } from '@nestjs/common';
|
|
835
|
+
import { InjectModel } from '@nestjs/mongoose';
|
|
836
|
+
import { Model } from 'mongoose';
|
|
837
|
+
|
|
838
|
+
@Injectable()
|
|
839
|
+
export class UsersService {
|
|
840
|
+
constructor(@InjectModel('User') private userModel: Model<any>) {}
|
|
841
|
+
|
|
842
|
+
async findById(id: string) {
|
|
843
|
+
const user = await this.userModel.findById(id).select('-password');
|
|
844
|
+
return user;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
async findByEmail(email: string) {
|
|
848
|
+
return this.userModel.findOne({ email });
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
`;
|
|
852
|
+
}
|
|
853
|
+
return `import { Injectable } from '@nestjs/common';
|
|
854
|
+
import { PrismaService } from '../database/prisma.service';
|
|
855
|
+
|
|
856
|
+
@Injectable()
|
|
857
|
+
export class UsersService {
|
|
858
|
+
constructor(private prisma: PrismaService) {}
|
|
859
|
+
|
|
860
|
+
async findById(id: string) {
|
|
861
|
+
return this.prisma.user.findUnique({
|
|
862
|
+
where: { id },
|
|
863
|
+
select: { id: true, email: true, name: true, createdAt: true },
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
async findByEmail(email: string) {
|
|
868
|
+
return this.prisma.user.findUnique({
|
|
869
|
+
where: { email },
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
`;
|
|
874
|
+
}
|
|
875
|
+
//# sourceMappingURL=backend.js.map
|