create-backlist 6.2.3 → 7.3.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.
- package/README.md +1 -10
- package/bin/index.js +470 -140
- package/package.json +11 -7
- package/src/ai-agent.js +171 -0
- package/src/analyzer.js +137 -22
- package/src/generators/dotnet.js +134 -133
- package/src/generators/java.js +248 -233
- package/src/generators/js.js +346 -0
- package/src/generators/nestjs.js +278 -0
- package/src/generators/node.js +57 -26
- package/src/generators/python.js +86 -104
- package/src/generators/template.js +10 -8
- package/src/templates/dotnet/partials/Dockerfile.ejs +27 -0
- package/src/templates/dotnet/partials/docker-compose.yml.ejs +33 -0
- package/src/templates/java-spring/partials/Controller.java.ejs +3 -3
- package/src/templates/js-express/base/server.js +59 -0
- package/src/templates/js-express/partials/Dockerfile.ejs +12 -0
- package/src/templates/js-express/partials/auth.controller.js.ejs +66 -0
- package/src/templates/js-express/partials/auth.middleware.js.ejs +19 -0
- package/src/templates/js-express/partials/auth.routes.js.ejs +9 -0
- package/src/templates/js-express/partials/controller.js.ejs +53 -0
- package/src/templates/js-express/partials/db.js.ejs +19 -0
- package/src/templates/js-express/partials/docker-compose.yml.ejs +46 -0
- package/src/templates/js-express/partials/model.js.ejs +18 -0
- package/src/templates/js-express/partials/package.json.ejs +17 -0
- package/src/templates/js-express/partials/prisma.schema.ejs +21 -0
- package/src/templates/js-express/partials/routes.js.ejs +19 -0
- package/src/templates/js-express/partials/seeder.js.ejs +103 -0
- package/src/templates/js-express/partials/service.js.ejs +51 -0
- package/src/templates/js-express/partials/swagger.js.ejs +30 -0
- package/src/templates/js-express/partials/test.js.ejs +46 -0
- package/src/templates/nestjs/base/app.module.ts +9 -0
- package/src/templates/nestjs/base/main.ts +23 -0
- package/src/templates/nestjs/base/tsconfig.json +21 -0
- package/src/templates/nestjs/partials/auth.controller.ts.ejs +17 -0
- package/src/templates/nestjs/partials/auth.module.ts.ejs +17 -0
- package/src/templates/nestjs/partials/auth.service.ts.ejs +70 -0
- package/src/templates/nestjs/partials/controller.ts.ejs +34 -0
- package/src/templates/nestjs/partials/create-dto.ts.ejs +22 -0
- package/src/templates/nestjs/partials/jwt-guard.ts.ejs +24 -0
- package/src/templates/nestjs/partials/module.ts.ejs +10 -0
- package/src/templates/nestjs/partials/package.json.ejs +27 -0
- package/src/templates/nestjs/partials/prisma.service.ts.ejs +13 -0
- package/src/templates/nestjs/partials/schema.ts.ejs +19 -0
- package/src/templates/nestjs/partials/service.ts.ejs +67 -0
- package/src/templates/nestjs/partials/update-dto.ts.ejs +4 -0
- package/src/templates/node-ts-express/partials/HexController.ts.ejs +56 -0
- package/src/templates/node-ts-express/partials/HexRepository.ts.ejs +26 -0
- package/src/templates/node-ts-express/partials/HexService.ts.ejs +27 -0
- package/src/utils.js +3 -5
- /package/src/templates/{node-ts-express → dotnet}/partials/DbContext.cs.ejs +0 -0
- /package/src/templates/{node-ts-express → dotnet}/partials/Model.cs.ejs +0 -0
package/src/generators/node.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { execa } from "execa";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import ejs from "ejs";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import { analyzeFrontend } from "../analyzer.js";
|
|
8
|
+
import { renderAndWrite, getTemplatePath } from "./template.js";
|
|
9
9
|
|
|
10
10
|
function stripQuery(p) {
|
|
11
11
|
return String(p || "").split("?")[0];
|
|
@@ -71,7 +71,7 @@ function sanitizeEndpoints(endpoints) {
|
|
|
71
71
|
});
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
async function generateNodeProject(options) {
|
|
74
|
+
export async function generateNodeProject(options) {
|
|
75
75
|
const {
|
|
76
76
|
projectDir,
|
|
77
77
|
projectName,
|
|
@@ -181,13 +181,20 @@ async function generateNodeProject(options) {
|
|
|
181
181
|
|
|
182
182
|
await fs.writeJson(path.join(projectDir, "package.json"), packageJsonContent, { spaces: 2 });
|
|
183
183
|
|
|
184
|
-
// --- Step 5: DB +
|
|
184
|
+
// --- Step 5: DB + Hexagonal Architecture Scaffolding ---
|
|
185
185
|
if (modelsToGenerate.size > 0) {
|
|
186
|
-
|
|
186
|
+
const portDir = path.join(destSrcDir, "application", "ports", "controllers");
|
|
187
|
+
const serviceDir = path.join(destSrcDir, "domain", "services");
|
|
188
|
+
const repoDir = path.join(destSrcDir, "infrastructure", "adapters", "repositories");
|
|
189
|
+
const modelDir = path.join(destSrcDir, "domain", "models");
|
|
190
|
+
|
|
191
|
+
await fs.ensureDir(portDir);
|
|
192
|
+
await fs.ensureDir(serviceDir);
|
|
193
|
+
await fs.ensureDir(repoDir);
|
|
187
194
|
|
|
188
195
|
if (dbType === "mongoose") {
|
|
189
|
-
console.log(chalk.blue(" -> Generating Mongoose models
|
|
190
|
-
await fs.ensureDir(
|
|
196
|
+
console.log(chalk.blue(" -> Generating Mongoose domain models..."));
|
|
197
|
+
await fs.ensureDir(modelDir);
|
|
191
198
|
|
|
192
199
|
for (const [modelName, modelData] of modelsToGenerate.entries()) {
|
|
193
200
|
const schema = (modelData.fields || []).reduce((acc, field) => {
|
|
@@ -196,28 +203,54 @@ async function generateNodeProject(options) {
|
|
|
196
203
|
}, {});
|
|
197
204
|
await renderAndWrite(
|
|
198
205
|
getTemplatePath("node-ts-express/partials/Model.ts.ejs"),
|
|
199
|
-
path.join(
|
|
206
|
+
path.join(modelDir, `${modelName}.model.ts`),
|
|
200
207
|
{ modelName, schema, projectName }
|
|
201
208
|
);
|
|
202
209
|
}
|
|
203
210
|
} else if (dbType === "prisma") {
|
|
204
211
|
console.log(chalk.blue(" -> Generating Prisma schema..."));
|
|
205
212
|
await fs.ensureDir(path.join(projectDir, "prisma"));
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
213
|
+
// Check if we already have a generated schema from AI Pass 1 (options.aiBlocks.prismaSchema)
|
|
214
|
+
if (options.aiBlocks && options.aiBlocks.prismaSchema) {
|
|
215
|
+
await fs.writeFile(path.join(projectDir, "prisma", "schema.prisma"), options.aiBlocks.prismaSchema);
|
|
216
|
+
} else {
|
|
217
|
+
await renderAndWrite(
|
|
218
|
+
getTemplatePath("node-ts-express/partials/PrismaSchema.prisma.ejs"),
|
|
219
|
+
path.join(projectDir, "prisma", "schema.prisma"),
|
|
220
|
+
{ modelsToGenerate: Array.from(modelsToGenerate.values()) }
|
|
221
|
+
);
|
|
222
|
+
}
|
|
211
223
|
}
|
|
212
224
|
|
|
213
|
-
console.log(chalk.blue(" -> Generating
|
|
225
|
+
console.log(chalk.blue(" -> Generating Hexagonal Architecture layers (Controllers, Services, Repositories)..."));
|
|
214
226
|
for (const [modelName] of modelsToGenerate.entries()) {
|
|
215
|
-
const templateFile = dbType === "mongoose" ? "Controller.ts.ejs" : "PrismaController.ts.ejs";
|
|
216
227
|
if (modelName !== "Auth") {
|
|
228
|
+
const blockData = {
|
|
229
|
+
modelName,
|
|
230
|
+
projectName,
|
|
231
|
+
dbType,
|
|
232
|
+
aiSecurityConfig: options.aiBlocks?.aiSecurityConfig,
|
|
233
|
+
aiDbRelations: options.aiBlocks?.aiDbRelations,
|
|
234
|
+
aiValidationLogic: options.aiBlocks?.aiValidationLogic
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Controller (Port)
|
|
238
|
+
await renderAndWrite(
|
|
239
|
+
getTemplatePath(`node-ts-express/partials/HexController.ts.ejs`),
|
|
240
|
+
path.join(portDir, `${modelName}.controller.ts`),
|
|
241
|
+
blockData
|
|
242
|
+
);
|
|
243
|
+
// Service (Domain)
|
|
217
244
|
await renderAndWrite(
|
|
218
|
-
getTemplatePath(`node-ts-express/partials
|
|
219
|
-
path.join(
|
|
220
|
-
|
|
245
|
+
getTemplatePath(`node-ts-express/partials/HexService.ts.ejs`),
|
|
246
|
+
path.join(serviceDir, `${modelName}.service.ts`),
|
|
247
|
+
blockData
|
|
248
|
+
);
|
|
249
|
+
// Repository (Adapter)
|
|
250
|
+
await renderAndWrite(
|
|
251
|
+
getTemplatePath(`node-ts-express/partials/HexRepository.ts.ejs`),
|
|
252
|
+
path.join(repoDir, `${modelName}.repository.ts`),
|
|
253
|
+
blockData
|
|
221
254
|
);
|
|
222
255
|
}
|
|
223
256
|
}
|
|
@@ -369,6 +402,4 @@ app.use('/api', apiRoutes);`
|
|
|
369
402
|
} catch (error) {
|
|
370
403
|
throw error;
|
|
371
404
|
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
module.exports = { generateNodeProject };
|
|
405
|
+
}
|
package/src/generators/python.js
CHANGED
|
@@ -1,104 +1,86 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
async function generatePythonProject(options) {
|
|
9
|
-
const { projectDir, projectName, frontendSrcDir } = options;
|
|
10
|
-
|
|
11
|
-
try {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
await fs.ensureDir(
|
|
37
|
-
await fs.ensureDir(
|
|
38
|
-
await fs.ensureDir(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
await renderAndWrite(getTemplatePath('python-fastapi/app/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
console.log(chalk.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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'));
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
} catch (error) {
|
|
97
|
-
if (error.code === 'ENOENT') {
|
|
98
|
-
throw new Error(`'${error.command}' command not found. Please ensure Python and venv are installed and in your system's PATH.`);
|
|
99
|
-
}
|
|
100
|
-
throw error;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
module.exports = { generatePythonProject };
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { analyzeFrontend } from '../analyzer.js';
|
|
6
|
+
import { renderAndWrite, getTemplatePath } from './template.js';
|
|
7
|
+
|
|
8
|
+
export async function generatePythonProject(options) {
|
|
9
|
+
const { projectDir, projectName, frontendSrcDir } = options;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
console.log(chalk.blue(' -> Analyzing frontend for Python (FastAPI) backend...'));
|
|
13
|
+
const endpoints = await analyzeFrontend(frontendSrcDir);
|
|
14
|
+
const modelsToGenerate = new Map();
|
|
15
|
+
endpoints.forEach(ep => {
|
|
16
|
+
if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
|
|
17
|
+
modelsToGenerate.set(ep.controllerName, { name: ep.controllerName, fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type })) });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (!modelsToGenerate.has('User')) {
|
|
22
|
+
modelsToGenerate.set('User', { name: 'User', fields: [{ name: 'name', type: 'String' }, { name: 'email', type: 'String' }] });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log(chalk.blue(' -> Scaffolding Python (FastAPI) project structure...'));
|
|
26
|
+
const appDir = path.join(projectDir, 'app');
|
|
27
|
+
const coreDir = path.join(appDir, 'core');
|
|
28
|
+
const dbDir = path.join(appDir, 'db');
|
|
29
|
+
const modelsDir = path.join(appDir, 'models');
|
|
30
|
+
const schemasDir = path.join(appDir, 'schemas');
|
|
31
|
+
const routesDir = path.join(appDir, 'routers');
|
|
32
|
+
|
|
33
|
+
await fs.ensureDir(appDir);
|
|
34
|
+
await fs.ensureDir(coreDir);
|
|
35
|
+
await fs.ensureDir(dbDir);
|
|
36
|
+
await fs.ensureDir(modelsDir);
|
|
37
|
+
await fs.ensureDir(schemasDir);
|
|
38
|
+
await fs.ensureDir(routesDir);
|
|
39
|
+
|
|
40
|
+
const controllers = Array.from(modelsToGenerate.keys());
|
|
41
|
+
|
|
42
|
+
await renderAndWrite(getTemplatePath('python-fastapi/main.py.ejs'), path.join(projectDir, 'app', 'main.py'), { projectName, controllers });
|
|
43
|
+
await renderAndWrite(getTemplatePath('python-fastapi/requirements.txt.ejs'), path.join(projectDir, 'requirements.txt'), {});
|
|
44
|
+
|
|
45
|
+
await renderAndWrite(getTemplatePath('python-fastapi/app/core/config.py.ejs'), path.join(coreDir, 'config.py'), { projectName });
|
|
46
|
+
await renderAndWrite(getTemplatePath('python-fastapi/app/core/security.py.ejs'), path.join(coreDir, 'security.py'), {});
|
|
47
|
+
|
|
48
|
+
await renderAndWrite(getTemplatePath('python-fastapi/app/db.py.ejs'), path.join(appDir, 'db.py'), {});
|
|
49
|
+
|
|
50
|
+
await renderAndWrite(getTemplatePath('python-fastapi/app/models/user.py.ejs'), path.join(modelsDir, 'user.py'), {});
|
|
51
|
+
await renderAndWrite(getTemplatePath('python-fastapi/app/schemas/user.py.ejs'), path.join(schemasDir, 'user.py'), {});
|
|
52
|
+
|
|
53
|
+
await renderAndWrite(getTemplatePath('python-fastapi/app/routers/auth.py.ejs'), path.join(routesDir, 'auth.py'), {});
|
|
54
|
+
|
|
55
|
+
for (const [modelName, modelData] of modelsToGenerate.entries()) {
|
|
56
|
+
if (modelName.toLowerCase() !== 'user') {
|
|
57
|
+
await renderAndWrite(getTemplatePath('python-fastapi/app/routers/model_routes.py.ejs'), path.join(routesDir, `${modelName.toLowerCase()}_routes.py`), { modelName, schema: modelData });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(chalk.magenta(' -> Setting up virtual environment and installing dependencies...'));
|
|
62
|
+
await execa('python', ['-m', 'venv', 'venv'], { cwd: projectDir });
|
|
63
|
+
|
|
64
|
+
const pipPath = process.platform === 'win32' ? path.join('venv', 'Scripts', 'pip') : path.join('venv', 'bin', 'pip');
|
|
65
|
+
await execa(path.join(projectDir, pipPath), ['install', '-r', 'requirements.txt'], { cwd: projectDir });
|
|
66
|
+
|
|
67
|
+
await renderAndWrite(getTemplatePath('python-fastapi/Dockerfile.ejs'), path.join(projectDir, 'Dockerfile'), {});
|
|
68
|
+
await renderAndWrite(getTemplatePath('python-fastapi/docker-compose.yml.ejs'), path.join(projectDir, 'docker-compose.yml'), { projectName });
|
|
69
|
+
|
|
70
|
+
const envContent = `DATABASE_URL="postgresql://postgres:password@db:5432/${projectName}"\nJWT_SECRET="a_very_secret_key_change_this"`;
|
|
71
|
+
await fs.writeFile(path.join(projectDir, '.env'), envContent);
|
|
72
|
+
await fs.writeFile(path.join(projectDir, '.env.example'), envContent);
|
|
73
|
+
|
|
74
|
+
console.log(chalk.green(' -> Python (FastAPI) backend generation is complete!'));
|
|
75
|
+
console.log(chalk.yellow('\nTo run your new Python backend with Docker:'));
|
|
76
|
+
console.log(chalk.cyan(' 1. Make sure Docker Desktop is running.'));
|
|
77
|
+
console.log(chalk.cyan(' 2. Run: `docker-compose up --build`'));
|
|
78
|
+
console.log(chalk.cyan(' 3. API will be available at http://localhost:8000 and docs at http://localhost:8000/docs'));
|
|
79
|
+
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error.code === 'ENOENT') {
|
|
82
|
+
throw new Error(`'${error.command}' command not found. Please ensure Python and venv are installed and in your system's PATH.`);
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import ejs from 'ejs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
export async function renderAndWrite(templatePath, outPath, data) {
|
|
6
10
|
try {
|
|
7
11
|
const tpl = await fs.readFile(templatePath, 'utf-8');
|
|
8
12
|
const code = ejs.render(tpl, data || {}, { filename: templatePath }); // filename helps with EJS errors
|
|
@@ -14,8 +18,6 @@ async function renderAndWrite(templatePath, outPath, data) {
|
|
|
14
18
|
}
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
function getTemplatePath(subpath) {
|
|
21
|
+
export function getTemplatePath(subpath) {
|
|
18
22
|
return path.join(__dirname, '..', 'templates', subpath);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
module.exports = { renderAndWrite, getTemplatePath };
|
|
23
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Auto-generated by create-backlist (.NET Core)
|
|
2
|
+
|
|
3
|
+
# ---- Build Stage ----
|
|
4
|
+
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
|
5
|
+
WORKDIR /src
|
|
6
|
+
|
|
7
|
+
COPY *.csproj .
|
|
8
|
+
RUN dotnet restore
|
|
9
|
+
|
|
10
|
+
COPY . .
|
|
11
|
+
RUN dotnet publish -c Release -o /app/publish --no-restore
|
|
12
|
+
|
|
13
|
+
# ---- Runtime Stage ----
|
|
14
|
+
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
|
15
|
+
WORKDIR /app
|
|
16
|
+
|
|
17
|
+
ENV ASPNETCORE_URLS=http://+:5000
|
|
18
|
+
ENV ASPNETCORE_ENVIRONMENT=Production
|
|
19
|
+
|
|
20
|
+
COPY --from=build /app/publish .
|
|
21
|
+
|
|
22
|
+
EXPOSE 5000
|
|
23
|
+
|
|
24
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
|
25
|
+
CMD curl -f http://localhost:5000/health || exit 1
|
|
26
|
+
|
|
27
|
+
ENTRYPOINT ["dotnet", "<%= projectName %>.dll"]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Auto-generated by create-backlist (.NET Core)
|
|
2
|
+
version: '3.8'
|
|
3
|
+
|
|
4
|
+
services:
|
|
5
|
+
app:
|
|
6
|
+
build: .
|
|
7
|
+
ports:
|
|
8
|
+
- "5000:5000"
|
|
9
|
+
environment:
|
|
10
|
+
- ASPNETCORE_ENVIRONMENT=Development
|
|
11
|
+
- ConnectionStrings__DefaultConnection=Host=db;Port=5432;Database=<%= projectName %>;Username=postgres;Password=postgres
|
|
12
|
+
depends_on:
|
|
13
|
+
db:
|
|
14
|
+
condition: service_healthy
|
|
15
|
+
|
|
16
|
+
db:
|
|
17
|
+
image: postgres:16-alpine
|
|
18
|
+
environment:
|
|
19
|
+
POSTGRES_USER: postgres
|
|
20
|
+
POSTGRES_PASSWORD: postgres
|
|
21
|
+
POSTGRES_DB: <%= projectName %>
|
|
22
|
+
ports:
|
|
23
|
+
- "5432:5432"
|
|
24
|
+
volumes:
|
|
25
|
+
- pgdata:/var/lib/postgresql/data
|
|
26
|
+
healthcheck:
|
|
27
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
28
|
+
interval: 5s
|
|
29
|
+
timeout: 3s
|
|
30
|
+
retries: 5
|
|
31
|
+
|
|
32
|
+
volumes:
|
|
33
|
+
pgdata:
|
|
@@ -27,7 +27,7 @@ public class <%= controllerName %>Controller {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
@GetMapping("/{id}")
|
|
30
|
-
public ResponseEntity<<%= controllerName %>> one(@PathVariable
|
|
30
|
+
public ResponseEntity<<%= controllerName %>> one(@PathVariable Long id) {
|
|
31
31
|
return service.findById(id)
|
|
32
32
|
.map(ResponseEntity::ok)
|
|
33
33
|
.orElse(ResponseEntity.notFound().build());
|
|
@@ -39,14 +39,14 @@ public class <%= controllerName %>Controller {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
@PutMapping("/{id}")
|
|
42
|
-
public ResponseEntity<<%= controllerName %>> update(@PathVariable
|
|
42
|
+
public ResponseEntity<<%= controllerName %>> update(@PathVariable Long id, @RequestBody <%= controllerName %> m) {
|
|
43
43
|
return service.update(id, m)
|
|
44
44
|
.map(ResponseEntity::ok)
|
|
45
45
|
.orElse(ResponseEntity.notFound().build());
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
@DeleteMapping("/{id}")
|
|
49
|
-
public ResponseEntity<Void> delete(@PathVariable
|
|
49
|
+
public ResponseEntity<Void> delete(@PathVariable Long id) {
|
|
50
50
|
return service.delete(id)
|
|
51
51
|
? ResponseEntity.noContent().build()
|
|
52
52
|
: ResponseEntity.notFound().build();
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import dotenv from 'dotenv';
|
|
4
|
+
import helmet from 'helmet';
|
|
5
|
+
import morgan from 'morgan';
|
|
6
|
+
|
|
7
|
+
dotenv.config();
|
|
8
|
+
|
|
9
|
+
const app = express();
|
|
10
|
+
|
|
11
|
+
app.use(helmet());
|
|
12
|
+
app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev'));
|
|
13
|
+
|
|
14
|
+
app.use(cors({
|
|
15
|
+
origin: process.env.CORS_ORIGIN
|
|
16
|
+
? process.env.CORS_ORIGIN.split(',').map(s => s.trim())
|
|
17
|
+
: ['http://localhost:3000', 'http://localhost:5173'],
|
|
18
|
+
credentials: true,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
app.use(express.json({ limit: '1mb' }));
|
|
22
|
+
|
|
23
|
+
app.get('/api/health', (req, res) => {
|
|
24
|
+
res.status(200).json({ status: 'ok' });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
app.get('/', (req, res) => {
|
|
28
|
+
res.send('Backend is running! Generated by create-backlist.');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// INJECT:ROUTES
|
|
32
|
+
|
|
33
|
+
app.use((req, res) => {
|
|
34
|
+
res.status(404).json({ message: 'Route not found' });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
app.use((err, req, res, _next) => {
|
|
38
|
+
console.error(err);
|
|
39
|
+
res.status(err?.statusCode || 500).json({
|
|
40
|
+
message: err?.message || 'Internal Server Error',
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const PORT = process.env.PORT || 8000;
|
|
45
|
+
|
|
46
|
+
const server = app.listen(PORT, () => {
|
|
47
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
function shutdown(signal) {
|
|
51
|
+
console.log(`Received ${signal}. Shutting down...`);
|
|
52
|
+
server.close(() => {
|
|
53
|
+
console.log('HTTP server closed.');
|
|
54
|
+
process.exit(0);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
59
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<% if (dbType === 'mongoose') { %>
|
|
2
|
+
import User from '../models/User.model.js';
|
|
3
|
+
<% } else { %>
|
|
4
|
+
import { prisma } from '../db.js';
|
|
5
|
+
<% } %>
|
|
6
|
+
import bcrypt from 'bcryptjs';
|
|
7
|
+
import jwt from 'jsonwebtoken';
|
|
8
|
+
|
|
9
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'changeme';
|
|
10
|
+
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '5h';
|
|
11
|
+
|
|
12
|
+
export class AuthController {
|
|
13
|
+
|
|
14
|
+
static async register(req, res) {
|
|
15
|
+
try {
|
|
16
|
+
const { name, email, password } = req.body;
|
|
17
|
+
if (!email || !password) {
|
|
18
|
+
return res.status(400).json({ message: 'Email and password are required' });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const hashedPassword = await bcrypt.hash(password, 12);
|
|
22
|
+
|
|
23
|
+
<% if (dbType === 'mongoose') { %>
|
|
24
|
+
const existing = await User.findOne({ email });
|
|
25
|
+
if (existing) return res.status(409).json({ message: 'Email already in use' });
|
|
26
|
+
|
|
27
|
+
const user = await User.create({ name, email, password: hashedPassword });
|
|
28
|
+
const token = jwt.sign({ id: user._id, email: user.email }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
|
|
29
|
+
<% } else { %>
|
|
30
|
+
const existing = await prisma.user.findUnique({ where: { email } });
|
|
31
|
+
if (existing) return res.status(409).json({ message: 'Email already in use' });
|
|
32
|
+
|
|
33
|
+
const user = await prisma.user.create({ data: { name, email, password: hashedPassword } });
|
|
34
|
+
const token = jwt.sign({ id: user.id, email: user.email }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
|
|
35
|
+
<% } %>
|
|
36
|
+
|
|
37
|
+
res.status(201).json({ token, user: { id: user.id || user._id, name: user.name, email: user.email } });
|
|
38
|
+
} catch (error) {
|
|
39
|
+
res.status(500).json({ message: error.message });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static async login(req, res) {
|
|
44
|
+
try {
|
|
45
|
+
const { email, password } = req.body;
|
|
46
|
+
if (!email || !password) {
|
|
47
|
+
return res.status(400).json({ message: 'Email and password are required' });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
<% if (dbType === 'mongoose') { %>
|
|
51
|
+
const user = await User.findOne({ email });
|
|
52
|
+
<% } else { %>
|
|
53
|
+
const user = await prisma.user.findUnique({ where: { email } });
|
|
54
|
+
<% } %>
|
|
55
|
+
if (!user) return res.status(401).json({ message: 'Invalid credentials' });
|
|
56
|
+
|
|
57
|
+
const isMatch = await bcrypt.compare(password, user.password);
|
|
58
|
+
if (!isMatch) return res.status(401).json({ message: 'Invalid credentials' });
|
|
59
|
+
|
|
60
|
+
const token = jwt.sign({ id: user.id || user._id, email: user.email }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
|
|
61
|
+
res.status(200).json({ token, user: { id: user.id || user._id, name: user.name, email: user.email } });
|
|
62
|
+
} catch (error) {
|
|
63
|
+
res.status(500).json({ message: error.message });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
|
|
3
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'changeme';
|
|
4
|
+
|
|
5
|
+
export function authMiddleware(req, res, next) {
|
|
6
|
+
const authHeader = req.headers.authorization;
|
|
7
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
8
|
+
return res.status(401).json({ message: 'Access denied. No token provided.' });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const token = authHeader.split(' ')[1];
|
|
12
|
+
try {
|
|
13
|
+
const decoded = jwt.verify(token, JWT_SECRET);
|
|
14
|
+
req.user = decoded;
|
|
15
|
+
next();
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return res.status(401).json({ message: 'Invalid or expired token.' });
|
|
18
|
+
}
|
|
19
|
+
}
|