create-backlist 5.0.7 → 6.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/backlist.js +227 -0
- package/package.json +10 -4
- package/src/analyzer.js +210 -89
- package/src/db/prisma.ts +4 -0
- package/src/generators/dotnet.js +120 -94
- package/src/generators/java.js +205 -75
- package/src/generators/node.js +262 -85
- package/src/generators/python.js +54 -25
- package/src/generators/template.js +38 -2
- package/src/scanner/index.js +99 -0
- package/src/templates/dotnet/partials/Controller.cs.ejs +7 -14
- package/src/templates/dotnet/partials/Dto.cs.ejs +8 -0
- package/src/templates/java-spring/partials/ApplicationSeeder.java.ejs +30 -0
- package/src/templates/java-spring/partials/AuthController.java.ejs +62 -0
- package/src/templates/java-spring/partials/Controller.java.ejs +40 -50
- package/src/templates/java-spring/partials/Dockerfile.ejs +16 -0
- package/src/templates/java-spring/partials/Entity.java.ejs +16 -15
- package/src/templates/java-spring/partials/JwtAuthFilter.java.ejs +66 -0
- package/src/templates/java-spring/partials/JwtService.java.ejs +58 -0
- package/src/templates/java-spring/partials/Repository.java.ejs +9 -3
- package/src/templates/java-spring/partials/SecurityConfig.java.ejs +44 -0
- package/src/templates/java-spring/partials/Service.java.ejs +69 -0
- package/src/templates/java-spring/partials/User.java.ejs +33 -0
- package/src/templates/java-spring/partials/UserDetailsServiceImpl.java.ejs +33 -0
- package/src/templates/java-spring/partials/UserRepository.java.ejs +20 -0
- package/src/templates/java-spring/partials/docker-compose.yml.ejs +35 -0
- package/src/templates/node-ts-express/base/server.ts +12 -5
- package/src/templates/node-ts-express/base/tsconfig.json +13 -3
- package/src/templates/node-ts-express/partials/ApiDocs.ts.ejs +17 -7
- package/src/templates/node-ts-express/partials/App.test.ts.ejs +27 -27
- package/src/templates/node-ts-express/partials/Auth.controller.ts.ejs +56 -62
- package/src/templates/node-ts-express/partials/Auth.middleware.ts.ejs +21 -10
- package/src/templates/node-ts-express/partials/Controller.ts.ejs +40 -40
- package/src/templates/node-ts-express/partials/DbContext.cs.ejs +3 -3
- package/src/templates/node-ts-express/partials/Dockerfile.ejs +9 -11
- package/src/templates/node-ts-express/partials/Model.cs.ejs +25 -7
- package/src/templates/node-ts-express/partials/Model.ts.ejs +20 -12
- package/src/templates/node-ts-express/partials/PrismaController.ts.ejs +72 -55
- package/src/templates/node-ts-express/partials/PrismaSchema.prisma.ejs +27 -12
- package/src/templates/node-ts-express/partials/README.md.ejs +9 -12
- package/src/templates/node-ts-express/partials/Seeder.ts.ejs +44 -64
- package/src/templates/node-ts-express/partials/docker-compose.yml.ejs +31 -16
- package/src/templates/node-ts-express/partials/package.json.ejs +3 -1
- package/src/templates/node-ts-express/partials/prismaClient.ts.ejs +4 -0
- package/src/templates/node-ts-express/partials/routes.ts.ejs +35 -24
- package/src/templates/python-fastapi/Dockerfile.ejs +8 -0
- package/src/templates/python-fastapi/app/core/config.py.ejs +8 -0
- package/src/templates/python-fastapi/app/core/security.py.ejs +8 -0
- package/src/templates/python-fastapi/app/db.py.ejs +7 -0
- package/src/templates/python-fastapi/app/main.py.ejs +24 -0
- package/src/templates/python-fastapi/app/models/user.py.ejs +9 -0
- package/src/templates/python-fastapi/app/routers/auth.py.ejs +33 -0
- package/src/templates/python-fastapi/app/routers/model_routes.py.ejs +72 -0
- package/src/templates/python-fastapi/app/schemas/user.py.ejs +16 -0
- package/src/templates/python-fastapi/docker-compose.yml.ejs +19 -0
- package/src/templates/python-fastapi/requirements.txt.ejs +5 -1
- package/src/utils.js +19 -4
- package/bin/index.js +0 -141
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Auto-generated by create-backlist
|
|
2
|
+
package <%= group %>.<%= projectName %>.model;
|
|
3
|
+
|
|
4
|
+
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
5
|
+
import jakarta.persistence.*;
|
|
6
|
+
import lombok.*;
|
|
7
|
+
|
|
8
|
+
@Data
|
|
9
|
+
@NoArgsConstructor
|
|
10
|
+
@AllArgsConstructor
|
|
11
|
+
@Entity
|
|
12
|
+
@Table(
|
|
13
|
+
name = "users",
|
|
14
|
+
indexes = {
|
|
15
|
+
@Index(name = "idx_users_email", columnList = "email", unique = true)
|
|
16
|
+
}
|
|
17
|
+
)
|
|
18
|
+
public class User {
|
|
19
|
+
|
|
20
|
+
@Id
|
|
21
|
+
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
22
|
+
private Long id;
|
|
23
|
+
|
|
24
|
+
@Column(nullable = false)
|
|
25
|
+
private String name;
|
|
26
|
+
|
|
27
|
+
@Column(nullable = false, unique = true)
|
|
28
|
+
private String email;
|
|
29
|
+
|
|
30
|
+
@JsonIgnore
|
|
31
|
+
@Column(nullable = false)
|
|
32
|
+
private String password;
|
|
33
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
|
|
7
|
+
import org.springframework.security.core.userdetails.UserDetailsService;
|
|
8
|
+
import org.springframework.security.core.userdetails.UserDetails;
|
|
9
|
+
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|
10
|
+
|
|
11
|
+
import org.springframework.stereotype.Service;
|
|
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)
|
|
25
|
+
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + email));
|
|
26
|
+
|
|
27
|
+
return org.springframework.security.core.userdetails.User
|
|
28
|
+
.withUsername(u.getEmail())
|
|
29
|
+
.password(u.getPassword())
|
|
30
|
+
.authorities("ROLE_USER")
|
|
31
|
+
.build();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Auto-generated by create-backlist
|
|
2
|
+
package <%= group %>.<%= projectName %>.repository;
|
|
3
|
+
|
|
4
|
+
import java.util.Optional;
|
|
5
|
+
|
|
6
|
+
import org.springframework.data.jpa.repository.JpaRepository;
|
|
7
|
+
import org.springframework.stereotype.Repository;
|
|
8
|
+
|
|
9
|
+
import <%= group %>.<%= projectName %>.model.User;
|
|
10
|
+
|
|
11
|
+
@Repository
|
|
12
|
+
public interface UserRepository extends JpaRepository<User, Long> {
|
|
13
|
+
|
|
14
|
+
Optional<User> findByEmail(String email);
|
|
15
|
+
|
|
16
|
+
boolean existsByEmail(String email);
|
|
17
|
+
|
|
18
|
+
// Optional: helpful if emails are stored normalized but requests vary
|
|
19
|
+
Optional<User> findByEmailIgnoreCase(String email);
|
|
20
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
version: "3.8"
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
db:
|
|
5
|
+
image: postgres:16-alpine
|
|
6
|
+
environment:
|
|
7
|
+
POSTGRES_USER: ${DB_USER:-postgres}
|
|
8
|
+
POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
|
|
9
|
+
POSTGRES_DB: ${DB_NAME:-<%= projectName %>}
|
|
10
|
+
ports:
|
|
11
|
+
- "${DB_PORT:-5432}:5432"
|
|
12
|
+
volumes:
|
|
13
|
+
- pgdata:/var/lib/postgresql/data
|
|
14
|
+
healthcheck:
|
|
15
|
+
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-<%= projectName %>}"]
|
|
16
|
+
interval: 5s
|
|
17
|
+
timeout: 5s
|
|
18
|
+
retries: 20
|
|
19
|
+
|
|
20
|
+
app:
|
|
21
|
+
build: .
|
|
22
|
+
depends_on:
|
|
23
|
+
db:
|
|
24
|
+
condition: service_healthy
|
|
25
|
+
environment:
|
|
26
|
+
JWT_SECRET: ${JWT_SECRET:-change_me_long_secret_change_me_long_secret}
|
|
27
|
+
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/${DB_NAME:-<%= projectName %>}
|
|
28
|
+
SPRING_DATASOURCE_USERNAME: ${DB_USER:-postgres}
|
|
29
|
+
SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD:-password}
|
|
30
|
+
SPRING_JPA_HIBERNATE_DDL_AUTO: ${JPA_DDL_AUTO:-update}
|
|
31
|
+
ports:
|
|
32
|
+
- "${APP_PORT:-8080}:8080"
|
|
33
|
+
|
|
34
|
+
volumes:
|
|
35
|
+
pgdata:
|
|
@@ -4,19 +4,26 @@ import dotenv from 'dotenv';
|
|
|
4
4
|
|
|
5
5
|
dotenv.config();
|
|
6
6
|
|
|
7
|
-
const app: Express = express();
|
|
7
|
+
const app: Express = express();
|
|
8
8
|
|
|
9
9
|
app.use(cors());
|
|
10
10
|
app.use(express.json());
|
|
11
11
|
|
|
12
|
-
app.get('/', (req: Request, res: Response) => {
|
|
12
|
+
app.get('/', (req: Request, res: Response) => {
|
|
13
13
|
res.send('Node.js Backend says Hello! Generated by Backlist.');
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
//
|
|
16
|
+
// <backlist:imports>
|
|
17
|
+
// </backlist:imports>
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
// <backlist:setup>
|
|
20
|
+
// </backlist:setup>
|
|
21
|
+
|
|
22
|
+
// <backlist:routes>
|
|
23
|
+
// </backlist:routes>
|
|
24
|
+
|
|
25
|
+
const PORT: number | string = process.env.PORT || 8000;
|
|
19
26
|
|
|
20
27
|
app.listen(PORT, () => {
|
|
21
|
-
console.log(
|
|
28
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
22
29
|
});
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
-
"target": "
|
|
3
|
+
"target": "ES2021",
|
|
4
4
|
"module": "commonjs",
|
|
5
|
+
"moduleResolution": "node",
|
|
5
6
|
"rootDir": "./src",
|
|
6
7
|
"outDir": "./dist",
|
|
8
|
+
|
|
7
9
|
"esModuleInterop": true,
|
|
8
|
-
"strict": true
|
|
9
|
-
|
|
10
|
+
"strict": true,
|
|
11
|
+
|
|
12
|
+
"types": ["node"],
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
|
|
17
|
+
"sourceMap": true
|
|
18
|
+
},
|
|
19
|
+
"include": ["src/**/*"]
|
|
10
20
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Auto-generated by create-backlist v5.
|
|
1
|
+
// Auto-generated by create-backlist v5.1
|
|
2
2
|
import swaggerUi from 'swagger-ui-express';
|
|
3
3
|
import swaggerJsdoc from 'swagger-jsdoc';
|
|
4
4
|
import { Express } from 'express';
|
|
@@ -17,17 +17,27 @@ const options: swaggerJsdoc.Options = {
|
|
|
17
17
|
description: 'Development server',
|
|
18
18
|
},
|
|
19
19
|
],
|
|
20
|
-
|
|
20
|
+
<% if (addAuth) { -%>
|
|
21
|
+
components: {
|
|
22
|
+
securitySchemes: {
|
|
23
|
+
bearerAuth: {
|
|
24
|
+
type: 'http',
|
|
25
|
+
scheme: 'bearer',
|
|
26
|
+
bearerFormat: 'JWT',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
security: [{ bearerAuth: [] }],
|
|
31
|
+
<% } -%>
|
|
21
32
|
},
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
|
|
34
|
+
// Scan all TS files (routes/controllers) for JSDoc @swagger comments
|
|
35
|
+
apis: ['./src/**/*.ts'],
|
|
24
36
|
};
|
|
25
37
|
|
|
26
38
|
const swaggerSpec = swaggerJsdoc(options);
|
|
27
39
|
|
|
28
40
|
export function setupSwagger(app: Express) {
|
|
29
41
|
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
|
|
30
|
-
console.log(
|
|
31
|
-
`📄 API documentation is available at http://localhost:<%= port %>/api-docs`
|
|
32
|
-
);
|
|
42
|
+
console.log(`API documentation: http://localhost:<%= port %>/api-docs`);
|
|
33
43
|
}
|
|
@@ -1,38 +1,38 @@
|
|
|
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
|
-
import authRoutes from '../routes/Auth.routes';
|
|
4
|
+
|
|
5
|
+
import apiRoutes from '../routes';
|
|
6
|
+
<% if (addAuth) { -%>
|
|
7
|
+
import authRoutes from '../routes/Auth.routes';
|
|
8
|
+
<% } -%>
|
|
8
9
|
|
|
9
10
|
const app = express();
|
|
10
11
|
app.use(express.json());
|
|
11
|
-
app.use('/api', apiRoutes);
|
|
12
|
-
app.use('/api/auth', authRoutes);
|
|
13
|
-
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
<% if (addAuth) { -%>
|
|
14
|
+
app.use('/api/auth', authRoutes);
|
|
15
|
+
<% } -%>
|
|
16
|
+
app.use('/api', apiRoutes);
|
|
16
17
|
|
|
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:
|
|
18
|
+
describe('API Endpoints (Generated)', () => {
|
|
19
|
+
it('sanity check', () => {
|
|
25
20
|
expect(1 + 1).toBe(2);
|
|
26
21
|
});
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
23
|
+
<% endpoints
|
|
24
|
+
.filter(ep => ep && ep.route && ep.method)
|
|
25
|
+
.forEach(ep => {
|
|
26
|
+
const method = ep.method.toLowerCase();
|
|
27
|
+
const url = (ep.route.startsWith('/api/') ? ep.route.replace(/^\/api/, '') : ep.route)
|
|
28
|
+
.replace(/:\w+/g, '1'); // replace path params with dummy value
|
|
29
|
+
-%>
|
|
30
|
+
it('<%= ep.method %> <%= url %> should respond', async () => {
|
|
31
|
+
const res = await request(app).<%= method %>('<%= '/api' + url %>')<% if (['post','put','patch'].includes(method) && ep.schemaFields) { %>
|
|
32
|
+
.send(<%- JSON.stringify(Object.fromEntries(Object.entries(ep.schemaFields).map(([k,t]) => [k, t === 'Number' ? 1 : (t === 'Boolean' ? true : 'test') ]))) %>)<% } %>;
|
|
33
|
+
// We only assert "not 404" because generated handlers may be TODO stubs,
|
|
34
|
+
// and auth/validation may affect exact status codes.
|
|
35
|
+
expect(res.statusCode).not.toBe(404);
|
|
36
|
+
});
|
|
37
|
+
<% }) -%>
|
|
38
38
|
});
|
|
@@ -3,87 +3,81 @@ 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') {
|
|
9
|
-
import { prisma } from '../
|
|
10
|
-
<% }
|
|
8
|
+
<% } else if (dbType === 'prisma') { -%>
|
|
9
|
+
import { prisma } from '../db/prisma';
|
|
10
|
+
<% } -%>
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
function signToken(res: Response, userId: string) {
|
|
13
|
+
const secret = process.env.JWT_SECRET;
|
|
14
|
+
if (!secret) {
|
|
15
|
+
return res.status(500).json({ message: 'JWT_SECRET is not configured' });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const payload = { user: { id: userId } };
|
|
19
|
+
|
|
20
|
+
jwt.sign(payload, secret, { expiresIn: '5h' }, (err, token) => {
|
|
21
|
+
if (err || !token) return res.status(500).json({ message: 'Failed to sign token' });
|
|
22
|
+
return res.status(201).json({ token });
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Register
|
|
13
27
|
export const registerUser = async (req: Request, res: Response) => {
|
|
14
|
-
const { name, email, password } = req.body;
|
|
28
|
+
const { name, email, password } = req.body || {};
|
|
15
29
|
|
|
16
30
|
try {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
let user = await User.findOne({ email });
|
|
20
|
-
if (user) {
|
|
21
|
-
return res.status(400).json({ message: 'User already exists' });
|
|
31
|
+
if (!name || !email || !password) {
|
|
32
|
+
return res.status(400).json({ message: 'name, email, password are required' });
|
|
22
33
|
}
|
|
23
|
-
|
|
34
|
+
|
|
35
|
+
<% if (dbType === 'mongoose') { -%>
|
|
36
|
+
const existing = await User.findOne({ email });
|
|
37
|
+
if (existing) return res.status(400).json({ message: 'User already exists' });
|
|
38
|
+
|
|
39
|
+
const user = new User({ name, email, password }); // password will be hashed by pre-save hook
|
|
24
40
|
await user.save();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
return res.status(400).json({ message: 'User already exists' });
|
|
30
|
-
}
|
|
31
|
-
const hashedPassword = await bcrypt.hash(password, 10);
|
|
32
|
-
const user = await prisma.user.create({
|
|
33
|
-
data: { name, email, password: hashedPassword },
|
|
34
|
-
});
|
|
35
|
-
<% } %>
|
|
41
|
+
return signToken(res, String(user._id));
|
|
42
|
+
<% } else if (dbType === 'prisma') { -%>
|
|
43
|
+
const existing = await prisma.user.findUnique({ where: { email } });
|
|
44
|
+
if (existing) return res.status(400).json({ message: 'User already exists' });
|
|
36
45
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (err) throw err;
|
|
44
|
-
res.status(201).json({ token });
|
|
45
|
-
}
|
|
46
|
-
);
|
|
47
|
-
} catch (error) {
|
|
48
|
-
console.error(error);
|
|
49
|
-
res.status(500).send('Server Error');
|
|
46
|
+
const hashedPassword = await bcrypt.hash(password, 10);
|
|
47
|
+
const user = await prisma.user.create({ data: { name, email, password: hashedPassword } });
|
|
48
|
+
return signToken(res, String(user.id));
|
|
49
|
+
<% } -%>
|
|
50
|
+
} catch (error: any) {
|
|
51
|
+
return res.status(500).json({ message: 'Server Error', error: error?.message || error });
|
|
50
52
|
}
|
|
51
53
|
};
|
|
52
54
|
|
|
53
|
-
//
|
|
55
|
+
// Login
|
|
54
56
|
export const loginUser = async (req: Request, res: Response) => {
|
|
55
|
-
const { email, password } = req.body;
|
|
57
|
+
const { email, password } = req.body || {};
|
|
56
58
|
|
|
57
59
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
if (!email || !password) {
|
|
61
|
+
return res.status(400).json({ message: 'email and password are required' });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
<% if (dbType === 'mongoose') { -%>
|
|
60
65
|
const user = await User.findOne({ email });
|
|
61
|
-
|
|
62
|
-
// Prisma Logic
|
|
66
|
+
<% } else if (dbType === 'prisma') { -%>
|
|
63
67
|
const user = await prisma.user.findUnique({ where: { email } });
|
|
64
|
-
|
|
68
|
+
<% } -%>
|
|
65
69
|
|
|
66
|
-
if (!user) {
|
|
67
|
-
return res.status(400).json({ message: 'Invalid Credentials' });
|
|
68
|
-
}
|
|
70
|
+
if (!user) return res.status(400).json({ message: 'Invalid Credentials' });
|
|
69
71
|
|
|
70
72
|
const isMatch = await bcrypt.compare(password, user.password);
|
|
71
|
-
if (!isMatch) {
|
|
72
|
-
return res.status(400).json({ message: 'Invalid Credentials' });
|
|
73
|
-
}
|
|
73
|
+
if (!isMatch) return res.status(400).json({ message: 'Invalid Credentials' });
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
res.json({ token });
|
|
83
|
-
}
|
|
84
|
-
);
|
|
85
|
-
} catch (error) {
|
|
86
|
-
console.error(error);
|
|
87
|
-
res.status(500).send('Server Error');
|
|
75
|
+
<% if (dbType === 'mongoose') { -%>
|
|
76
|
+
return signToken(res, String(user._id));
|
|
77
|
+
<% } else { -%>
|
|
78
|
+
return signToken(res, String(user.id));
|
|
79
|
+
<% } -%>
|
|
80
|
+
} catch (error: any) {
|
|
81
|
+
return res.status(500).json({ message: 'Server Error', error: error?.message || error });
|
|
88
82
|
}
|
|
89
83
|
};
|
|
@@ -1,27 +1,38 @@
|
|
|
1
|
-
// Auto-generated by create-backlist
|
|
1
|
+
// Auto-generated by create-backlist v5.1 on <%= new Date().toISOString() %>
|
|
2
2
|
import { Request, Response, NextFunction } from 'express';
|
|
3
3
|
import jwt from 'jsonwebtoken';
|
|
4
4
|
|
|
5
|
-
// Extend the default Request interface to include our 'user' property
|
|
6
5
|
interface AuthRequest extends Request {
|
|
7
6
|
user?: any;
|
|
8
7
|
}
|
|
9
8
|
|
|
10
9
|
export const protect = (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const secret = process.env.JWT_SECRET;
|
|
11
|
+
if (!secret) return res.status(500).json({ message: 'JWT_SECRET is not configured' });
|
|
12
|
+
|
|
13
|
+
// Support both:
|
|
14
|
+
// 1) Authorization: Bearer <token>
|
|
15
|
+
// 2) x-auth-token: <token>
|
|
16
|
+
const authHeader = req.header('authorization');
|
|
17
|
+
const xToken = req.header('x-auth-token');
|
|
18
|
+
|
|
19
|
+
let token: string | undefined;
|
|
20
|
+
|
|
21
|
+
if (authHeader && authHeader.toLowerCase().startsWith('bearer ')) {
|
|
22
|
+
token = authHeader.slice(7).trim();
|
|
23
|
+
} else if (xToken) {
|
|
24
|
+
token = xToken.trim();
|
|
25
|
+
}
|
|
13
26
|
|
|
14
|
-
// Check if not token
|
|
15
27
|
if (!token) {
|
|
16
28
|
return res.status(401).json({ message: 'No token, authorization denied' });
|
|
17
29
|
}
|
|
18
30
|
|
|
19
|
-
// Verify token
|
|
20
31
|
try {
|
|
21
|
-
const decoded = jwt.verify(token,
|
|
32
|
+
const decoded: any = jwt.verify(token, secret);
|
|
22
33
|
req.user = decoded.user;
|
|
23
|
-
next();
|
|
24
|
-
} catch
|
|
25
|
-
res.status(401).json({ message: 'Token is not valid' });
|
|
34
|
+
return next();
|
|
35
|
+
} catch {
|
|
36
|
+
return res.status(401).json({ message: 'Token is not valid' });
|
|
26
37
|
}
|
|
27
38
|
};
|
|
@@ -1,50 +1,50 @@
|
|
|
1
1
|
// Auto-generated by create-backlist on <%= new Date().toISOString() %>
|
|
2
2
|
import { Request, Response } from 'express';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} catch (error) {
|
|
9
|
-
res.status(500).json({ message: 'Error creating <%= modelName %>', error });
|
|
10
|
-
}
|
|
11
|
-
};
|
|
4
|
+
/**
|
|
5
|
+
* Controller: <%= controllerName %>
|
|
6
|
+
* Generated from frontend AST scan.
|
|
7
|
+
*/
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
res.status(500).json({ message: 'Error fetching <%= modelName %>s', error });
|
|
19
|
-
}
|
|
20
|
-
};
|
|
9
|
+
<%
|
|
10
|
+
function safeAction(name, fallback) {
|
|
11
|
+
if (!name) return fallback;
|
|
12
|
+
return String(name).replace(/[^a-zA-Z0-9_]/g, '');
|
|
13
|
+
}
|
|
21
14
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
} catch (error) {
|
|
28
|
-
res.status(500).json({ message: 'Error fetching <%= modelName %>', error });
|
|
29
|
-
}
|
|
30
|
-
};
|
|
15
|
+
function hasBody(method) {
|
|
16
|
+
const m = String(method || 'GET').toUpperCase();
|
|
17
|
+
return ['POST', 'PUT', 'PATCH'].includes(m);
|
|
18
|
+
}
|
|
19
|
+
%>
|
|
31
20
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} catch (error) {
|
|
38
|
-
res.status(500).json({ message: 'Error updating <%= modelName %>', error });
|
|
39
|
-
}
|
|
40
|
-
};
|
|
21
|
+
<% if (!Array.isArray(endpoints) || endpoints.length === 0) { %>
|
|
22
|
+
export async function health(_req: Request, res: Response) {
|
|
23
|
+
return res.status(200).json({ ok: true });
|
|
24
|
+
}
|
|
25
|
+
<% } %>
|
|
41
26
|
|
|
42
|
-
|
|
27
|
+
<% (endpoints || []).forEach((ep, i) => {
|
|
28
|
+
const method = String(ep.method || 'GET').toUpperCase();
|
|
29
|
+
const actionName = safeAction(ep.actionName, `handler${i}`);
|
|
30
|
+
const route = ep.route || ep.path || '';
|
|
31
|
+
-%>
|
|
32
|
+
/**
|
|
33
|
+
* <%= method %> <%= route %>
|
|
34
|
+
*/
|
|
35
|
+
export async function <%= actionName %>(req: Request, res: Response) {
|
|
43
36
|
try {
|
|
44
|
-
|
|
45
|
-
//
|
|
46
|
-
|
|
37
|
+
<% if (hasBody(method)) { -%>
|
|
38
|
+
// Body schema (inferred):
|
|
39
|
+
// <%- JSON.stringify(ep.schemaFields || ep.requestBody?.fields || {}, null, 2).split('\n').map(l => '// ' + l).join('\n') %>
|
|
40
|
+
const payload = req.body;
|
|
41
|
+
return res.status(<%= method === 'POST' ? 201 : 200 %>).json({ message: 'TODO: implement', payload });
|
|
42
|
+
<% } else { -%>
|
|
43
|
+
return res.status(200).json({ message: 'TODO: implement', params: req.params, query: req.query });
|
|
44
|
+
<% } -%>
|
|
47
45
|
} catch (error) {
|
|
48
|
-
res.status(500).json({ message: '
|
|
46
|
+
return res.status(500).json({ message: 'Internal Server Error', error });
|
|
49
47
|
}
|
|
50
|
-
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
<% }) -%>
|
|
@@ -8,8 +8,8 @@ namespace <%= projectName %>.Data
|
|
|
8
8
|
{
|
|
9
9
|
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
public DbSet<<%= model.name %>> <%= model.name %>
|
|
13
|
-
|
|
11
|
+
<% modelsToGenerate.forEach(model => { -%>
|
|
12
|
+
public DbSet<<%= model.name %>> <%= model.name %> { get; set; } = default!;
|
|
13
|
+
<% }); -%>
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -1,33 +1,31 @@
|
|
|
1
|
-
# Auto-generated by create-backlist v5.
|
|
1
|
+
# Auto-generated by create-backlist v5.1
|
|
2
2
|
|
|
3
|
-
# ---- Base Stage ----
|
|
4
3
|
FROM node:18-alpine AS base
|
|
5
4
|
WORKDIR /usr/src/app
|
|
6
5
|
COPY package*.json ./
|
|
7
6
|
|
|
8
|
-
# ---- Dependencies Stage ----
|
|
9
7
|
FROM base AS dependencies
|
|
10
|
-
|
|
8
|
+
# Use npm ci for reproducible installs
|
|
9
|
+
RUN npm ci
|
|
11
10
|
|
|
12
|
-
# ---- Build Stage ----
|
|
13
11
|
FROM base AS build
|
|
14
12
|
COPY --from=dependencies /usr/src/app/node_modules ./node_modules
|
|
15
13
|
COPY . .
|
|
16
|
-
<% if (dbType === 'prisma') {
|
|
14
|
+
<% if (dbType === 'prisma') { -%>
|
|
17
15
|
RUN npx prisma generate
|
|
18
|
-
<% }
|
|
16
|
+
<% } -%>
|
|
19
17
|
RUN npm run build
|
|
20
18
|
|
|
21
|
-
# ---- Production Stage ----
|
|
22
19
|
FROM node:18-alpine AS production
|
|
23
20
|
WORKDIR /usr/src/app
|
|
21
|
+
ENV NODE_ENV=production
|
|
22
|
+
|
|
24
23
|
COPY --from=build /usr/src/app/dist ./dist
|
|
25
24
|
COPY --from=dependencies /usr/src/app/node_modules ./node_modules
|
|
26
25
|
COPY package*.json ./
|
|
27
|
-
<% if (dbType === 'prisma') {
|
|
28
|
-
# Copy Prisma schema for runtime
|
|
26
|
+
<% if (dbType === 'prisma') { -%>
|
|
29
27
|
COPY prisma ./prisma
|
|
30
|
-
<% }
|
|
28
|
+
<% } -%>
|
|
31
29
|
|
|
32
30
|
EXPOSE <%= port %>
|
|
33
31
|
CMD ["node", "dist/server.js"]
|