nodejs-quickstart-structure 2.0.0 → 2.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/CHANGELOG.md +26 -0
- package/README.md +44 -40
- package/bin/index.js +6 -3
- package/lib/generator.js +10 -4
- package/lib/modules/app-setup.js +76 -6
- package/lib/modules/auth-setup.js +143 -0
- package/lib/modules/caching-setup.js +8 -1
- package/lib/modules/config-files.js +10 -0
- package/lib/modules/database-setup.js +2 -1
- package/lib/modules/project-setup.js +1 -0
- package/lib/prompts.js +40 -1
- package/package.json +5 -4
- package/templates/clean-architecture/js/src/domain/models/User.js +3 -1
- package/templates/clean-architecture/js/src/index.js.ejs +2 -0
- package/templates/clean-architecture/js/src/infrastructure/config/env.js.ejs +12 -3
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +25 -2
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +27 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +3 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.spec.js.ejs +49 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/swagger.spec.js.ejs +14 -0
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +41 -4
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +69 -4
- package/templates/clean-architecture/js/src/interfaces/graphql/context.js.ejs +13 -6
- package/templates/clean-architecture/js/src/interfaces/graphql/context.spec.js.ejs +38 -21
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +10 -5
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +32 -10
- package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/user.types.js.ejs +1 -1
- package/templates/clean-architecture/js/src/interfaces/routes/api.js.ejs +15 -0
- package/templates/clean-architecture/js/src/interfaces/routes/api.spec.js.ejs +4 -0
- package/templates/clean-architecture/js/src/usecases/CreateUser.js.ejs +34 -0
- package/templates/clean-architecture/js/src/usecases/CreateUser.spec.js.ejs +3 -2
- package/templates/clean-architecture/js/src/usecases/DeleteUser.js.ejs +27 -0
- package/templates/clean-architecture/js/src/usecases/GetAllUsers.js.ejs +36 -0
- package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +14 -0
- package/templates/clean-architecture/js/src/usecases/GetUserById.js.ejs +36 -0
- package/templates/clean-architecture/js/src/usecases/GetUserById.spec.js.ejs +48 -0
- package/templates/clean-architecture/js/src/usecases/UpdateUser.js.ejs +28 -0
- package/templates/clean-architecture/js/src/utils/errorMessages.js +1 -0
- package/templates/clean-architecture/js/src/utils/httpCodes.js +2 -0
- package/templates/clean-architecture/ts/src/config/env.ts.ejs +12 -3
- package/templates/clean-architecture/ts/src/domain/user.ts +3 -1
- package/templates/clean-architecture/ts/src/index.ts.ejs +4 -0
- package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +55 -9
- package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +32 -3
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +26 -6
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +57 -15
- package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +38 -23
- package/templates/clean-architecture/ts/src/interfaces/graphql/context.ts.ejs +14 -8
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +33 -10
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +15 -5
- package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/user.types.ts.ejs +1 -1
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +9 -1
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts.ejs +16 -0
- package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +3 -2
- package/templates/clean-architecture/ts/src/usecases/createUser.ts.ejs +35 -0
- package/templates/clean-architecture/ts/src/usecases/deleteUser.spec.ts.ejs +1 -0
- package/templates/clean-architecture/ts/src/usecases/deleteUser.ts.ejs +24 -0
- package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts.ejs +21 -0
- package/templates/clean-architecture/ts/src/usecases/getUserById.spec.ts.ejs +47 -0
- package/templates/clean-architecture/ts/src/usecases/getUserById.ts.ejs +23 -0
- package/templates/clean-architecture/ts/src/usecases/updateUser.spec.ts.ejs +1 -0
- package/templates/clean-architecture/ts/src/usecases/updateUser.ts.ejs +25 -0
- package/templates/clean-architecture/ts/src/utils/errorMessages.ts +1 -0
- package/templates/clean-architecture/ts/src/utils/httpCodes.ts +1 -0
- package/templates/common/.cursorrules.ejs +9 -0
- package/templates/common/.env.example.ejs +17 -10
- package/templates/common/.gitlab-ci.yml.ejs +3 -1
- package/templates/common/Jenkinsfile.ejs +10 -1
- package/templates/common/README.md.ejs +64 -19
- package/templates/common/_circleci/config.yml.ejs +96 -0
- package/templates/common/_github/workflows/ci.yml.ejs +1 -1
- package/templates/common/auth/js/controllers/authController.js.ejs +168 -0
- package/templates/common/auth/js/controllers/authController.spec.js.ejs +148 -0
- package/templates/common/auth/js/middleware/authMiddleware.js.ejs +58 -0
- package/templates/common/auth/js/middleware/authMiddleware.spec.js.ejs +108 -0
- package/templates/common/auth/js/routes/authRoutes.js.ejs +16 -0
- package/templates/common/auth/js/services/jwtService.js.ejs +54 -0
- package/templates/common/auth/js/services/jwtService.spec.js.ejs +84 -0
- package/templates/common/auth/ts/controllers/authController.spec.ts.ejs +161 -0
- package/templates/common/auth/ts/controllers/authController.ts.ejs +165 -0
- package/templates/common/auth/ts/middleware/authMiddleware.spec.ts.ejs +128 -0
- package/templates/common/auth/ts/middleware/authMiddleware.ts.ejs +59 -0
- package/templates/common/auth/ts/routes/authRoutes.ts.ejs +20 -0
- package/templates/common/auth/ts/services/jwtService.spec.ts.ejs +89 -0
- package/templates/common/auth/ts/services/jwtService.ts.ejs +60 -0
- package/templates/common/bitbucket-pipelines.yml.ejs +60 -0
- package/templates/common/caching/clean/js/CreateUser.js.ejs +14 -5
- package/templates/common/caching/clean/js/DeleteUser.js.ejs +2 -1
- package/templates/common/caching/clean/js/GetUserById.js.ejs +39 -0
- package/templates/common/caching/clean/js/UpdateUser.js.ejs +2 -1
- package/templates/common/caching/clean/ts/createUser.ts.ejs +14 -6
- package/templates/common/caching/clean/ts/deleteUser.ts.ejs +2 -1
- package/templates/common/caching/clean/ts/getUserById.ts.ejs +32 -0
- package/templates/common/caching/clean/ts/updateUser.ts.ejs +2 -2
- package/templates/common/database/js/models/User.js.ejs +14 -1
- package/templates/common/database/js/models/User.js.mongoose.ejs +7 -0
- package/templates/common/database/js/models/User.spec.js.ejs +12 -0
- package/templates/common/database/ts/models/User.spec.ts.ejs +10 -0
- package/templates/common/database/ts/models/User.ts.ejs +17 -0
- package/templates/common/database/ts/models/User.ts.mongoose.ejs +8 -0
- package/templates/common/docker-compose.yml.ejs +14 -0
- package/templates/common/ecosystem.config.js.ejs +9 -3
- package/templates/common/eslint.config.mjs.ejs +3 -0
- package/templates/common/jest.config.js.ejs +11 -9
- package/templates/common/kafka/js/messaging/baseConsumer.js.ejs +1 -1
- package/templates/common/kafka/js/services/kafkaService.js.ejs +1 -1
- package/templates/common/migrations/init.js.ejs +5 -4
- package/templates/common/package.json.ejs +10 -2
- package/templates/common/prompts/project-context.md.ejs +8 -1
- package/templates/common/scripts/run-e2e.js.ejs +26 -10
- package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +149 -107
- package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +88 -47
- package/templates/common/swagger.yml.ejs +148 -0
- package/templates/common/tsconfig.eslint.json +15 -0
- package/templates/common/tsconfig.json +2 -1
- package/templates/common/views/ejs/index.ejs +264 -30
- package/templates/common/views/ejs/login.ejs.ejs +244 -0
- package/templates/common/views/ejs/signup.ejs.ejs +282 -0
- package/templates/common/views/pug/index.pug +269 -38
- package/templates/common/views/pug/login.pug.ejs +195 -0
- package/templates/common/views/pug/signup.pug.ejs +241 -0
- package/templates/db/mysql/V1__Initial_Setup.sql.ejs +6 -0
- package/templates/db/postgres/V1__Initial_Setup.sql.ejs +6 -0
- package/templates/mvc/js/src/config/env.js.ejs +12 -3
- package/templates/mvc/js/src/controllers/userController.js.ejs +29 -5
- package/templates/mvc/js/src/controllers/userController.spec.js.ejs +27 -12
- package/templates/mvc/js/src/graphql/context.js.ejs +14 -3
- package/templates/mvc/js/src/graphql/context.spec.js.ejs +36 -21
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +10 -5
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +32 -10
- package/templates/mvc/js/src/graphql/typeDefs/user.types.js.ejs +1 -1
- package/templates/mvc/js/src/index.js.ejs +16 -3
- package/templates/mvc/js/src/routes/api.js.ejs +14 -0
- package/templates/mvc/js/src/routes/api.spec.js.ejs +3 -0
- package/templates/mvc/js/src/utils/errorMessages.js +1 -0
- package/templates/mvc/js/src/utils/httpCodes.js +1 -0
- package/templates/mvc/ts/src/config/env.ts.ejs +12 -3
- package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +95 -7
- package/templates/mvc/ts/src/controllers/userController.ts.ejs +68 -11
- package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +36 -23
- package/templates/mvc/ts/src/graphql/context.ts.ejs +15 -6
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +32 -10
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +15 -5
- package/templates/mvc/ts/src/graphql/typeDefs/user.types.ts.ejs +1 -1
- package/templates/mvc/ts/src/index.ts.ejs +15 -3
- package/templates/mvc/ts/src/routes/api.spec.ts.ejs +6 -0
- package/templates/mvc/ts/src/routes/api.ts.ejs +15 -0
- package/templates/mvc/ts/src/utils/errorMessages.ts +1 -0
- package/templates/mvc/ts/src/utils/httpCodes.ts +1 -0
- package/templates/clean-architecture/js/src/interfaces/routes/api.js +0 -12
- package/templates/clean-architecture/js/src/usecases/CreateUser.js +0 -14
- package/templates/clean-architecture/js/src/usecases/DeleteUser.js +0 -11
- package/templates/clean-architecture/js/src/usecases/GetAllUsers.js +0 -12
- package/templates/clean-architecture/js/src/usecases/UpdateUser.js +0 -11
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +0 -13
- package/templates/clean-architecture/ts/src/usecases/createUser.ts +0 -13
- package/templates/clean-architecture/ts/src/usecases/deleteUser.ts +0 -9
- package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +0 -10
- package/templates/clean-architecture/ts/src/usecases/updateUser.ts +0 -9
- package/templates/mvc/js/src/routes/api.js +0 -10
- package/templates/mvc/ts/src/routes/api.ts +0 -12
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import bcrypt from 'bcryptjs';
|
|
3
|
+
<% if (architecture === 'MVC') { -%>
|
|
4
|
+
import User from '@/models/User';
|
|
5
|
+
import { JwtService } from '@/services/jwtService';
|
|
6
|
+
import logger from '@/utils/logger';
|
|
7
|
+
<% if (caching !== 'None') { -%>
|
|
8
|
+
import cacheService from '<% if (caching === "Redis") { %>@/config/redisClient<% } else { %>@/config/memoryCache<% } %>';
|
|
9
|
+
<% } -%>
|
|
10
|
+
<% } else { -%>
|
|
11
|
+
import User from '@/infrastructure/database/models/User';
|
|
12
|
+
import { JwtService } from '@/infrastructure/auth/jwtService';
|
|
13
|
+
import logger from '@/infrastructure/log/logger';
|
|
14
|
+
<% if (caching !== 'None') { -%>
|
|
15
|
+
import cacheService from '<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>';
|
|
16
|
+
<% } -%>
|
|
17
|
+
<% } -%>
|
|
18
|
+
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
19
|
+
|
|
20
|
+
export class AuthController {
|
|
21
|
+
async login(req: Request, res: Response, next: NextFunction) {
|
|
22
|
+
try {
|
|
23
|
+
const { email, password } = req.body;
|
|
24
|
+
<%_ if (database === 'MongoDB' || database === 'None') { -%>
|
|
25
|
+
const user = await User.findOne({ email });
|
|
26
|
+
<%_ } else { -%>
|
|
27
|
+
const user = await User.findOne({ where: { email } });
|
|
28
|
+
<%_ } -%>
|
|
29
|
+
if (!user) {
|
|
30
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Invalid credentials' });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const isPasswordValid = await bcrypt.compare(password, user.password!);
|
|
34
|
+
if (!isPasswordValid) {
|
|
35
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Invalid credentials' });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const userId = String(user.id || ((user as unknown) as { _id?: string | number })._id);
|
|
39
|
+
|
|
40
|
+
const refreshToken = JwtService.generateRefreshToken({ id: userId, email: user.email });
|
|
41
|
+
const refreshJti = JwtService.decodeToken(refreshToken)?.jti;
|
|
42
|
+
const accessToken = JwtService.generateToken({ id: userId, email: user.email, sid: refreshJti });
|
|
43
|
+
|
|
44
|
+
// Store refresh token
|
|
45
|
+
<%_ if (caching !== 'None') { -%>
|
|
46
|
+
const cacheKey = `refresh_tokens:${userId}`;
|
|
47
|
+
const activeTokens = await cacheService.get<string[]>(cacheKey) || [];
|
|
48
|
+
activeTokens.push(refreshJti!);
|
|
49
|
+
await cacheService.set(cacheKey, activeTokens, 7 * 24 * 60 * 60); // 7 days
|
|
50
|
+
<%_ } else { -%>
|
|
51
|
+
const activeTokens = JwtService.activeRefreshTokens.get(userId) || [];
|
|
52
|
+
activeTokens.push(refreshJti!);
|
|
53
|
+
JwtService.activeRefreshTokens.set(userId, activeTokens);<% } %>
|
|
54
|
+
|
|
55
|
+
res.json({ token: accessToken, accessToken, refreshToken });
|
|
56
|
+
} catch (error) {
|
|
57
|
+
logger.error('Login error:', error);
|
|
58
|
+
next(error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async refresh(req: Request, res: Response, next: NextFunction) {
|
|
63
|
+
try {
|
|
64
|
+
const { refreshToken } = req.body;
|
|
65
|
+
if (!refreshToken) {
|
|
66
|
+
return res.status(HTTP_STATUS.BAD_REQUEST).json({ message: 'Refresh token is required' });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const decoded = JwtService.verifyRefreshToken(refreshToken);
|
|
70
|
+
if (!decoded || !decoded.id || !decoded.jti) {
|
|
71
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Invalid refresh token' });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const userId = String(decoded.id);
|
|
75
|
+
const incomingJti = decoded.jti;
|
|
76
|
+
|
|
77
|
+
<% if (caching !== 'None') { %>
|
|
78
|
+
const cacheKey = `refresh_tokens:${userId}`;
|
|
79
|
+
let activeTokens = await cacheService.get<string[]>(cacheKey) || [];
|
|
80
|
+
|
|
81
|
+
if (!activeTokens.includes(incomingJti)) {
|
|
82
|
+
// Theft detection! Revoke all sessions
|
|
83
|
+
logger.warn(`Token theft detected for user ${userId}. Revoking all sessions.`);
|
|
84
|
+
await cacheService.del(cacheKey);
|
|
85
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Invalid session' });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Valid rotation
|
|
89
|
+
activeTokens = activeTokens.filter(t => t !== incomingJti);
|
|
90
|
+
const newRefreshToken = JwtService.generateRefreshToken({ id: userId, email: decoded.email });
|
|
91
|
+
const newRefreshJti = JwtService.decodeToken(newRefreshToken)?.jti;
|
|
92
|
+
const newAccessToken = JwtService.generateToken({ id: userId, email: decoded.email, sid: newRefreshJti });
|
|
93
|
+
|
|
94
|
+
activeTokens.push(newRefreshJti!);
|
|
95
|
+
await cacheService.set(cacheKey, activeTokens, 7 * 24 * 60 * 60);
|
|
96
|
+
<% } else { -%>
|
|
97
|
+
let activeTokens = JwtService.activeRefreshTokens.get(userId) || [];
|
|
98
|
+
|
|
99
|
+
if (!activeTokens.includes(incomingJti)) {
|
|
100
|
+
// Theft detection!
|
|
101
|
+
logger.warn(`Token theft detected for user ${userId}. Revoking all sessions.`);
|
|
102
|
+
JwtService.activeRefreshTokens.delete(userId);
|
|
103
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Invalid session' });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
activeTokens = activeTokens.filter(t => t !== incomingJti);
|
|
107
|
+
const newRefreshToken = JwtService.generateRefreshToken({ id: userId, email: decoded.email });
|
|
108
|
+
const newRefreshJti = JwtService.decodeToken(newRefreshToken)?.jti;
|
|
109
|
+
const newAccessToken = JwtService.generateToken({ id: userId, email: decoded.email, sid: newRefreshJti });
|
|
110
|
+
|
|
111
|
+
activeTokens.push(newRefreshJti!);
|
|
112
|
+
JwtService.activeRefreshTokens.set(userId, activeTokens);
|
|
113
|
+
<% } %>
|
|
114
|
+
res.json({ accessToken: newAccessToken, refreshToken: newRefreshToken });
|
|
115
|
+
} catch (error) {
|
|
116
|
+
logger.error('Refresh token error:', error);
|
|
117
|
+
next(error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async logout(req: Request, res: Response, next: NextFunction) {
|
|
122
|
+
try {
|
|
123
|
+
const authHeader = req.headers.authorization;
|
|
124
|
+
if (!authHeader) {
|
|
125
|
+
return res.status(HTTP_STATUS.BAD_REQUEST).json({ message: 'No token provided' });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const accessTokenStr = authHeader.split(' ')[1];
|
|
129
|
+
const decodedAccess = JwtService.decodeToken(accessTokenStr);
|
|
130
|
+
|
|
131
|
+
if (decodedAccess && decodedAccess.jti && decodedAccess.exp) {
|
|
132
|
+
const remainingTime = Math.max(0, decodedAccess.exp - Math.floor(Date.now() / 1000));
|
|
133
|
+
<%_ if (caching !== 'None') { -%>
|
|
134
|
+
if (remainingTime > 0) {
|
|
135
|
+
await cacheService.set(`blacklist:${decodedAccess.jti}`, true, remainingTime);
|
|
136
|
+
}<%_ } else { -%>
|
|
137
|
+
if (remainingTime > 0) {
|
|
138
|
+
JwtService.blacklistedTokens.set(decodedAccess.jti, Date.now() + remainingTime * 1000);
|
|
139
|
+
}<%_ } -%>
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const { refreshToken } = req.body;
|
|
143
|
+
if (refreshToken) {
|
|
144
|
+
const decodedRefresh = JwtService.decodeToken(refreshToken);
|
|
145
|
+
if (decodedRefresh && decodedRefresh.id && decodedRefresh.jti) {
|
|
146
|
+
const userId = String(decodedRefresh.id);
|
|
147
|
+
<%_ if (caching !== 'None') { -%>
|
|
148
|
+
const cacheKey = `refresh_tokens:${userId}`;
|
|
149
|
+
let activeTokens = await cacheService.get<string[]>(cacheKey) || [];
|
|
150
|
+
activeTokens = activeTokens.filter(t => t !== decodedRefresh.jti);
|
|
151
|
+
await cacheService.set(cacheKey, activeTokens, 7 * 24 * 60 * 60);
|
|
152
|
+
<%_ } else { -%>
|
|
153
|
+
let activeTokens = JwtService.activeRefreshTokens.get(userId) || [];
|
|
154
|
+
activeTokens = activeTokens.filter(t => t !== decodedRefresh.jti);
|
|
155
|
+
JwtService.activeRefreshTokens.set(userId, activeTokens);<% } %>
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
res.json({ message: 'Logged out successfully' });
|
|
160
|
+
} catch (error) {
|
|
161
|
+
logger.error('Logout error:', error);
|
|
162
|
+
next(error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<%_ if (architecture === 'Clean Architecture') { _%>
|
|
2
|
+
import { authMiddleware } from '@/infrastructure/webserver/middleware/authMiddleware';
|
|
3
|
+
import { JwtService } from '@/infrastructure/auth/jwtService';
|
|
4
|
+
<%_ } else { _%>
|
|
5
|
+
import { authMiddleware } from '@/middleware/authMiddleware';
|
|
6
|
+
import { JwtService } from '@/services/jwtService';
|
|
7
|
+
<%_ } _%>
|
|
8
|
+
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
9
|
+
import { Request, Response, NextFunction } from 'express';
|
|
10
|
+
|
|
11
|
+
<%_ if (architecture === 'Clean Architecture') { _%>
|
|
12
|
+
jest.mock('@/infrastructure/auth/jwtService');
|
|
13
|
+
<%_ } else { _%>
|
|
14
|
+
jest.mock('@/services/jwtService');
|
|
15
|
+
<%_ } _%>
|
|
16
|
+
|
|
17
|
+
<%_ if (caching !== 'None') { _%>
|
|
18
|
+
<%_ const cachePath = (architecture === "MVC") ? (caching === "Redis" ? "@/config/redisClient" : "@/config/memoryCache") : (caching === "Redis" ? "@/infrastructure/caching/redisClient" : "@/infrastructure/caching/memoryCache"); _%>
|
|
19
|
+
jest.mock('<%= cachePath %>', () => ({
|
|
20
|
+
__esModule: true,
|
|
21
|
+
default: {
|
|
22
|
+
get: jest.fn(),
|
|
23
|
+
set: jest.fn(),
|
|
24
|
+
del: jest.fn()
|
|
25
|
+
}
|
|
26
|
+
}), { virtual: true });
|
|
27
|
+
import cacheService from '<%= cachePath %>';
|
|
28
|
+
<%_ } _%>
|
|
29
|
+
|
|
30
|
+
describe('AuthMiddleware', () => {
|
|
31
|
+
let mockRequest: any;
|
|
32
|
+
let mockResponse: any;
|
|
33
|
+
const nextFunction: NextFunction = jest.fn();
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
mockRequest = {
|
|
37
|
+
headers: {}
|
|
38
|
+
};
|
|
39
|
+
mockResponse = {
|
|
40
|
+
status: jest.fn().mockReturnThis(),
|
|
41
|
+
json: jest.fn()
|
|
42
|
+
};
|
|
43
|
+
jest.clearAllMocks();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should return 401 if no authorization header is provided', async () => {
|
|
47
|
+
await authMiddleware(mockRequest as Request, mockResponse as Response, nextFunction);
|
|
48
|
+
|
|
49
|
+
expect(mockResponse.status).toHaveBeenCalledWith(HTTP_STATUS.UNAUTHORIZED);
|
|
50
|
+
expect(mockResponse.json).toHaveBeenCalledWith({ message: 'No token provided' });
|
|
51
|
+
expect(nextFunction).not.toHaveBeenCalled();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should return 401 if token is invalid', async () => {
|
|
55
|
+
mockRequest.headers.authorization = 'Bearer invalid-token';
|
|
56
|
+
(JwtService.verifyToken as jest.Mock).mockReturnValue(null);
|
|
57
|
+
|
|
58
|
+
await authMiddleware(mockRequest as Request, mockResponse as Response, nextFunction);
|
|
59
|
+
|
|
60
|
+
expect(mockResponse.status).toHaveBeenCalledWith(HTTP_STATUS.UNAUTHORIZED);
|
|
61
|
+
expect(mockResponse.json).toHaveBeenCalledWith({ message: 'Invalid or expired token' });
|
|
62
|
+
expect(nextFunction).not.toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should return 401 if token is blacklisted', async () => {
|
|
66
|
+
const payload = { id: 1, email: 'test@example.com', jti: 'blacklisted-jti' };
|
|
67
|
+
mockRequest.headers.authorization = 'Bearer valid-token';
|
|
68
|
+
(JwtService.verifyToken as jest.Mock).mockReturnValue(payload);
|
|
69
|
+
|
|
70
|
+
// Mock the blacklist check
|
|
71
|
+
<% if (caching !== 'None') { %>
|
|
72
|
+
(cacheService.get as jest.Mock).mockResolvedValue(true);
|
|
73
|
+
<% } else { %>
|
|
74
|
+
JwtService.blacklistedTokens.set('blacklisted-jti', Date.now() + 10000);
|
|
75
|
+
<% } %>
|
|
76
|
+
|
|
77
|
+
await authMiddleware(mockRequest as Request, mockResponse as Response, nextFunction);
|
|
78
|
+
|
|
79
|
+
expect(mockResponse.status).toHaveBeenCalledWith(HTTP_STATUS.UNAUTHORIZED);
|
|
80
|
+
expect(mockResponse.json).toHaveBeenCalledWith({ message: 'Token revoked' });
|
|
81
|
+
expect(nextFunction).not.toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should return 401 if session is expired (sid not in activeTokens)', async () => {
|
|
85
|
+
const payload = { id: 1, email: 'test@example.com', jti: 'valid-jti', sid: 'expired-sid' };
|
|
86
|
+
mockRequest.headers.authorization = 'Bearer valid-token';
|
|
87
|
+
(JwtService.verifyToken as jest.Mock).mockReturnValue(payload);
|
|
88
|
+
|
|
89
|
+
<% if (caching !== 'None') { %>
|
|
90
|
+
(cacheService.get as jest.Mock).mockImplementation((key: string) => {
|
|
91
|
+
if (key.startsWith('blacklist:')) return Promise.resolve(false);
|
|
92
|
+
if (key === 'refresh_tokens:1') return Promise.resolve(['other-sid']);
|
|
93
|
+
return Promise.resolve(null);
|
|
94
|
+
});
|
|
95
|
+
<% } else { %>
|
|
96
|
+
JwtService.blacklistedTokens.delete('valid-jti');
|
|
97
|
+
JwtService.activeRefreshTokens.set('1', ['other-sid']);
|
|
98
|
+
<% } %>
|
|
99
|
+
|
|
100
|
+
await authMiddleware(mockRequest as Request, mockResponse as Response, nextFunction);
|
|
101
|
+
|
|
102
|
+
expect(mockResponse.status).toHaveBeenCalledWith(HTTP_STATUS.UNAUTHORIZED);
|
|
103
|
+
expect(mockResponse.json).toHaveBeenCalledWith({ message: 'Session expired' });
|
|
104
|
+
expect(nextFunction).not.toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should call next() and set req.user if token is valid, not blacklisted and session is active', async () => {
|
|
108
|
+
const payload = { id: 1, email: 'test@example.com', jti: 'valid-jti', sid: 'active-sid' };
|
|
109
|
+
mockRequest.headers.authorization = 'Bearer valid-token';
|
|
110
|
+
(JwtService.verifyToken as jest.Mock).mockReturnValue(payload);
|
|
111
|
+
|
|
112
|
+
<% if (caching !== 'None') { %>
|
|
113
|
+
(cacheService.get as jest.Mock).mockImplementation((key: string) => {
|
|
114
|
+
if (key.startsWith('blacklist:')) return Promise.resolve(false);
|
|
115
|
+
if (key === 'refresh_tokens:1') return Promise.resolve(['active-sid']);
|
|
116
|
+
return Promise.resolve(null);
|
|
117
|
+
});
|
|
118
|
+
<% } else { %>
|
|
119
|
+
JwtService.blacklistedTokens.delete('valid-jti');
|
|
120
|
+
JwtService.activeRefreshTokens.set('1', ['active-sid']);
|
|
121
|
+
<% } %>
|
|
122
|
+
|
|
123
|
+
await authMiddleware(mockRequest as Request, mockResponse as Response, nextFunction);
|
|
124
|
+
|
|
125
|
+
expect(mockRequest.user).toEqual(payload);
|
|
126
|
+
expect(nextFunction).toHaveBeenCalled();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
<% if (architecture === 'MVC') { %>
|
|
3
|
+
import { JwtService, JwtPayload } from '@/services/jwtService';
|
|
4
|
+
<% if (caching !== 'None') { -%>
|
|
5
|
+
import cacheService from '<% if (caching === "Redis") { %>@/config/redisClient<% } else { %>@/config/memoryCache<% } %>';
|
|
6
|
+
<% } -%>
|
|
7
|
+
<% } else { %>
|
|
8
|
+
import { JwtService, JwtPayload } from '@/infrastructure/auth/jwtService';
|
|
9
|
+
<% if (caching !== 'None') { -%>
|
|
10
|
+
import cacheService from '<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>';
|
|
11
|
+
<% } -%>
|
|
12
|
+
<% } %>
|
|
13
|
+
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
14
|
+
|
|
15
|
+
interface CustomRequest extends Request {
|
|
16
|
+
user?: JwtPayload;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const authMiddleware = async (req: CustomRequest, res: Response, next: NextFunction) => {
|
|
20
|
+
const authHeader = req.headers.authorization;
|
|
21
|
+
|
|
22
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
23
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'No token provided' });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const token = authHeader.split(' ')[1];
|
|
27
|
+
const decoded = JwtService.verifyToken(token);
|
|
28
|
+
|
|
29
|
+
if (!decoded) {
|
|
30
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Invalid or expired token' });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (decoded.jti) {
|
|
34
|
+
<%_ if (caching !== 'None') { -%>
|
|
35
|
+
const isBlacklisted = await cacheService.get(`blacklist:${decoded.jti}`);
|
|
36
|
+
if (isBlacklisted) {
|
|
37
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Token revoked' });
|
|
38
|
+
}<%_ } else { -%>
|
|
39
|
+
const expiryDate = JwtService.blacklistedTokens.get(decoded.jti);
|
|
40
|
+
if (expiryDate && Date.now() < expiryDate) {
|
|
41
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Token revoked' });
|
|
42
|
+
}<% } %>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (decoded.sid) {
|
|
46
|
+
<%_ if (caching !== 'None') { -%>
|
|
47
|
+
const activeTokens = await cacheService.get<string[]>(`refresh_tokens:${decoded.id}`) || [];
|
|
48
|
+
if (!activeTokens.includes(decoded.sid)) {
|
|
49
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Session expired' });
|
|
50
|
+
}<%_ } else { -%>
|
|
51
|
+
const activeTokens = JwtService.activeRefreshTokens.get(String(decoded.id)) || [];
|
|
52
|
+
if (!activeTokens.includes(decoded.sid)) {
|
|
53
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Session expired' });
|
|
54
|
+
}<% } %>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
req.user = decoded;
|
|
58
|
+
next();
|
|
59
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
<%_ if (architecture === 'MVC') { -%>
|
|
3
|
+
import { AuthController } from '@/controllers/authController';
|
|
4
|
+
<%_ } else { -%>
|
|
5
|
+
import { AuthController } from '@/interfaces/controllers/auth/authController';
|
|
6
|
+
<%_ } -%>
|
|
7
|
+
<%_ if (architecture === 'MVC') { -%>
|
|
8
|
+
import { authMiddleware } from '@/middleware/authMiddleware';
|
|
9
|
+
<%_ } else { -%>
|
|
10
|
+
import { authMiddleware } from '@/infrastructure/webserver/middleware/authMiddleware';
|
|
11
|
+
<%_ } -%>
|
|
12
|
+
|
|
13
|
+
const router = Router();
|
|
14
|
+
const authController = new AuthController();
|
|
15
|
+
|
|
16
|
+
router.post('/login', (req, res, next) => authController.login(req, res, next));
|
|
17
|
+
router.post('/refresh', (req, res, next) => authController.refresh(req, res, next));
|
|
18
|
+
router.post('/logout', authMiddleware, (req, res, next) => authController.logout(req, res, next));
|
|
19
|
+
|
|
20
|
+
export default router;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<% if (architecture === 'Clean Architecture') { -%>
|
|
2
|
+
import { JwtService } from '@/infrastructure/auth/jwtService';
|
|
3
|
+
<% } else { -%>
|
|
4
|
+
import { JwtService } from '@/services/jwtService';
|
|
5
|
+
<% } -%>
|
|
6
|
+
import jwt from 'jsonwebtoken';
|
|
7
|
+
|
|
8
|
+
jest.mock('jsonwebtoken');
|
|
9
|
+
jest.mock('@/config/env', () => ({
|
|
10
|
+
env: {
|
|
11
|
+
JWT_SECRET: 'test-secret',
|
|
12
|
+
JWT_REFRESH_SECRET: 'test-refresh-secret',
|
|
13
|
+
JWT_EXPIRES_IN: '15m'
|
|
14
|
+
}
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
describe('JwtService', () => {
|
|
18
|
+
const secret = 'test-secret';
|
|
19
|
+
const refreshSecret = 'test-refresh-secret';
|
|
20
|
+
const payload = { id: 1, email: 'test@example.com' };
|
|
21
|
+
const token = 'mock-token';
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
jest.clearAllMocks();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('generateToken', () => {
|
|
28
|
+
it('should generate a token with standard expiration and jti', () => {
|
|
29
|
+
(jwt.sign as jest.Mock).mockReturnValue(token);
|
|
30
|
+
|
|
31
|
+
const result = JwtService.generateToken(payload);
|
|
32
|
+
|
|
33
|
+
expect(jwt.sign).toHaveBeenCalledWith(
|
|
34
|
+
expect.objectContaining({ ...payload, jti: expect.any(String) }),
|
|
35
|
+
secret,
|
|
36
|
+
{ expiresIn: '15m' }
|
|
37
|
+
);
|
|
38
|
+
expect(result).toBe(token);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('generateRefreshToken', () => {
|
|
43
|
+
it('should generate a refresh token with refresh secret and jti', () => {
|
|
44
|
+
(jwt.sign as jest.Mock).mockReturnValue(token);
|
|
45
|
+
|
|
46
|
+
const result = JwtService.generateRefreshToken(payload);
|
|
47
|
+
|
|
48
|
+
expect(jwt.sign).toHaveBeenCalledWith(
|
|
49
|
+
expect.objectContaining({ ...payload, jti: expect.any(String) }),
|
|
50
|
+
refreshSecret,
|
|
51
|
+
{ expiresIn: '7d' }
|
|
52
|
+
);
|
|
53
|
+
expect(result).toBe(token);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('verifyToken', () => {
|
|
58
|
+
it('should return decoded payload for a valid token', () => {
|
|
59
|
+
(jwt.verify as jest.Mock).mockReturnValue(payload);
|
|
60
|
+
|
|
61
|
+
const result = JwtService.verifyToken(token);
|
|
62
|
+
|
|
63
|
+
expect(jwt.verify).toHaveBeenCalledWith(token, secret);
|
|
64
|
+
expect(result).toEqual(payload);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('verifyRefreshToken', () => {
|
|
69
|
+
it('should return decoded payload for a valid refresh token', () => {
|
|
70
|
+
(jwt.verify as jest.Mock).mockReturnValue(payload);
|
|
71
|
+
|
|
72
|
+
const result = JwtService.verifyRefreshToken(token);
|
|
73
|
+
|
|
74
|
+
expect(jwt.verify).toHaveBeenCalledWith(token, refreshSecret);
|
|
75
|
+
expect(result).toEqual(payload);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('decodeToken', () => {
|
|
80
|
+
it('should return decoded payload without verification', () => {
|
|
81
|
+
(jwt.decode as jest.Mock).mockReturnValue(payload);
|
|
82
|
+
|
|
83
|
+
const result = JwtService.decodeToken(token);
|
|
84
|
+
|
|
85
|
+
expect(jwt.decode).toHaveBeenCalledWith(token);
|
|
86
|
+
expect(result).toEqual(payload);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
import { env } from '@/config/env';
|
|
4
|
+
|
|
5
|
+
export interface JwtPayload {
|
|
6
|
+
id: string | number;
|
|
7
|
+
email: string;
|
|
8
|
+
jti?: string;
|
|
9
|
+
sid?: string;
|
|
10
|
+
exp?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class JwtService {
|
|
14
|
+
private static readonly SECRET = env.JWT_SECRET || 'your-secret-key';
|
|
15
|
+
private static readonly REFRESH_SECRET = env.JWT_REFRESH_SECRET || 'your-refresh-secret-key';
|
|
16
|
+
private static readonly EXPIRES_IN = env.JWT_EXPIRES_IN || '15m'; // Access tokens should be short-lived
|
|
17
|
+
private static readonly REFRESH_EXPIRES_IN = env.JWT_REFRESH_EXPIRES_IN || '7d';
|
|
18
|
+
|
|
19
|
+
static generateToken(payload: Partial<JwtPayload>, expiresIn?: string): string {
|
|
20
|
+
const jti = crypto.randomUUID();
|
|
21
|
+
return jwt.sign({ ...payload, jti }, this.SECRET, {
|
|
22
|
+
expiresIn: expiresIn || this.EXPIRES_IN
|
|
23
|
+
} as jwt.SignOptions);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static generateRefreshToken(payload: Partial<JwtPayload>, expiresIn?: string): string {
|
|
27
|
+
const jti = crypto.randomUUID();
|
|
28
|
+
return jwt.sign({ ...payload, jti }, this.REFRESH_SECRET, {
|
|
29
|
+
expiresIn: expiresIn || this.REFRESH_EXPIRES_IN
|
|
30
|
+
} as jwt.SignOptions);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static verifyToken(token: string): JwtPayload | null {
|
|
34
|
+
try {
|
|
35
|
+
return jwt.verify(token, this.SECRET) as JwtPayload;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static verifyRefreshToken(token: string): JwtPayload | null {
|
|
42
|
+
try {
|
|
43
|
+
return jwt.verify(token, this.REFRESH_SECRET) as JwtPayload;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static decodeToken(token: string): JwtPayload | null {
|
|
50
|
+
try {
|
|
51
|
+
return jwt.decode(token) as JwtPayload;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Fallback in-memory storage if caching = 'None'
|
|
58
|
+
static activeRefreshTokens = new Map<string, string[]>();
|
|
59
|
+
static blacklistedTokens = new Map<string, number>();
|
|
60
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
image: node:22-slim
|
|
2
|
+
|
|
3
|
+
pipelines:
|
|
4
|
+
default:
|
|
5
|
+
- step:
|
|
6
|
+
name: Install Dependencies
|
|
7
|
+
caches:
|
|
8
|
+
- node
|
|
9
|
+
script:
|
|
10
|
+
- npm ci
|
|
11
|
+
- parallel:
|
|
12
|
+
- step:
|
|
13
|
+
name: Lint Code
|
|
14
|
+
script:
|
|
15
|
+
- npm ci
|
|
16
|
+
- npm run lint
|
|
17
|
+
- step:
|
|
18
|
+
name: Run Unit Tests
|
|
19
|
+
script:
|
|
20
|
+
- npm ci
|
|
21
|
+
- npm run test:coverage -- --maxWorkers=2
|
|
22
|
+
- step:
|
|
23
|
+
name: Run E2E Tests
|
|
24
|
+
image: docker:20.10.16
|
|
25
|
+
services:
|
|
26
|
+
- docker
|
|
27
|
+
script:
|
|
28
|
+
- apk add --no-cache nodejs npm docker-compose
|
|
29
|
+
- npm ci
|
|
30
|
+
- export DOCKER_BUILDKIT=0
|
|
31
|
+
- npm run test:e2e
|
|
32
|
+
<% if (includeSecurity) { %>
|
|
33
|
+
- parallel:
|
|
34
|
+
- step:
|
|
35
|
+
name: Snyk Security Scan
|
|
36
|
+
script:
|
|
37
|
+
- pipe: snyk/snyk-scan:1.0.0
|
|
38
|
+
variables:
|
|
39
|
+
SNYK_TOKEN: $SNYK_TOKEN
|
|
40
|
+
LANGUAGE: "npm"
|
|
41
|
+
SEVERITY_THRESHOLD: "high"
|
|
42
|
+
- step:
|
|
43
|
+
name: SonarQube Analysis
|
|
44
|
+
script:
|
|
45
|
+
- pipe: sonarsource/sonarcloud-scan:2.0.0
|
|
46
|
+
variables:
|
|
47
|
+
SONAR_TOKEN: $SONAR_TOKEN
|
|
48
|
+
<% } %>
|
|
49
|
+
- step:
|
|
50
|
+
name: Build Application
|
|
51
|
+
script:
|
|
52
|
+
- npm ci
|
|
53
|
+
- npm run build --if-present
|
|
54
|
+
|
|
55
|
+
definitions:
|
|
56
|
+
services:
|
|
57
|
+
docker:
|
|
58
|
+
memory: 2048
|
|
59
|
+
caches:
|
|
60
|
+
node: ~/.npm
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const User = require('../domain/models/User');
|
|
2
|
-
<%
|
|
2
|
+
<% if (auth.includes('JWT')) { -%>
|
|
3
|
+
const bcrypt = require('bcryptjs');
|
|
4
|
+
<% } -%>
|
|
5
|
+
<% if (caching === 'Redis') { -%>
|
|
3
6
|
const cacheService = require('../infrastructure/caching/redisClient');
|
|
4
|
-
<%
|
|
7
|
+
<% } else if (caching === 'Memory Cache') { -%>
|
|
5
8
|
const cacheService = require('../infrastructure/caching/memoryCache');
|
|
6
|
-
<%
|
|
9
|
+
<% } -%>
|
|
7
10
|
const logger = require('../infrastructure/log/logger');
|
|
8
11
|
|
|
9
12
|
class CreateUser {
|
|
@@ -11,8 +14,14 @@ class CreateUser {
|
|
|
11
14
|
this.userRepository = userRepository;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
async execute(name, email) {
|
|
15
|
-
|
|
17
|
+
async execute(name, email, password) {
|
|
18
|
+
let finalPassword = password;
|
|
19
|
+
<% if (auth.includes('JWT')) { -%>
|
|
20
|
+
if (password) {
|
|
21
|
+
finalPassword = await bcrypt.hash(password, 10);
|
|
22
|
+
}
|
|
23
|
+
<% } -%>
|
|
24
|
+
const user = new User(null, name, email, finalPassword);
|
|
16
25
|
const savedUser = await this.userRepository.save(user);
|
|
17
26
|
|
|
18
27
|
try {
|
|
@@ -15,7 +15,8 @@ class DeleteUser {
|
|
|
15
15
|
|
|
16
16
|
try {
|
|
17
17
|
await cacheService.del('users:all');
|
|
18
|
-
|
|
18
|
+
await cacheService.del(`user:${id}`);
|
|
19
|
+
logger.info(`Invalidated cache for user:${id} and all users`);
|
|
19
20
|
} catch (error) {
|
|
20
21
|
logger.error('Cache error (del):', error);
|
|
21
22
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<%_ if (caching === 'Redis') { -%>
|
|
2
|
+
const cacheService = require('../infrastructure/caching/redisClient');
|
|
3
|
+
<%_ } else if (caching === 'Memory Cache') { -%>
|
|
4
|
+
const cacheService = require('../infrastructure/caching/memoryCache');
|
|
5
|
+
<%_ } -%>
|
|
6
|
+
const logger = require('../infrastructure/log/logger');
|
|
7
|
+
|
|
8
|
+
class GetUserById {
|
|
9
|
+
constructor(userRepository) {
|
|
10
|
+
this.userRepository = userRepository;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async execute(id) {
|
|
14
|
+
const cacheKey = `user:${id}`;
|
|
15
|
+
try {
|
|
16
|
+
const cachedUser = await cacheService.get(cacheKey);
|
|
17
|
+
if (cachedUser) {
|
|
18
|
+
logger.info(`Serving user ${id} from cache`);
|
|
19
|
+
return cachedUser;
|
|
20
|
+
}
|
|
21
|
+
} catch (error) {
|
|
22
|
+
logger.error('Cache error (get):', error);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const user = await this.userRepository.findById(id);
|
|
26
|
+
|
|
27
|
+
if (user) {
|
|
28
|
+
try {
|
|
29
|
+
await cacheService.set(cacheKey, user, 3600); // Cache for 1 hour
|
|
30
|
+
} catch (error) {
|
|
31
|
+
logger.error('Cache error (set):', error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return user;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = GetUserById;
|
|
@@ -15,7 +15,8 @@ class UpdateUser {
|
|
|
15
15
|
|
|
16
16
|
try {
|
|
17
17
|
await cacheService.del('users:all');
|
|
18
|
-
|
|
18
|
+
await cacheService.del(`user:${id}`);
|
|
19
|
+
logger.info(`Invalidated cache for user:${id} and all users`);
|
|
19
20
|
} catch (error) {
|
|
20
21
|
logger.error('Cache error (del):', error);
|
|
21
22
|
}
|