express-genix 1.1.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 +110 -0
- package/index.js +114 -0
- package/lib/cleanup.js +129 -0
- package/lib/generator.js +205 -0
- package/lib/utils.js +92 -0
- package/package.json +49 -0
- package/templates/config/database.mongo.js.ejs +36 -0
- package/templates/config/database.postgres.js.ejs +41 -0
- package/templates/config/swagger.json.ejs +194 -0
- package/templates/controllers/authController.js.ejs +129 -0
- package/templates/controllers/exampleController.js.ejs +152 -0
- package/templates/controllers/userController.js.ejs +60 -0
- package/templates/core/Dockerfile.ejs +32 -0
- package/templates/core/README.md.ejs +179 -0
- package/templates/core/app.js.ejs +65 -0
- package/templates/core/docker-compose.yml.ejs +48 -0
- package/templates/core/env.ejs +20 -0
- package/templates/core/eslintrc.json.ejs +20 -0
- package/templates/core/gitignore.ejs +51 -0
- package/templates/core/healthcheck.js.ejs +25 -0
- package/templates/core/index.js.ejs +24 -0
- package/templates/core/jest.config.js.ejs +23 -0
- package/templates/core/package.json.ejs +33 -0
- package/templates/core/prettierrc.json.ejs +12 -0
- package/templates/core/server.js.ejs +44 -0
- package/templates/middleware/auth.js.ejs +66 -0
- package/templates/middleware/errorHandler.js.ejs +47 -0
- package/templates/middleware/validation.js.ejs +48 -0
- package/templates/models/User.mongo.js.ejs +33 -0
- package/templates/models/User.postgres.js.ejs +41 -0
- package/templates/models/index.mongo.js.ejs +8 -0
- package/templates/models/index.postgres.js.ejs +12 -0
- package/templates/routes/authRoutes.js.ejs +14 -0
- package/templates/routes/exampleRoutes.js.ejs +13 -0
- package/templates/routes/index.js.ejs +24 -0
- package/templates/routes/userRoutes.js.ejs +16 -0
- package/templates/services/authService.js.ejs +36 -0
- package/templates/services/exampleService.js.ejs +113 -0
- package/templates/services/userService.mongo.js.ejs +34 -0
- package/templates/services/userService.postgres.js.ejs +31 -0
- package/templates/tests/auth.test.js.ejs +67 -0
- package/templates/tests/example.test.js.ejs +113 -0
- package/templates/tests/setup.js.ejs +12 -0
- package/templates/tests/users.test.js.ejs +43 -0
- package/templates/utils/errors.js.ejs +13 -0
- package/templates/utils/logger.js.ejs +28 -0
- package/templates/utils/validators.js.ejs +35 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const mongoose = require('mongoose');
|
|
2
|
+
|
|
3
|
+
const mongoUri = process.env.MONGO_URI || 'mongodb://localhost:27017/<%= projectName %>';
|
|
4
|
+
|
|
5
|
+
const connect = async () => {
|
|
6
|
+
try {
|
|
7
|
+
await mongoose.connect(mongoUri, {
|
|
8
|
+
useNewUrlParser: true,
|
|
9
|
+
useUnifiedTopology: true,
|
|
10
|
+
});
|
|
11
|
+
console.log('MongoDB connected successfully');
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error('MongoDB connection error:', error);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const disconnect = async () => {
|
|
19
|
+
try {
|
|
20
|
+
await mongoose.disconnect();
|
|
21
|
+
console.log('MongoDB disconnected');
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error('MongoDB disconnection error:', error);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Handle connection events
|
|
28
|
+
mongoose.connection.on('error', (error) => {
|
|
29
|
+
console.error('MongoDB connection error:', error);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
mongoose.connection.on('disconnected', () => {
|
|
33
|
+
console.log('MongoDB disconnected');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
module.exports = { connect, disconnect };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const { Sequelize } = require('sequelize');
|
|
2
|
+
|
|
3
|
+
const databaseUrl = process.env.DATABASE_URL || 'postgresql://user:password@localhost:5432/<%= projectName %>';
|
|
4
|
+
|
|
5
|
+
const sequelize = new Sequelize(databaseUrl, {
|
|
6
|
+
dialect: 'postgres',
|
|
7
|
+
logging: process.env.NODE_ENV === 'development' ? console.log : false,
|
|
8
|
+
pool: {
|
|
9
|
+
max: 5,
|
|
10
|
+
min: 0,
|
|
11
|
+
acquire: 30000,
|
|
12
|
+
idle: 10000,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const connect = async () => {
|
|
17
|
+
try {
|
|
18
|
+
await sequelize.authenticate();
|
|
19
|
+
console.log('PostgreSQL connected successfully');
|
|
20
|
+
|
|
21
|
+
// Sync database in development
|
|
22
|
+
if (process.env.NODE_ENV === 'development') {
|
|
23
|
+
await sequelize.sync({ alter: true });
|
|
24
|
+
console.log('Database synced');
|
|
25
|
+
}
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('PostgreSQL connection error:', error);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const disconnect = async () => {
|
|
33
|
+
try {
|
|
34
|
+
await sequelize.close();
|
|
35
|
+
console.log('PostgreSQL disconnected');
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('PostgreSQL disconnection error:', error);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
module.exports = { connect, disconnect, sequelize };
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.0",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "<%= projectName %> API",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"description": "A production-grade Express boilerplate<% if (hasDatabase) { %> with JWT authentication<% } %>"
|
|
7
|
+
},
|
|
8
|
+
"servers": [
|
|
9
|
+
{
|
|
10
|
+
"url": "http://localhost:3000/api",
|
|
11
|
+
"description": "Development server"
|
|
12
|
+
}
|
|
13
|
+
],<% if (hasDatabase) { %>
|
|
14
|
+
"components": {
|
|
15
|
+
"securitySchemes": {
|
|
16
|
+
"bearerAuth": {
|
|
17
|
+
"type": "http",
|
|
18
|
+
"scheme": "bearer",
|
|
19
|
+
"bearerFormat": "JWT"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},<% } %>
|
|
23
|
+
"paths": {<% if (hasDatabase) { %>
|
|
24
|
+
"/auth/register": {
|
|
25
|
+
"post": {
|
|
26
|
+
"summary": "Register a new user",
|
|
27
|
+
"tags": ["Authentication"],
|
|
28
|
+
"requestBody": {
|
|
29
|
+
"required": true,
|
|
30
|
+
"content": {
|
|
31
|
+
"application/json": {
|
|
32
|
+
"schema": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"required": ["username", "email", "password"],
|
|
35
|
+
"properties": {
|
|
36
|
+
"username": { "type": "string" },
|
|
37
|
+
"email": { "type": "string", "format": "email" },
|
|
38
|
+
"password": { "type": "string", "minLength": 6 }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"responses": {
|
|
45
|
+
"201": { "description": "User registered successfully" },
|
|
46
|
+
"409": { "description": "User already exists" }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"/auth/login": {
|
|
51
|
+
"post": {
|
|
52
|
+
"summary": "Login user",
|
|
53
|
+
"tags": ["Authentication"],
|
|
54
|
+
"requestBody": {
|
|
55
|
+
"required": true,
|
|
56
|
+
"content": {
|
|
57
|
+
"application/json": {
|
|
58
|
+
"schema": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"required": ["email", "password"],
|
|
61
|
+
"properties": {
|
|
62
|
+
"email": { "type": "string", "format": "email" },
|
|
63
|
+
"password": { "type": "string" }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"responses": {
|
|
70
|
+
"200": { "description": "Login successful" },
|
|
71
|
+
"401": { "description": "Invalid credentials" }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"/users/profile": {
|
|
76
|
+
"get": {
|
|
77
|
+
"summary": "Get user profile",
|
|
78
|
+
"tags": ["Users"],
|
|
79
|
+
"security": [{ "bearerAuth": [] }],
|
|
80
|
+
"responses": {
|
|
81
|
+
"200": { "description": "User profile retrieved" },
|
|
82
|
+
"401": { "description": "Unauthorized" }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}<% } else { %>
|
|
86
|
+
"/examples": {
|
|
87
|
+
"get": {
|
|
88
|
+
"summary": "Get all examples",
|
|
89
|
+
"tags": ["Examples"],
|
|
90
|
+
"parameters": [
|
|
91
|
+
{
|
|
92
|
+
"name": "page",
|
|
93
|
+
"in": "query",
|
|
94
|
+
"schema": { "type": "integer", "default": 1 }
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "limit",
|
|
98
|
+
"in": "query",
|
|
99
|
+
"schema": { "type": "integer", "default": 10 }
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"responses": {
|
|
103
|
+
"200": { "description": "Examples retrieved successfully" }
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"post": {
|
|
107
|
+
"summary": "Create new example",
|
|
108
|
+
"tags": ["Examples"],
|
|
109
|
+
"requestBody": {
|
|
110
|
+
"required": true,
|
|
111
|
+
"content": {
|
|
112
|
+
"application/json": {
|
|
113
|
+
"schema": {
|
|
114
|
+
"type": "object",
|
|
115
|
+
"required": ["title", "description"],
|
|
116
|
+
"properties": {
|
|
117
|
+
"title": { "type": "string" },
|
|
118
|
+
"description": { "type": "string" }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"responses": {
|
|
125
|
+
"201": { "description": "Example created successfully" },
|
|
126
|
+
"400": { "description": "Invalid input" }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"/examples/{id}": {
|
|
131
|
+
"get": {
|
|
132
|
+
"summary": "Get example by ID",
|
|
133
|
+
"tags": ["Examples"],
|
|
134
|
+
"parameters": [
|
|
135
|
+
{
|
|
136
|
+
"name": "id",
|
|
137
|
+
"in": "path",
|
|
138
|
+
"required": true,
|
|
139
|
+
"schema": { "type": "string" }
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
"responses": {
|
|
143
|
+
"200": { "description": "Example retrieved successfully" },
|
|
144
|
+
"404": { "description": "Example not found" }
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
"put": {
|
|
148
|
+
"summary": "Update example",
|
|
149
|
+
"tags": ["Examples"],
|
|
150
|
+
"parameters": [
|
|
151
|
+
{
|
|
152
|
+
"name": "id",
|
|
153
|
+
"in": "path",
|
|
154
|
+
"required": true,
|
|
155
|
+
"schema": { "type": "string" }
|
|
156
|
+
}
|
|
157
|
+
],
|
|
158
|
+
"requestBody": {
|
|
159
|
+
"content": {
|
|
160
|
+
"application/json": {
|
|
161
|
+
"schema": {
|
|
162
|
+
"type": "object",
|
|
163
|
+
"properties": {
|
|
164
|
+
"title": { "type": "string" },
|
|
165
|
+
"description": { "type": "string" }
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"responses": {
|
|
172
|
+
"200": { "description": "Example updated successfully" },
|
|
173
|
+
"404": { "description": "Example not found" }
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"delete": {
|
|
177
|
+
"summary": "Delete example",
|
|
178
|
+
"tags": ["Examples"],
|
|
179
|
+
"parameters": [
|
|
180
|
+
{
|
|
181
|
+
"name": "id",
|
|
182
|
+
"in": "path",
|
|
183
|
+
"required": true,
|
|
184
|
+
"schema": { "type": "string" }
|
|
185
|
+
}
|
|
186
|
+
],
|
|
187
|
+
"responses": {
|
|
188
|
+
"200": { "description": "Example deleted successfully" },
|
|
189
|
+
"404": { "description": "Example not found" }
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}<% } %>
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const jwt = require('jsonwebtoken');
|
|
2
|
+
const bcrypt = require('bcryptjs');
|
|
3
|
+
const authService = require('../services/authService');
|
|
4
|
+
const userService = require('../services/userService');
|
|
5
|
+
const { AppError } = require('../utils/errors');
|
|
6
|
+
|
|
7
|
+
const jwtSecret = process.env.JWT_SECRET || 'your-secret-key';
|
|
8
|
+
const jwtRefreshSecret = process.env.JWT_REFRESH_SECRET || 'your-refresh-secret-key';
|
|
9
|
+
|
|
10
|
+
const register = async (req, res, next) => {
|
|
11
|
+
try {
|
|
12
|
+
const { username, email, password } = req.body;
|
|
13
|
+
|
|
14
|
+
// Check if user already exists
|
|
15
|
+
const existingUser = await userService.findByEmail(email);
|
|
16
|
+
if (existingUser) {
|
|
17
|
+
throw new AppError('User already exists with this email', 409);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Hash password
|
|
21
|
+
const hashedPassword = await bcrypt.hash(password, 12);
|
|
22
|
+
|
|
23
|
+
// Create user
|
|
24
|
+
const user = await userService.create({
|
|
25
|
+
username,
|
|
26
|
+
email,
|
|
27
|
+
password: hashedPassword,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Generate tokens
|
|
31
|
+
const { accessToken, refreshToken } = authService.generateTokens(user);
|
|
32
|
+
|
|
33
|
+
res.status(201).json({
|
|
34
|
+
message: 'User registered successfully',
|
|
35
|
+
user: { id: user.id, username: user.username, email: user.email },
|
|
36
|
+
accessToken,
|
|
37
|
+
refreshToken,
|
|
38
|
+
});
|
|
39
|
+
} catch (error) {
|
|
40
|
+
next(error);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const login = async (req, res, next) => {
|
|
45
|
+
try {
|
|
46
|
+
const { email, password } = req.body;
|
|
47
|
+
|
|
48
|
+
// Find user
|
|
49
|
+
const user = await userService.findByEmail(email);
|
|
50
|
+
if (!user) {
|
|
51
|
+
throw new AppError('Invalid credentials', 401);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check password
|
|
55
|
+
const isValidPassword = await bcrypt.compare(password, user.password);
|
|
56
|
+
if (!isValidPassword) {
|
|
57
|
+
throw new AppError('Invalid credentials', 401);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Generate tokens
|
|
61
|
+
const { accessToken, refreshToken } = authService.generateTokens(user);
|
|
62
|
+
|
|
63
|
+
res.json({
|
|
64
|
+
message: 'Login successful',
|
|
65
|
+
user: { id: user.id, username: user.username, email: user.email },
|
|
66
|
+
accessToken,
|
|
67
|
+
refreshToken,
|
|
68
|
+
});
|
|
69
|
+
} catch (error) {
|
|
70
|
+
next(error);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const refreshToken = async (req, res, next) => {
|
|
75
|
+
try {
|
|
76
|
+
const { refreshToken } = req.body;
|
|
77
|
+
|
|
78
|
+
if (!refreshToken) {
|
|
79
|
+
throw new AppError('Refresh token is required', 400);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const decoded = jwt.verify(refreshToken, jwtRefreshSecret);
|
|
83
|
+
const user = await userService.findById(decoded.userId);
|
|
84
|
+
|
|
85
|
+
if (!user) {
|
|
86
|
+
throw new AppError('Invalid refresh token', 401);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const tokens = authService.generateTokens(user);
|
|
90
|
+
|
|
91
|
+
res.json({
|
|
92
|
+
accessToken: tokens.accessToken,
|
|
93
|
+
refreshToken: tokens.refreshToken,
|
|
94
|
+
});
|
|
95
|
+
} catch (error) {
|
|
96
|
+
next(error);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const logout = (req, res) => {
|
|
101
|
+
// In a real app, you might want to blacklist the token
|
|
102
|
+
res.json({ message: 'Logout successful' });
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const authenticateToken = (req, res, next) => {
|
|
106
|
+
const authHeader = req.headers['authorization'];
|
|
107
|
+
const token = authHeader && authHeader.split(' ')[1];
|
|
108
|
+
|
|
109
|
+
if (!token) {
|
|
110
|
+
return res.status(401).json({ error: 'Access token is required' });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
jwt.verify(token, jwtSecret, (err, decoded) => {
|
|
114
|
+
if (err) {
|
|
115
|
+
return res.status(403).json({ error: 'Invalid or expired token' });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
req.user = decoded;
|
|
119
|
+
next();
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
module.exports = {
|
|
124
|
+
register,
|
|
125
|
+
login,
|
|
126
|
+
refreshToken,
|
|
127
|
+
logout,
|
|
128
|
+
authenticateToken,
|
|
129
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
const exampleService = require('../services/exampleService');
|
|
2
|
+
const { AppError } = require('../utils/errors');
|
|
3
|
+
const { createLogger } = require('../utils/logger');
|
|
4
|
+
|
|
5
|
+
const logger = createLogger('ExampleController');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get all examples
|
|
9
|
+
*/
|
|
10
|
+
const getAllExamples = async (req, res, next) => {
|
|
11
|
+
try {
|
|
12
|
+
const { page = 1, limit = 10 } = req.query;
|
|
13
|
+
|
|
14
|
+
logger.info('Fetching examples', { page, limit });
|
|
15
|
+
|
|
16
|
+
const examples = await exampleService.getAllExamples(
|
|
17
|
+
parseInt(page),
|
|
18
|
+
parseInt(limit)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
res.json({
|
|
22
|
+
success: true,
|
|
23
|
+
data: examples,
|
|
24
|
+
pagination: {
|
|
25
|
+
page: parseInt(page),
|
|
26
|
+
limit: parseInt(limit),
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
} catch (error) {
|
|
30
|
+
logger.error('Error fetching examples', { error: error.message });
|
|
31
|
+
next(error);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get example by ID
|
|
37
|
+
*/
|
|
38
|
+
const getExampleById = async (req, res, next) => {
|
|
39
|
+
try {
|
|
40
|
+
const { id } = req.params;
|
|
41
|
+
|
|
42
|
+
logger.info('Fetching example by ID', { id });
|
|
43
|
+
|
|
44
|
+
const example = await exampleService.getExampleById(id);
|
|
45
|
+
|
|
46
|
+
if (!example) {
|
|
47
|
+
throw new AppError('Example not found', 404);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
res.json({
|
|
51
|
+
success: true,
|
|
52
|
+
data: example,
|
|
53
|
+
});
|
|
54
|
+
} catch (error) {
|
|
55
|
+
logger.error('Error fetching example', { id: req.params.id, error: error.message });
|
|
56
|
+
next(error);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create new example
|
|
62
|
+
*/
|
|
63
|
+
const createExample = async (req, res, next) => {
|
|
64
|
+
try {
|
|
65
|
+
const { title, description } = req.body;
|
|
66
|
+
|
|
67
|
+
if (!title || !description) {
|
|
68
|
+
throw new AppError('Title and description are required', 400);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
logger.info('Creating new example', { title });
|
|
72
|
+
|
|
73
|
+
const example = await exampleService.createExample({
|
|
74
|
+
title,
|
|
75
|
+
description,
|
|
76
|
+
createdAt: new Date(),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
res.status(201).json({
|
|
80
|
+
success: true,
|
|
81
|
+
message: 'Example created successfully',
|
|
82
|
+
data: example,
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
logger.error('Error creating example', { error: error.message });
|
|
86
|
+
next(error);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Update example
|
|
92
|
+
*/
|
|
93
|
+
const updateExample = async (req, res, next) => {
|
|
94
|
+
try {
|
|
95
|
+
const { id } = req.params;
|
|
96
|
+
const { title, description } = req.body;
|
|
97
|
+
|
|
98
|
+
logger.info('Updating example', { id });
|
|
99
|
+
|
|
100
|
+
const example = await exampleService.updateExample(id, {
|
|
101
|
+
title,
|
|
102
|
+
description,
|
|
103
|
+
updatedAt: new Date(),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (!example) {
|
|
107
|
+
throw new AppError('Example not found', 404);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
res.json({
|
|
111
|
+
success: true,
|
|
112
|
+
message: 'Example updated successfully',
|
|
113
|
+
data: example,
|
|
114
|
+
});
|
|
115
|
+
} catch (error) {
|
|
116
|
+
logger.error('Error updating example', { id: req.params.id, error: error.message });
|
|
117
|
+
next(error);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Delete example
|
|
123
|
+
*/
|
|
124
|
+
const deleteExample = async (req, res, next) => {
|
|
125
|
+
try {
|
|
126
|
+
const { id } = req.params;
|
|
127
|
+
|
|
128
|
+
logger.info('Deleting example', { id });
|
|
129
|
+
|
|
130
|
+
const deleted = await exampleService.deleteExample(id);
|
|
131
|
+
|
|
132
|
+
if (!deleted) {
|
|
133
|
+
throw new AppError('Example not found', 404);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
res.json({
|
|
137
|
+
success: true,
|
|
138
|
+
message: 'Example deleted successfully',
|
|
139
|
+
});
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logger.error('Error deleting example', { id: req.params.id, error: error.message });
|
|
142
|
+
next(error);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
getAllExamples,
|
|
148
|
+
getExampleById,
|
|
149
|
+
createExample,
|
|
150
|
+
updateExample,
|
|
151
|
+
deleteExample,
|
|
152
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const userService = require('../services/userService');
|
|
2
|
+
const { AppError } = require('../utils/errors');
|
|
3
|
+
|
|
4
|
+
const getProfile = async (req, res, next) => {
|
|
5
|
+
try {
|
|
6
|
+
const user = await userService.findById(req.user.userId);
|
|
7
|
+
if (!user) {
|
|
8
|
+
throw new AppError('User not found', 404);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
res.json({
|
|
12
|
+
user: {
|
|
13
|
+
id: user.id,
|
|
14
|
+
username: user.username,
|
|
15
|
+
email: user.email,
|
|
16
|
+
createdAt: user.createdAt,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
} catch (error) {
|
|
20
|
+
next(error);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const updateProfile = async (req, res, next) => {
|
|
25
|
+
try {
|
|
26
|
+
const { username } = req.body;
|
|
27
|
+
const userId = req.user.userId;
|
|
28
|
+
|
|
29
|
+
const updatedUser = await userService.updateById(userId, { username });
|
|
30
|
+
|
|
31
|
+
res.json({
|
|
32
|
+
message: 'Profile updated successfully',
|
|
33
|
+
user: {
|
|
34
|
+
id: updatedUser.id,
|
|
35
|
+
username: updatedUser.username,
|
|
36
|
+
email: updatedUser.email,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
} catch (error) {
|
|
40
|
+
next(error);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const deleteProfile = async (req, res, next) => {
|
|
45
|
+
try {
|
|
46
|
+
const userId = req.user.userId;
|
|
47
|
+
|
|
48
|
+
await userService.deleteById(userId);
|
|
49
|
+
|
|
50
|
+
res.json({ message: 'Profile deleted successfully' });
|
|
51
|
+
} catch (error) {
|
|
52
|
+
next(error);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
getProfile,
|
|
58
|
+
updateProfile,
|
|
59
|
+
deleteProfile,
|
|
60
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# templates/core/Dockerfile.ejs
|
|
2
|
+
FROM node:20-alpine
|
|
3
|
+
|
|
4
|
+
# Set working directory
|
|
5
|
+
WORKDIR /app
|
|
6
|
+
|
|
7
|
+
# Copy package files
|
|
8
|
+
COPY package*.json ./
|
|
9
|
+
|
|
10
|
+
# Install dependencies
|
|
11
|
+
RUN npm ci --only=production && npm cache clean --force
|
|
12
|
+
|
|
13
|
+
# Copy application code
|
|
14
|
+
COPY . .
|
|
15
|
+
|
|
16
|
+
# Create non-root user
|
|
17
|
+
RUN addgroup -g 1001 -S nodejs
|
|
18
|
+
RUN adduser -S nodeuser -u 1001
|
|
19
|
+
|
|
20
|
+
# Change ownership of app directory
|
|
21
|
+
RUN chown -R nodeuser:nodejs /app
|
|
22
|
+
USER nodeuser
|
|
23
|
+
|
|
24
|
+
# Expose port
|
|
25
|
+
EXPOSE 3000
|
|
26
|
+
|
|
27
|
+
# Health check
|
|
28
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
29
|
+
CMD node healthcheck.js
|
|
30
|
+
|
|
31
|
+
# Start application
|
|
32
|
+
CMD ["npm", "start"]
|