create-backlist 6.0.0 → 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 (45) 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 +157 -109
  7. package/src/generators/node.js +262 -85
  8. package/src/generators/template.js +38 -2
  9. package/src/scanner/index.js +99 -0
  10. package/src/templates/dotnet/partials/Controller.cs.ejs +7 -14
  11. package/src/templates/dotnet/partials/Dto.cs.ejs +8 -0
  12. package/src/templates/java-spring/partials/ApplicationSeeder.java.ejs +7 -2
  13. package/src/templates/java-spring/partials/AuthController.java.ejs +23 -10
  14. package/src/templates/java-spring/partials/Controller.java.ejs +17 -6
  15. package/src/templates/java-spring/partials/Dockerfile.ejs +6 -1
  16. package/src/templates/java-spring/partials/Entity.java.ejs +15 -5
  17. package/src/templates/java-spring/partials/JwtAuthFilter.java.ejs +30 -7
  18. package/src/templates/java-spring/partials/JwtService.java.ejs +38 -10
  19. package/src/templates/java-spring/partials/Repository.java.ejs +10 -1
  20. package/src/templates/java-spring/partials/Service.java.ejs +45 -7
  21. package/src/templates/java-spring/partials/User.java.ejs +17 -4
  22. package/src/templates/java-spring/partials/UserDetailsServiceImpl.java.ejs +10 -4
  23. package/src/templates/java-spring/partials/UserRepository.java.ejs +8 -0
  24. package/src/templates/java-spring/partials/docker-compose.yml.ejs +16 -8
  25. package/src/templates/node-ts-express/base/server.ts +12 -5
  26. package/src/templates/node-ts-express/base/tsconfig.json +13 -3
  27. package/src/templates/node-ts-express/partials/ApiDocs.ts.ejs +17 -7
  28. package/src/templates/node-ts-express/partials/App.test.ts.ejs +27 -27
  29. package/src/templates/node-ts-express/partials/Auth.controller.ts.ejs +56 -62
  30. package/src/templates/node-ts-express/partials/Auth.middleware.ts.ejs +21 -10
  31. package/src/templates/node-ts-express/partials/Controller.ts.ejs +40 -40
  32. package/src/templates/node-ts-express/partials/DbContext.cs.ejs +3 -3
  33. package/src/templates/node-ts-express/partials/Dockerfile.ejs +9 -11
  34. package/src/templates/node-ts-express/partials/Model.cs.ejs +25 -7
  35. package/src/templates/node-ts-express/partials/Model.ts.ejs +20 -12
  36. package/src/templates/node-ts-express/partials/PrismaController.ts.ejs +72 -55
  37. package/src/templates/node-ts-express/partials/PrismaSchema.prisma.ejs +27 -12
  38. package/src/templates/node-ts-express/partials/README.md.ejs +9 -12
  39. package/src/templates/node-ts-express/partials/Seeder.ts.ejs +44 -64
  40. package/src/templates/node-ts-express/partials/docker-compose.yml.ejs +31 -16
  41. package/src/templates/node-ts-express/partials/package.json.ejs +3 -1
  42. package/src/templates/node-ts-express/partials/prismaClient.ts.ejs +4 -0
  43. package/src/templates/node-ts-express/partials/routes.ts.ejs +35 -24
  44. package/src/utils.js +19 -4
  45. package/bin/index.js +0 -141
@@ -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"]
@@ -1,16 +1,34 @@
1
1
  // Auto-generated by create-backlist
2
+ using System;
3
+
2
4
  namespace <%= projectName %>.Models
3
5
  {
4
6
  public class <%= modelName %>
5
7
  {
6
- public Guid Id { get; set; }
8
+ public Guid Id { get; set; } = Guid.NewGuid();
9
+
10
+ <%
11
+ function pascal(s){
12
+ return String(s || '')
13
+ .replace(/[-_]+(.)/g, (_,c)=>c.toUpperCase())
14
+ .replace(/^\w/, c => c.toUpperCase())
15
+ .replace(/[^a-zA-Z0-9]/g,'');
16
+ }
17
+
18
+ function mapType(t){
19
+ if (!t) return "string";
20
+ const x = String(t).toLowerCase();
21
+ if (x === 'number' || x === 'int' || x === 'integer') return "int";
22
+ if (x === 'float' || x === 'double') return "double";
23
+ if (x === 'boolean' || x === 'bool') return "bool";
24
+ if (x === 'date' || x === 'datetime') return "DateTime";
25
+ return "string";
26
+ }
27
+ -%>
7
28
 
8
- <% model.fields.forEach(field => { %>
9
- <% let csharpType = 'string'; %>
10
- <% if (field.type === 'Number') csharpType = 'int'; %>
11
- <% if (field.type === 'Boolean') csharpType = 'bool'; %>
12
- public <%= csharpType %> <%= field.name %> { get; set; }
13
- <% }); %>
29
+ <% (model.fields || []).forEach(field => { -%>
30
+ public <%= mapType(field.type) %> <%= pascal(field.name) %> { get; set; }
31
+ <% }); -%>
14
32
 
15
33
  public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
16
34
  public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
@@ -1,22 +1,30 @@
1
1
  // Auto-generated by create-backlist on <%= new Date().toISOString() %>
2
2
  import mongoose, { Schema, Document } from 'mongoose';
3
3
 
4
- // Define the interface for the Document
4
+ type FieldType = 'string' | 'number' | 'boolean';
5
+
5
6
  export interface I<%= modelName %> extends Document {
6
- <% Object.keys(schema).forEach(key => { %>
7
- <%= key %>: <%= schema[key].toLowerCase() %>;
7
+ <% Object.keys(schema).forEach(key => {
8
+ const t = String(schema[key] || 'string').toLowerCase();
9
+ const tsType = (t === 'number') ? 'number' : (t === 'boolean') ? 'boolean' : 'string';
10
+ -%>
11
+ <%= key %>: <%= tsType %>;
8
12
  <% }); %>
9
13
  }
10
14
 
11
- // Define the Mongoose Schema
12
- const <%= modelName %>Schema: Schema = new Schema({
13
- <% Object.keys(schema).forEach(key => { %>
14
- <%= key %>: {
15
- type: <%= schema[key] %>,
16
- // TODO: Add 'required', 'unique', etc. here
17
- },
15
+ const <%= modelName %>Schema: Schema = new Schema(
16
+ {
17
+ <% Object.keys(schema).forEach(key => {
18
+ const t = String(schema[key] || 'string').toLowerCase();
19
+ const mongoType = (t === 'number') ? 'Number' : (t === 'boolean') ? 'Boolean' : 'String';
20
+ -%>
21
+ <%= key %>: {
22
+ type: <%= mongoType %>,
23
+ required: false,
24
+ },
18
25
  <% }); %>
19
- }, { timestamps: true });
26
+ },
27
+ { timestamps: true }
28
+ );
20
29
 
21
- // Create and export the Model
22
30
  export default mongoose.model<I<%= modelName %>>('<%= modelName %>', <%= modelName %>Schema);
@@ -1,66 +1,83 @@
1
- // Auto-generated by create-backlist v5.0 (Prisma Version)
1
+ // Auto-generated by create-backlist v5.1 (Prisma, From Endpoints)
2
2
  import { Request, Response } from 'express';
3
- import { prisma } from '../server'; // Import the Prisma client instance
3
+ import { prisma } from '../db/prisma';
4
4
 
5
- // @desc Create a new <%= modelName %>
6
- export const create<%= modelName %> = async (req: Request, res: Response) => {
7
- try {
8
- const newDoc = await prisma.<%= modelName.toLowerCase() %>.create({
9
- data: req.body,
10
- });
11
- res.status(201).json(newDoc);
12
- } catch (error) {
13
- res.status(500).json({ message: 'Error creating document', error });
14
- }
15
- };
5
+ <%
6
+ function safeAction(name, fallback) {
7
+ if (!name) return fallback;
8
+ return String(name).replace(/[^a-zA-Z0-9_]/g, '');
9
+ }
16
10
 
17
- // @desc Get all <%= modelName %>s
18
- export const getAll<%= modelName %>s = async (req: Request, res: Response) => {
19
- try {
20
- const docs = await prisma.<%= modelName.toLowerCase() %>.findMany();
21
- res.status(200).json(docs);
22
- } catch (error) {
23
- res.status(500).json({ message: 'Error fetching documents', error });
24
- }
25
- };
11
+ function hasIdInRoute(route) {
12
+ return /:\w+/.test(route || '');
13
+ }
26
14
 
27
- // @desc Get a single <%= modelName %> by ID
28
- export const get<%= modelName %>ById = async (req: Request, res: Response) => {
29
- try {
30
- const { id } = req.params;
31
- const doc = await prisma.<%= modelName.toLowerCase() %>.findUnique({
32
- where: { id },
33
- });
34
- if (!doc) return res.status(404).json({ message: 'Document not found' });
35
- res.status(200).json(doc);
36
- } catch (error) {
37
- res.status(500).json({ message: 'Error fetching document', error });
38
- }
39
- };
15
+ function hasBody(method) {
16
+ const m = String(method || 'GET').toUpperCase();
17
+ return ['POST', 'PUT', 'PATCH'].includes(m);
18
+ }
40
19
 
41
- // @desc Update a <%= modelName %> by ID
42
- export const update<%= modelName %>ById = async (req: Request, res: Response) => {
20
+ // prisma client accessor name. Usually lowerCamelCase in prisma client.
21
+ const prismaAccessor = controllerName.charAt(0).toLowerCase() + controllerName.slice(1);
22
+ %>
23
+
24
+ /**
25
+ * Prisma model: <%= controllerName %> (client: prisma.<%= prismaAccessor %>)
26
+ * NOTE: If your prisma model name differs, adjust mapping in generator.
27
+ */
28
+
29
+ <% (endpoints || []).forEach((ep, i) => {
30
+ const method = String(ep.method || 'GET').toUpperCase();
31
+ const actionName = safeAction(ep.actionName, `handler${i}`);
32
+ const route = (ep.route || ep.path || '').replace(/^\/api/, '');
33
+ const usesId = hasIdInRoute(route);
34
+ -%>
35
+ /**
36
+ * <%= method %> <%= ep.route || ep.path || '' %>
37
+ */
38
+ export async function <%= actionName %>(req: Request, res: Response) {
43
39
  try {
44
- const { id } = req.params;
45
- const doc = await prisma.<%= modelName.toLowerCase() %>.update({
40
+ <% if (method === 'POST') { -%>
41
+ const created = await prisma.<%= prismaAccessor %>.create({ data: req.body });
42
+ return res.status(201).json(created);
43
+ <% } else if (method === 'GET' && !usesId) { -%>
44
+ const list = await prisma.<%= prismaAccessor %>.findMany();
45
+ return res.status(200).json(list);
46
+ <% } else if (method === 'GET' && usesId) { -%>
47
+ const idRaw = req.params.id;
48
+ // Try number first; fallback to string
49
+ const id: any = (idRaw !== undefined && idRaw !== null && String(Number(idRaw)) === idRaw) ? Number(idRaw) : idRaw;
50
+
51
+ const found = await prisma.<%= prismaAccessor %>.findUnique({ where: { id } });
52
+ if (!found) return res.status(404).json({ message: 'Not found' });
53
+ return res.status(200).json(found);
54
+ <% } else if ((method === 'PUT' || method === 'PATCH') && usesId) { -%>
55
+ const idRaw = req.params.id;
56
+ const id: any = (idRaw !== undefined && idRaw !== null && String(Number(idRaw)) === idRaw) ? Number(idRaw) : idRaw;
57
+
58
+ const updated = await prisma.<%= prismaAccessor %>.update({
46
59
  where: { id },
47
60
  data: req.body,
48
61
  });
49
- res.status(200).json(doc);
50
- } catch (error) {
51
- res.status(500).json({ message: 'Error updating document', error });
52
- }
53
- };
62
+ return res.status(200).json(updated);
63
+ <% } else if (method === 'DELETE' && usesId) { -%>
64
+ const idRaw = req.params.id;
65
+ const id: any = (idRaw !== undefined && idRaw !== null && String(Number(idRaw)) === idRaw) ? Number(idRaw) : idRaw;
54
66
 
55
- // @desc Delete a <%= modelName %> by ID
56
- export const delete<%= modelName %>ById = async (req: Request, res: Response) => {
57
- try {
58
- const { id } = req.params;
59
- await prisma.<%= modelName.toLowerCase() %>.delete({
60
- where: { id },
61
- });
62
- res.status(200).json({ message: 'Document deleted successfully' });
63
- } catch (error) {
64
- res.status(500).json({ message: 'Error deleting document', error });
67
+ await prisma.<%= prismaAccessor %>.delete({ where: { id } });
68
+ return res.status(200).json({ message: 'Deleted' });
69
+ <% } else { -%>
70
+ // Non-CRUD or route not matching the default patterns
71
+ // TODO: implement custom logic (query params, nested routes, etc.)
72
+ <% if (hasBody(method)) { -%>
73
+ return res.status(200).json({ message: 'TODO: implement', body: req.body, params: req.params, query: req.query });
74
+ <% } else { -%>
75
+ return res.status(200).json({ message: 'TODO: implement', params: req.params, query: req.query });
76
+ <% } -%>
77
+ <% } -%>
78
+ } catch (error: any) {
79
+ return res.status(500).json({ message: 'Internal Server Error', error: error?.message || error });
65
80
  }
66
- };
81
+ }
82
+
83
+ <% }) -%>