create-backlist 7.0.1 → 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 +242 -275
- package/package.json +3 -2
- package/src/ai-agent.js +171 -171
- package/src/analyzer.js +550 -495
- 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 +404 -404
- package/src/generators/python.js +86 -104
- package/src/generators/template.js +22 -22
- 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 -56
- package/src/templates/node-ts-express/partials/HexRepository.ts.ejs +26 -26
- package/src/templates/node-ts-express/partials/HexService.ts.ejs +27 -27
- package/src/utils.js +11 -11
- /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/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,23 +1,23 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import ejs from 'ejs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
-
const __dirname = path.dirname(__filename);
|
|
8
|
-
|
|
9
|
-
export async function renderAndWrite(templatePath, outPath, data) {
|
|
10
|
-
try {
|
|
11
|
-
const tpl = await fs.readFile(templatePath, 'utf-8');
|
|
12
|
-
const code = ejs.render(tpl, data || {}, { filename: templatePath }); // filename helps with EJS errors
|
|
13
|
-
await fs.outputFile(outPath, code);
|
|
14
|
-
} catch (err) {
|
|
15
|
-
console.error('EJS render failed for:', templatePath);
|
|
16
|
-
console.error('Data keys:', Object.keys(data || {}));
|
|
17
|
-
throw err;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function getTemplatePath(subpath) {
|
|
22
|
-
return path.join(__dirname, '..', 'templates', subpath);
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import ejs from 'ejs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
export async function renderAndWrite(templatePath, outPath, data) {
|
|
10
|
+
try {
|
|
11
|
+
const tpl = await fs.readFile(templatePath, 'utf-8');
|
|
12
|
+
const code = ejs.render(tpl, data || {}, { filename: templatePath }); // filename helps with EJS errors
|
|
13
|
+
await fs.outputFile(outPath, code);
|
|
14
|
+
} catch (err) {
|
|
15
|
+
console.error('EJS render failed for:', templatePath);
|
|
16
|
+
console.error('Data keys:', Object.keys(data || {}));
|
|
17
|
+
throw err;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getTemplatePath(subpath) {
|
|
22
|
+
return path.join(__dirname, '..', 'templates', subpath);
|
|
23
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
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { <%= modelName %>Service } from '../services/<%= modelName %>.service.js';
|
|
2
|
+
|
|
3
|
+
export class <%= modelName %>Controller {
|
|
4
|
+
|
|
5
|
+
static async getAll(req, res) {
|
|
6
|
+
try {
|
|
7
|
+
const data = await <%= modelName %>Service.getAll();
|
|
8
|
+
res.status(200).json(data);
|
|
9
|
+
} catch (error) {
|
|
10
|
+
res.status(500).json({ message: error.message });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static async getById(req, res) {
|
|
15
|
+
try {
|
|
16
|
+
const { id } = req.params;
|
|
17
|
+
const data = await <%= modelName %>Service.getById(id);
|
|
18
|
+
if (!data) return res.status(404).json({ message: 'Not found' });
|
|
19
|
+
res.status(200).json(data);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
res.status(500).json({ message: error.message });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static async create(req, res) {
|
|
26
|
+
try {
|
|
27
|
+
const data = await <%= modelName %>Service.create(req.body);
|
|
28
|
+
res.status(201).json(data);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
res.status(400).json({ message: error.message });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static async update(req, res) {
|
|
35
|
+
try {
|
|
36
|
+
const { id } = req.params;
|
|
37
|
+
const data = await <%= modelName %>Service.update(id, req.body);
|
|
38
|
+
res.status(200).json(data);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
res.status(400).json({ message: error.message });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static async delete(req, res) {
|
|
45
|
+
try {
|
|
46
|
+
const { id } = req.params;
|
|
47
|
+
await <%= modelName %>Service.delete(id);
|
|
48
|
+
res.status(204).send();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
res.status(500).json({ message: error.message });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<% if (dbType === 'prisma') { %>
|
|
2
|
+
import { PrismaClient } from '@prisma/client';
|
|
3
|
+
|
|
4
|
+
export const prisma = new PrismaClient();
|
|
5
|
+
<% } else { %>
|
|
6
|
+
import mongoose from 'mongoose';
|
|
7
|
+
|
|
8
|
+
const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/<%= projectName %>';
|
|
9
|
+
|
|
10
|
+
export async function connectDB() {
|
|
11
|
+
try {
|
|
12
|
+
await mongoose.connect(MONGO_URI);
|
|
13
|
+
console.log('MongoDB Connected...');
|
|
14
|
+
} catch (err) {
|
|
15
|
+
console.error('MongoDB connection error:', err.message);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
<% } %>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
app:
|
|
5
|
+
build: .
|
|
6
|
+
container_name: <%= projectName %>-app
|
|
7
|
+
ports:
|
|
8
|
+
- '<%= port %>:<%= port %>'
|
|
9
|
+
env_file:
|
|
10
|
+
- .env
|
|
11
|
+
depends_on:
|
|
12
|
+
<% if (dbType === 'mongoose') { %>
|
|
13
|
+
- mongo
|
|
14
|
+
<% } else { %>
|
|
15
|
+
- postgres
|
|
16
|
+
<% } %>
|
|
17
|
+
restart: unless-stopped
|
|
18
|
+
|
|
19
|
+
<% if (dbType === 'mongoose') { %>
|
|
20
|
+
mongo:
|
|
21
|
+
image: mongo:7
|
|
22
|
+
container_name: <%= projectName %>-mongo
|
|
23
|
+
ports:
|
|
24
|
+
- '27017:27017'
|
|
25
|
+
volumes:
|
|
26
|
+
- mongo_data:/data/db
|
|
27
|
+
<% } else { %>
|
|
28
|
+
postgres:
|
|
29
|
+
image: postgres:16-alpine
|
|
30
|
+
container_name: <%= projectName %>-postgres
|
|
31
|
+
ports:
|
|
32
|
+
- '5432:5432'
|
|
33
|
+
environment:
|
|
34
|
+
POSTGRES_USER: user
|
|
35
|
+
POSTGRES_PASSWORD: password
|
|
36
|
+
POSTGRES_DB: <%= projectName %>
|
|
37
|
+
volumes:
|
|
38
|
+
- pg_data:/var/lib/postgresql/data
|
|
39
|
+
<% } %>
|
|
40
|
+
|
|
41
|
+
volumes:
|
|
42
|
+
<% if (dbType === 'mongoose') { %>
|
|
43
|
+
mongo_data:
|
|
44
|
+
<% } else { %>
|
|
45
|
+
pg_data:
|
|
46
|
+
<% } %>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
|
|
3
|
+
const <%= modelName %>Schema = new mongoose.Schema({
|
|
4
|
+
<% fields.forEach((field, i) => { -%>
|
|
5
|
+
<%= field.name %>: {
|
|
6
|
+
type: <%= field.type %>,
|
|
7
|
+
<% if (field.isUnique) { -%>
|
|
8
|
+
unique: true,
|
|
9
|
+
<% } -%>
|
|
10
|
+
<% if (field.name === 'email') { -%>
|
|
11
|
+
lowercase: true,
|
|
12
|
+
trim: true,
|
|
13
|
+
<% } -%>
|
|
14
|
+
},
|
|
15
|
+
<% }); -%>
|
|
16
|
+
}, { timestamps: true });
|
|
17
|
+
|
|
18
|
+
export default mongoose.model('<%= modelName %>', <%= modelName %>Schema);
|