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.
Files changed (161) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +44 -40
  3. package/bin/index.js +6 -3
  4. package/lib/generator.js +10 -4
  5. package/lib/modules/app-setup.js +76 -6
  6. package/lib/modules/auth-setup.js +143 -0
  7. package/lib/modules/caching-setup.js +8 -1
  8. package/lib/modules/config-files.js +10 -0
  9. package/lib/modules/database-setup.js +2 -1
  10. package/lib/modules/project-setup.js +1 -0
  11. package/lib/prompts.js +40 -1
  12. package/package.json +5 -4
  13. package/templates/clean-architecture/js/src/domain/models/User.js +3 -1
  14. package/templates/clean-architecture/js/src/index.js.ejs +2 -0
  15. package/templates/clean-architecture/js/src/infrastructure/config/env.js.ejs +12 -3
  16. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +25 -2
  17. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +27 -0
  18. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +3 -0
  19. package/templates/clean-architecture/js/src/infrastructure/webserver/server.spec.js.ejs +49 -0
  20. package/templates/clean-architecture/js/src/infrastructure/webserver/swagger.spec.js.ejs +14 -0
  21. package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +41 -4
  22. package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +69 -4
  23. package/templates/clean-architecture/js/src/interfaces/graphql/context.js.ejs +13 -6
  24. package/templates/clean-architecture/js/src/interfaces/graphql/context.spec.js.ejs +38 -21
  25. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +10 -5
  26. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +32 -10
  27. package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/user.types.js.ejs +1 -1
  28. package/templates/clean-architecture/js/src/interfaces/routes/api.js.ejs +15 -0
  29. package/templates/clean-architecture/js/src/interfaces/routes/api.spec.js.ejs +4 -0
  30. package/templates/clean-architecture/js/src/usecases/CreateUser.js.ejs +34 -0
  31. package/templates/clean-architecture/js/src/usecases/CreateUser.spec.js.ejs +3 -2
  32. package/templates/clean-architecture/js/src/usecases/DeleteUser.js.ejs +27 -0
  33. package/templates/clean-architecture/js/src/usecases/GetAllUsers.js.ejs +36 -0
  34. package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +14 -0
  35. package/templates/clean-architecture/js/src/usecases/GetUserById.js.ejs +36 -0
  36. package/templates/clean-architecture/js/src/usecases/GetUserById.spec.js.ejs +48 -0
  37. package/templates/clean-architecture/js/src/usecases/UpdateUser.js.ejs +28 -0
  38. package/templates/clean-architecture/js/src/utils/errorMessages.js +1 -0
  39. package/templates/clean-architecture/js/src/utils/httpCodes.js +2 -0
  40. package/templates/clean-architecture/ts/src/config/env.ts.ejs +12 -3
  41. package/templates/clean-architecture/ts/src/domain/user.ts +3 -1
  42. package/templates/clean-architecture/ts/src/index.ts.ejs +4 -0
  43. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +55 -9
  44. package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +32 -3
  45. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +26 -6
  46. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +57 -15
  47. package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +38 -23
  48. package/templates/clean-architecture/ts/src/interfaces/graphql/context.ts.ejs +14 -8
  49. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +33 -10
  50. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +15 -5
  51. package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/user.types.ts.ejs +1 -1
  52. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +9 -1
  53. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts.ejs +16 -0
  54. package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +3 -2
  55. package/templates/clean-architecture/ts/src/usecases/createUser.ts.ejs +35 -0
  56. package/templates/clean-architecture/ts/src/usecases/deleteUser.spec.ts.ejs +1 -0
  57. package/templates/clean-architecture/ts/src/usecases/deleteUser.ts.ejs +24 -0
  58. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts.ejs +21 -0
  59. package/templates/clean-architecture/ts/src/usecases/getUserById.spec.ts.ejs +47 -0
  60. package/templates/clean-architecture/ts/src/usecases/getUserById.ts.ejs +23 -0
  61. package/templates/clean-architecture/ts/src/usecases/updateUser.spec.ts.ejs +1 -0
  62. package/templates/clean-architecture/ts/src/usecases/updateUser.ts.ejs +25 -0
  63. package/templates/clean-architecture/ts/src/utils/errorMessages.ts +1 -0
  64. package/templates/clean-architecture/ts/src/utils/httpCodes.ts +1 -0
  65. package/templates/common/.cursorrules.ejs +9 -0
  66. package/templates/common/.env.example.ejs +17 -10
  67. package/templates/common/.gitlab-ci.yml.ejs +3 -1
  68. package/templates/common/Jenkinsfile.ejs +10 -1
  69. package/templates/common/README.md.ejs +64 -19
  70. package/templates/common/_circleci/config.yml.ejs +96 -0
  71. package/templates/common/_github/workflows/ci.yml.ejs +1 -1
  72. package/templates/common/auth/js/controllers/authController.js.ejs +168 -0
  73. package/templates/common/auth/js/controllers/authController.spec.js.ejs +148 -0
  74. package/templates/common/auth/js/middleware/authMiddleware.js.ejs +58 -0
  75. package/templates/common/auth/js/middleware/authMiddleware.spec.js.ejs +108 -0
  76. package/templates/common/auth/js/routes/authRoutes.js.ejs +16 -0
  77. package/templates/common/auth/js/services/jwtService.js.ejs +54 -0
  78. package/templates/common/auth/js/services/jwtService.spec.js.ejs +84 -0
  79. package/templates/common/auth/ts/controllers/authController.spec.ts.ejs +161 -0
  80. package/templates/common/auth/ts/controllers/authController.ts.ejs +165 -0
  81. package/templates/common/auth/ts/middleware/authMiddleware.spec.ts.ejs +128 -0
  82. package/templates/common/auth/ts/middleware/authMiddleware.ts.ejs +59 -0
  83. package/templates/common/auth/ts/routes/authRoutes.ts.ejs +20 -0
  84. package/templates/common/auth/ts/services/jwtService.spec.ts.ejs +89 -0
  85. package/templates/common/auth/ts/services/jwtService.ts.ejs +60 -0
  86. package/templates/common/bitbucket-pipelines.yml.ejs +60 -0
  87. package/templates/common/caching/clean/js/CreateUser.js.ejs +14 -5
  88. package/templates/common/caching/clean/js/DeleteUser.js.ejs +2 -1
  89. package/templates/common/caching/clean/js/GetUserById.js.ejs +39 -0
  90. package/templates/common/caching/clean/js/UpdateUser.js.ejs +2 -1
  91. package/templates/common/caching/clean/ts/createUser.ts.ejs +14 -6
  92. package/templates/common/caching/clean/ts/deleteUser.ts.ejs +2 -1
  93. package/templates/common/caching/clean/ts/getUserById.ts.ejs +32 -0
  94. package/templates/common/caching/clean/ts/updateUser.ts.ejs +2 -2
  95. package/templates/common/database/js/models/User.js.ejs +14 -1
  96. package/templates/common/database/js/models/User.js.mongoose.ejs +7 -0
  97. package/templates/common/database/js/models/User.spec.js.ejs +12 -0
  98. package/templates/common/database/ts/models/User.spec.ts.ejs +10 -0
  99. package/templates/common/database/ts/models/User.ts.ejs +17 -0
  100. package/templates/common/database/ts/models/User.ts.mongoose.ejs +8 -0
  101. package/templates/common/docker-compose.yml.ejs +14 -0
  102. package/templates/common/ecosystem.config.js.ejs +9 -3
  103. package/templates/common/eslint.config.mjs.ejs +3 -0
  104. package/templates/common/jest.config.js.ejs +11 -9
  105. package/templates/common/kafka/js/messaging/baseConsumer.js.ejs +1 -1
  106. package/templates/common/kafka/js/services/kafkaService.js.ejs +1 -1
  107. package/templates/common/migrations/init.js.ejs +5 -4
  108. package/templates/common/package.json.ejs +10 -2
  109. package/templates/common/prompts/project-context.md.ejs +8 -1
  110. package/templates/common/scripts/run-e2e.js.ejs +26 -10
  111. package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +149 -107
  112. package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +88 -47
  113. package/templates/common/swagger.yml.ejs +148 -0
  114. package/templates/common/tsconfig.eslint.json +15 -0
  115. package/templates/common/tsconfig.json +2 -1
  116. package/templates/common/views/ejs/index.ejs +264 -30
  117. package/templates/common/views/ejs/login.ejs.ejs +244 -0
  118. package/templates/common/views/ejs/signup.ejs.ejs +282 -0
  119. package/templates/common/views/pug/index.pug +269 -38
  120. package/templates/common/views/pug/login.pug.ejs +195 -0
  121. package/templates/common/views/pug/signup.pug.ejs +241 -0
  122. package/templates/db/mysql/V1__Initial_Setup.sql.ejs +6 -0
  123. package/templates/db/postgres/V1__Initial_Setup.sql.ejs +6 -0
  124. package/templates/mvc/js/src/config/env.js.ejs +12 -3
  125. package/templates/mvc/js/src/controllers/userController.js.ejs +29 -5
  126. package/templates/mvc/js/src/controllers/userController.spec.js.ejs +27 -12
  127. package/templates/mvc/js/src/graphql/context.js.ejs +14 -3
  128. package/templates/mvc/js/src/graphql/context.spec.js.ejs +36 -21
  129. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +10 -5
  130. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +32 -10
  131. package/templates/mvc/js/src/graphql/typeDefs/user.types.js.ejs +1 -1
  132. package/templates/mvc/js/src/index.js.ejs +16 -3
  133. package/templates/mvc/js/src/routes/api.js.ejs +14 -0
  134. package/templates/mvc/js/src/routes/api.spec.js.ejs +3 -0
  135. package/templates/mvc/js/src/utils/errorMessages.js +1 -0
  136. package/templates/mvc/js/src/utils/httpCodes.js +1 -0
  137. package/templates/mvc/ts/src/config/env.ts.ejs +12 -3
  138. package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +95 -7
  139. package/templates/mvc/ts/src/controllers/userController.ts.ejs +68 -11
  140. package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +36 -23
  141. package/templates/mvc/ts/src/graphql/context.ts.ejs +15 -6
  142. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +32 -10
  143. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +15 -5
  144. package/templates/mvc/ts/src/graphql/typeDefs/user.types.ts.ejs +1 -1
  145. package/templates/mvc/ts/src/index.ts.ejs +15 -3
  146. package/templates/mvc/ts/src/routes/api.spec.ts.ejs +6 -0
  147. package/templates/mvc/ts/src/routes/api.ts.ejs +15 -0
  148. package/templates/mvc/ts/src/utils/errorMessages.ts +1 -0
  149. package/templates/mvc/ts/src/utils/httpCodes.ts +1 -0
  150. package/templates/clean-architecture/js/src/interfaces/routes/api.js +0 -12
  151. package/templates/clean-architecture/js/src/usecases/CreateUser.js +0 -14
  152. package/templates/clean-architecture/js/src/usecases/DeleteUser.js +0 -11
  153. package/templates/clean-architecture/js/src/usecases/GetAllUsers.js +0 -12
  154. package/templates/clean-architecture/js/src/usecases/UpdateUser.js +0 -11
  155. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +0 -13
  156. package/templates/clean-architecture/ts/src/usecases/createUser.ts +0 -13
  157. package/templates/clean-architecture/ts/src/usecases/deleteUser.ts +0 -9
  158. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +0 -10
  159. package/templates/clean-architecture/ts/src/usecases/updateUser.ts +0 -9
  160. package/templates/mvc/js/src/routes/api.js +0 -10
  161. package/templates/mvc/ts/src/routes/api.ts +0 -12
@@ -13,40 +13,62 @@ jest.mock('@/interfaces/controllers/userController', () => {
13
13
  });
14
14
 
15
15
  describe('User Resolvers', () => {
16
+ const mockContext = {
17
+ <% if (auth.includes('JWT')) { %>user: { id: 'admin', email: 'admin@test.com' }<% } %>
18
+ };
19
+
16
20
  afterEach(() => {
17
21
  jest.clearAllMocks();
18
22
  });
19
23
 
20
24
  describe('Query.getAllUsers', () => {
21
- it('should return all users', async () => {
22
- const result = await userResolvers.Query.getAllUsers();
25
+ it('should return all users when authorized', async () => {
26
+ const result = await userResolvers.Query.getAllUsers(null, null, mockContext);
23
27
  expect(result).toEqual([{ id: '1', name: 'John Doe', email: 'john@example.com' }]);
24
28
  expect(mockGetUsers).toHaveBeenCalledTimes(1);
25
29
  });
30
+
31
+ <% if (auth.includes('JWT')) { %>
32
+ it('should throw error when unauthorized', async () => {
33
+ await expect(userResolvers.Query.getAllUsers(null, null, {})).rejects.toThrow('Unauthorized');
34
+ });
35
+ <% } %>
26
36
  });
27
37
 
28
38
  describe('Mutation.createUser', () => {
29
- it('should create and return a new user', async () => {
30
- const result = await userResolvers.Mutation.createUser(null, { name: 'Jane', email: 'jane@example.com' });
39
+ it('should create and return a new user (Public)', async () => {
40
+ const payload = { name: 'Jane', email: 'jane@example.com'<% if (auth.some(a => a !=='None')) { %>, password: 'password123'<% } %> };
41
+ const result = await userResolvers.Mutation.createUser(null, payload);
31
42
  expect(result).toEqual({ id: '1', name: 'Jane', email: 'jane@example.com' });
32
- expect(mockCreateUser).toHaveBeenCalledWith({ name: 'Jane', email: 'jane@example.com' });
33
- expect(mockCreateUser).toHaveBeenCalledTimes(1);
43
+ expect(mockCreateUser).toHaveBeenCalledWith(payload);
34
44
  });
35
45
  });
36
46
 
37
47
  describe('Mutation.updateUser', () => {
38
- it('should update and return the user', async () => {
48
+ it('should update and return the user when authorized', async () => {
39
49
  const payload = { name: 'Updated' };
40
- const result = await userResolvers.Mutation.updateUser(null, { id: '1', ...payload });
50
+ const result = await userResolvers.Mutation.updateUser(null, { id: '1', ...payload }, mockContext);
41
51
  expect(result).toMatchObject(payload);
42
52
  });
53
+
54
+ <% if (auth.includes('JWT')) { %>
55
+ it('should throw error when unauthorized', async () => {
56
+ await expect(userResolvers.Mutation.updateUser(null, { id: '1', name: 'N' }, {})).rejects.toThrow('Unauthorized');
57
+ });
58
+ <% } %>
43
59
  });
44
60
 
45
61
  describe('Mutation.deleteUser', () => {
46
- it('should delete and return true', async () => {
47
- const result = await userResolvers.Mutation.deleteUser(null, { id: '1' });
62
+ it('should delete and return true when authorized', async () => {
63
+ const result = await userResolvers.Mutation.deleteUser(null, { id: '1' }, mockContext);
48
64
  expect(result).toBe(true);
49
65
  });
66
+
67
+ <% if (auth.includes('JWT')) { %>
68
+ it('should throw error when unauthorized', async () => {
69
+ await expect(userResolvers.Mutation.deleteUser(null, { id: '1' }, {})).rejects.toThrow('Unauthorized');
70
+ });
71
+ <% } %>
50
72
  });
51
73
  <%_ if (database === 'MongoDB') { -%>
52
74
  describe('User.id', () => {
@@ -10,7 +10,7 @@ const userTypes = `#graphql
10
10
  }
11
11
 
12
12
  type Mutation {
13
- createUser(name: String!, email: String!): User
13
+ createUser(name: String!, email: String!<%_ if (auth.some(a => a !=='None')) { _%>, password: String!<%_ } _%>): User
14
14
  updateUser(id: ID!, name: String, email: String): User
15
15
  deleteUser(id: ID!): Boolean
16
16
  }
@@ -0,0 +1,15 @@
1
+ const express = require('express');
2
+ const UserController = require('../controllers/userController');
3
+ <%_ if (auth.includes('JWT')) { _%>
4
+ const authMiddleware = require('../../infrastructure/webserver/middleware/authMiddleware');
5
+ <%_ } _%>
6
+ const router = express.Router();
7
+ const userController = new UserController();
8
+
9
+ router.get('/users', <% if (auth.includes('JWT')) { %>authMiddleware, <% } %>(req, res, next) => userController.getUsers(req, res, next));
10
+ router.get('/users/:id', <% if (auth.includes('JWT')) { %>authMiddleware, <% } %>(req, res, next) => userController.getUserById(req, res, next));
11
+ router.post('/users', (req, res, next) => userController.createUser(req, res, next));
12
+ router.patch('/users/:id', <% if (auth.includes('JWT')) { %>authMiddleware, <% } %>(req, res, next) => userController.updateUser(req, res, next));
13
+ router.delete('/users/:id', <% if (auth.includes('JWT')) { %>authMiddleware, <% } %>(req, res, next) => userController.deleteUser(req, res, next));
14
+
15
+ module.exports = router;
@@ -12,6 +12,10 @@ jest.mock('@/interfaces/controllers/userController', () => {
12
12
  }));
13
13
  });
14
14
 
15
+ <% if (auth.includes('JWT')) { %>
16
+ jest.mock('@/infrastructure/webserver/middleware/authMiddleware', () => (req, res, next) => next());
17
+ <% } %>
18
+
15
19
  describe('ApiRoutes', () => {
16
20
  let app;
17
21
 
@@ -0,0 +1,34 @@
1
+ const User = require('../domain/models/User');
2
+ <%_ if (auth.includes('JWT')) { _%>
3
+ const bcrypt = require('bcryptjs');
4
+ <%_ } _%>
5
+ <%_ if (caching !== 'None') { _%>
6
+ const cacheService = require('<% if (caching === "Redis") { %>../infrastructure/caching/redisClient<% } else { %>../infrastructure/caching/memoryCache<% } %>');
7
+ const logger = require('../infrastructure/log/logger');
8
+ <%_ } _%>
9
+
10
+ class CreateUser {
11
+ constructor(userRepository) {
12
+ this.userRepository = userRepository;
13
+ }
14
+
15
+ async execute(name, email, password) {
16
+ let finalPassword = password;
17
+ <%_ if (auth.includes('JWT')) { -%>
18
+ if (password) {
19
+ finalPassword = await bcrypt.hash(password, 10);
20
+ }<%_ } -%>
21
+ const user = new User(null, name, email, finalPassword);
22
+ const savedUser = await this.userRepository.save(user);
23
+ <%_ if (caching !== 'None') { -%>
24
+ try {
25
+ await cacheService.del('users:all');
26
+ logger.info('Invalidated users:all cache');
27
+ } catch (error) {
28
+ logger.error('Cache error (del):', error);
29
+ }<%_ } _%>
30
+ return savedUser;
31
+ }
32
+ }
33
+
34
+ module.exports = CreateUser;
@@ -26,11 +26,12 @@ describe('CreateUser UseCase', () => {
26
26
  it('should create and save a new user', async () => {
27
27
  const name = 'Test User';
28
28
  const email = 'test@example.com';
29
+ const password = 'password123';
29
30
  const expectedResult = { id: 1, name, email };
30
31
 
31
32
  mockUserRepository.save.mockResolvedValue(expectedResult);
32
33
 
33
- const result = await createUser.execute(name, email);
34
+ const result = await createUser.execute(name, email, password);
34
35
 
35
36
  expect(mockUserRepository.save).toHaveBeenCalledTimes(1);
36
37
  const savedUser = mockUserRepository.save.mock.calls[0][0];
@@ -46,6 +47,6 @@ describe('CreateUser UseCase', () => {
46
47
  const error = new Error('Database error');
47
48
  mockUserRepository.save.mockRejectedValue(error);
48
49
 
49
- await expect(createUser.execute('Test', 'test@test.com')).rejects.toThrow(error);
50
+ await expect(createUser.execute('Test', 'test@test.com', 'pwd')).rejects.toThrow(error);
50
51
  });
51
52
  });
@@ -0,0 +1,27 @@
1
+ <%_ if (caching !== 'None') { _%>
2
+ const cacheService = require('<% if (caching === "Redis") { %>../infrastructure/caching/redisClient<% } else { %>../infrastructure/caching/memoryCache<% } %>');
3
+ const logger = require('../infrastructure/log/logger');
4
+ <%_ } _%>
5
+
6
+ class DeleteUser {
7
+ constructor(userRepository) {
8
+ this.userRepository = userRepository;
9
+ }
10
+
11
+ async execute(id) {
12
+ const result = await this.userRepository.delete(id);
13
+ <%_ if (caching !== 'None') { -%>
14
+ if (result) {
15
+ try {
16
+ await cacheService.del('users:all');
17
+ await cacheService.del(`user:${id}`);
18
+ logger.info(`Invalidated cache for user ${id}`);
19
+ } catch (error) {
20
+ logger.error('Cache error (del):', error);
21
+ }
22
+ }<%_ } -%>
23
+ return result;
24
+ }
25
+ }
26
+
27
+ module.exports = DeleteUser;
@@ -0,0 +1,36 @@
1
+ <%_ if (caching !== 'None') { _%>
2
+ const cacheService = require('<% if (caching === "Redis") { %>../infrastructure/caching/redisClient<% } else { %>../infrastructure/caching/memoryCache<% } %>');
3
+ const logger = require('../infrastructure/log/logger');
4
+ <%_ } _%>
5
+
6
+ class GetAllUsers {
7
+ constructor(userRepository) {
8
+ this.userRepository = userRepository;
9
+ }
10
+
11
+ async execute() {
12
+ <%_ if (caching !== 'None') { -%>
13
+ const cacheKey = 'users:all';
14
+ try {
15
+ const cachedUsers = await cacheService.get(cacheKey);
16
+ if (cachedUsers) {
17
+ logger.info('Serving users from cache');
18
+ return cachedUsers;
19
+ }
20
+ } catch (error) {
21
+ logger.error('Cache error (get):', error);
22
+ }<%_ } -%>
23
+ const users = await this.userRepository.getUsers();
24
+ <%_ if (caching !== 'None') { -%>
25
+ if (users) {
26
+ try {
27
+ await cacheService.set(cacheKey, users, 3600); // Cache for 1 hour
28
+ } catch (error) {
29
+ logger.error('Cache error (set):', error);
30
+ }
31
+ }<%_ } -%>
32
+ return users;
33
+ }
34
+ }
35
+
36
+ module.exports = GetAllUsers;
@@ -1,4 +1,18 @@
1
1
  const GetAllUsers = require('@/usecases/GetAllUsers');
2
+
3
+ <% if (caching === 'Redis') { -%>
4
+ jest.mock('@/infrastructure/caching/redisClient', () => ({
5
+ get: jest.fn(),
6
+ set: jest.fn(),
7
+ del: jest.fn()
8
+ }));
9
+ <% } else if (caching === 'Memory Cache') { -%>
10
+ jest.mock('@/infrastructure/caching/memoryCache', () => ({
11
+ get: jest.fn(),
12
+ set: jest.fn(),
13
+ del: jest.fn()
14
+ }));
15
+ <% } -%>
2
16
  const UserRepository = require('@/infrastructure/repositories/UserRepository');
3
17
  <%_ if (caching !== 'None') { -%>
4
18
  const cacheService = require('<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>');
@@ -0,0 +1,36 @@
1
+ <%_ if (caching !== 'None') { _%>
2
+ const cacheService = require('<% if (caching === "Redis") { %>../infrastructure/caching/redisClient<% } else { %>../infrastructure/caching/memoryCache<% } %>');
3
+ const logger = require('../infrastructure/log/logger');
4
+ <%_ } _%>
5
+
6
+ class GetUserById {
7
+ constructor(userRepository) {
8
+ this.userRepository = userRepository;
9
+ }
10
+
11
+ async execute(id) {
12
+ <%_ if (caching !== 'None') { -%>
13
+ const cacheKey = `user:${id}`;
14
+ try {
15
+ const cachedUser = await cacheService.get(cacheKey);
16
+ if (cachedUser) {
17
+ logger.info(`Serving user ${id} from cache`);
18
+ return cachedUser;
19
+ }
20
+ } catch (error) {
21
+ logger.error('Cache error (get):', error);
22
+ }<%_ } -%>
23
+ const user = await this.userRepository.findById(id);
24
+ <%_ if (caching !== 'None') { -%>
25
+ if (user) {
26
+ try {
27
+ await cacheService.set(cacheKey, user, 3600); // Cache for 1 hour
28
+ } catch (error) {
29
+ logger.error('Cache error (set):', error);
30
+ }
31
+ }<%_ } -%>
32
+ return user;
33
+ }
34
+ }
35
+
36
+ module.exports = GetUserById;
@@ -0,0 +1,48 @@
1
+ const GetUserById = require('@/usecases/GetUserById');
2
+
3
+ <% if (caching === 'Redis') { -%>
4
+ jest.mock('@/infrastructure/caching/redisClient', () => ({
5
+ get: jest.fn(),
6
+ set: jest.fn(),
7
+ del: jest.fn()
8
+ }));
9
+ <% } else if (caching === 'Memory Cache') { -%>
10
+ jest.mock('@/infrastructure/caching/memoryCache', () => ({
11
+ get: jest.fn(),
12
+ set: jest.fn(),
13
+ del: jest.fn()
14
+ }));
15
+ <% } -%>
16
+
17
+ describe('GetUserById Use Case', () => {
18
+ let userRepository;
19
+ let getUserById;
20
+
21
+ beforeEach(() => {
22
+ userRepository = {
23
+ findById: jest.fn(),
24
+ };
25
+ getUserById = new GetUserById(userRepository);
26
+ });
27
+
28
+ it('should return a user if found (Happy Path)', async () => {
29
+ const id = '1';
30
+ const user = { id, name: 'Test User', email: 'test@example.com' };
31
+ userRepository.findById.mockResolvedValue(user);
32
+
33
+ const result = await getUserById.execute(id);
34
+
35
+ expect(result).toEqual(user);
36
+ expect(userRepository.findById).toHaveBeenCalledWith(id);
37
+ });
38
+
39
+ it('should return null if user not found (Error Handling)', async () => {
40
+ const id = '999';
41
+ userRepository.findById.mockResolvedValue(null);
42
+
43
+ const result = await getUserById.execute(id);
44
+
45
+ expect(result).toBeNull();
46
+ expect(userRepository.findById).toHaveBeenCalledWith(id);
47
+ });
48
+ });
@@ -0,0 +1,28 @@
1
+ <%_ if (caching !== 'None') { _%>
2
+ const cacheService = require('<% if (caching === "Redis") { %>../infrastructure/caching/redisClient<% } else { %>../infrastructure/caching/memoryCache<% } %>');
3
+ const logger = require('../infrastructure/log/logger');
4
+ <%_ } _%>
5
+
6
+ class UpdateUser {
7
+ constructor(userRepository) {
8
+ this.userRepository = userRepository;
9
+ }
10
+
11
+ async execute(id, data) {
12
+ const user = await this.userRepository.update(id, data);
13
+ <%_ if (caching !== 'None') { -%>
14
+ if (user) {
15
+ try {
16
+ await cacheService.del('users:all');
17
+ await cacheService.del(`user:${id}`);
18
+ logger.info(`Invalidated cache for user ${id}`);
19
+ } catch (error) {
20
+ logger.error('Cache error (del):', error);
21
+ }
22
+ }
23
+ <%_ } -%>
24
+ return user;
25
+ }
26
+ }
27
+
28
+ module.exports = UpdateUser;
@@ -5,6 +5,7 @@ const ERROR_MESSAGES = {
5
5
  INTERNAL_SERVER_ERROR: 'Internal Server Error',
6
6
  BAD_REQUEST: 'Bad Request',
7
7
  FETCH_USERS_ERROR: 'Error fetching users',
8
+ FETCH_USER_ERROR: 'Error fetching user',
8
9
  CREATE_USER_ERROR: 'Error creating user',
9
10
  UPDATE_USER_ERROR: 'Error updating user',
10
11
  DELETE_USER_ERROR: 'Error deleting user',
@@ -2,6 +2,8 @@ const HTTP_STATUS = {
2
2
  OK: 200,
3
3
  CREATED: 201,
4
4
  BAD_REQUEST: 400,
5
+ UNAUTHORIZED: 401,
6
+ FORBIDDEN: 403,
5
7
  NOT_FOUND: 404,
6
8
  INTERNAL_SERVER_ERROR: 500
7
9
  };
@@ -34,13 +34,22 @@ const envSchema = z.object({
34
34
  <%_ if (communication === 'Kafka') { -%>
35
35
  KAFKA_BROKER: z.string(),
36
36
  <%_ } -%>
37
+ <%_ if (auth.includes('JWT')) { -%>
38
+ JWT_SECRET: z.string(),
39
+ JWT_EXPIRES_IN: z.string().default('15m'),
40
+ JWT_REFRESH_SECRET: z.string().default('your-secret-refresh-key'),
41
+ JWT_REFRESH_EXPIRES_IN: z.string().default('7d'),
42
+ <%_ } -%>
37
43
  });
38
44
 
39
45
  const _env = envSchema.safeParse(process.env);
40
46
 
41
47
  if (!_env.success) {
42
- logger.error('❌ Invalid environment variables:', _env.error.format());
43
- process.exit(1);
48
+ if (process.env.NODE_ENV !== 'test') {
49
+ logger.error('❌ Invalid environment variables:', _env.error.format());
50
+ process.exit(1);
51
+ }
52
+ logger.warn('⚠️ Environment validation failed. Continuing in test mode.');
44
53
  }
45
54
 
46
- export const env = _env.data;
55
+ export const env = (_env.success ? _env.data : process.env) as unknown as z.infer<typeof envSchema>;
@@ -2,6 +2,8 @@ export class User {
2
2
  constructor(
3
3
  public id: number | string | null,
4
4
  public name: string,
5
- public email: string
5
+ public email: string,
6
+ public password?: string
6
7
  ) { }
8
+
7
9
  }
@@ -13,6 +13,8 @@ import healthRoutes from '@/interfaces/routes/healthRoute';
13
13
  import userRoutes from '@/interfaces/routes/userRoutes';
14
14
  import swaggerUi from 'swagger-ui-express';
15
15
  import swaggerSpecs from '@/config/swagger';<% } %>
16
+ <%_ if (auth.includes('JWT')) { -%>import authRoutes from '@/interfaces/routes/authRoutes';<%_ } -%>
17
+
16
18
  <%_ if (communication === 'Kafka') { -%>import { kafkaService } from '@/infrastructure/messaging/kafkaClient';<%_ } -%>
17
19
  <%_ if (communication === 'GraphQL') { -%>
18
20
  import { ApolloServer } from '@apollo/server';
@@ -54,6 +56,8 @@ app.use(morgan('combined', { stream: { write: (message) => logger.info(message.t
54
56
  <%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>
55
57
  app.use('/api/users', userRoutes);
56
58
  <%_ } -%>
59
+ <%_ if (auth.includes('JWT')) { -%>app.use('/api/auth', authRoutes);<%_ } -%>
60
+
57
61
  <%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>
58
62
  app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
59
63
  <%_ } -%>
@@ -15,7 +15,7 @@ describe('UserRepository', () => {
15
15
  describe('save', () => {
16
16
  it('should save and return a newly created user (Happy Path)', async () => {
17
17
  // Arrange
18
- const payload = { id: '', name: 'TestUser', email: 'test@example.com' };
18
+ const payload = { id: '', name: 'TestUser', email: 'test@example.com', password: 'password123' };
19
19
  <%_ if (database === 'MongoDB') { -%>
20
20
  const mockDbRecord = { _id: { toString: () => '1' }, name: 'TestUser', email: 'test@example.com' };
21
21
  (UserModel.create as jest.Mock).mockResolvedValue(mockDbRecord);
@@ -34,7 +34,11 @@ describe('UserRepository', () => {
34
34
  expect(result.name).toEqual(payload.name)
35
35
  <%_ } else { -%>
36
36
  expect(result).toEqual({ id: '1', name: 'TestUser', email: 'test@example.com' });
37
- expect(UserModel.create).toHaveBeenCalledWith({ name: payload.name, email: payload.email });
37
+ expect(UserModel.create).toHaveBeenCalledWith({
38
+ name: payload.name,
39
+ email: payload.email,
40
+ <% if (auth.includes('JWT')) { %>password: payload.password<% } %>
41
+ });
38
42
  <%_ } -%>
39
43
  });
40
44
 
@@ -43,7 +47,7 @@ describe('UserRepository', () => {
43
47
  // Mocks do not naturally fail
44
48
  <%_ } else { -%>
45
49
  // Arrange
46
- const payload = { id: '', name: 'FailUser', email: 'fail@example.com' };
50
+ const payload = { id: '', name: 'FailUser', email: 'fail@example.com', password: 'password123' };
47
51
  const error = new Error('DB Connection Refused');
48
52
  (UserModel.create as jest.Mock).mockRejectedValue(error);
49
53
 
@@ -53,6 +57,49 @@ describe('UserRepository', () => {
53
57
  });
54
58
  });
55
59
 
60
+ describe('findById', () => {
61
+ it('should find and return a user by id (Happy Path)', async () => {
62
+ // Arrange
63
+ const id = '1';
64
+ <%_ if (database === 'MongoDB') { -%>
65
+ const mockDbRecord = { _id: { toString: () => '1' }, name: 'TestUser', email: 'test@example.com' };
66
+ (UserModel.findById as jest.Mock).mockResolvedValue(mockDbRecord);
67
+ <%_ } else if (database === 'None') { -%>
68
+ const mockDbRecord = { id: '1', name: 'TestUser', email: 'test@example.com' };
69
+ (UserModel.findByPk as jest.Mock).mockResolvedValue(mockDbRecord);
70
+ <%_ } else { -%>
71
+ const mockDbRecord = { id: '1', name: 'TestUser', email: 'test@example.com' };
72
+ (UserModel.findByPk as jest.Mock).mockResolvedValue(mockDbRecord);
73
+ <%_ } -%>
74
+
75
+ // Act
76
+ const result = await userRepository.findById(id);
77
+
78
+ // Assert
79
+ expect(result).toEqual({ id: '1', name: 'TestUser', email: 'test@example.com' });
80
+ <%_ if (database === 'MongoDB') { -%>
81
+ expect(UserModel.findById).toHaveBeenCalledWith(id);
82
+ <%_ } else { -%>
83
+ expect(UserModel.findByPk).toHaveBeenCalledWith(id);
84
+ <%_ } -%>
85
+ });
86
+
87
+ it('should return null if user not found', async () => {
88
+ // Arrange
89
+ <%_ if (database === 'MongoDB') { -%>
90
+ (UserModel.findById as jest.Mock).mockResolvedValue(null);
91
+ <%_ } else { -%>
92
+ (UserModel.findByPk as jest.Mock).mockResolvedValue(null);
93
+ <%_ } -%>
94
+
95
+ // Act
96
+ const result = await userRepository.findById('999');
97
+
98
+ // Assert
99
+ expect(result).toBeNull();
100
+ });
101
+ });
102
+
56
103
  describe('getUsers', () => {
57
104
  it('should return a list of mapped UserEntities (Happy Path)', async () => {
58
105
  // Arrange
@@ -88,17 +135,16 @@ describe('UserRepository', () => {
88
135
  // Arrange
89
136
  const id = '1';
90
137
  const data = { name: 'Updated' };
91
- const expectedUser = { id: '1', name: 'Updated', email: 'test@example.com' };
92
-
93
138
  <%_ if (database === 'MongoDB') { -%>
94
139
  const mockDbRecord = { _id: { toString: () => '1' }, name: 'Updated', email: 'test@example.com' };
95
140
  (UserModel.findByIdAndUpdate as jest.Mock).mockResolvedValue(mockDbRecord);
96
- <%_ } else if (database === 'None') { -%>
97
- (UserModel.update as jest.Mock).mockResolvedValue(expectedUser);
98
- <%_ } else { -%>
141
+ <% } else if (database === 'None') { -%>
142
+ const mockDbRecord = { id: '1', name: 'Updated', email: 'test@example.com' };
143
+ (UserModel.update as jest.Mock).mockResolvedValue(mockDbRecord);
144
+ <% } else { -%>
99
145
  const mockDbRecord = { id: '1', name: 'Updated', email: 'test@example.com', update: jest.fn().mockResolvedValue(true) };
100
146
  (UserModel.findByPk as jest.Mock).mockResolvedValue(mockDbRecord);
101
- <%_ } -%>
147
+ <% } -%>
102
148
 
103
149
  // Act
104
150
  const result = await userRepository.update(id, data);
@@ -4,17 +4,46 @@ import UserModel from '@/infrastructure/database/models/User';
4
4
  export class UserRepository {
5
5
  async save(user: UserEntity): Promise<UserEntity> {
6
6
  <%_ if (database === 'MongoDB') { -%>
7
- const newUser = await UserModel.create({ name: user.name, email: user.email });
7
+ const newUser = await UserModel.create({
8
+ name: user.name,
9
+ email: user.email,
10
+ <% if (auth.includes('JWT')) { %>password: user.password<% } %>
11
+ });
8
12
  return { id: newUser._id.toString(), name: newUser.name, email: newUser.email };
9
13
  <%_ } else if (database === 'None') { -%>
10
- const newUser = await UserModel.create({ name: user.name, email: user.email });
14
+ const newUser = await UserModel.create({
15
+ name: user.name,
16
+ email: user.email,
17
+ <% if (auth.includes('JWT')) { %>password: user.password<% } %>
18
+ });
11
19
  return { id: newUser.id, name: newUser.name, email: newUser.email };
12
20
  <%_ } else { -%>
13
- const newUser = await UserModel.create({ name: user.name, email: user.email });
21
+ const newUser = await UserModel.create({
22
+ name: user.name,
23
+ email: user.email,
24
+ <% if (auth.includes('JWT')) { %>password: user.password<% } %>
25
+ });
14
26
  return { id: newUser.id, name: newUser.name, email: newUser.email };
15
27
  <%_ } -%>
16
28
  }
17
29
 
30
+ async findById(id: number | string): Promise<UserEntity | null> {
31
+ <%_ if (database === 'MongoDB') { -%>
32
+ const user = await UserModel.findById(id);
33
+ if (!user) return null;
34
+ return { id: user._id.toString(), name: user.name, email: user.email };
35
+ <%_ } else if (database === 'None') { -%>
36
+ const user = await UserModel.findByPk(id);
37
+ if (!user) return null;
38
+ return { id: user.id, name: user.name, email: user.email };
39
+ <%_ } else { -%>
40
+ const user = await UserModel.findByPk(id);
41
+ if (!user) return null;
42
+ return { id: user.id, name: user.name, email: user.email };
43
+ <%_ } -%>
44
+ }
45
+
46
+
18
47
  async getUsers(): Promise<UserEntity[]> {
19
48
  <%_ if (database === 'MongoDB') { -%>
20
49
  const users = await UserModel.find();