create-backlist 6.1.6 → 6.1.7
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/package.json +1 -1
- package/src/analyzer.js +410 -99
- package/src/generators/dotnet.js +1 -1
- package/src/generators/java.js +154 -97
- package/src/generators/node.js +213 -211
- package/src/templates/java-spring/partials/ApplicationSeeder.java.ejs +29 -7
- package/src/templates/java-spring/partials/AuthController.java.ejs +45 -14
- package/src/templates/java-spring/partials/Controller.java.ejs +25 -11
- package/src/templates/java-spring/partials/Dockerfile.ejs +25 -3
- package/src/templates/java-spring/partials/Entity.java.ejs +28 -3
- package/src/templates/java-spring/partials/JwtAuthFilter.java.ejs +41 -7
- package/src/templates/java-spring/partials/JwtService.java.ejs +47 -12
- package/src/templates/java-spring/partials/Repository.java.ejs +8 -1
- package/src/templates/java-spring/partials/Service.java.ejs +30 -6
- package/src/templates/java-spring/partials/User.java.ejs +26 -3
- package/src/templates/java-spring/partials/UserDetailsServiceImpl.java.ejs +10 -4
- package/src/templates/java-spring/partials/UserRepository.java.ejs +6 -0
- package/src/templates/java-spring/partials/docker-compose.yml.ejs +27 -5
- package/src/templates/node-ts-express/base/server.ts +63 -9
- package/src/templates/node-ts-express/base/tsconfig.json +19 -4
- package/src/templates/node-ts-express/partials/ApiDocs.ts.ejs +24 -9
- package/src/templates/node-ts-express/partials/App.test.ts.ejs +47 -27
- package/src/templates/node-ts-express/partials/Auth.controller.ts.ejs +68 -45
- package/src/templates/node-ts-express/partials/Auth.middleware.ts.ejs +45 -14
- package/src/templates/node-ts-express/partials/Auth.routes.ts.ejs +44 -5
- package/src/templates/node-ts-express/partials/Controller.ts.ejs +30 -16
- package/src/templates/node-ts-express/partials/Dockerfile.ejs +33 -11
- package/src/templates/node-ts-express/partials/Model.cs.ejs +38 -5
- package/src/templates/node-ts-express/partials/Model.ts.ejs +42 -12
- package/src/templates/node-ts-express/partials/PrismaController.ts.ejs +57 -23
- package/src/templates/node-ts-express/partials/PrismaSchema.prisma.ejs +33 -10
- package/src/templates/node-ts-express/partials/README.md.ejs +8 -10
- package/src/templates/node-ts-express/partials/Seeder.ts.ejs +99 -56
- package/src/templates/node-ts-express/partials/docker-compose.yml.ejs +30 -3
- package/src/templates/node-ts-express/partials/package.json.ejs +12 -7
- package/src/templates/node-ts-express/partials/routes.ts.ejs +31 -18
|
@@ -1,22 +1,76 @@
|
|
|
1
|
-
import express, { Express, Request, Response } from 'express';
|
|
1
|
+
import express, { Express, Request, Response, NextFunction } from 'express';
|
|
2
2
|
import cors from 'cors';
|
|
3
3
|
import dotenv from 'dotenv';
|
|
4
|
+
import helmet from 'helmet';
|
|
5
|
+
import morgan from 'morgan';
|
|
4
6
|
|
|
5
7
|
dotenv.config();
|
|
6
8
|
|
|
7
|
-
const app: Express = express();
|
|
9
|
+
const app: Express = express();
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Core middleware
|
|
13
|
+
*/
|
|
14
|
+
app.use(helmet());
|
|
15
|
+
app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev'));
|
|
11
16
|
|
|
12
|
-
app.
|
|
17
|
+
app.use(cors({
|
|
18
|
+
origin: process.env.CORS_ORIGIN
|
|
19
|
+
? process.env.CORS_ORIGIN.split(',').map(s => s.trim())
|
|
20
|
+
: true,
|
|
21
|
+
credentials: true,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
app.use(express.json({ limit: '1mb' }));
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Health + root
|
|
28
|
+
*/
|
|
29
|
+
app.get('/api/health', (req: Request, res: Response) => {
|
|
30
|
+
res.status(200).json({ status: 'ok' });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
app.get('/', (req: Request, res: Response) => {
|
|
13
34
|
res.send('Node.js Backend says Hello! Generated by Backlist.');
|
|
14
35
|
});
|
|
15
36
|
|
|
16
37
|
// INJECT:ROUTES
|
|
17
38
|
|
|
18
|
-
|
|
39
|
+
/**
|
|
40
|
+
* 404 handler (must be after routes)
|
|
41
|
+
*/
|
|
42
|
+
app.use((req: Request, res: Response) => {
|
|
43
|
+
res.status(404).json({ message: 'Route not found' });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Global error handler (must be last)
|
|
48
|
+
*/
|
|
49
|
+
app.use((err: any, req: Request, res: Response, _next: NextFunction) => {
|
|
50
|
+
// Log full error on server
|
|
51
|
+
console.error(err);
|
|
52
|
+
|
|
53
|
+
res.status(err?.statusCode || 500).json({
|
|
54
|
+
message: err?.message || 'Internal Server Error',
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const PORT: number | string = process.env.PORT || 8000;
|
|
59
|
+
|
|
60
|
+
const server = app.listen(PORT, () => {
|
|
61
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Graceful shutdown (Docker / Ctrl+C)
|
|
66
|
+
*/
|
|
67
|
+
function shutdown(signal: string) {
|
|
68
|
+
console.log(`Received ${signal}. Shutting down...`);
|
|
69
|
+
server.close(() => {
|
|
70
|
+
console.log('HTTP server closed.');
|
|
71
|
+
process.exit(0);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
19
74
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
75
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
76
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
-
"target": "
|
|
4
|
-
"
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"lib": ["ES2020"],
|
|
5
|
+
"module": "CommonJS",
|
|
6
|
+
"moduleResolution": "Node",
|
|
5
7
|
"rootDir": "./src",
|
|
6
8
|
"outDir": "./dist",
|
|
9
|
+
|
|
10
|
+
"strict": true,
|
|
7
11
|
"esModuleInterop": true,
|
|
8
|
-
"
|
|
9
|
-
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"sourceMap": true,
|
|
17
|
+
|
|
18
|
+
"noImplicitReturns": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true,
|
|
20
|
+
|
|
21
|
+
"types": ["node"]
|
|
22
|
+
},
|
|
23
|
+
"include": ["src/**/*.ts"],
|
|
24
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
|
10
25
|
}
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
// Auto-generated by
|
|
1
|
+
// Auto-generated by Backlist
|
|
2
2
|
import swaggerUi from 'swagger-ui-express';
|
|
3
3
|
import swaggerJsdoc from 'swagger-jsdoc';
|
|
4
4
|
import { Express } from 'express';
|
|
5
5
|
|
|
6
|
+
<% if (addAuth) { -%>
|
|
7
|
+
const securitySchemes = {
|
|
8
|
+
bearerAuth: {
|
|
9
|
+
type: 'http',
|
|
10
|
+
scheme: 'bearer',
|
|
11
|
+
bearerFormat: 'JWT',
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
<% } else { -%>
|
|
15
|
+
const securitySchemes = {};
|
|
16
|
+
<% } -%>
|
|
17
|
+
|
|
6
18
|
const options: swaggerJsdoc.Options = {
|
|
7
19
|
definition: {
|
|
8
20
|
openapi: '3.0.0',
|
|
@@ -13,21 +25,24 @@ const options: swaggerJsdoc.Options = {
|
|
|
13
25
|
},
|
|
14
26
|
servers: [
|
|
15
27
|
{
|
|
16
|
-
url: 'http://localhost:<%= port %>',
|
|
17
|
-
description: '
|
|
28
|
+
url: process.env.API_BASE_URL || 'http://localhost:<%= port %>',
|
|
29
|
+
description: 'Server',
|
|
18
30
|
},
|
|
19
31
|
],
|
|
20
|
-
|
|
32
|
+
components: {
|
|
33
|
+
securitySchemes: securitySchemes as any,
|
|
34
|
+
},
|
|
35
|
+
// Auto-generated paths (no JSDoc required)
|
|
36
|
+
paths: <%- JSON.stringify(paths || {}, null, 2) %>,
|
|
21
37
|
},
|
|
22
|
-
|
|
23
|
-
|
|
38
|
+
|
|
39
|
+
// Keep this too (optional) - if you later add JSDoc annotations
|
|
40
|
+
apis: ['./src/routes/*.ts', './src/routes.ts'],
|
|
24
41
|
};
|
|
25
42
|
|
|
26
43
|
const swaggerSpec = swaggerJsdoc(options);
|
|
27
44
|
|
|
28
45
|
export function setupSwagger(app: Express) {
|
|
29
46
|
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
|
|
30
|
-
console.log(
|
|
31
|
-
`📄 API documentation is available at http://localhost:<%= port %>/api-docs`
|
|
32
|
-
);
|
|
47
|
+
console.log(`API docs: http://localhost:<%= port %>/api-docs`);
|
|
33
48
|
}
|
|
@@ -1,38 +1,58 @@
|
|
|
1
|
-
// Auto-generated by create-backlist v5.
|
|
1
|
+
// Auto-generated by create-backlist v5.1
|
|
2
2
|
import request from 'supertest';
|
|
3
3
|
import express from 'express';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
|
|
5
|
+
import apiRoutes from '../routes';
|
|
6
|
+
|
|
7
|
+
<% if (addAuth) { -%>
|
|
8
|
+
import authRoutes from '../routes/Auth.routes';
|
|
9
|
+
<% } -%>
|
|
8
10
|
|
|
9
11
|
const app = express();
|
|
10
12
|
app.use(express.json());
|
|
11
|
-
app.use('/api', apiRoutes);
|
|
12
|
-
app.use('/api/auth', authRoutes);
|
|
13
|
-
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
// Mount auth first to avoid conflicts
|
|
15
|
+
<% if (addAuth) { -%>
|
|
16
|
+
app.use('/api/auth', authRoutes);
|
|
17
|
+
<% } -%>
|
|
18
|
+
app.use('/api', apiRoutes);
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// You might need to adjust this to a real endpoint from your app.
|
|
20
|
-
// Example for GET /api/users
|
|
21
|
-
// const res = await request(app).get('/api/users');
|
|
22
|
-
// expect(res.statusCode).toEqual(200);
|
|
23
|
-
|
|
24
|
-
// For now, a placeholder test:
|
|
20
|
+
describe('API Endpoints (Generated)', () => {
|
|
21
|
+
it('sanity check', () => {
|
|
25
22
|
expect(1 + 1).toBe(2);
|
|
26
23
|
});
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
<% endpoints
|
|
26
|
+
.filter(ep => ep && ep.route && ep.method)
|
|
27
|
+
.forEach(ep => {
|
|
28
|
+
const method = String(ep.method).toLowerCase();
|
|
29
|
+
|
|
30
|
+
// make url relative to /api mount
|
|
31
|
+
const url = (String(ep.route).startsWith('/api/')
|
|
32
|
+
? String(ep.route).replace(/^\/api/, '')
|
|
33
|
+
: String(ep.route)
|
|
34
|
+
)
|
|
35
|
+
// replace path params ":id" with dummy
|
|
36
|
+
.replace(/:\w+/g, '1');
|
|
37
|
+
-%>
|
|
38
|
+
it('<%= ep.method %> <%= url %> should respond', async () => {
|
|
39
|
+
const req = request(app).<%= method %>('<%= '/api' + url %>');
|
|
40
|
+
|
|
41
|
+
<% if (['post','put','patch'].includes(method) && ep.schemaFields) { -%>
|
|
42
|
+
req.send(
|
|
43
|
+
<%- JSON.stringify(
|
|
44
|
+
Object.fromEntries(
|
|
45
|
+
Object.entries(ep.schemaFields).map(([k, t]) => [
|
|
46
|
+
k,
|
|
47
|
+
t === 'Number' ? 1 : (t === 'Boolean' ? true : 'test')
|
|
48
|
+
])
|
|
49
|
+
)
|
|
50
|
+
) %>
|
|
51
|
+
);
|
|
52
|
+
<% } -%>
|
|
53
|
+
|
|
54
|
+
const res = await req;
|
|
55
|
+
expect(res.statusCode).not.toBe(404);
|
|
56
|
+
});
|
|
57
|
+
<% }) -%>
|
|
38
58
|
});
|
|
@@ -3,87 +3,110 @@ import { Request, Response } from 'express';
|
|
|
3
3
|
import bcrypt from 'bcryptjs';
|
|
4
4
|
import jwt from 'jsonwebtoken';
|
|
5
5
|
|
|
6
|
-
<% if (dbType === 'mongoose') {
|
|
6
|
+
<% if (dbType === 'mongoose') { -%>
|
|
7
7
|
import User from '../models/User.model';
|
|
8
|
-
<% } else if (dbType === 'prisma') {
|
|
8
|
+
<% } else if (dbType === 'prisma') { -%>
|
|
9
9
|
import { prisma } from '../server';
|
|
10
|
-
<% }
|
|
10
|
+
<% } -%>
|
|
11
|
+
|
|
12
|
+
function signJwt(payload: any) {
|
|
13
|
+
const secret = process.env.JWT_SECRET;
|
|
14
|
+
if (!secret) {
|
|
15
|
+
throw new Error('JWT_SECRET is not set');
|
|
16
|
+
}
|
|
17
|
+
const expiresIn = process.env.JWT_EXPIRES_IN || '5h';
|
|
18
|
+
return jwt.sign(payload, secret, { expiresIn });
|
|
19
|
+
}
|
|
11
20
|
|
|
12
21
|
// @desc Register a new user
|
|
13
22
|
export const registerUser = async (req: Request, res: Response) => {
|
|
14
|
-
const { name, email, password } = req.body;
|
|
23
|
+
const { name, email, password } = req.body as { name: string; email: string; password: string };
|
|
24
|
+
|
|
25
|
+
if (!name || !email || !password) {
|
|
26
|
+
return res.status(400).json({ message: 'name, email, password are required' });
|
|
27
|
+
}
|
|
15
28
|
|
|
16
29
|
try {
|
|
17
|
-
<% if (dbType === 'mongoose') {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return res.status(400).json({ message: 'User already exists' });
|
|
30
|
+
<% if (dbType === 'mongoose') { -%>
|
|
31
|
+
const existing = await User.findOne({ email });
|
|
32
|
+
if (existing) {
|
|
33
|
+
return res.status(409).json({ message: 'User already exists' });
|
|
22
34
|
}
|
|
23
|
-
|
|
24
|
-
await
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
|
|
36
|
+
const hashedPassword = await bcrypt.hash(password, 10);
|
|
37
|
+
|
|
38
|
+
const user = await User.create({ name, email, password: hashedPassword });
|
|
39
|
+
|
|
40
|
+
const payload = { user: { id: user._id.toString() } };
|
|
41
|
+
const token = signJwt(payload);
|
|
42
|
+
|
|
43
|
+
return res.status(201).json({
|
|
44
|
+
token,
|
|
45
|
+
user: { id: user._id.toString(), name: user.name, email: user.email }
|
|
46
|
+
});
|
|
47
|
+
<% } else if (dbType === 'prisma') { -%>
|
|
27
48
|
const existingUser = await prisma.user.findUnique({ where: { email } });
|
|
28
49
|
if (existingUser) {
|
|
29
|
-
|
|
50
|
+
return res.status(409).json({ message: 'User already exists' });
|
|
30
51
|
}
|
|
52
|
+
|
|
31
53
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|
54
|
+
|
|
32
55
|
const user = await prisma.user.create({
|
|
33
|
-
|
|
56
|
+
data: { name, email, password: hashedPassword },
|
|
57
|
+
select: { id: true, name: true, email: true } // never return password
|
|
34
58
|
});
|
|
35
|
-
<% } %>
|
|
36
59
|
|
|
37
60
|
const payload = { user: { id: user.id } };
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
(err, token) => {
|
|
43
|
-
if (err) throw err;
|
|
44
|
-
res.status(201).json({ token });
|
|
45
|
-
}
|
|
46
|
-
);
|
|
61
|
+
const token = signJwt(payload);
|
|
62
|
+
|
|
63
|
+
return res.status(201).json({ token, user });
|
|
64
|
+
<% } -%>
|
|
47
65
|
} catch (error) {
|
|
48
66
|
console.error(error);
|
|
49
|
-
res.status(500).
|
|
67
|
+
return res.status(500).json({ message: 'Server Error' });
|
|
50
68
|
}
|
|
51
69
|
};
|
|
52
70
|
|
|
53
71
|
// @desc Authenticate user & get token (Login)
|
|
54
72
|
export const loginUser = async (req: Request, res: Response) => {
|
|
55
|
-
const { email, password } = req.body;
|
|
73
|
+
const { email, password } = req.body as { email: string; password: string };
|
|
74
|
+
|
|
75
|
+
if (!email || !password) {
|
|
76
|
+
return res.status(400).json({ message: 'email and password are required' });
|
|
77
|
+
}
|
|
56
78
|
|
|
57
79
|
try {
|
|
58
|
-
<% if (dbType === 'mongoose') {
|
|
59
|
-
// Mongoose Logic
|
|
80
|
+
<% if (dbType === 'mongoose') { -%>
|
|
60
81
|
const user = await User.findOne({ email });
|
|
61
|
-
<% } else if (dbType === 'prisma') {
|
|
62
|
-
//
|
|
82
|
+
<% } else if (dbType === 'prisma') { -%>
|
|
83
|
+
// Need password for compare
|
|
63
84
|
const user = await prisma.user.findUnique({ where: { email } });
|
|
64
|
-
<% }
|
|
85
|
+
<% } -%>
|
|
65
86
|
|
|
66
87
|
if (!user) {
|
|
67
|
-
return res.status(
|
|
88
|
+
return res.status(401).json({ message: 'Invalid Credentials' });
|
|
68
89
|
}
|
|
69
90
|
|
|
70
91
|
const isMatch = await bcrypt.compare(password, user.password);
|
|
71
92
|
if (!isMatch) {
|
|
72
|
-
return res.status(
|
|
93
|
+
return res.status(401).json({ message: 'Invalid Credentials' });
|
|
73
94
|
}
|
|
74
95
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
);
|
|
96
|
+
<% if (dbType === 'mongoose') { -%>
|
|
97
|
+
const userId = user._id.toString();
|
|
98
|
+
const safeUser = { id: userId, name: user.name, email: user.email };
|
|
99
|
+
<% } else if (dbType === 'prisma') { -%>
|
|
100
|
+
const userId = user.id;
|
|
101
|
+
const safeUser = { id: user.id, name: user.name, email: user.email };
|
|
102
|
+
<% } -%>
|
|
103
|
+
|
|
104
|
+
const payload = { user: { id: userId } };
|
|
105
|
+
const token = signJwt(payload);
|
|
106
|
+
|
|
107
|
+
return res.json({ token, user: safeUser });
|
|
85
108
|
} catch (error) {
|
|
86
109
|
console.error(error);
|
|
87
|
-
res.status(500).
|
|
110
|
+
return res.status(500).json({ message: 'Server Error' });
|
|
88
111
|
}
|
|
89
112
|
};
|
|
@@ -1,27 +1,58 @@
|
|
|
1
|
-
// Auto-generated by create-backlist
|
|
1
|
+
// Auto-generated by create-backlist on <%= new Date().toISOString() %>
|
|
2
2
|
import { Request, Response, NextFunction } from 'express';
|
|
3
|
-
import jwt from 'jsonwebtoken';
|
|
3
|
+
import jwt, { JwtPayload } from 'jsonwebtoken';
|
|
4
|
+
|
|
5
|
+
type AuthUser = {
|
|
6
|
+
id: string;
|
|
7
|
+
email?: string;
|
|
8
|
+
role?: string;
|
|
9
|
+
};
|
|
4
10
|
|
|
5
|
-
// Extend the default Request interface to include our 'user' property
|
|
6
11
|
interface AuthRequest extends Request {
|
|
7
|
-
user?:
|
|
12
|
+
user?: AuthUser;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getTokenFromRequest(req: Request): string | null {
|
|
16
|
+
// 1) Standard: Authorization: Bearer <token>
|
|
17
|
+
const authHeader = req.headers.authorization;
|
|
18
|
+
if (authHeader && typeof authHeader === 'string') {
|
|
19
|
+
const [type, token] = authHeader.split(' ');
|
|
20
|
+
if (type?.toLowerCase() === 'bearer' && token) return token.trim();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 2) Fallback: x-auth-token (your old way)
|
|
24
|
+
const xToken = req.header('x-auth-token');
|
|
25
|
+
if (xToken) return xToken.trim();
|
|
26
|
+
|
|
27
|
+
return null;
|
|
8
28
|
}
|
|
9
29
|
|
|
10
30
|
export const protect = (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
11
|
-
|
|
12
|
-
const token = req.header('x-auth-token');
|
|
31
|
+
const token = getTokenFromRequest(req);
|
|
13
32
|
|
|
14
|
-
// Check if not token
|
|
15
33
|
if (!token) {
|
|
16
|
-
return res.status(401).json({ message: '
|
|
34
|
+
return res.status(401).json({ message: 'Authorization token missing' });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const secret = process.env.JWT_SECRET;
|
|
38
|
+
if (!secret) {
|
|
39
|
+
// server misconfiguration
|
|
40
|
+
return res.status(500).json({ message: 'JWT_SECRET is not configured' });
|
|
17
41
|
}
|
|
18
42
|
|
|
19
|
-
// Verify token
|
|
20
43
|
try {
|
|
21
|
-
const decoded = jwt.verify(token,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
44
|
+
const decoded = jwt.verify(token, secret) as JwtPayload;
|
|
45
|
+
|
|
46
|
+
// Expect payload shape: { user: { id: "..." } }
|
|
47
|
+
const user = (decoded as any).user;
|
|
48
|
+
|
|
49
|
+
if (!user?.id) {
|
|
50
|
+
return res.status(401).json({ message: 'Token payload is invalid' });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
req.user = { id: String(user.id), email: user.email, role: user.role };
|
|
54
|
+
return next();
|
|
55
|
+
} catch {
|
|
56
|
+
return res.status(401).json({ message: 'Token is not valid' });
|
|
26
57
|
}
|
|
27
58
|
};
|
|
@@ -1,15 +1,54 @@
|
|
|
1
|
-
// Auto-generated by create-backlist
|
|
1
|
+
// Auto-generated by create-backlist on <%= new Date().toISOString() %>
|
|
2
2
|
import { Router } from 'express';
|
|
3
3
|
import { registerUser, loginUser } from '../controllers/Auth.controller';
|
|
4
4
|
|
|
5
5
|
const router = Router();
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* @openapi
|
|
9
|
+
* /api/auth/register:
|
|
10
|
+
* post:
|
|
11
|
+
* tags: [Auth]
|
|
12
|
+
* summary: Register a new user
|
|
13
|
+
* requestBody:
|
|
14
|
+
* required: true
|
|
15
|
+
* content:
|
|
16
|
+
* application/json:
|
|
17
|
+
* schema:
|
|
18
|
+
* type: object
|
|
19
|
+
* required: [name, email, password]
|
|
20
|
+
* properties:
|
|
21
|
+
* name: { type: string }
|
|
22
|
+
* email: { type: string }
|
|
23
|
+
* password: { type: string }
|
|
24
|
+
* responses:
|
|
25
|
+
* 201: { description: Created }
|
|
26
|
+
* 400: { description: Bad Request }
|
|
27
|
+
* 409: { description: Conflict (User exists) }
|
|
28
|
+
*/
|
|
9
29
|
router.post('/register', registerUser);
|
|
10
30
|
|
|
11
|
-
|
|
12
|
-
|
|
31
|
+
/**
|
|
32
|
+
* @openapi
|
|
33
|
+
* /api/auth/login:
|
|
34
|
+
* post:
|
|
35
|
+
* tags: [Auth]
|
|
36
|
+
* summary: Login and get JWT token
|
|
37
|
+
* requestBody:
|
|
38
|
+
* required: true
|
|
39
|
+
* content:
|
|
40
|
+
* application/json:
|
|
41
|
+
* schema:
|
|
42
|
+
* type: object
|
|
43
|
+
* required: [email, password]
|
|
44
|
+
* properties:
|
|
45
|
+
* email: { type: string }
|
|
46
|
+
* password: { type: string }
|
|
47
|
+
* responses:
|
|
48
|
+
* 200: { description: OK }
|
|
49
|
+
* 400: { description: Bad Request }
|
|
50
|
+
* 401: { description: Unauthorized }
|
|
51
|
+
*/
|
|
13
52
|
router.post('/login', loginUser);
|
|
14
53
|
|
|
15
54
|
export default router;
|
|
@@ -1,50 +1,64 @@
|
|
|
1
1
|
// Auto-generated by create-backlist on <%= new Date().toISOString() %>
|
|
2
2
|
import { Request, Response } from 'express';
|
|
3
3
|
|
|
4
|
+
const modelLabel = '<%= modelName %>';
|
|
5
|
+
|
|
6
|
+
function serverError(res: Response, action: string, error: unknown) {
|
|
7
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8
|
+
return res.status(500).json({ success: false, message: `Error ${action} ${modelLabel}`, error: message });
|
|
9
|
+
}
|
|
10
|
+
|
|
4
11
|
export const create<%= modelName %> = async (req: Request, res: Response) => {
|
|
5
12
|
try {
|
|
6
|
-
// TODO: implement create logic
|
|
7
|
-
res.status(201).json({
|
|
13
|
+
// TODO: implement create logic (DB)
|
|
14
|
+
return res.status(201).json({ success: true, message: `${modelLabel} created`, data: req.body });
|
|
8
15
|
} catch (error) {
|
|
9
|
-
res
|
|
16
|
+
return serverError(res, 'creating', error);
|
|
10
17
|
}
|
|
11
18
|
};
|
|
12
19
|
|
|
13
|
-
export const getAll<%= modelName %>s = async (
|
|
20
|
+
export const getAll<%= modelName %>s = async (req: Request, res: Response) => {
|
|
14
21
|
try {
|
|
15
|
-
//
|
|
16
|
-
|
|
22
|
+
// Optional scaffold for pagination
|
|
23
|
+
const page = Number(req.query.page || 1);
|
|
24
|
+
const limit = Number(req.query.limit || 20);
|
|
25
|
+
|
|
26
|
+
// TODO: implement list logic (DB)
|
|
27
|
+
return res.status(200).json({ success: true, page, limit, data: [] });
|
|
17
28
|
} catch (error) {
|
|
18
|
-
res
|
|
29
|
+
return serverError(res, 'fetching', error);
|
|
19
30
|
}
|
|
20
31
|
};
|
|
21
32
|
|
|
22
33
|
export const get<%= modelName %>ById = async (req: Request, res: Response) => {
|
|
23
34
|
try {
|
|
24
35
|
const { id } = req.params;
|
|
25
|
-
// TODO: implement get by id logic
|
|
26
|
-
res.status(
|
|
36
|
+
// TODO: implement get by id logic (DB)
|
|
37
|
+
// If not found: return res.status(404).json({ success:false, message:`${modelLabel} not found` })
|
|
38
|
+
return res.status(200).json({ success: true, data: { id } });
|
|
27
39
|
} catch (error) {
|
|
28
|
-
res
|
|
40
|
+
return serverError(res, 'fetching', error);
|
|
29
41
|
}
|
|
30
42
|
};
|
|
31
43
|
|
|
32
44
|
export const update<%= modelName %>ById = async (req: Request, res: Response) => {
|
|
33
45
|
try {
|
|
34
46
|
const { id } = req.params;
|
|
35
|
-
// TODO: implement update logic
|
|
36
|
-
|
|
47
|
+
// TODO: implement update logic (DB)
|
|
48
|
+
// If not found: return 404
|
|
49
|
+
return res.status(200).json({ success: true, message: `${modelLabel} updated`, data: { id, ...req.body } });
|
|
37
50
|
} catch (error) {
|
|
38
|
-
res
|
|
51
|
+
return serverError(res, 'updating', error);
|
|
39
52
|
}
|
|
40
53
|
};
|
|
41
54
|
|
|
42
55
|
export const delete<%= modelName %>ById = async (req: Request, res: Response) => {
|
|
43
56
|
try {
|
|
44
57
|
const { id } = req.params;
|
|
45
|
-
// TODO: implement delete logic
|
|
46
|
-
|
|
58
|
+
// TODO: implement delete logic (DB)
|
|
59
|
+
// If not found: return 404
|
|
60
|
+
return res.status(204).send();
|
|
47
61
|
} catch (error) {
|
|
48
|
-
res
|
|
62
|
+
return serverError(res, 'deleting', error);
|
|
49
63
|
}
|
|
50
64
|
};
|