create-backlist 5.0.6 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/package.json +1 -1
  2. package/src/generators/java.js +136 -54
  3. package/src/generators/python.js +54 -25
  4. package/src/templates/java-spring/partials/ApplicationSeeder.java.ejs +25 -0
  5. package/src/templates/java-spring/partials/AuthController.java.ejs +49 -0
  6. package/src/templates/java-spring/partials/Controller.java.ejs +28 -49
  7. package/src/templates/java-spring/partials/Dockerfile.ejs +11 -0
  8. package/src/templates/java-spring/partials/Entity.java.ejs +7 -16
  9. package/src/templates/java-spring/partials/JwtAuthFilter.java.ejs +43 -0
  10. package/src/templates/java-spring/partials/JwtService.java.ejs +30 -0
  11. package/src/templates/java-spring/partials/Repository.java.ejs +2 -5
  12. package/src/templates/java-spring/partials/SecurityConfig.java.ejs +44 -0
  13. package/src/templates/java-spring/partials/Service.java.ejs +31 -0
  14. package/src/templates/java-spring/partials/User.java.ejs +20 -0
  15. package/src/templates/java-spring/partials/UserDetailsServiceImpl.java.ejs +27 -0
  16. package/src/templates/java-spring/partials/UserRepository.java.ejs +12 -0
  17. package/src/templates/java-spring/partials/docker-compose.yml.ejs +27 -0
  18. package/src/templates/node-ts-express/partials/routes.ts.ejs +45 -51
  19. package/src/templates/python-fastapi/Dockerfile.ejs +8 -0
  20. package/src/templates/python-fastapi/app/core/config.py.ejs +8 -0
  21. package/src/templates/python-fastapi/app/core/security.py.ejs +8 -0
  22. package/src/templates/python-fastapi/app/db.py.ejs +7 -0
  23. package/src/templates/python-fastapi/app/main.py.ejs +24 -0
  24. package/src/templates/python-fastapi/app/models/user.py.ejs +9 -0
  25. package/src/templates/python-fastapi/app/routers/auth.py.ejs +33 -0
  26. package/src/templates/python-fastapi/app/routers/model_routes.py.ejs +72 -0
  27. package/src/templates/python-fastapi/app/schemas/user.py.ejs +16 -0
  28. package/src/templates/python-fastapi/docker-compose.yml.ejs +19 -0
  29. package/src/templates/python-fastapi/requirements.txt.ejs +5 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-backlist",
3
- "version": "5.0.6",
3
+ "version": "6.0.0",
4
4
  "description": "An advanced, multi-language backend generator based on frontend analysis.",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -2,95 +2,177 @@ const chalk = require('chalk');
2
2
  const { execa } = require('execa');
3
3
  const fs = require('fs-extra');
4
4
  const path = require('path');
5
- const axios = require('axios'); // For making HTTP requests
6
- const unzipper = require('unzipper'); // For extracting .zip files
5
+ const axios = require('axios');
6
+ const unzipper = require('unzipper');
7
7
  const { analyzeFrontend } = require('../analyzer');
8
8
  const { renderAndWrite, getTemplatePath } = require('./template');
9
9
 
10
+ function sanitizeArtifactId(name) {
11
+ // Lowercase, keep letters, numbers and dashes; replace others with dashes
12
+ return String(name || 'backend').toLowerCase().replace(/[^a-z0-9\-]/g, '-').replace(/-+/g, '-');
13
+ }
14
+
15
+ async function downloadInitializrZip({ groupId, artifactId, name, bootVersion, dependencies }) {
16
+ const params = new URLSearchParams({
17
+ type: 'maven-project',
18
+ language: 'java',
19
+ groupId,
20
+ artifactId,
21
+ name,
22
+ packageName: `${groupId}.${artifactId.replace(/-/g, '')}`, // com.example.myapp
23
+ dependencies: dependencies.join(','),
24
+ });
25
+
26
+ if (bootVersion) params.set('bootVersion', bootVersion);
27
+
28
+ const url = `https://start.spring.io/starter.zip?${params.toString()}`;
29
+
30
+ const res = await axios.get(url, {
31
+ responseType: 'stream',
32
+ headers: { Accept: 'application/zip' }
33
+ });
34
+
35
+ return res;
36
+ }
37
+
38
+ async function extractZipStream(stream, dest) {
39
+ await new Promise((resolve, reject) => {
40
+ const out = stream.pipe(unzipper.Extract({ path: dest }));
41
+ out.on('close', resolve);
42
+ out.on('finish', resolve);
43
+ out.on('error', reject);
44
+ });
45
+ }
46
+
10
47
  async function generateJavaProject(options) {
11
48
  const { projectDir, projectName, frontendSrcDir } = options;
12
- const group = 'com.backlist.generated'; // A default Java group ID
49
+ const groupId = 'com.backlist.generated';
50
+ const artifactId = sanitizeArtifactId(projectName || 'backend');
51
+ const name = projectName || 'backend';
13
52
 
14
53
  try {
15
- // --- Step 1: Download Base Project from Spring Initializr ---
16
54
  console.log(chalk.blue(' -> Contacting Spring Initializr to download a base Spring Boot project...'));
17
-
18
- // Define standard dependencies for a web API
19
- const dependencies = 'web,data-jpa,lombok,postgresql'; // Using PostgreSQL as an example DB driver
20
- const springInitializrUrl = `https://start.spring.io/starter.zip?type=maven-project&language=java&bootVersion=3.2.0&groupId=${group}&artifactId=${projectName}&name=${projectName}&dependencies=${dependencies}`;
21
-
22
- const response = await axios({
23
- url: springInitializrUrl,
24
- method: 'GET',
25
- responseType: 'stream'
26
- });
27
55
 
28
- // --- Step 2: Unzip the Downloaded Project ---
29
- console.log(chalk.blue(' -> Unzipping the Spring Boot project...'));
30
- await new Promise((resolve, reject) => {
31
- const stream = response.data.pipe(unzipper.Extract({ path: projectDir }));
32
- stream.on('finish', () => {
33
- console.log(chalk.gray(' -> Project unzipped successfully.'));
34
- resolve();
56
+ // Primary attempt: current stable Boot version. If this fails, we’ll retry without bootVersion.
57
+ const deps = ['web', 'data-jpa', 'lombok', 'postgresql']; // valid Initializr ids
58
+
59
+ let response;
60
+ try {
61
+ response = await downloadInitializrZip({
62
+ groupId,
63
+ artifactId,
64
+ name,
65
+ bootVersion: '3.3.4', // current stable; adjust as needed
66
+ dependencies: deps
35
67
  });
36
- stream.on('error', reject);
37
- });
68
+ } catch (err) {
69
+ // Fallback – remove bootVersion and also try smaller dependency set if needed
70
+ const fallbackDeps = ['web', 'data-jpa', 'lombok'];
71
+ try {
72
+ console.log(chalk.yellow(' -> Initial attempt failed. Retrying with default Boot version...'));
73
+ response = await downloadInitializrZip({
74
+ groupId,
75
+ artifactId,
76
+ name,
77
+ bootVersion: '', // let Initializr pick latest
78
+ dependencies: deps
79
+ });
80
+ } catch {
81
+ console.log(chalk.yellow(' -> Second attempt failed. Retrying with minimal dependencies...'));
82
+ response = await downloadInitializrZip({
83
+ groupId,
84
+ artifactId,
85
+ name,
86
+ bootVersion: '',
87
+ dependencies: fallbackDeps
88
+ });
89
+ }
90
+ }
38
91
 
39
- // --- Step 3: Analyze Frontend ---
92
+ console.log(chalk.blue(' -> Unzipping the Spring Boot project...'));
93
+ await extractZipStream(response.data, projectDir);
94
+
95
+ // Analyze frontend and plan entities/controllers
40
96
  const endpoints = await analyzeFrontend(frontendSrcDir);
41
97
  const modelsToGenerate = new Map();
42
- endpoints.forEach(ep => {
43
- if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
44
- modelsToGenerate.set(ep.controllerName, { name: ep.controllerName, fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type })) });
98
+ (Array.isArray(endpoints) ? endpoints : []).forEach(ep => {
99
+ if (ep && ep.schemaFields && ep.controllerName && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
100
+ modelsToGenerate.set(ep.controllerName, {
101
+ name: ep.controllerName,
102
+ fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type }))
103
+ });
45
104
  }
46
105
  });
47
106
 
48
- // --- Step 4: Generate Java Entities and Controllers ---
107
+ // Generate Entities/Repositories/Controllers (basic)
49
108
  if (modelsToGenerate.size > 0) {
50
- console.log(chalk.blue(' -> Generating Java entities and controllers...'));
51
-
52
- const javaSrcPath = path.join(projectDir, 'src', 'main', 'java', ...group.split('.'), projectName);
53
- const entityDir = path.join(javaSrcPath, 'model');
54
- const controllerDir = path.join(javaSrcPath, 'controller');
109
+ console.log(chalk.blue(' -> Generating Java entities, repositories, and controllers...'));
110
+
111
+ // Spring Initializr zips project as <artifactId> root folder; if you extracted to projectDir,
112
+ // files are already in the right place. Compute Java src path:
113
+ const javaSrcRoot = path.join(projectDir, 'src', 'main', 'java', ...groupId.split('.'), artifactId.replace(/-/g, ''));
114
+ const entityDir = path.join(javaSrcRoot, 'model');
115
+ const repoDir = path.join(javaSrcRoot, 'repository');
116
+ const controllerDir = path.join(javaSrcRoot, 'controller');
117
+
55
118
  await fs.ensureDir(entityDir);
119
+ await fs.ensureDir(repoDir);
56
120
  await fs.ensureDir(controllerDir);
57
121
 
58
122
  for (const [modelName, modelData] of modelsToGenerate.entries()) {
59
123
  await renderAndWrite(
60
124
  getTemplatePath('java-spring/partials/Entity.java.ejs'),
61
125
  path.join(entityDir, `${modelName}.java`),
62
- { group, projectName, modelName, model: modelData }
126
+ { group: groupId, projectName: artifactId.replace(/-/g, ''), modelName, model: modelData }
127
+ );
128
+ await renderAndWrite(
129
+ getTemplatePath('java-spring/partials/Repository.java.ejs'),
130
+ path.join(repoDir, `${modelName}Repository.java`),
131
+ { group: groupId, projectName: artifactId.replace(/-/g, ''), modelName }
63
132
  );
64
133
  await renderAndWrite(
65
134
  getTemplatePath('java-spring/partials/Controller.java.ejs'),
66
135
  path.join(controllerDir, `${modelName}Controller.java`),
67
- { group, projectName, controllerName: modelName }
136
+ { group: groupId, projectName: artifactId.replace(/-/g, ''), controllerName: modelName, model: modelData }
68
137
  );
69
138
  }
70
139
  }
71
140
 
72
- // --- Step 5: Configure application.properties ---
73
- console.log(chalk.blue(' -> Configuring database in application.properties...'));
74
- const propsPath = path.join(projectDir, 'src', 'main', 'resources', 'application.properties');
75
- const dbProps = [
76
- `\n\n# --- Auto-generated by create-backlist ---`,
77
- `# --- Database Configuration (PostgreSQL) ---`,
78
- `spring.datasource.url=jdbc:postgresql://localhost:5432/${projectName}`,
79
- `spring.datasource.username=postgres`,
80
- `spring.datasource.password=password`,
81
- `spring.jpa.hibernate.ddl-auto=update`,
82
- `spring.jpa.show-sql=true`,
83
- ];
84
- await fs.appendFile(propsPath, dbProps.join('\n'));
85
-
86
- console.log(chalk.green(' -> Java (Spring Boot) backend generation is complete!'));
87
- console.log(chalk.yellow('\nTo run your new Java backend:'));
88
- console.log(chalk.cyan(' 1. Open the project in a Java IDE (like IntelliJ IDEA or VS Code).'));
89
- console.log(chalk.cyan(' 2. Run the main application file.'));
141
+ // Append DB config (PostgreSQL) to application.properties (non-fatal if fails)
142
+ try {
143
+ const propsPath = path.join(projectDir, 'src', 'main', 'resources', 'application.properties');
144
+ const dbProps = [
145
+ `\n\n# --- Auto-generated by create-backlist ---`,
146
+ `spring.datasource.url=jdbc:postgresql://localhost:5432/${artifactId}`,
147
+ `spring.datasource.username=postgres`,
148
+ `spring.datasource.password=password`,
149
+ `spring.jpa.hibernate.ddl-auto=update`,
150
+ `spring.jpa.show-sql=true`,
151
+ ].join('\n');
152
+ await fs.appendFile(propsPath, dbProps);
153
+ } catch (e) {
154
+ console.log(chalk.yellow(' -> Could not update application.properties (continuing).'));
155
+ }
90
156
 
157
+ console.log(chalk.green(' -> Java (Spring Boot) backend generation is complete!'));
158
+ console.log(chalk.yellow('\nNext steps:'));
159
+ console.log(chalk.cyan(` cd ${path.basename(projectDir)}`));
160
+ console.log(chalk.cyan(' ./mvnw spring-boot:run # or use your IDE to run Application class'));
91
161
 
92
162
  } catch (error) {
93
- if (error.response) {
163
+ if (error.response && error.response.status) {
164
+ console.error(chalk.red(` -> Initializr error status: ${error.response.status}`));
165
+ if (error.response.data) {
166
+ try {
167
+ // Try read error text body for hints
168
+ const text = (await (async () => {
169
+ let buf = '';
170
+ for await (const chunk of error.response.data) buf += chunk.toString();
171
+ return buf;
172
+ })());
173
+ console.error(chalk.yellow(' -> Initializr response body:'), text);
174
+ } catch {}
175
+ }
94
176
  throw new Error(`Failed to download from Spring Initializr. Status: ${error.response.status}`);
95
177
  }
96
178
  throw error;
@@ -19,52 +19,81 @@ async function generatePythonProject(options) {
19
19
  }
20
20
  });
21
21
 
22
- // --- Step 2: Scaffold Base Python Project ---
23
- console.log(chalk.blue(' -> Scaffolding Python (FastAPI) project...'));
22
+ // Add a default User model if none was detected but auth might be added later
23
+ if (!modelsToGenerate.has('User')) {
24
+ modelsToGenerate.set('User', { name: 'User', fields: [{ name: 'name', type: 'String' }, { name: 'email', type: 'String'}] });
25
+ }
26
+
27
+ // --- Step 2: Scaffold Base Python Project Directories ---
28
+ console.log(chalk.blue(' -> Scaffolding Python (FastAPI) project structure...'));
24
29
  const appDir = path.join(projectDir, 'app');
25
- const routesDir = path.join(appDir, 'routes');
30
+ const coreDir = path.join(appDir, 'core');
31
+ const dbDir = path.join(appDir, 'db'); // For DB connection
32
+ const modelsDir = path.join(appDir, 'models');
33
+ const schemasDir = path.join(appDir, 'schemas');
34
+ const routesDir = path.join(appDir, 'routers');
35
+
26
36
  await fs.ensureDir(appDir);
37
+ await fs.ensureDir(coreDir);
38
+ await fs.ensureDir(dbDir);
39
+ await fs.ensureDir(modelsDir);
40
+ await fs.ensureDir(schemasDir);
27
41
  await fs.ensureDir(routesDir);
28
42
 
29
- // --- Step 3: Generate Files from Templates ---
43
+ // --- Step 3: Generate All Python Files from Templates ---
30
44
  const controllers = Array.from(modelsToGenerate.keys());
31
45
 
32
- // Generate main.py
46
+ // Generate main application file
33
47
  await renderAndWrite(getTemplatePath('python-fastapi/main.py.ejs'), path.join(projectDir, 'app', 'main.py'), { projectName, controllers });
34
-
35
- // Generate requirements.txt
48
+ // Generate dependency file
36
49
  await renderAndWrite(getTemplatePath('python-fastapi/requirements.txt.ejs'), path.join(projectDir, 'requirements.txt'), {});
37
50
 
38
- // Generate route file for each model
51
+ // Generate core files (config, security)
52
+ await renderAndWrite(getTemplatePath('python-fastapi/app/core/config.py.ejs'), path.join(coreDir, 'config.py'), { projectName });
53
+ await renderAndWrite(getTemplatePath('python-fastapi/app/core/security.py.ejs'), path.join(coreDir, 'security.py'), {});
54
+
55
+ // Generate DB connection and base model
56
+ await renderAndWrite(getTemplatePath('python-fastapi/app/db.py.ejs'), path.join(appDir, 'db.py'), {});
57
+
58
+ // Generate model and schema files for User (for auth)
59
+ await renderAndWrite(getTemplatePath('python-fastapi/app/models/user.py.ejs'), path.join(modelsDir, 'user.py'), {});
60
+ await renderAndWrite(getTemplatePath('python-fastapi/app/schemas/user.py.ejs'), path.join(schemasDir, 'user.py'), {});
61
+
62
+ // Generate router for auth
63
+ await renderAndWrite(getTemplatePath('python-fastapi/app/routers/auth.py.ejs'), path.join(routesDir, 'auth.py'), {});
64
+
65
+ // Generate router for each detected model
39
66
  for (const [modelName, modelData] of modelsToGenerate.entries()) {
40
- await renderAndWrite(
41
- getTemplatePath('python-fastapi/routes.py.ejs'),
42
- path.join(routesDir, `${modelName.toLowerCase()}_routes.py`),
43
- { modelName, schema: modelData }
44
- );
67
+ if(modelName.toLowerCase() !== 'user') { // User model is handled separately
68
+ // In a full implementation, you'd have generic model/schema templates too
69
+ }
70
+ await renderAndWrite(getTemplatePath('python-fastapi/app/routers/model_routes.py.ejs'), path.join(routesDir, `${modelName.toLowerCase()}_routes.py`), { modelName, schema: modelData });
45
71
  }
46
72
 
47
73
  // --- Step 4: Setup Virtual Environment and Install Dependencies ---
48
74
  console.log(chalk.magenta(' -> Setting up virtual environment and installing dependencies...'));
49
- // Create a virtual environment
50
75
  await execa('python', ['-m', 'venv', 'venv'], { cwd: projectDir });
51
76
 
52
- // Determine the correct pip executable path based on OS
53
- const pipPath = process.platform === 'win32'
54
- ? path.join('venv', 'Scripts', 'pip')
55
- : path.join('venv', 'bin', 'pip');
56
-
57
- // Install dependencies using the virtual environment's pip
77
+ const pipPath = process.platform === 'win32' ? path.join('venv', 'Scripts', 'pip') : path.join('venv', 'bin', 'pip');
58
78
  await execa(path.join(projectDir, pipPath), ['install', '-r', 'requirements.txt'], { cwd: projectDir });
59
79
 
60
- console.log(chalk.green(' -> Python backend generation is complete!'));
61
- console.log(chalk.yellow('\nTo run your new Python backend:'));
62
- console.log(chalk.cyan(' 1. Activate the virtual environment: `source venv/bin/activate` (or `venv\\Scripts\\activate` on Windows)'));
63
- console.log(chalk.cyan(' 2. Start the server: `uvicorn app.main:app --reload`'));
80
+ // --- Step 5: Generate Docker and .env files ---
81
+ await renderAndWrite(getTemplatePath('python-fastapi/Dockerfile.ejs'), path.join(projectDir, 'Dockerfile'), {});
82
+ await renderAndWrite(getTemplatePath('python-fastapi/docker-compose.yml.ejs'), path.join(projectDir, 'docker-compose.yml'), { projectName });
83
+
84
+ const envContent = `DATABASE_URL="postgresql://postgres:password@db:5432/${projectName}"\nJWT_SECRET="a_very_secret_key_change_this"`;
85
+ await fs.writeFile(path.join(projectDir, '.env'), envContent);
86
+ await fs.writeFile(path.join(projectDir, '.env.example'), envContent);
87
+
88
+
89
+ console.log(chalk.green(' -> Python (FastAPI) backend generation is complete!'));
90
+ console.log(chalk.yellow('\nTo run your new Python backend with Docker:'));
91
+ console.log(chalk.cyan(' 1. Make sure Docker Desktop is running.'));
92
+ console.log(chalk.cyan(' 2. Run: `docker-compose up --build`'));
93
+ console.log(chalk.cyan(' 3. API will be available at http://localhost:8000 and docs at http://localhost:8000/docs'));
64
94
 
65
95
 
66
96
  } catch (error) {
67
- // Improve error message for command not found
68
97
  if (error.code === 'ENOENT') {
69
98
  throw new Error(`'${error.command}' command not found. Please ensure Python and venv are installed and in your system's PATH.`);
70
99
  }
@@ -0,0 +1,25 @@
1
+ // Auto-generated by create-backlist
2
+ package <%= group %>.<%= projectName %>;
3
+
4
+ import org.springframework.boot.CommandLineRunner;
5
+ import org.springframework.context.annotation.Bean;
6
+ import org.springframework.context.annotation.Configuration;
7
+ import <%= group %>.<%= projectName %>.model.User;
8
+ import <%= group %>.<%= projectName %>.repository.UserRepository;
9
+ import org.springframework.security.crypto.password.PasswordEncoder;
10
+
11
+ @Configuration
12
+ public class ApplicationSeeder {
13
+ @Bean
14
+ CommandLineRunner seed(UserRepository userRepository, PasswordEncoder encoder) {
15
+ return args -> {
16
+ if (userRepository.findByEmail("admin@example.com").isEmpty()) {
17
+ User admin = new User();
18
+ admin.setName("Admin");
19
+ admin.setEmail("admin@example.com");
20
+ admin.setPassword(encoder.encode("admin123"));
21
+ userRepository.save(admin);
22
+ }
23
+ };
24
+ }
25
+ }
@@ -0,0 +1,49 @@
1
+ // Auto-generated by create-backlist
2
+ package <%= group %>.<%= projectName %>.controller;
3
+
4
+ import <%= group %>.<%= projectName %>.model.User;
5
+ import <%= group %>.<%= projectName %>.repository.UserRepository;
6
+ import <%= group %>.<%= projectName %>.security.JwtService;
7
+ import org.springframework.http.ResponseEntity;
8
+ import org.springframework.http.HttpStatus;
9
+ import org.springframework.security.crypto.password.PasswordEncoder;
10
+ import org.springframework.web.bind.annotation.*;
11
+
12
+ import java.util.Optional;
13
+
14
+ @RestController
15
+ @RequestMapping("/api/auth")
16
+ @CrossOrigin(origins = "*")
17
+ public class AuthController {
18
+
19
+ private final UserRepository repo;
20
+ private final PasswordEncoder encoder;
21
+ private final JwtService jwt;
22
+
23
+ public AuthController(UserRepository repo, PasswordEncoder encoder, JwtService jwt) {
24
+ this.repo = repo; this.encoder = encoder; this.jwt = jwt;
25
+ }
26
+
27
+ @PostMapping("/register")
28
+ public ResponseEntity<?> register(@RequestBody User req) {
29
+ if (repo.findByEmail(req.getEmail()).isPresent()) {
30
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("User already exists");
31
+ }
32
+ req.setPassword(encoder.encode(req.getPassword()));
33
+ repo.save(req);
34
+ String token = jwt.generateToken(req.getEmail());
35
+ return ResponseEntity.status(HttpStatus.CREATED).body(new TokenResponse(token));
36
+ }
37
+
38
+ @PostMapping("/login")
39
+ public ResponseEntity<?> login(@RequestBody User req) {
40
+ Optional<User> current = repo.findByEmail(req.getEmail());
41
+ if (current.isEmpty()) return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid credentials");
42
+ if (!encoder.matches(req.getPassword(), current.get().getPassword()))
43
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid credentials");
44
+ String token = jwt.generateToken(req.getEmail());
45
+ return ResponseEntity.ok(new TokenResponse(token));
46
+ }
47
+
48
+ public record TokenResponse(String token) {}
49
+ }
@@ -1,61 +1,40 @@
1
1
  // Auto-generated by create-backlist
2
2
  package <%= group %>.<%= projectName %>.controller;
3
3
 
4
- import <%= group %>.<%= projectName %>.model.<%= controllerName %>;
5
- import <%= group %>.<%= projectName %>.repository.<%= controllerName %>Repository;
6
- import org.springframework.beans.factory.annotation.Autowired;
7
- import org.springframework.http.HttpStatus;
8
4
  import org.springframework.http.ResponseEntity;
5
+ import org.springframework.http.HttpStatus;
9
6
  import org.springframework.web.bind.annotation.*;
10
-
11
7
  import java.util.List;
12
8
  import java.util.Optional;
9
+ import <%= group %>.<%= projectName %>.service.<%= controllerName %>Service;
10
+ import <%= group %>.<%= projectName %>.model.<%= controllerName %>;
13
11
 
14
12
  @RestController
15
- @CrossOrigin(origins = "*") // Allow all origins for development
16
13
  @RequestMapping("/api/<%= controllerName.toLowerCase() %>s")
14
+ @CrossOrigin(origins = "*")
17
15
  public class <%= controllerName %>Controller {
18
-
19
- @Autowired
20
- private <%= controllerName %>Repository repository;
21
-
22
- @GetMapping
23
- public List<<%= controllerName %>> getAll<%= controllerName %>s() {
24
- return repository.findAll();
25
- }
26
-
27
- @GetMapping("/{id}")
28
- public ResponseEntity<<%= controllerName %>> get<%= controllerName %>ById(@PathVariable Long id) {
29
- Optional<<%= controllerName %>> item = repository.findById(id);
30
- return item.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
31
- }
32
-
33
- @PostMapping
34
- public ResponseEntity<<%= controllerName %>> create<%= controllerName %>(@RequestBody <%= controllerName %> newItem) {
35
- <%= controllerName %> savedItem = repository.save(newItem);
36
- return new ResponseEntity<>(savedItem, HttpStatus.CREATED);
37
- }
38
-
39
- @PutMapping("/{id}")
40
- public ResponseEntity<<%= controllerName %>> update<%= controllerName %>(@PathVariable Long id, @RequestBody <%= controllerName %> updatedItem) {
41
- return repository.findById(id)
42
- .map(item -> {
43
- // Manually map fields to update. For simplicity, we assume all fields are updatable.
44
- <% model.fields.forEach(field => { %>
45
- item.set<%= field.name.charAt(0).toUpperCase() + field.name.slice(1) %>(updatedItem.get<%= field.name.charAt(0).toUpperCase() + field.name.slice(1) %>());
46
- <% }); %>
47
- <%= controllerName %> savedItem = repository.save(item);
48
- return ResponseEntity.ok(savedItem);
49
- })
50
- .orElseGet(() -> ResponseEntity.notFound().build());
51
- }
52
-
53
- @DeleteMapping("/{id}")
54
- public ResponseEntity<Void> delete<%= controllerName %>(@PathVariable Long id) {
55
- if (!repository.existsById(id)) {
56
- return ResponseEntity.notFound().build();
57
- }
58
- repository.deleteById(id);
59
- return ResponseEntity.noContent().build();
60
- }
16
+ private final <%= controllerName %>Service service;
17
+ public <%= controllerName %>Controller(<%= controllerName %>Service service) { this.service = service; }
18
+
19
+ @GetMapping public List<<%= controllerName %>> all() { return service.findAll(); }
20
+
21
+ @GetMapping("/{id}")
22
+ public ResponseEntity<<%= controllerName %>> one(@PathVariable Long id) {
23
+ return service.findById(id).map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
24
+ }
25
+
26
+ @PostMapping
27
+ public ResponseEntity<<%= controllerName %>> create(@RequestBody <%= controllerName %> m) {
28
+ return new ResponseEntity<>(service.create(m), HttpStatus.CREATED);
29
+ }
30
+
31
+ @PutMapping("/{id}")
32
+ public ResponseEntity<<%= controllerName %>> update(@PathVariable Long id, @RequestBody <%= controllerName %> m) {
33
+ return service.update(id, m).map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
34
+ }
35
+
36
+ @DeleteMapping("/{id}")
37
+ public ResponseEntity<Void> delete(@PathVariable Long id) {
38
+ return service.delete(id) ? ResponseEntity.noContent().build() : ResponseEntity.notFound().build();
39
+ }
61
40
  }
@@ -0,0 +1,11 @@
1
+ # Auto-generated by create-backlist
2
+ FROM eclipse-temurin:21-jdk AS build
3
+ WORKDIR /app
4
+ COPY . .
5
+ RUN ./mvnw -q -DskipTests package
6
+
7
+ FROM eclipse-temurin:21-jre
8
+ WORKDIR /app
9
+ COPY --from=build /app/target/*.jar app.jar
10
+ EXPOSE 8080
11
+ ENTRYPOINT ["java", "-jar", "app.jar"]
@@ -1,24 +1,15 @@
1
- // Auto-generated by create-backlist v6.0
1
+ // Auto-generated by create-backlist
2
2
  package <%= group %>.<%= projectName %>.model;
3
3
 
4
- import jakarta.persistence.Entity;
5
- import jakarta.persistence.Id;
6
- import jakarta.persistence.GeneratedValue;
7
- import jakarta.persistence.GenerationType;
4
+ import jakarta.persistence.*;
8
5
  import lombok.Data;
9
6
 
10
7
  @Data
11
8
  @Entity
12
9
  public class <%= modelName %> {
13
-
14
- @Id
15
- @GeneratedValue(strategy = GenerationType.AUTO)
16
- private Long id;
17
-
18
- <% model.fields.forEach(field => { %>
19
- <% let javaType = 'String'; %>
20
- <% if (field.type === 'Number') javaType = 'Integer'; %>
21
- <% if (field.type === 'Boolean') javaType = 'boolean'; %>
22
- private <%= javaType %> <%= field.name %>;
23
- <% }); %>
10
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
11
+ private Long id;
12
+ <% model.fields.forEach(f => { %>
13
+ private <%= f.type === 'Number' ? 'Integer' : (f.type === 'Boolean' ? 'Boolean' : 'String') %> <%= f.name %>;
14
+ <% }) %>
24
15
  }
@@ -0,0 +1,43 @@
1
+ // Auto-generated by create-backlist
2
+ package <%= group %>.<%= projectName %>.security;
3
+
4
+ import jakarta.servlet.FilterChain;
5
+ import jakarta.servlet.ServletException;
6
+ import jakarta.servlet.http.HttpServletRequest;
7
+ import jakarta.servlet.http.HttpServletResponse;
8
+ import org.springframework.security.core.userdetails.UserDetailsService;
9
+ import org.springframework.security.core.userdetails.UserDetails;
10
+ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
11
+ import org.springframework.security.core.context.SecurityContextHolder;
12
+ import org.springframework.stereotype.Component;
13
+ import org.springframework.web.filter.OncePerRequestFilter;
14
+
15
+ import java.io.IOException;
16
+
17
+ @Component
18
+ public class JwtAuthFilter extends OncePerRequestFilter {
19
+
20
+ private final JwtService jwtService;
21
+ private final UserDetailsService uds;
22
+
23
+ public JwtAuthFilter(JwtService jwtService, UserDetailsService uds) {
24
+ this.jwtService = jwtService;
25
+ this.uds = uds;
26
+ }
27
+
28
+ @Override
29
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
30
+ throws ServletException, IOException {
31
+ String header = request.getHeader("Authorization");
32
+ if (header != null && header.startsWith("Bearer ")) {
33
+ String token = header.substring(7);
34
+ try {
35
+ String subject = jwtService.validateAndGetSubject(token);
36
+ UserDetails ud = uds.loadUserByUsername(subject);
37
+ var auth = new UsernamePasswordAuthenticationToken(ud, null, ud.getAuthorities());
38
+ SecurityContextHolder.getContext().setAuthentication(auth);
39
+ } catch (Exception ignored) {}
40
+ }
41
+ chain.doFilter(request, response);
42
+ }
43
+ }
@@ -0,0 +1,30 @@
1
+ // Auto-generated by create-backlist
2
+ package <%= group %>.<%= projectName %>.security;
3
+
4
+ import io.jsonwebtoken.*;
5
+ import io.jsonwebtoken.security.Keys;
6
+ import org.springframework.stereotype.Service;
7
+
8
+ import java.security.Key;
9
+ import java.util.Date;
10
+
11
+ @Service
12
+ public class JwtService {
13
+ private final Key key = Keys.hmacShaKeyFor(System.getenv("JWT_SECRET") != null
14
+ ? System.getenv("JWT_SECRET").getBytes()
15
+ : "change_this_dev_secret_change_this_dev_secret".getBytes());
16
+
17
+ public String generateToken(String userId) {
18
+ return Jwts.builder()
19
+ .setSubject(userId)
20
+ .setIssuedAt(new Date())
21
+ .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 5))
22
+ .signWith(key, SignatureAlgorithm.HS256)
23
+ .compact();
24
+ }
25
+
26
+ public String validateAndGetSubject(String token) {
27
+ return Jwts.parserBuilder().setSigningKey(key).build()
28
+ .parseClaimsJws(token).getBody().getSubject();
29
+ }
30
+ }
@@ -1,12 +1,9 @@
1
1
  // Auto-generated by create-backlist
2
2
  package <%= group %>.<%= projectName %>.repository;
3
3
 
4
- import <%= group %>.<%= projectName %>.model.<%= modelName %>;
5
4
  import org.springframework.data.jpa.repository.JpaRepository;
6
5
  import org.springframework.stereotype.Repository;
6
+ import <%= group %>.<%= projectName %>.model.<%= modelName %>;
7
7
 
8
8
  @Repository
9
- public interface <%= modelName %>Repository extends JpaRepository<<%= modelName %>, Long> {
10
- // Spring Data JPA automatically provides CRUD methods like findAll(), findById(), save(), deleteById()
11
- // You can add custom query methods here if needed.
12
- }
9
+ public interface <%= modelName %>Repository extends JpaRepository<<%= modelName %>, Long> {}
@@ -0,0 +1,44 @@
1
+ // Auto-generated by create-backlist
2
+ package <%= group %>.<%= projectName %>.config;
3
+
4
+ import <%= group %>.<%= projectName %>.security.JwtAuthFilter;
5
+ import org.springframework.context.annotation.Bean;
6
+ import org.springframework.context.annotation.Configuration;
7
+ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
8
+ import org.springframework.security.config.Customizer;
9
+ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
10
+ import org.springframework.security.web.SecurityFilterChain;
11
+ import org.springframework.security.authentication.AuthenticationManager;
12
+ import org.springframework.security.authentication.AuthenticationManagerResolver;
13
+ import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
14
+ import org.springframework.security.core.userdetails.UserDetailsService;
15
+ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
16
+ import org.springframework.security.crypto.password.PasswordEncoder;
17
+ import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
18
+
19
+ @Configuration
20
+ @EnableWebSecurity
21
+ public class SecurityConfig {
22
+
23
+ private final JwtAuthFilter jwtAuthFilter;
24
+ private final UserDetailsService userDetailsService;
25
+
26
+ public SecurityConfig(JwtAuthFilter jwtAuthFilter, UserDetailsService uds) {
27
+ this.jwtAuthFilter = jwtAuthFilter;
28
+ this.userDetailsService = uds;
29
+ }
30
+
31
+ @Bean
32
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
33
+ http.csrf(csrf -> csrf.disable())
34
+ .authorizeHttpRequests(auth -> auth
35
+ .requestMatchers("/api/auth/**", "/actuator/**").permitAll()
36
+ .anyRequest().authenticated()
37
+ )
38
+ .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
39
+ .httpBasic(Customizer.withDefaults());
40
+ return http.build();
41
+ }
42
+
43
+ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
44
+ }
@@ -0,0 +1,31 @@
1
+ // Auto-generated by create-backlist
2
+ package <%= group %>.<%= projectName %>.service;
3
+
4
+ import org.springframework.stereotype.Service;
5
+ import java.util.List;
6
+ import java.util.Optional;
7
+ import <%= group %>.<%= projectName %>.repository.<%= modelName %>Repository;
8
+ import <%= group %>.<%= projectName %>.model.<%= modelName %>;
9
+
10
+ @Service
11
+ public class <%= modelName %>Service {
12
+ private final <%= modelName %>Repository repo;
13
+ public <%= modelName %>Service(<%= modelName %>Repository repo) { this.repo = repo; }
14
+
15
+ public List<<%= modelName %>> findAll() { return repo.findAll(); }
16
+ public Optional<<%= modelName %>> findById(Long id) { return repo.findById(id); }
17
+ public <%= modelName %> create(<%= modelName %> m) { return repo.save(m); }
18
+ public Optional<<%= modelName %>> update(Long id, <%= modelName %> m) {
19
+ return repo.findById(id).map(existing -> {
20
+ <% model.fields.forEach(f => { %>
21
+ existing.set<%= f.name.charAt(0).toUpperCase() + f.name.slice(1) %>(m.get<%= f.name.charAt(0).toUpperCase() + f.name.slice(1) %>());
22
+ <% }) %>
23
+ return repo.save(existing);
24
+ });
25
+ }
26
+ public boolean delete(Long id) {
27
+ if (!repo.existsById(id)) return false;
28
+ repo.deleteById(id);
29
+ return true;
30
+ }
31
+ }
@@ -0,0 +1,20 @@
1
+ // Auto-generated by create-backlist
2
+ package <%= group %>.<%= projectName %>.model;
3
+
4
+ import jakarta.persistence.*;
5
+ import lombok.Data;
6
+
7
+ @Data
8
+ @Entity
9
+ @Table(name="users")
10
+ public class User {
11
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
12
+ private Long id;
13
+
14
+ private String name;
15
+
16
+ @Column(unique = true)
17
+ private String email;
18
+
19
+ private String password;
20
+ }
@@ -0,0 +1,27 @@
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
+ import org.springframework.security.core.userdetails.UserDetailsService;
7
+ import org.springframework.security.core.userdetails.UserDetails;
8
+ import org.springframework.security.core.userdetails.UsernameNotFoundException;
9
+ import org.springframework.stereotype.Service;
10
+ import org.springframework.security.core.userdetails.User.UserBuilder;
11
+ import org.springframework.security.core.userdetails.User.*;
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).orElseThrow(() -> new UsernameNotFoundException("Not found"));
25
+ return withUsername(u.getEmail()).password(u.getPassword()).authorities("USER").build();
26
+ }
27
+ }
@@ -0,0 +1,12 @@
1
+ // Auto-generated by create-backlist
2
+ package <%= group %>.<%= projectName %>.repository;
3
+
4
+ import java.util.Optional;
5
+ import org.springframework.data.jpa.repository.JpaRepository;
6
+ import org.springframework.stereotype.Repository;
7
+ import <%= group %>.<%= projectName %>.model.User;
8
+
9
+ @Repository
10
+ public interface UserRepository extends JpaRepository<User, Long> {
11
+ Optional<User> findByEmail(String email);
12
+ }
@@ -0,0 +1,27 @@
1
+ version: '3.8'
2
+ services:
3
+ db:
4
+ image: postgres:16-alpine
5
+ environment:
6
+ POSTGRES_USER: ${DB_USER:-postgres}
7
+ POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
8
+ POSTGRES_DB: ${DB_NAME:-<%= projectName %>}
9
+ ports:
10
+ - "5432:5432"
11
+ volumes:
12
+ - pgdata:/var/lib/postgresql/data
13
+
14
+ app:
15
+ build: .
16
+ depends_on:
17
+ - db
18
+ environment:
19
+ - JWT_SECRET=${JWT_SECRET:-change_me_long_secret}
20
+ - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/${DB_NAME:-<%= projectName %>}
21
+ - SPRING_DATASOURCE_USERNAME=${DB_USER:-postgres}
22
+ - SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD:-password}
23
+ ports:
24
+ - "8080:8080"
25
+
26
+ volumes:
27
+ pgdata:
@@ -1,64 +1,58 @@
1
- // Auto-generated by create-backlist v3.0 on <%= new Date().toISOString() %>
1
+ // Auto-generated by create-backlist on <%= new Date().toISOString() %>
2
2
  import { Router, Request, Response } from 'express';
3
- <%# Create a unique set of controller names from the endpoints array %>
4
- <% const controllersToImport = new Set(endpoints.map(ep => ep.controllerName).filter(name => name !== 'Default')); %>
5
-
6
- // Import all the generated controllers
7
- <% for (const controller of controllersToImport) { %>
8
- import * as <%= controller %>Controller from '../controllers/<%= controller %>.controller';
9
- <% } %>
3
+ <%
4
+ // Build unique controller list safely
5
+ const controllers = [];
6
+ if (Array.isArray(endpoints)) {
7
+ endpoints.forEach((ep) => {
8
+ if (ep && ep.controllerName && ep.controllerName !== 'Default' && !controllers.includes(ep.controllerName)) {
9
+ controllers.push(ep.controllerName);
10
+ }
11
+ });
12
+ }
13
+ %>
14
+ <% controllers.forEach((ctrl) => { %>
15
+ import * as <%= ctrl %>Controller from './controllers/<%= ctrl %>.controller';
16
+ <% }) %>
10
17
 
11
- <%# Import the protect middleware only if authentication is enabled %>
12
18
  <% if (addAuth) { %>
13
- import { protect } from '../middleware/Auth.middleware';
19
+ import { protect } from './middleware/Auth.middleware';
14
20
  <% } %>
15
21
 
16
22
  const router = Router();
17
23
 
18
- <%# Loop through each endpoint found by the analyzer %>
19
- <% endpoints.forEach(endpoint => { %>
20
- <%
21
- // Convert URL path for Express router (e.g., /api/users/{id} -> /users/:id)
22
- const expressPath = endpoint.path.replace('/api', '').replace(/{(\w+)}/g, ':$1');
23
- const controllerName = endpoint.controllerName;
24
- let handlerFunction;
24
+ // If no endpoints detected, emit a basic route so file is valid
25
+ <% if (!Array.isArray(endpoints) || endpoints.length === 0) { %>
26
+ router.get('/health', (_req: Request, res: Response) => {
27
+ res.status(200).json({ ok: true, message: 'Auto-generated routes alive' });
28
+ });
29
+ <% } %>
25
30
 
26
- // --- LOGIC TO MAP ENDPOINT TO A CRUD CONTROLLER FUNCTION ---
27
- if (controllerName !== 'Default') {
28
- if (endpoint.method === 'POST' && !expressPath.includes(':')) {
29
- handlerFunction = `${controllerName}Controller.create${controllerName}`;
30
- } else if (endpoint.method === 'GET' && !expressPath.includes(':')) {
31
- handlerFunction = `${controllerName}Controller.getAll${controllerName}s`;
32
- } else if (endpoint.method === 'GET' && expressPath.includes(':')) {
33
- handlerFunction = `${controllerName}Controller.get${controllerName}ById`;
34
- } else if (endpoint.method === 'PUT' && expressPath.includes(':')) {
35
- handlerFunction = `${controllerName}Controller.update${controllerName}ById`;
36
- } else if (endpoint.method === 'DELETE' && expressPath.includes(':')) {
37
- handlerFunction = `${controllerName}Controller.delete${controllerName}ById`;
31
+ <%
32
+ 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
+
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`;
38
47
  }
39
- }
40
-
41
- // If no specific CRUD function matches, create a placeholder handler.
42
- if (!handlerFunction) {
43
- handlerFunction = `(req: Request, res: Response) => {
44
- res.status(501).json({ message: 'Handler not implemented for <%= endpoint.method %> <%= expressPath %>' });
45
- }`;
46
- }
47
48
 
48
- // --- V3.0 AUTH LOGIC: Decide if the route should be protected ---
49
- // We protect all routes that modify data (POST, PUT, DELETE) if auth is enabled.
50
- // We leave GET routes public by default. This is a common pattern.
51
- const middleware = (addAuth && (endpoint.method === 'POST' || endpoint.method === 'PUT' || endpoint.method === 'DELETE'))
52
- ? 'protect, '
53
- : '';
49
+ const needsProtect = !!addAuth && (method === 'post' || method === 'put' || method === 'delete');
50
+ const middleware = needsProtect ? 'protect, ' : '';
51
+ %>
52
+ router.<%= method %>('<%- expressPath || "/" %>', <%- middleware %><%- handler || '(req: Request, res: Response) => res.status(501).json({ message: "Not Implemented" })' %>);
53
+ <%
54
+ });
55
+ }
54
56
  %>
55
- /**
56
- * Route for <%= endpoint.method.toUpperCase() %> <%= endpoint.path %>
57
- * Mapped to: <%- handlerFunction.includes('=>') ? 'Inline Handler' : handlerFunction %>
58
- * Protected: <%= middleware ? 'Yes' : 'No' %>
59
- */
60
- router.<%= endpoint.method.toLowerCase() %>('<%- expressPath %>', <%- middleware %><%- handlerFunction %>);
61
-
62
- <% }); %>
63
57
 
64
58
  export default router;
@@ -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")
@@ -0,0 +1,7 @@
1
+ from sqlalchemy import create_engine
2
+ from sqlalchemy.orm import sessionmaker, declarative_base
3
+ from app.core.config import settings
4
+
5
+ engine = create_engine(settings.DB_URL)
6
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
7
+ Base = declarative_base()
@@ -0,0 +1,24 @@
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from app.db import Base, engine
4
+ from app.models.user import User
5
+ from app.routers import auth as auth_routes
6
+ <% (controllers || []).forEach(c => { %>from app.routers import <%= c.toLowerCase() %>_routes
7
+ <% }) %>
8
+
9
+ app = FastAPI(title="<%= projectName %>")
10
+
11
+ app.add_middleware(
12
+ CORSMiddleware,
13
+ allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"],
14
+ )
15
+
16
+ Base.metadata.create_all(bind=engine)
17
+
18
+ @app.get("/")
19
+ def root():
20
+ return {"ok": True, "message": "FastAPI backend generated by create-backlist"}
21
+
22
+ app.include_router(auth_routes.router)
23
+ <% (controllers || []).forEach(c => { %>app.include_router(<%= c.toLowerCase() %>_routes.router)
24
+ <% }) %>
@@ -0,0 +1,9 @@
1
+ from sqlalchemy import Column, Integer, String
2
+ from app.db import Base
3
+
4
+ class User(Base):
5
+ __tablename__ = "users"
6
+ id = Column(Integer, primary_key=True, index=True)
7
+ name = Column(String)
8
+ email = Column(String, unique=True, index=True)
9
+ password = Column(String)
@@ -0,0 +1,33 @@
1
+ from fastapi import APIRouter, Depends, HTTPException, status
2
+ from sqlalchemy.orm import Session
3
+ from passlib.hash import bcrypt
4
+ from app.db import SessionLocal
5
+ from app.models.user import User
6
+ from app.schemas.user import UserCreate, Token
7
+ from app.core.security import create_token
8
+
9
+ router = APIRouter(prefix="/api/auth", tags=["Auth"])
10
+
11
+ def get_db():
12
+ db = SessionLocal()
13
+ try:
14
+ yield db
15
+ finally:
16
+ db.close()
17
+
18
+ @router.post("/register", response_model=Token, status_code=status.HTTP_201_CREATED)
19
+ def register(payload: UserCreate, db: Session = Depends(get_db)):
20
+ if db.query(User).filter(User.email == payload.email).first():
21
+ raise HTTPException(status_code=400, detail="User already exists")
22
+ u = User(name=payload.name, email=payload.email, password=bcrypt.hash(payload.password))
23
+ db.add(u)
24
+ db.commit()
25
+ db.refresh(u)
26
+ return {"token": create_token(u.email)}
27
+
28
+ @router.post("/login", response_model=Token)
29
+ def login(payload: UserCreate, db: Session = Depends(get_db)):
30
+ u = db.query(User).filter(User.email == payload.email).first()
31
+ if not u or not bcrypt.verify(payload.password, u.password):
32
+ raise HTTPException(status_code=400, detail="Invalid credentials")
33
+ return {"token": create_token(u.email)}
@@ -0,0 +1,72 @@
1
+ from fastapi import APIRouter, Depends, HTTPException, status
2
+ from typing import List
3
+ from sqlalchemy.orm import Session
4
+ from app.db import SessionLocal
5
+ from pydantic import BaseModel
6
+
7
+ # Build schema from fields
8
+ <% if (schema && schema.fields) { %>
9
+ class <%= modelName %>Base(BaseModel):
10
+ <% schema.fields.forEach(f => { %>
11
+ <%= f.name %>: <%= f.type === 'Number' ? 'int' : 'str' %>
12
+ <% }) %>
13
+
14
+ class <%= modelName %>Create(<%= modelName %>Base):
15
+ pass
16
+
17
+ class <%= modelName %>(<%= modelName %>Base):
18
+ id: int
19
+ class Config:
20
+ orm_mode = True
21
+ <% } %>
22
+
23
+ router = APIRouter(prefix="/api/<%= modelName.toLowerCase() %>s", tags=["<%= modelName %>s"])
24
+
25
+ def get_db():
26
+ db = SessionLocal()
27
+ try:
28
+ yield db
29
+ finally:
30
+ db.close()
31
+
32
+ # NOTE: For brevity this uses a generic in-memory table replacement.
33
+ # In a full implementation you'd generate SQLAlchemy model files per entity.
34
+
35
+ _db = []
36
+ _counter = 0
37
+
38
+ @router.get("/", response_model=List[<%= modelName %>])
39
+ def list_items():
40
+ return _db
41
+
42
+ @router.post("/", response_model=<%= modelName %>, status_code=status.HTTP_201_CREATED)
43
+ def create_item(payload: <%= modelName %>Create):
44
+ global _counter
45
+ _counter += 1
46
+ item = {"id": _counter, **payload.dict()}
47
+ _db.append(item)
48
+ return item
49
+
50
+ @router.get("/{item_id}", response_model=<%= modelName %>)
51
+ def get_item(item_id: int):
52
+ for it in _db:
53
+ if it["id"] == item_id:
54
+ return it
55
+ raise HTTPException(status_code=404, detail="Not found")
56
+
57
+ @router.put("/{item_id}", response_model=<%= modelName %>)
58
+ def update_item(item_id: int, payload: <%= modelName %>Create):
59
+ for idx, it in enumerate(_db):
60
+ if it["id"] == item_id:
61
+ new_it = {"id": item_id, **payload.dict()}
62
+ _db[idx] = new_it
63
+ return new_it
64
+ raise HTTPException(status_code=404, detail="Not found")
65
+
66
+ @router.delete("/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
67
+ def delete_item(item_id: int):
68
+ for idx, it in enumerate(_db):
69
+ if it["id"] == item_id:
70
+ _db.pop(idx)
71
+ return
72
+ raise HTTPException(status_code=404, detail="Not found")
@@ -0,0 +1,16 @@
1
+ from pydantic import BaseModel
2
+
3
+ class UserBase(BaseModel):
4
+ name: str
5
+ email: str
6
+
7
+ class UserCreate(UserBase):
8
+ password: str
9
+
10
+ class UserOut(UserBase):
11
+ id: int
12
+ class Config:
13
+ orm_mode = True
14
+
15
+ class Token(BaseModel):
16
+ token: str
@@ -0,0 +1,19 @@
1
+ version: '3.8'
2
+ services:
3
+ db:
4
+ image: postgres:16-alpine
5
+ environment:
6
+ POSTGRES_USER: ${DB_USER:-postgres}
7
+ POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
8
+ POSTGRES_DB: ${DB_NAME:-<%= projectName %>}
9
+ ports: ["5432:5432"]
10
+ volumes: [pgdata:/var/lib/postgresql/data]
11
+ api:
12
+ build: .
13
+ environment:
14
+ - DB_URL=postgresql://postgres:password@db:5432/<%= projectName %>
15
+ - JWT_SECRET=change_me_super_secret
16
+ ports: ["8000:8000"]
17
+ depends_on: [db]
18
+ volumes:
19
+ pgdata:
@@ -1,4 +1,8 @@
1
1
  fastapi
2
2
  uvicorn[standard]
3
+ python-dotenv
4
+ sqlalchemy
5
+ psycopg2-binary
3
6
  pydantic
4
- # Add other dependencies like 'sqlalchemy', 'psycopg2-binary' if using a database
7
+ passlib[bcrypt]
8
+ python-jose[cryptography]