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
@@ -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
+ <% }) -%>
@@ -1,26 +1,41 @@
1
- // Auto-generated by create-backlist v5.0
1
+ // Auto-generated by create-backlist v5.1
2
2
 
3
3
  generator client {
4
4
  provider = "prisma-client-js"
5
5
  }
6
6
 
7
7
  datasource db {
8
- provider = "postgresql" // User can change to "mysql", "sqlite", "sqlserver", etc.
8
+ provider = "postgresql"
9
9
  url = env("DATABASE_URL")
10
10
  }
11
11
 
12
- <%# Loop through each model identified by the analyzer %>
12
+ <%
13
+ function mapPrismaType(t) {
14
+ const x = String(t || '').toLowerCase();
15
+ if (x === 'number' || x === 'int' || x === 'integer') return 'Int';
16
+ if (x === 'float' || x === 'double') return 'Float';
17
+ if (x === 'boolean' || x === 'bool') return 'Boolean';
18
+ if (x === 'date' || x === 'datetime') return 'DateTime';
19
+ return 'String';
20
+ }
21
+
22
+ function safeName(n) {
23
+ return String(n || '').replace(/[^a-zA-Z0-9_]/g, '');
24
+ }
25
+ %>
26
+
13
27
  <% modelsToGenerate.forEach(model => { %>
14
- model <%= model.name %> {
28
+ model <%= safeName(model.name) %> {
15
29
  id String @id @default(cuid())
16
- <%# Loop through each field in the model %>
17
- <% model.fields.forEach(field => { %>
18
- <%# Map JS types to Prisma types. This is a basic mapping. %>
19
- <% let prismaType = 'String'; %>
20
- <% if (field.type === 'Number') prismaType = 'Int'; %>
21
- <% if (field.type === 'Boolean') prismaType = 'Boolean'; %>
22
- <%= field.name.padEnd(10) %> <%= prismaType %><%- field.isOptional ? '?' : '' %><%- field.isUnique ? ' @unique' : '' %>
23
- <% }); %>
30
+
31
+ <% (model.fields || []).forEach(field => {
32
+ const fname = safeName(field.name);
33
+ const prismaType = mapPrismaType(field.type);
34
+ const optional = field.isOptional ? '?' : '';
35
+ const unique = field.isUnique ? ' @unique' : '';
36
+ -%>
37
+ <%= fname %> <%= prismaType %><%= optional %><%= unique %>
38
+ <% }); -%>
24
39
 
25
40
  createdAt DateTime @default(now())
26
41
  updatedAt DateTime @updatedAt
@@ -2,15 +2,12 @@
2
2
 
3
3
  This backend was auto-generated by **Backlist**.
4
4
 
5
- ## 🚀 Getting Started
6
-
7
- 1. **Navigate to the directory:**
8
- ```bash
9
- cd <%= projectName %>
10
- ```
11
-
12
- 2. **Run the development server:**
13
- ```bash
14
- npm run dev
15
- ```
16
- The server will start on `http://localhost:8000`.
5
+ ## Requirements
6
+ - Node.js 18+
7
+ - npm 9+
8
+
9
+ <% if (dbType === 'prisma') { -%>
10
+ ## Database (Prisma + PostgreSQL)
11
+ 1. Copy env:
12
+ ```bash
13
+ cp .env.example .env
@@ -1,83 +1,63 @@
1
- // Auto-generated by create-backlist v4.0 on <%= new Date().toISOString() %>
1
+ // Auto-generated by create-backlist v5.1 on <%= new Date().toISOString() %>
2
2
  import mongoose from 'mongoose';
3
3
  import dotenv from 'dotenv';
4
4
  import { faker } from '@faker-js/faker';
5
- import chalk from 'chalk'; // For colorful console logs
5
+ import chalk from 'chalk';
6
6
 
7
- // Load env vars
8
7
  dotenv.config();
9
8
 
10
- // We assume a User model exists for seeding.
11
- // The path is relative to the generated 'backend' project root.
12
9
  import User from '../src/models/User.model';
13
10
 
14
- // --- Connect to DB ---
15
- const connectDB = async () => {
16
- try {
17
- const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/<%= projectName %>';
18
- if (!MONGO_URI) {
19
- throw new Error('MONGO_URI is not defined in your .env file');
20
- }
21
- await mongoose.connect(MONGO_URI);
22
- console.log(chalk.green('MongoDB Connected for Seeder...'));
23
- } catch (err) {
24
- console.error(chalk.red(`Seeder DB Connection Error: ${err.message}`));
25
- process.exit(1);
26
- }
27
- };
11
+ function getErrorMessage(err: unknown) {
12
+ if (err instanceof Error) return err.message;
13
+ return String(err);
14
+ }
28
15
 
29
- // --- Import Data ---
30
- const importData = async () => {
31
- try {
32
- // Clear existing data
33
- await User.deleteMany();
16
+ async function connectDB() {
17
+ const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/<%= projectName %>';
18
+ if (!MONGO_URI) throw new Error('MONGO_URI is not defined');
34
19
 
35
- const sampleUsers = [];
36
- const userCount = 10; // Number of sample users to create
20
+ await mongoose.connect(MONGO_URI);
21
+ console.log(chalk.green('MongoDB Connected for Seeder...'));
22
+ }
37
23
 
38
- for (let i = 0; i < userCount; i++) {
39
- sampleUsers.push({
40
- name: faker.person.fullName(),
41
- email: faker.internet.email().toLowerCase(),
42
- password: 'password123', // All sample users will have the same password for easy testing
43
- });
44
- }
24
+ async function importData() {
25
+ // Clear existing data
26
+ await User.deleteMany({});
45
27
 
46
- await User.insertMany(sampleUsers);
28
+ const sampleUsers = Array.from({ length: 10 }).map(() => ({
29
+ name: faker.person.fullName(),
30
+ email: faker.internet.email().toLowerCase(),
31
+ password: 'password123',
32
+ }));
47
33
 
48
- console.log(chalk.green.bold('✅ Data Imported Successfully!'));
49
- process.exit();
50
- } catch (error) {
51
- console.error(chalk.red(`Error with data import: ${error.message}`));
52
- process.exit(1);
53
- }
54
- };
34
+ await User.insertMany(sampleUsers);
55
35
 
56
- // --- Destroy Data ---
57
- const destroyData = async () => {
58
- try {
59
- await User.deleteMany();
60
- // If you have other models, you can add them here for destruction
61
- // e.g., await Product.deleteMany();
36
+ console.log(chalk.green.bold('Data Imported Successfully!'));
37
+ }
62
38
 
63
- console.log(chalk.red.bold('🔥 Data Destroyed Successfully!'));
64
- process.exit();
65
- } catch (error) {
66
- console.error(chalk.red(`Error with data destruction: ${error.message}`));
67
- process.exit(1);
68
- }
69
- };
39
+ async function destroyData() {
40
+ await User.deleteMany({});
41
+ console.log(chalk.red.bold('Data Destroyed Successfully!'));
42
+ }
70
43
 
71
- // --- CLI Logic to run the seeder ---
72
- const runSeeder = async () => {
73
- await connectDB();
44
+ async function run() {
45
+ try {
46
+ await connectDB();
74
47
 
75
- // process.argv[2] will be '-d' if the script is run with `npm run destroy`
76
- if (process.argv[2] === '-d') {
77
- await destroyData();
78
- } else {
79
- await importData();
48
+ if (process.argv.includes('-d')) {
49
+ await destroyData();
50
+ } else {
51
+ await importData();
52
+ }
53
+ } catch (err) {
54
+ console.error(chalk.red(`Seeder error: ${getErrorMessage(err)}`));
55
+ process.exitCode = 1;
56
+ } finally {
57
+ try {
58
+ await mongoose.disconnect();
59
+ } catch {}
80
60
  }
81
- };
61
+ }
82
62
 
83
- runSeeder();
63
+ run();
@@ -1,4 +1,4 @@
1
- # Auto-generated by create-backlist v5.0
1
+ # Auto-generated by create-backlist v5.1
2
2
  version: '3.8'
3
3
 
4
4
  services:
@@ -8,40 +8,55 @@ services:
8
8
  ports:
9
9
  - '<%= port %>:<%= port %>'
10
10
  environment:
11
- - PORT=<%= port %>
12
- - DATABASE_URL=${DATABASE_URL}
13
- - JWT_SECRET=${JWT_SECRET}
11
+ PORT: <%= port %>
12
+ JWT_SECRET: ${JWT_SECRET:-change_me_long_secret_change_me_long_secret}
13
+ <% if (dbType === 'mongoose') { -%>
14
+ MONGO_URI: ${MONGO_URI:-mongodb://db:27017/<%= projectName %>}
15
+ <% } else if (dbType === 'prisma') { -%>
16
+ DATABASE_URL: ${DATABASE_URL:-postgresql://${DB_USER:-postgres}:${DB_PASSWORD:-password}@db:5432/${DB_NAME:-<%= projectName %>}?schema=public}
17
+ <% } -%>
14
18
  depends_on:
15
- - db
19
+ db:
20
+ condition: service_healthy
16
21
  volumes:
17
22
  - .:/usr/src/app
18
23
  - /usr/src/app/node_modules
19
24
  command: npm run dev
20
25
 
21
26
  db:
22
- <% if (dbType === 'mongoose') { %>
23
- image: mongo:latest
27
+ <% if (dbType === 'mongoose') { -%>
28
+ image: mongo:7
24
29
  container_name: <%= projectName %>-mongo-db
25
30
  ports:
26
31
  - '27017:27017'
27
32
  volumes:
28
33
  - mongo-data:/data/db
29
- <% } else if (dbType === 'prisma') { %>
30
- image: postgres:14-alpine
34
+ healthcheck:
35
+ test: ["CMD", "mongosh", "--quiet", "mongodb://localhost:27017/admin", "--eval", "db.adminCommand('ping').ok"]
36
+ interval: 5s
37
+ timeout: 5s
38
+ retries: 20
39
+ <% } else if (dbType === 'prisma') { -%>
40
+ image: postgres:16-alpine
31
41
  container_name: <%= projectName %>-postgres-db
32
42
  ports:
33
43
  - '5432:5432'
34
44
  environment:
35
- - POSTGRES_USER=${DB_USER}
36
- - POSTGRES_PASSWORD=${DB_PASSWORD}
37
- - POSTGRES_DB=${DB_NAME}
45
+ POSTGRES_USER: ${DB_USER:-postgres}
46
+ POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
47
+ POSTGRES_DB: ${DB_NAME:-<%= projectName %>}
38
48
  volumes:
39
49
  - postgres-data:/var/lib/postgresql/data
40
- <% } %>
50
+ healthcheck:
51
+ test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-<%= projectName %>}"]
52
+ interval: 5s
53
+ timeout: 5s
54
+ retries: 20
55
+ <% } -%>
41
56
 
42
57
  volumes:
43
- <% if (dbType === 'mongoose') { %>
58
+ <% if (dbType === 'mongoose') { -%>
44
59
  mongo-data:
45
- <% } else if (dbType === 'prisma') { %>
60
+ <% } else if (dbType === 'prisma') { -%>
46
61
  postgres-data:
47
- <% } %>
62
+ <% } -%>
@@ -4,9 +4,11 @@
4
4
  "private": true,
5
5
  "main": "dist/server.js",
6
6
  "scripts": {
7
+ "dev": "ts-node-dev --respawn --transpile-only src/server.ts",
7
8
  "build": "tsc",
8
9
  "start": "node dist/server.js",
9
- "dev": "ts-node-dev --respawn --transpile-only src/server.ts"
10
+ "typecheck": "tsc --noEmit",
11
+ "clean": "rimraf dist"
10
12
  },
11
13
  "dependencies": {
12
14
  "cors": "^2.8.5",
@@ -0,0 +1,4 @@
1
+ // Auto-generated by create-backlist
2
+ import { PrismaClient } from '@prisma/client';
3
+
4
+ export const prisma = new PrismaClient();
@@ -1,56 +1,67 @@
1
1
  // Auto-generated by create-backlist on <%= new Date().toISOString() %>
2
2
  import { Router, Request, Response } from 'express';
3
- <%
4
- // Build unique controller list safely
3
+
4
+ <%
5
+ /**
6
+ * Collect controllers safely
7
+ */
5
8
  const controllers = [];
6
9
  if (Array.isArray(endpoints)) {
7
- endpoints.forEach((ep) => {
10
+ endpoints.forEach(ep => {
8
11
  if (ep && ep.controllerName && ep.controllerName !== 'Default' && !controllers.includes(ep.controllerName)) {
9
12
  controllers.push(ep.controllerName);
10
13
  }
11
14
  });
12
15
  }
13
16
  %>
17
+
14
18
  <% controllers.forEach((ctrl) => { %>
15
19
  import * as <%= ctrl %>Controller from './controllers/<%= ctrl %>.controller';
16
20
  <% }) %>
17
21
 
18
- <% if (addAuth) { %>
22
+ <% if (addAuth) { -%>
19
23
  import { protect } from './middleware/Auth.middleware';
20
- <% } %>
24
+ <% } -%>
21
25
 
22
26
  const router = Router();
23
27
 
24
- // If no endpoints detected, emit a basic route so file is valid
25
28
  <% if (!Array.isArray(endpoints) || endpoints.length === 0) { %>
26
29
  router.get('/health', (_req: Request, res: Response) => {
27
30
  res.status(200).json({ ok: true, message: 'Auto-generated routes alive' });
28
31
  });
29
32
  <% } %>
30
33
 
31
- <%
34
+ <%
35
+ /**
36
+ * Render endpoints
37
+ * Prefer ep.route (normalized) and fallback to ep.path
38
+ */
32
39
  if (Array.isArray(endpoints)) {
33
- endpoints.forEach((ep) => {
34
- const rawPath = (ep && ep.path) ? ep.path : '/';
35
- const expressPath = (rawPath.replace(/^\/api/, '') || '/').replace(/{(\w+)}/g, ':$1');
36
- const method = ((ep && ep.method) ? ep.method : 'GET').toLowerCase();
37
- const ctrl = (ep && ep.controllerName) ? ep.controllerName : 'Default';
38
- const hasId = expressPath.includes(':');
39
- let handler = '';
40
+ endpoints.forEach((ep) => {
41
+ if (!ep) return;
40
42
 
41
- if (ctrl !== 'Default') {
42
- if (method === 'post' && !hasId) handler = `${ctrl}Controller.create${ctrl}`;
43
- else if (method === 'get' && !hasId) handler = `${ctrl}Controller.getAll${ctrl}s`;
44
- else if (method === 'get' && hasId) handler = `${ctrl}Controller.get${ctrl}ById`;
45
- else if (method === 'put' && hasId) handler = `${ctrl}Controller.update${ctrl}ById`;
46
- else if (method === 'delete' && hasId) handler = `${ctrl}Controller.delete${ctrl}ById`;
47
- }
43
+ const raw = (ep.route || ep.path || '/');
44
+ // mount router at /api in server.ts, so here we remove leading /api
45
+ const expressPath = (String(raw).replace(/^\/api/, '') || '/')
46
+ .replace(/{(\w+)}/g, ':$1'); // backward compat if analyzer still produces {id}
47
+
48
+ const method = String(ep.method || 'GET').toLowerCase();
49
+ const ctrl = ep.controllerName || 'Default';
50
+ const action = ep.actionName || null;
48
51
 
49
- const needsProtect = !!addAuth && (method === 'post' || method === 'put' || method === 'delete');
52
+ const needsProtect = !!addAuth && method !== 'get'; // default: protect non-GET
50
53
  const middleware = needsProtect ? 'protect, ' : '';
54
+
55
+ let handler = '';
56
+ if (ctrl !== 'Default' && action) {
57
+ handler = `${ctrl}Controller.${action}`;
58
+ }
51
59
  %>
52
- router.<%= method %>('<%- expressPath || "/" %>', <%- middleware %><%- handler || '(req: Request, res: Response) => res.status(501).json({ message: "Not Implemented" })' %>);
53
- <%
60
+ router.<%= method %>(
61
+ '<%- expressPath %>',
62
+ <%- middleware %><%- handler || '(req: Request, res: Response) => res.status(501).json({ message: "Not Implemented" })' %>
63
+ );
64
+ <%
54
65
  });
55
66
  }
56
67
  %>
@@ -0,0 +1,8 @@
1
+ FROM python:3.12-slim
2
+ WORKDIR /app
3
+ COPY requirements.txt .
4
+ RUN pip install --no-cache-dir -r requirements.txt
5
+ COPY ./app ./app
6
+ ENV PORT=8000
7
+ EXPOSE 8000
8
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
@@ -0,0 +1,8 @@
1
+ from pydantic import BaseSettings
2
+
3
+ class Settings(BaseSettings):
4
+ DB_URL: str = "postgresql://postgres:password@localhost:5432/<%= projectName %>"
5
+ JWT_SECRET: str = "change_me_super_secret"
6
+ JWT_EXPIRE_MINUTES: int = 300
7
+
8
+ settings = Settings()
@@ -0,0 +1,8 @@
1
+ from datetime import datetime, timedelta
2
+ from jose import jwt
3
+ from app.core.config import settings
4
+
5
+ def create_token(sub: str):
6
+ expire = datetime.utcnow() + timedelta(minutes=settings.JWT_EXPIRE_MINUTES)
7
+ payload = {"sub": sub, "exp": expire}
8
+ return jwt.encode(payload, settings.JWT_SECRET, algorithm="HS256")