create-backlist 5.0.7 → 6.0.2

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 (58) hide show
  1. package/bin/backlist.js +227 -0
  2. package/package.json +10 -4
  3. package/src/analyzer.js +210 -89
  4. package/src/db/prisma.ts +4 -0
  5. package/src/generators/dotnet.js +120 -94
  6. package/src/generators/java.js +205 -75
  7. package/src/generators/node.js +262 -85
  8. package/src/generators/python.js +54 -25
  9. package/src/generators/template.js +38 -2
  10. package/src/scanner/index.js +99 -0
  11. package/src/templates/dotnet/partials/Controller.cs.ejs +7 -14
  12. package/src/templates/dotnet/partials/Dto.cs.ejs +8 -0
  13. package/src/templates/java-spring/partials/ApplicationSeeder.java.ejs +30 -0
  14. package/src/templates/java-spring/partials/AuthController.java.ejs +62 -0
  15. package/src/templates/java-spring/partials/Controller.java.ejs +40 -50
  16. package/src/templates/java-spring/partials/Dockerfile.ejs +16 -0
  17. package/src/templates/java-spring/partials/Entity.java.ejs +16 -15
  18. package/src/templates/java-spring/partials/JwtAuthFilter.java.ejs +66 -0
  19. package/src/templates/java-spring/partials/JwtService.java.ejs +58 -0
  20. package/src/templates/java-spring/partials/Repository.java.ejs +9 -3
  21. package/src/templates/java-spring/partials/SecurityConfig.java.ejs +44 -0
  22. package/src/templates/java-spring/partials/Service.java.ejs +69 -0
  23. package/src/templates/java-spring/partials/User.java.ejs +33 -0
  24. package/src/templates/java-spring/partials/UserDetailsServiceImpl.java.ejs +33 -0
  25. package/src/templates/java-spring/partials/UserRepository.java.ejs +20 -0
  26. package/src/templates/java-spring/partials/docker-compose.yml.ejs +35 -0
  27. package/src/templates/node-ts-express/base/server.ts +12 -5
  28. package/src/templates/node-ts-express/base/tsconfig.json +13 -3
  29. package/src/templates/node-ts-express/partials/ApiDocs.ts.ejs +17 -7
  30. package/src/templates/node-ts-express/partials/App.test.ts.ejs +27 -27
  31. package/src/templates/node-ts-express/partials/Auth.controller.ts.ejs +56 -62
  32. package/src/templates/node-ts-express/partials/Auth.middleware.ts.ejs +21 -10
  33. package/src/templates/node-ts-express/partials/Controller.ts.ejs +40 -40
  34. package/src/templates/node-ts-express/partials/DbContext.cs.ejs +3 -3
  35. package/src/templates/node-ts-express/partials/Dockerfile.ejs +9 -11
  36. package/src/templates/node-ts-express/partials/Model.cs.ejs +25 -7
  37. package/src/templates/node-ts-express/partials/Model.ts.ejs +20 -12
  38. package/src/templates/node-ts-express/partials/PrismaController.ts.ejs +72 -55
  39. package/src/templates/node-ts-express/partials/PrismaSchema.prisma.ejs +27 -12
  40. package/src/templates/node-ts-express/partials/README.md.ejs +9 -12
  41. package/src/templates/node-ts-express/partials/Seeder.ts.ejs +44 -64
  42. package/src/templates/node-ts-express/partials/docker-compose.yml.ejs +31 -16
  43. package/src/templates/node-ts-express/partials/package.json.ejs +3 -1
  44. package/src/templates/node-ts-express/partials/prismaClient.ts.ejs +4 -0
  45. package/src/templates/node-ts-express/partials/routes.ts.ejs +35 -24
  46. package/src/templates/python-fastapi/Dockerfile.ejs +8 -0
  47. package/src/templates/python-fastapi/app/core/config.py.ejs +8 -0
  48. package/src/templates/python-fastapi/app/core/security.py.ejs +8 -0
  49. package/src/templates/python-fastapi/app/db.py.ejs +7 -0
  50. package/src/templates/python-fastapi/app/main.py.ejs +24 -0
  51. package/src/templates/python-fastapi/app/models/user.py.ejs +9 -0
  52. package/src/templates/python-fastapi/app/routers/auth.py.ejs +33 -0
  53. package/src/templates/python-fastapi/app/routers/model_routes.py.ejs +72 -0
  54. package/src/templates/python-fastapi/app/schemas/user.py.ejs +16 -0
  55. package/src/templates/python-fastapi/docker-compose.yml.ejs +19 -0
  56. package/src/templates/python-fastapi/requirements.txt.ejs +5 -1
  57. package/src/utils.js +19 -4
  58. package/bin/index.js +0 -141
@@ -0,0 +1,33 @@
1
+ // Auto-generated by create-backlist
2
+ package <%= group %>.<%= projectName %>.model;
3
+
4
+ import com.fasterxml.jackson.annotation.JsonIgnore;
5
+ import jakarta.persistence.*;
6
+ import lombok.*;
7
+
8
+ @Data
9
+ @NoArgsConstructor
10
+ @AllArgsConstructor
11
+ @Entity
12
+ @Table(
13
+ name = "users",
14
+ indexes = {
15
+ @Index(name = "idx_users_email", columnList = "email", unique = true)
16
+ }
17
+ )
18
+ public class User {
19
+
20
+ @Id
21
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
22
+ private Long id;
23
+
24
+ @Column(nullable = false)
25
+ private String name;
26
+
27
+ @Column(nullable = false, unique = true)
28
+ private String email;
29
+
30
+ @JsonIgnore
31
+ @Column(nullable = false)
32
+ private String password;
33
+ }
@@ -0,0 +1,33 @@
1
+ // Auto-generated by create-backlist
2
+ package <%= group %>.<%= projectName %>.security;
3
+
4
+ import <%= group %>.<%= projectName %>.model.User;
5
+ import <%= group %>.<%= projectName %>.repository.UserRepository;
6
+
7
+ import org.springframework.security.core.userdetails.UserDetailsService;
8
+ import org.springframework.security.core.userdetails.UserDetails;
9
+ import org.springframework.security.core.userdetails.UsernameNotFoundException;
10
+
11
+ import org.springframework.stereotype.Service;
12
+
13
+ @Service
14
+ public class UserDetailsServiceImpl implements UserDetailsService {
15
+
16
+ private final UserRepository repo;
17
+
18
+ public UserDetailsServiceImpl(UserRepository repo) {
19
+ this.repo = repo;
20
+ }
21
+
22
+ @Override
23
+ public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
24
+ User u = repo.findByEmail(email)
25
+ .orElseThrow(() -> new UsernameNotFoundException("User not found: " + email));
26
+
27
+ return org.springframework.security.core.userdetails.User
28
+ .withUsername(u.getEmail())
29
+ .password(u.getPassword())
30
+ .authorities("ROLE_USER")
31
+ .build();
32
+ }
33
+ }
@@ -0,0 +1,20 @@
1
+ // Auto-generated by create-backlist
2
+ package <%= group %>.<%= projectName %>.repository;
3
+
4
+ import java.util.Optional;
5
+
6
+ import org.springframework.data.jpa.repository.JpaRepository;
7
+ import org.springframework.stereotype.Repository;
8
+
9
+ import <%= group %>.<%= projectName %>.model.User;
10
+
11
+ @Repository
12
+ public interface UserRepository extends JpaRepository<User, Long> {
13
+
14
+ Optional<User> findByEmail(String email);
15
+
16
+ boolean existsByEmail(String email);
17
+
18
+ // Optional: helpful if emails are stored normalized but requests vary
19
+ Optional<User> findByEmailIgnoreCase(String email);
20
+ }
@@ -0,0 +1,35 @@
1
+ version: "3.8"
2
+
3
+ services:
4
+ db:
5
+ image: postgres:16-alpine
6
+ environment:
7
+ POSTGRES_USER: ${DB_USER:-postgres}
8
+ POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
9
+ POSTGRES_DB: ${DB_NAME:-<%= projectName %>}
10
+ ports:
11
+ - "${DB_PORT:-5432}:5432"
12
+ volumes:
13
+ - pgdata:/var/lib/postgresql/data
14
+ healthcheck:
15
+ test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-<%= projectName %>}"]
16
+ interval: 5s
17
+ timeout: 5s
18
+ retries: 20
19
+
20
+ app:
21
+ build: .
22
+ depends_on:
23
+ db:
24
+ condition: service_healthy
25
+ environment:
26
+ JWT_SECRET: ${JWT_SECRET:-change_me_long_secret_change_me_long_secret}
27
+ SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/${DB_NAME:-<%= projectName %>}
28
+ SPRING_DATASOURCE_USERNAME: ${DB_USER:-postgres}
29
+ SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD:-password}
30
+ SPRING_JPA_HIBERNATE_DDL_AUTO: ${JPA_DDL_AUTO:-update}
31
+ ports:
32
+ - "${APP_PORT:-8080}:8080"
33
+
34
+ volumes:
35
+ pgdata:
@@ -4,19 +4,26 @@ import dotenv from 'dotenv';
4
4
 
5
5
  dotenv.config();
6
6
 
7
- const app: Express = express(); // Added :Express type
7
+ const app: Express = express();
8
8
 
9
9
  app.use(cors());
10
10
  app.use(express.json());
11
11
 
12
- app.get('/', (req: Request, res: Response) => { // Added :Request and :Response types
12
+ app.get('/', (req: Request, res: Response) => {
13
13
  res.send('Node.js Backend says Hello! Generated by Backlist.');
14
14
  });
15
15
 
16
- // INJECT:ROUTES
16
+ // <backlist:imports>
17
+ // </backlist:imports>
17
18
 
18
- const PORT: number | string = process.env.PORT || 8000; // Added type for PORT
19
+ // <backlist:setup>
20
+ // </backlist:setup>
21
+
22
+ // <backlist:routes>
23
+ // </backlist:routes>
24
+
25
+ const PORT: number | string = process.env.PORT || 8000;
19
26
 
20
27
  app.listen(PORT, () => {
21
- console.log(`⚡️ Server running on http://localhost:${PORT}`);
28
+ console.log(`Server running on http://localhost:${PORT}`);
22
29
  });
@@ -1,10 +1,20 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "es6",
3
+ "target": "ES2021",
4
4
  "module": "commonjs",
5
+ "moduleResolution": "node",
5
6
  "rootDir": "./src",
6
7
  "outDir": "./dist",
8
+
7
9
  "esModuleInterop": true,
8
- "strict": true
9
- }
10
+ "strict": true,
11
+
12
+ "types": ["node"],
13
+ "resolveJsonModule": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "skipLibCheck": true,
16
+
17
+ "sourceMap": true
18
+ },
19
+ "include": ["src/**/*"]
10
20
  }
@@ -1,4 +1,4 @@
1
- // Auto-generated by create-backlist v5.0
1
+ // Auto-generated by create-backlist v5.1
2
2
  import swaggerUi from 'swagger-ui-express';
3
3
  import swaggerJsdoc from 'swagger-jsdoc';
4
4
  import { Express } from 'express';
@@ -17,17 +17,27 @@ const options: swaggerJsdoc.Options = {
17
17
  description: 'Development server',
18
18
  },
19
19
  ],
20
- // TODO: Add components (e.g., securitySchemes for JWT)
20
+ <% if (addAuth) { -%>
21
+ components: {
22
+ securitySchemes: {
23
+ bearerAuth: {
24
+ type: 'http',
25
+ scheme: 'bearer',
26
+ bearerFormat: 'JWT',
27
+ },
28
+ },
29
+ },
30
+ security: [{ bearerAuth: [] }],
31
+ <% } -%>
21
32
  },
22
- // Path to the API docs
23
- apis: ['./src/routes/*.ts', './src/routes.ts'], // Looks for JSDoc comments in routes
33
+
34
+ // Scan all TS files (routes/controllers) for JSDoc @swagger comments
35
+ apis: ['./src/**/*.ts'],
24
36
  };
25
37
 
26
38
  const swaggerSpec = swaggerJsdoc(options);
27
39
 
28
40
  export function setupSwagger(app: Express) {
29
41
  app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
30
- console.log(
31
- `📄 API documentation is available at http://localhost:<%= port %>/api-docs`
32
- );
42
+ console.log(`API documentation: http://localhost:<%= port %>/api-docs`);
33
43
  }
@@ -1,38 +1,38 @@
1
- // Auto-generated by create-backlist v5.0
1
+ // Auto-generated by create-backlist v5.1
2
2
  import request from 'supertest';
3
3
  import express from 'express';
4
- // Import your main app configuration. This might need path adjustment.
5
- // For simplicity, we create a test server here.
6
- import apiRoutes from '../routes'; // Assuming main routes
7
- import authRoutes from '../routes/Auth.routes'; // Assuming auth routes
4
+
5
+ import apiRoutes from '../routes';
6
+ <% if (addAuth) { -%>
7
+ import authRoutes from '../routes/Auth.routes';
8
+ <% } -%>
8
9
 
9
10
  const app = express();
10
11
  app.use(express.json());
11
- app.use('/api', apiRoutes);
12
- app.use('/api/auth', authRoutes);
13
-
14
12
 
15
- describe('API Endpoints', () => {
13
+ <% if (addAuth) { -%>
14
+ app.use('/api/auth', authRoutes);
15
+ <% } -%>
16
+ app.use('/api', apiRoutes);
16
17
 
17
- it('should respond to the root GET endpoint', async () => {
18
- // This test assumes a GET /api/ endpoint exists or a similar public one.
19
- // You might need to adjust this to a real endpoint from your app.
20
- // Example for GET /api/users
21
- // const res = await request(app).get('/api/users');
22
- // expect(res.statusCode).toEqual(200);
23
-
24
- // For now, a placeholder test:
18
+ describe('API Endpoints (Generated)', () => {
19
+ it('sanity check', () => {
25
20
  expect(1 + 1).toBe(2);
26
21
  });
27
22
 
28
- // TODO: Add more specific tests for your generated endpoints
29
- // describe('POST /api/users', () => {
30
- // it('should create a new user', async () => {
31
- // const res = await request(app)
32
- // .post('/api/users')
33
- // .send({ name: 'Test User', email: 'test@example.com', password: 'password123' });
34
- // expect(res.statusCode).toEqual(201);
35
- // expect(res.body).toHaveProperty('name', 'Test User');
36
- // });
37
- // });
23
+ <% endpoints
24
+ .filter(ep => ep && ep.route && ep.method)
25
+ .forEach(ep => {
26
+ const method = ep.method.toLowerCase();
27
+ const url = (ep.route.startsWith('/api/') ? ep.route.replace(/^\/api/, '') : ep.route)
28
+ .replace(/:\w+/g, '1'); // replace path params with dummy value
29
+ -%>
30
+ it('<%= ep.method %> <%= url %> should respond', async () => {
31
+ const res = await request(app).<%= method %>('<%= '/api' + url %>')<% if (['post','put','patch'].includes(method) && ep.schemaFields) { %>
32
+ .send(<%- JSON.stringify(Object.fromEntries(Object.entries(ep.schemaFields).map(([k,t]) => [k, t === 'Number' ? 1 : (t === 'Boolean' ? true : 'test') ]))) %>)<% } %>;
33
+ // We only assert "not 404" because generated handlers may be TODO stubs,
34
+ // and auth/validation may affect exact status codes.
35
+ expect(res.statusCode).not.toBe(404);
36
+ });
37
+ <% }) -%>
38
38
  });
@@ -3,87 +3,81 @@ import { Request, Response } from 'express';
3
3
  import bcrypt from 'bcryptjs';
4
4
  import jwt from 'jsonwebtoken';
5
5
 
6
- <% if (dbType === 'mongoose') { %>
6
+ <% if (dbType === 'mongoose') { -%>
7
7
  import User from '../models/User.model';
8
- <% } else if (dbType === 'prisma') { %>
9
- import { prisma } from '../server';
10
- <% } %>
8
+ <% } else if (dbType === 'prisma') { -%>
9
+ import { prisma } from '../db/prisma';
10
+ <% } -%>
11
11
 
12
- // @desc Register a new user
12
+ function signToken(res: Response, userId: string) {
13
+ const secret = process.env.JWT_SECRET;
14
+ if (!secret) {
15
+ return res.status(500).json({ message: 'JWT_SECRET is not configured' });
16
+ }
17
+
18
+ const payload = { user: { id: userId } };
19
+
20
+ jwt.sign(payload, secret, { expiresIn: '5h' }, (err, token) => {
21
+ if (err || !token) return res.status(500).json({ message: 'Failed to sign token' });
22
+ return res.status(201).json({ token });
23
+ });
24
+ }
25
+
26
+ // Register
13
27
  export const registerUser = async (req: Request, res: Response) => {
14
- const { name, email, password } = req.body;
28
+ const { name, email, password } = req.body || {};
15
29
 
16
30
  try {
17
- <% if (dbType === 'mongoose') { %>
18
- // Mongoose Logic
19
- let user = await User.findOne({ email });
20
- if (user) {
21
- return res.status(400).json({ message: 'User already exists' });
31
+ if (!name || !email || !password) {
32
+ return res.status(400).json({ message: 'name, email, password are required' });
22
33
  }
23
- user = new User({ name, email, password });
34
+
35
+ <% if (dbType === 'mongoose') { -%>
36
+ const existing = await User.findOne({ email });
37
+ if (existing) return res.status(400).json({ message: 'User already exists' });
38
+
39
+ const user = new User({ name, email, password }); // password will be hashed by pre-save hook
24
40
  await user.save();
25
- <% } else if (dbType === 'prisma') { %>
26
- // Prisma Logic
27
- const existingUser = await prisma.user.findUnique({ where: { email } });
28
- if (existingUser) {
29
- return res.status(400).json({ message: 'User already exists' });
30
- }
31
- const hashedPassword = await bcrypt.hash(password, 10);
32
- const user = await prisma.user.create({
33
- data: { name, email, password: hashedPassword },
34
- });
35
- <% } %>
41
+ return signToken(res, String(user._id));
42
+ <% } else if (dbType === 'prisma') { -%>
43
+ const existing = await prisma.user.findUnique({ where: { email } });
44
+ if (existing) return res.status(400).json({ message: 'User already exists' });
36
45
 
37
- const payload = { user: { id: user.id } };
38
- jwt.sign(
39
- payload,
40
- process.env.JWT_SECRET as string,
41
- { expiresIn: '5h' },
42
- (err, token) => {
43
- if (err) throw err;
44
- res.status(201).json({ token });
45
- }
46
- );
47
- } catch (error) {
48
- console.error(error);
49
- res.status(500).send('Server Error');
46
+ const hashedPassword = await bcrypt.hash(password, 10);
47
+ const user = await prisma.user.create({ data: { name, email, password: hashedPassword } });
48
+ return signToken(res, String(user.id));
49
+ <% } -%>
50
+ } catch (error: any) {
51
+ return res.status(500).json({ message: 'Server Error', error: error?.message || error });
50
52
  }
51
53
  };
52
54
 
53
- // @desc Authenticate user & get token (Login)
55
+ // Login
54
56
  export const loginUser = async (req: Request, res: Response) => {
55
- const { email, password } = req.body;
57
+ const { email, password } = req.body || {};
56
58
 
57
59
  try {
58
- <% if (dbType === 'mongoose') { %>
59
- // Mongoose Logic
60
+ if (!email || !password) {
61
+ return res.status(400).json({ message: 'email and password are required' });
62
+ }
63
+
64
+ <% if (dbType === 'mongoose') { -%>
60
65
  const user = await User.findOne({ email });
61
- <% } else if (dbType === 'prisma') { %>
62
- // Prisma Logic
66
+ <% } else if (dbType === 'prisma') { -%>
63
67
  const user = await prisma.user.findUnique({ where: { email } });
64
- <% } %>
68
+ <% } -%>
65
69
 
66
- if (!user) {
67
- return res.status(400).json({ message: 'Invalid Credentials' });
68
- }
70
+ if (!user) return res.status(400).json({ message: 'Invalid Credentials' });
69
71
 
70
72
  const isMatch = await bcrypt.compare(password, user.password);
71
- if (!isMatch) {
72
- return res.status(400).json({ message: 'Invalid Credentials' });
73
- }
73
+ if (!isMatch) return res.status(400).json({ message: 'Invalid Credentials' });
74
74
 
75
- const payload = { user: { id: user.id } };
76
- jwt.sign(
77
- payload,
78
- process.env.JWT_SECRET as string,
79
- { expiresIn: '5h' },
80
- (err, token) => {
81
- if (err) throw err;
82
- res.json({ token });
83
- }
84
- );
85
- } catch (error) {
86
- console.error(error);
87
- res.status(500).send('Server Error');
75
+ <% if (dbType === 'mongoose') { -%>
76
+ return signToken(res, String(user._id));
77
+ <% } else { -%>
78
+ return signToken(res, String(user.id));
79
+ <% } -%>
80
+ } catch (error: any) {
81
+ return res.status(500).json({ message: 'Server Error', error: error?.message || error });
88
82
  }
89
83
  };
@@ -1,27 +1,38 @@
1
- // Auto-generated by create-backlist v3.0 on <%= new Date().toISOString() %>
1
+ // Auto-generated by create-backlist v5.1 on <%= new Date().toISOString() %>
2
2
  import { Request, Response, NextFunction } from 'express';
3
3
  import jwt from 'jsonwebtoken';
4
4
 
5
- // Extend the default Request interface to include our 'user' property
6
5
  interface AuthRequest extends Request {
7
6
  user?: any;
8
7
  }
9
8
 
10
9
  export const protect = (req: AuthRequest, res: Response, next: NextFunction) => {
11
- // Get token from header
12
- const token = req.header('x-auth-token');
10
+ const secret = process.env.JWT_SECRET;
11
+ if (!secret) return res.status(500).json({ message: 'JWT_SECRET is not configured' });
12
+
13
+ // Support both:
14
+ // 1) Authorization: Bearer <token>
15
+ // 2) x-auth-token: <token>
16
+ const authHeader = req.header('authorization');
17
+ const xToken = req.header('x-auth-token');
18
+
19
+ let token: string | undefined;
20
+
21
+ if (authHeader && authHeader.toLowerCase().startsWith('bearer ')) {
22
+ token = authHeader.slice(7).trim();
23
+ } else if (xToken) {
24
+ token = xToken.trim();
25
+ }
13
26
 
14
- // Check if not token
15
27
  if (!token) {
16
28
  return res.status(401).json({ message: 'No token, authorization denied' });
17
29
  }
18
30
 
19
- // Verify token
20
31
  try {
21
- const decoded = jwt.verify(token, process.env.JWT_SECRET as string);
32
+ const decoded: any = jwt.verify(token, secret);
22
33
  req.user = decoded.user;
23
- next();
24
- } catch (err) {
25
- res.status(401).json({ message: 'Token is not valid' });
34
+ return next();
35
+ } catch {
36
+ return res.status(401).json({ message: 'Token is not valid' });
26
37
  }
27
38
  };
@@ -1,50 +1,50 @@
1
1
  // Auto-generated by create-backlist on <%= new Date().toISOString() %>
2
2
  import { Request, Response } from 'express';
3
3
 
4
- export const create<%= modelName %> = async (req: Request, res: Response) => {
5
- try {
6
- // TODO: implement create logic
7
- res.status(201).json({ message: '<%= modelName %> created', data: req.body });
8
- } catch (error) {
9
- res.status(500).json({ message: 'Error creating <%= modelName %>', error });
10
- }
11
- };
4
+ /**
5
+ * Controller: <%= controllerName %>
6
+ * Generated from frontend AST scan.
7
+ */
12
8
 
13
- export const getAll<%= modelName %>s = async (_req: Request, res: Response) => {
14
- try {
15
- // TODO: implement list logic
16
- res.status(200).json([]);
17
- } catch (error) {
18
- res.status(500).json({ message: 'Error fetching <%= modelName %>s', error });
19
- }
20
- };
9
+ <%
10
+ function safeAction(name, fallback) {
11
+ if (!name) return fallback;
12
+ return String(name).replace(/[^a-zA-Z0-9_]/g, '');
13
+ }
21
14
 
22
- export const get<%= modelName %>ById = async (req: Request, res: Response) => {
23
- try {
24
- const { id } = req.params;
25
- // TODO: implement get by id logic
26
- res.status(200).json({ id });
27
- } catch (error) {
28
- res.status(500).json({ message: 'Error fetching <%= modelName %>', error });
29
- }
30
- };
15
+ function hasBody(method) {
16
+ const m = String(method || 'GET').toUpperCase();
17
+ return ['POST', 'PUT', 'PATCH'].includes(m);
18
+ }
19
+ %>
31
20
 
32
- export const update<%= modelName %>ById = async (req: Request, res: Response) => {
33
- try {
34
- const { id } = req.params;
35
- // TODO: implement update logic
36
- res.status(200).json({ id, ...req.body });
37
- } catch (error) {
38
- res.status(500).json({ message: 'Error updating <%= modelName %>', error });
39
- }
40
- };
21
+ <% if (!Array.isArray(endpoints) || endpoints.length === 0) { %>
22
+ export async function health(_req: Request, res: Response) {
23
+ return res.status(200).json({ ok: true });
24
+ }
25
+ <% } %>
41
26
 
42
- export const delete<%= modelName %>ById = async (req: Request, res: Response) => {
27
+ <% (endpoints || []).forEach((ep, i) => {
28
+ const method = String(ep.method || 'GET').toUpperCase();
29
+ const actionName = safeAction(ep.actionName, `handler${i}`);
30
+ const route = ep.route || ep.path || '';
31
+ -%>
32
+ /**
33
+ * <%= method %> <%= route %>
34
+ */
35
+ export async function <%= actionName %>(req: Request, res: Response) {
43
36
  try {
44
- const { id } = req.params;
45
- // TODO: implement delete logic
46
- res.status(204).send();
37
+ <% if (hasBody(method)) { -%>
38
+ // Body schema (inferred):
39
+ // <%- JSON.stringify(ep.schemaFields || ep.requestBody?.fields || {}, null, 2).split('\n').map(l => '// ' + l).join('\n') %>
40
+ const payload = req.body;
41
+ return res.status(<%= method === 'POST' ? 201 : 200 %>).json({ message: 'TODO: implement', payload });
42
+ <% } else { -%>
43
+ return res.status(200).json({ message: 'TODO: implement', params: req.params, query: req.query });
44
+ <% } -%>
47
45
  } catch (error) {
48
- res.status(500).json({ message: 'Error deleting <%= modelName %>', error });
46
+ return res.status(500).json({ message: 'Internal Server Error', error });
49
47
  }
50
- };
48
+ }
49
+
50
+ <% }) -%>
@@ -8,8 +8,8 @@ namespace <%= projectName %>.Data
8
8
  {
9
9
  public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
10
10
 
11
- <% modelsToGenerate.forEach(model => { %>
12
- public DbSet<<%= model.name %>> <%= model.name %>s { get; set; }
13
- <% }); %>
11
+ <% modelsToGenerate.forEach(model => { -%>
12
+ public DbSet<<%= model.name %>> <%= model.name %> { get; set; } = default!;
13
+ <% }); -%>
14
14
  }
15
15
  }
@@ -1,33 +1,31 @@
1
- # Auto-generated by create-backlist v5.0
1
+ # Auto-generated by create-backlist v5.1
2
2
 
3
- # ---- Base Stage ----
4
3
  FROM node:18-alpine AS base
5
4
  WORKDIR /usr/src/app
6
5
  COPY package*.json ./
7
6
 
8
- # ---- Dependencies Stage ----
9
7
  FROM base AS dependencies
10
- RUN npm install --frozen-lockfile
8
+ # Use npm ci for reproducible installs
9
+ RUN npm ci
11
10
 
12
- # ---- Build Stage ----
13
11
  FROM base AS build
14
12
  COPY --from=dependencies /usr/src/app/node_modules ./node_modules
15
13
  COPY . .
16
- <% if (dbType === 'prisma') { %>
14
+ <% if (dbType === 'prisma') { -%>
17
15
  RUN npx prisma generate
18
- <% } %>
16
+ <% } -%>
19
17
  RUN npm run build
20
18
 
21
- # ---- Production Stage ----
22
19
  FROM node:18-alpine AS production
23
20
  WORKDIR /usr/src/app
21
+ ENV NODE_ENV=production
22
+
24
23
  COPY --from=build /usr/src/app/dist ./dist
25
24
  COPY --from=dependencies /usr/src/app/node_modules ./node_modules
26
25
  COPY package*.json ./
27
- <% if (dbType === 'prisma') { %>
28
- # Copy Prisma schema for runtime
26
+ <% if (dbType === 'prisma') { -%>
29
27
  COPY prisma ./prisma
30
- <% } %>
28
+ <% } -%>
31
29
 
32
30
  EXPOSE <%= port %>
33
31
  CMD ["node", "dist/server.js"]