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