create-backlist 7.0.1 → 7.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +1 -10
  2. package/bin/index.js +242 -275
  3. package/package.json +3 -2
  4. package/src/ai-agent.js +171 -171
  5. package/src/analyzer.js +750 -495
  6. package/src/env-resolver.js +70 -0
  7. package/src/generators/dotnet.js +134 -133
  8. package/src/generators/java.js +248 -233
  9. package/src/generators/js.js +346 -0
  10. package/src/generators/nestjs.js +278 -0
  11. package/src/generators/node.js +404 -404
  12. package/src/generators/python.js +86 -104
  13. package/src/generators/template.js +22 -22
  14. package/src/project-detector.js +131 -0
  15. package/src/templates/dotnet/partials/Dockerfile.ejs +27 -0
  16. package/src/templates/dotnet/partials/docker-compose.yml.ejs +33 -0
  17. package/src/templates/java-spring/partials/Controller.java.ejs +3 -3
  18. package/src/templates/js-express/base/server.js +59 -0
  19. package/src/templates/js-express/partials/Dockerfile.ejs +12 -0
  20. package/src/templates/js-express/partials/auth.controller.js.ejs +66 -0
  21. package/src/templates/js-express/partials/auth.middleware.js.ejs +19 -0
  22. package/src/templates/js-express/partials/auth.routes.js.ejs +9 -0
  23. package/src/templates/js-express/partials/controller.js.ejs +53 -0
  24. package/src/templates/js-express/partials/db.js.ejs +19 -0
  25. package/src/templates/js-express/partials/docker-compose.yml.ejs +46 -0
  26. package/src/templates/js-express/partials/model.js.ejs +18 -0
  27. package/src/templates/js-express/partials/package.json.ejs +17 -0
  28. package/src/templates/js-express/partials/prisma.schema.ejs +21 -0
  29. package/src/templates/js-express/partials/routes.js.ejs +19 -0
  30. package/src/templates/js-express/partials/seeder.js.ejs +103 -0
  31. package/src/templates/js-express/partials/service.js.ejs +51 -0
  32. package/src/templates/js-express/partials/swagger.js.ejs +30 -0
  33. package/src/templates/js-express/partials/test.js.ejs +46 -0
  34. package/src/templates/nestjs/base/app.module.ts +9 -0
  35. package/src/templates/nestjs/base/main.ts +23 -0
  36. package/src/templates/nestjs/base/tsconfig.json +21 -0
  37. package/src/templates/nestjs/partials/auth.controller.ts.ejs +17 -0
  38. package/src/templates/nestjs/partials/auth.module.ts.ejs +17 -0
  39. package/src/templates/nestjs/partials/auth.service.ts.ejs +70 -0
  40. package/src/templates/nestjs/partials/controller.ts.ejs +34 -0
  41. package/src/templates/nestjs/partials/create-dto.ts.ejs +22 -0
  42. package/src/templates/nestjs/partials/jwt-guard.ts.ejs +24 -0
  43. package/src/templates/nestjs/partials/module.ts.ejs +10 -0
  44. package/src/templates/nestjs/partials/package.json.ejs +27 -0
  45. package/src/templates/nestjs/partials/prisma.service.ts.ejs +13 -0
  46. package/src/templates/nestjs/partials/schema.ts.ejs +19 -0
  47. package/src/templates/nestjs/partials/service.ts.ejs +67 -0
  48. package/src/templates/nestjs/partials/update-dto.ts.ejs +4 -0
  49. package/src/templates/node-ts-express/partials/HexController.ts.ejs +56 -56
  50. package/src/templates/node-ts-express/partials/HexRepository.ts.ejs +26 -26
  51. package/src/templates/node-ts-express/partials/HexService.ts.ejs +27 -27
  52. package/src/utils.js +11 -11
  53. /package/src/templates/{node-ts-express → dotnet}/partials/DbContext.cs.ejs +0 -0
  54. /package/src/templates/{node-ts-express → dotnet}/partials/Model.cs.ejs +0 -0
@@ -0,0 +1,53 @@
1
+ import { <%= modelName %>Service } from '../services/<%= modelName %>.service.js';
2
+
3
+ export class <%= modelName %>Controller {
4
+
5
+ static async getAll(req, res) {
6
+ try {
7
+ const data = await <%= modelName %>Service.getAll();
8
+ res.status(200).json(data);
9
+ } catch (error) {
10
+ res.status(500).json({ message: error.message });
11
+ }
12
+ }
13
+
14
+ static async getById(req, res) {
15
+ try {
16
+ const { id } = req.params;
17
+ const data = await <%= modelName %>Service.getById(id);
18
+ if (!data) return res.status(404).json({ message: 'Not found' });
19
+ res.status(200).json(data);
20
+ } catch (error) {
21
+ res.status(500).json({ message: error.message });
22
+ }
23
+ }
24
+
25
+ static async create(req, res) {
26
+ try {
27
+ const data = await <%= modelName %>Service.create(req.body);
28
+ res.status(201).json(data);
29
+ } catch (error) {
30
+ res.status(400).json({ message: error.message });
31
+ }
32
+ }
33
+
34
+ static async update(req, res) {
35
+ try {
36
+ const { id } = req.params;
37
+ const data = await <%= modelName %>Service.update(id, req.body);
38
+ res.status(200).json(data);
39
+ } catch (error) {
40
+ res.status(400).json({ message: error.message });
41
+ }
42
+ }
43
+
44
+ static async delete(req, res) {
45
+ try {
46
+ const { id } = req.params;
47
+ await <%= modelName %>Service.delete(id);
48
+ res.status(204).send();
49
+ } catch (error) {
50
+ res.status(500).json({ message: error.message });
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,19 @@
1
+ <% if (dbType === 'prisma') { %>
2
+ import { PrismaClient } from '@prisma/client';
3
+
4
+ export const prisma = new PrismaClient();
5
+ <% } else { %>
6
+ import mongoose from 'mongoose';
7
+
8
+ const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/<%= projectName %>';
9
+
10
+ export async function connectDB() {
11
+ try {
12
+ await mongoose.connect(MONGO_URI);
13
+ console.log('MongoDB Connected...');
14
+ } catch (err) {
15
+ console.error('MongoDB connection error:', err.message);
16
+ process.exit(1);
17
+ }
18
+ }
19
+ <% } %>
@@ -0,0 +1,46 @@
1
+ version: '3.8'
2
+
3
+ services:
4
+ app:
5
+ build: .
6
+ container_name: <%= projectName %>-app
7
+ ports:
8
+ - '<%= port %>:<%= port %>'
9
+ env_file:
10
+ - .env
11
+ depends_on:
12
+ <% if (dbType === 'mongoose') { %>
13
+ - mongo
14
+ <% } else { %>
15
+ - postgres
16
+ <% } %>
17
+ restart: unless-stopped
18
+
19
+ <% if (dbType === 'mongoose') { %>
20
+ mongo:
21
+ image: mongo:7
22
+ container_name: <%= projectName %>-mongo
23
+ ports:
24
+ - '27017:27017'
25
+ volumes:
26
+ - mongo_data:/data/db
27
+ <% } else { %>
28
+ postgres:
29
+ image: postgres:16-alpine
30
+ container_name: <%= projectName %>-postgres
31
+ ports:
32
+ - '5432:5432'
33
+ environment:
34
+ POSTGRES_USER: user
35
+ POSTGRES_PASSWORD: password
36
+ POSTGRES_DB: <%= projectName %>
37
+ volumes:
38
+ - pg_data:/var/lib/postgresql/data
39
+ <% } %>
40
+
41
+ volumes:
42
+ <% if (dbType === 'mongoose') { %>
43
+ mongo_data:
44
+ <% } else { %>
45
+ pg_data:
46
+ <% } %>
@@ -0,0 +1,18 @@
1
+ import mongoose from 'mongoose';
2
+
3
+ const <%= modelName %>Schema = new mongoose.Schema({
4
+ <% fields.forEach((field, i) => { -%>
5
+ <%= field.name %>: {
6
+ type: <%= field.type %>,
7
+ <% if (field.isUnique) { -%>
8
+ unique: true,
9
+ <% } -%>
10
+ <% if (field.name === 'email') { -%>
11
+ lowercase: true,
12
+ trim: true,
13
+ <% } -%>
14
+ },
15
+ <% }); -%>
16
+ }, { timestamps: true });
17
+
18
+ export default mongoose.model('<%= modelName %>', <%= modelName %>Schema);
@@ -0,0 +1,17 @@
1
+ <%= JSON.stringify({
2
+ name: projectName,
3
+ version: "1.0.0",
4
+ type: "module",
5
+ scripts: {
6
+ start: "node src/server.js",
7
+ dev: "node --watch src/server.js"
8
+ },
9
+ dependencies: {
10
+ express: "^4.18.2",
11
+ cors: "^2.8.5",
12
+ dotenv: "^16.3.1",
13
+ helmet: "^7.1.0",
14
+ morgan: "^1.10.0"
15
+ },
16
+ devDependencies: {}
17
+ }, null, 2) %>
@@ -0,0 +1,21 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "postgresql"
7
+ url = env("DATABASE_URL")
8
+ }
9
+
10
+ <% models.forEach(model => { %>
11
+ model <%= model.name %> {
12
+ id String @id @default(cuid())
13
+ <% model.fields.forEach(field => { %>
14
+ <% const prismaType = field.type === 'Number' ? 'Int' : field.type === 'Boolean' ? 'Boolean' : 'String'; %>
15
+ <%= field.name.padEnd(10) %> <%= prismaType %><%= field.isUnique ? ' @unique' : '' %>
16
+ <% }); %>
17
+ createdAt DateTime @default(now())
18
+ updatedAt DateTime @updatedAt
19
+ }
20
+
21
+ <% }); %>
@@ -0,0 +1,19 @@
1
+ import { Router } from 'express';
2
+ <% endpoints.forEach(ep => { %>
3
+ import { <%= ep.controllerName %>Controller } from '../controllers/<%= ep.controllerName %>.controller.js';
4
+ <% }); %>
5
+
6
+ const router = Router();
7
+
8
+ <% const grouped = {}; endpoints.forEach(ep => { if (!grouped[ep.controllerName]) grouped[ep.controllerName] = []; grouped[ep.controllerName].push(ep); }); %>
9
+ <% Object.entries(grouped).forEach(([ctrl, eps]) => { %>
10
+ // --- <%= ctrl %> routes ---
11
+ <% eps.forEach(ep => {
12
+ const method = ep.method.toLowerCase();
13
+ const routePath = ep.route.replace(/^\/api/, '');
14
+ %>
15
+ router.<%= method %>('<%= routePath %>', <%= ctrl %>Controller.<%= ep.functionName %>);
16
+ <% }); %>
17
+ <% }); %>
18
+
19
+ export default router;
@@ -0,0 +1,103 @@
1
+ import { faker } from '@faker-js/faker';
2
+ <% if (dbType === 'mongoose') { %>
3
+ import mongoose from 'mongoose';
4
+ <% models.forEach(m => { %>
5
+ import <%= m.name %> from '../src/models/<%= m.name %>.model.js';
6
+ <% }); %>
7
+
8
+ const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/<%= projectName %>';
9
+
10
+ async function seed() {
11
+ await mongoose.connect(MONGO_URI);
12
+ console.log('Connected to MongoDB for seeding...');
13
+
14
+ <% models.forEach(m => { %>
15
+ await <%= m.name %>.deleteMany({});
16
+ const <%= m.name.toLowerCase() %>Data = Array.from({ length: 10 }, () => ({
17
+ <% m.fields.forEach(f => { %>
18
+ <% if (f.name === 'email') { %>
19
+ email: faker.internet.email(),
20
+ <% } else if (f.name === 'name') { %>
21
+ name: faker.person.fullName(),
22
+ <% } else if (f.name === 'password') { %>
23
+ password: faker.internet.password(),
24
+ <% } else if (f.type === 'Number') { %>
25
+ <%= f.name %>: faker.number.int({ min: 1, max: 1000 }),
26
+ <% } else if (f.type === 'Boolean') { %>
27
+ <%= f.name %>: faker.datatype.boolean(),
28
+ <% } else { %>
29
+ <%= f.name %>: faker.lorem.word(),
30
+ <% } %>
31
+ <% }); %>
32
+ }));
33
+ await <%= m.name %>.insertMany(<%= m.name.toLowerCase() %>Data);
34
+ console.log('<%= m.name %>: seeded 10 records');
35
+ <% }); %>
36
+
37
+ await mongoose.disconnect();
38
+ console.log('Seeding complete.');
39
+ }
40
+ <% } else { %>
41
+ import { prisma } from '../src/db.js';
42
+
43
+ async function seed() {
44
+ console.log('Seeding database...');
45
+
46
+ <% models.forEach(m => { %>
47
+ await prisma.<%= m.name.charAt(0).toLowerCase() + m.name.slice(1) %>.deleteMany({});
48
+ for (let i = 0; i < 10; i++) {
49
+ await prisma.<%= m.name.charAt(0).toLowerCase() + m.name.slice(1) %>.create({
50
+ data: {
51
+ <% m.fields.forEach(f => { %>
52
+ <% if (f.name === 'email') { %>
53
+ email: faker.internet.email(),
54
+ <% } else if (f.name === 'name') { %>
55
+ name: faker.person.fullName(),
56
+ <% } else if (f.name === 'password') { %>
57
+ password: faker.internet.password(),
58
+ <% } else if (f.type === 'Number' || f.type === 'Int') { %>
59
+ <%= f.name %>: faker.number.int({ min: 1, max: 1000 }),
60
+ <% } else if (f.type === 'Boolean') { %>
61
+ <%= f.name %>: faker.datatype.boolean(),
62
+ <% } else { %>
63
+ <%= f.name %>: faker.lorem.word(),
64
+ <% } %>
65
+ <% }); %>
66
+ },
67
+ });
68
+ }
69
+ console.log('<%= m.name %>: seeded 10 records');
70
+ <% }); %>
71
+
72
+ await prisma.$disconnect();
73
+ console.log('Seeding complete.');
74
+ }
75
+ <% } %>
76
+
77
+ const isDestroy = process.argv.includes('-d');
78
+
79
+ if (isDestroy) {
80
+ <% if (dbType === 'mongoose') { %>
81
+ import('mongoose').then(async (m) => {
82
+ await m.default.connect(process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/<%= projectName %>');
83
+ <% models.forEach(m => { %>
84
+ await <%= m.name %>.deleteMany({});
85
+ <% }); %>
86
+ await m.default.disconnect();
87
+ console.log('All data destroyed.');
88
+ });
89
+ <% } else { %>
90
+ (async () => {
91
+ <% models.forEach(m => { %>
92
+ await prisma.<%= m.name.charAt(0).toLowerCase() + m.name.slice(1) %>.deleteMany({});
93
+ <% }); %>
94
+ await prisma.$disconnect();
95
+ console.log('All data destroyed.');
96
+ })();
97
+ <% } %>
98
+ } else {
99
+ seed().catch(err => {
100
+ console.error(err);
101
+ process.exit(1);
102
+ });
103
+ }
@@ -0,0 +1,51 @@
1
+ <% if (dbType === 'mongoose') { %>
2
+ import <%= modelName %> from '../models/<%= modelName %>.model.js';
3
+
4
+ export class <%= modelName %>Service {
5
+
6
+ static async getAll() {
7
+ return <%= modelName %>.find();
8
+ }
9
+
10
+ static async getById(id) {
11
+ return <%= modelName %>.findById(id);
12
+ }
13
+
14
+ static async create(data) {
15
+ return <%= modelName %>.create(data);
16
+ }
17
+
18
+ static async update(id, data) {
19
+ return <%= modelName %>.findByIdAndUpdate(id, data, { new: true, runValidators: true });
20
+ }
21
+
22
+ static async delete(id) {
23
+ return <%= modelName %>.findByIdAndDelete(id);
24
+ }
25
+ }
26
+ <% } else { %>
27
+ import { prisma } from '../db.js';
28
+
29
+ export class <%= modelName %>Service {
30
+
31
+ static async getAll() {
32
+ return prisma.<%= modelName.charAt(0).toLowerCase() + modelName.slice(1) %>.findMany();
33
+ }
34
+
35
+ static async getById(id) {
36
+ return prisma.<%= modelName.charAt(0).toLowerCase() + modelName.slice(1) %>.findUnique({ where: { id } });
37
+ }
38
+
39
+ static async create(data) {
40
+ return prisma.<%= modelName.charAt(0).toLowerCase() + modelName.slice(1) %>.create({ data });
41
+ }
42
+
43
+ static async update(id, data) {
44
+ return prisma.<%= modelName.charAt(0).toLowerCase() + modelName.slice(1) %>.update({ where: { id }, data });
45
+ }
46
+
47
+ static async delete(id) {
48
+ return prisma.<%= modelName.charAt(0).toLowerCase() + modelName.slice(1) %>.delete({ where: { id } });
49
+ }
50
+ }
51
+ <% } %>
@@ -0,0 +1,30 @@
1
+ import swaggerJsdoc from 'swagger-jsdoc';
2
+ import swaggerUi from 'swagger-ui-express';
3
+
4
+ const options = {
5
+ definition: {
6
+ openapi: '3.0.0',
7
+ info: {
8
+ title: '<%= projectName %> API',
9
+ version: '1.0.0',
10
+ description: 'Auto-generated API documentation by create-backlist',
11
+ },
12
+ servers: [{ url: `http://localhost:${process.env.PORT || <%= port %>}` }],
13
+ <% if (addAuth) { %>
14
+ components: {
15
+ securitySchemes: {
16
+ bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
17
+ },
18
+ },
19
+ security: [{ bearerAuth: [] }],
20
+ <% } %>
21
+ },
22
+ apis: ['./src/routes*.js', './src/controllers/**/*.js'],
23
+ };
24
+
25
+ const swaggerSpec = swaggerJsdoc(options);
26
+
27
+ export function setupSwagger(app) {
28
+ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
29
+ console.log(`Swagger docs available at /api-docs`);
30
+ }
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import request from 'supertest';
3
+
4
+ const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
5
+
6
+ describe('API Health Check', () => {
7
+ it('GET /api/health should return 200', async () => {
8
+ const res = await request(BASE_URL).get('/api/health');
9
+ expect(res.status).toBe(200);
10
+ expect(res.body.status).toBe('ok');
11
+ });
12
+ });
13
+
14
+ <% if (addAuth) { %>
15
+ describe('Auth Endpoints', () => {
16
+ const testUser = {
17
+ name: 'Test User',
18
+ email: `test_${Date.now()}@example.com`,
19
+ password: 'TestPass123!',
20
+ };
21
+
22
+ it('POST /api/auth/register should create a user', async () => {
23
+ const res = await request(BASE_URL).post('/api/auth/register').send(testUser);
24
+ expect(res.status).toBe(201);
25
+ expect(res.body).toHaveProperty('token');
26
+ });
27
+
28
+ it('POST /api/auth/login should return a token', async () => {
29
+ const res = await request(BASE_URL).post('/api/auth/login').send({
30
+ email: testUser.email,
31
+ password: testUser.password,
32
+ });
33
+ expect(res.status).toBe(200);
34
+ expect(res.body).toHaveProperty('token');
35
+ });
36
+ });
37
+ <% } %>
38
+
39
+ <% endpoints.forEach(ep => { %>
40
+ describe('<%= ep.method.toUpperCase() %> <%= ep.path %>', () => {
41
+ it('should respond without 500', async () => {
42
+ const res = await request(BASE_URL).<%= ep.method.toLowerCase() %>('<%= ep.path %>');
43
+ expect(res.status).not.toBe(500);
44
+ });
45
+ });
46
+ <% }); %>
@@ -0,0 +1,9 @@
1
+ import { Module } from '@nestjs/common';
2
+ // INJECT:IMPORTS
3
+
4
+ @Module({
5
+ imports: [
6
+ // INJECT:MODULES
7
+ ],
8
+ })
9
+ export class AppModule {}
@@ -0,0 +1,23 @@
1
+ import { NestFactory } from '@nestjs/core';
2
+ import { ValidationPipe } from '@nestjs/common';
3
+ import { AppModule } from './app.module';
4
+
5
+ async function bootstrap() {
6
+ const app = await NestFactory.create(AppModule);
7
+
8
+ app.enableCors({
9
+ origin: process.env.CORS_ORIGIN
10
+ ? process.env.CORS_ORIGIN.split(',').map(s => s.trim())
11
+ : ['http://localhost:3000', 'http://localhost:5173'],
12
+ credentials: true,
13
+ });
14
+
15
+ app.setGlobalPrefix('api');
16
+ app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
17
+
18
+ const port = process.env.PORT || 8000;
19
+ await app.listen(port);
20
+ console.log(`NestJS server running on http://localhost:${port}`);
21
+ }
22
+
23
+ bootstrap();
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "declaration": true,
5
+ "removeComments": true,
6
+ "emitDecoratorMetadata": true,
7
+ "experimentalDecorators": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "target": "ES2021",
10
+ "sourceMap": true,
11
+ "outDir": "./dist",
12
+ "baseUrl": "./",
13
+ "incremental": true,
14
+ "skipLibCheck": true,
15
+ "strictNullChecks": true,
16
+ "noImplicitAny": true,
17
+ "strictBindCallApply": true,
18
+ "forceConsistentCasingInFileNames": true,
19
+ "noFallthroughCasesInSwitch": true
20
+ }
21
+ }
@@ -0,0 +1,17 @@
1
+ import { Controller, Post, Body } from '@nestjs/common';
2
+ import { AuthService } from './auth.service';
3
+
4
+ @Controller('auth')
5
+ export class AuthController {
6
+ constructor(private readonly authService: AuthService) {}
7
+
8
+ @Post('register')
9
+ register(@Body() body: { name: string; email: string; password: string }) {
10
+ return this.authService.register(body.name, body.email, body.password);
11
+ }
12
+
13
+ @Post('login')
14
+ login(@Body() body: { email: string; password: string }) {
15
+ return this.authService.login(body.email, body.password);
16
+ }
17
+ }
@@ -0,0 +1,17 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { JwtModule } from '@nestjs/jwt';
3
+ import { AuthController } from './auth.controller';
4
+ import { AuthService } from './auth.service';
5
+
6
+ @Module({
7
+ imports: [
8
+ JwtModule.register({
9
+ secret: process.env.JWT_SECRET || 'changeme',
10
+ signOptions: { expiresIn: process.env.JWT_EXPIRES_IN || '5h' },
11
+ }),
12
+ ],
13
+ controllers: [AuthController],
14
+ providers: [AuthService],
15
+ exports: [AuthService, JwtModule],
16
+ })
17
+ export class AuthModule {}
@@ -0,0 +1,70 @@
1
+ <% if (dbType === 'mongoose') { %>
2
+ import { Injectable, ConflictException, UnauthorizedException } from '@nestjs/common';
3
+ import { JwtService } from '@nestjs/jwt';
4
+ import { InjectModel } from '@nestjs/mongoose';
5
+ import { Model } from 'mongoose';
6
+ import * as bcrypt from 'bcryptjs';
7
+
8
+ @Injectable()
9
+ export class AuthService {
10
+ constructor(
11
+ @InjectModel('User') private readonly userModel: Model<any>,
12
+ private readonly jwtService: JwtService,
13
+ ) {}
14
+
15
+ async register(name: string, email: string, password: string) {
16
+ const existing = await this.userModel.findOne({ email });
17
+ if (existing) throw new ConflictException('Email already in use');
18
+
19
+ const hashed = await bcrypt.hash(password, 12);
20
+ const user = await this.userModel.create({ name, email, password: hashed });
21
+ const token = this.jwtService.sign({ id: user._id, email: user.email });
22
+ return { token, user: { id: user._id, name: user.name, email: user.email } };
23
+ }
24
+
25
+ async login(email: string, password: string) {
26
+ const user = await this.userModel.findOne({ email });
27
+ if (!user) throw new UnauthorizedException('Invalid credentials');
28
+
29
+ const isMatch = await bcrypt.compare(password, user.password);
30
+ if (!isMatch) throw new UnauthorizedException('Invalid credentials');
31
+
32
+ const token = this.jwtService.sign({ id: user._id, email: user.email });
33
+ return { token, user: { id: user._id, name: user.name, email: user.email } };
34
+ }
35
+ }
36
+ <% } else { %>
37
+ import { Injectable, ConflictException, UnauthorizedException } from '@nestjs/common';
38
+ import { JwtService } from '@nestjs/jwt';
39
+ import { PrismaService } from '../prisma/prisma.service';
40
+ import * as bcrypt from 'bcryptjs';
41
+
42
+ @Injectable()
43
+ export class AuthService {
44
+ constructor(
45
+ private readonly prisma: PrismaService,
46
+ private readonly jwtService: JwtService,
47
+ ) {}
48
+
49
+ async register(name: string, email: string, password: string) {
50
+ const existing = await this.prisma.user.findUnique({ where: { email } });
51
+ if (existing) throw new ConflictException('Email already in use');
52
+
53
+ const hashed = await bcrypt.hash(password, 12);
54
+ const user = await this.prisma.user.create({ data: { name, email, password: hashed } });
55
+ const token = this.jwtService.sign({ id: user.id, email: user.email });
56
+ return { token, user: { id: user.id, name: user.name, email: user.email } };
57
+ }
58
+
59
+ async login(email: string, password: string) {
60
+ const user = await this.prisma.user.findUnique({ where: { email } });
61
+ if (!user) throw new UnauthorizedException('Invalid credentials');
62
+
63
+ const isMatch = await bcrypt.compare(password, user.password);
64
+ if (!isMatch) throw new UnauthorizedException('Invalid credentials');
65
+
66
+ const token = this.jwtService.sign({ id: user.id, email: user.email });
67
+ return { token, user: { id: user.id, name: user.name, email: user.email } };
68
+ }
69
+ }
70
+ <% } %>
@@ -0,0 +1,34 @@
1
+ import { Controller, Get, Post, Put, Delete, Param, Body } from '@nestjs/common';
2
+ import { <%= modelName %>Service } from './<%= modelName.toLowerCase() %>.service';
3
+ import { Create<%= modelName %>Dto } from './dto/create-<%= modelName.toLowerCase() %>.dto';
4
+ import { Update<%= modelName %>Dto } from './dto/update-<%= modelName.toLowerCase() %>.dto';
5
+
6
+ @Controller('<%= modelName.toLowerCase() %>s')
7
+ export class <%= modelName %>Controller {
8
+ constructor(private readonly <%= modelName.toLowerCase() %>Service: <%= modelName %>Service) {}
9
+
10
+ @Get()
11
+ findAll() {
12
+ return this.<%= modelName.toLowerCase() %>Service.findAll();
13
+ }
14
+
15
+ @Get(':id')
16
+ findOne(@Param('id') id: string) {
17
+ return this.<%= modelName.toLowerCase() %>Service.findOne(id);
18
+ }
19
+
20
+ @Post()
21
+ create(@Body() dto: Create<%= modelName %>Dto) {
22
+ return this.<%= modelName.toLowerCase() %>Service.create(dto);
23
+ }
24
+
25
+ @Put(':id')
26
+ update(@Param('id') id: string, @Body() dto: Update<%= modelName %>Dto) {
27
+ return this.<%= modelName.toLowerCase() %>Service.update(id, dto);
28
+ }
29
+
30
+ @Delete(':id')
31
+ remove(@Param('id') id: string) {
32
+ return this.<%= modelName.toLowerCase() %>Service.remove(id);
33
+ }
34
+ }
@@ -0,0 +1,22 @@
1
+ import { IsString, IsOptional, IsNotEmpty } from 'class-validator';
2
+
3
+ export class Create<%= modelName %>Dto {
4
+ <% fields.forEach(field => { %>
5
+ <% if (field.name === 'password' || field.name === 'email' || field.name === 'name') { %>
6
+ @IsString()
7
+ @IsNotEmpty()
8
+ <%= field.name %>: string;
9
+ <% } else if (field.type === 'Number' || field.type === 'Int') { %>
10
+ @IsOptional()
11
+ <%= field.name %>?: number;
12
+ <% } else if (field.type === 'Boolean') { %>
13
+ @IsOptional()
14
+ <%= field.name %>?: boolean;
15
+ <% } else { %>
16
+ @IsOptional()
17
+ @IsString()
18
+ <%= field.name %>?: string;
19
+ <% } %>
20
+
21
+ <% }); %>
22
+ }
@@ -0,0 +1,24 @@
1
+ import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
2
+ import { JwtService } from '@nestjs/jwt';
3
+
4
+ @Injectable()
5
+ export class JwtAuthGuard implements CanActivate {
6
+ constructor(private readonly jwtService: JwtService) {}
7
+
8
+ canActivate(context: ExecutionContext): boolean {
9
+ const request = context.switchToHttp().getRequest();
10
+ const authHeader = request.headers.authorization;
11
+
12
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
13
+ throw new UnauthorizedException('Access denied. No token provided.');
14
+ }
15
+
16
+ const token = authHeader.split(' ')[1];
17
+ try {
18
+ request.user = this.jwtService.verify(token);
19
+ return true;
20
+ } catch {
21
+ throw new UnauthorizedException('Invalid or expired token.');
22
+ }
23
+ }
24
+ }