nodejs-quickstart-structure 2.0.1 → 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 (154) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +43 -39
  3. package/bin/index.js +5 -2
  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/database-setup.js +2 -1
  9. package/lib/modules/project-setup.js +1 -0
  10. package/lib/prompts.js +39 -0
  11. package/package.json +5 -4
  12. package/templates/clean-architecture/js/src/domain/models/User.js +3 -1
  13. package/templates/clean-architecture/js/src/index.js.ejs +2 -0
  14. package/templates/clean-architecture/js/src/infrastructure/config/env.js.ejs +12 -3
  15. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +25 -2
  16. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +27 -0
  17. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +3 -0
  18. package/templates/clean-architecture/js/src/infrastructure/webserver/server.spec.js.ejs +49 -0
  19. package/templates/clean-architecture/js/src/infrastructure/webserver/swagger.spec.js.ejs +14 -0
  20. package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +41 -4
  21. package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +69 -4
  22. package/templates/clean-architecture/js/src/interfaces/graphql/context.js.ejs +13 -6
  23. package/templates/clean-architecture/js/src/interfaces/graphql/context.spec.js.ejs +38 -21
  24. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +10 -5
  25. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +32 -10
  26. package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/user.types.js.ejs +1 -1
  27. package/templates/clean-architecture/js/src/interfaces/routes/api.js.ejs +15 -0
  28. package/templates/clean-architecture/js/src/interfaces/routes/api.spec.js.ejs +4 -0
  29. package/templates/clean-architecture/js/src/usecases/CreateUser.js.ejs +34 -0
  30. package/templates/clean-architecture/js/src/usecases/CreateUser.spec.js.ejs +3 -2
  31. package/templates/clean-architecture/js/src/usecases/DeleteUser.js.ejs +27 -0
  32. package/templates/clean-architecture/js/src/usecases/GetAllUsers.js.ejs +36 -0
  33. package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +14 -0
  34. package/templates/clean-architecture/js/src/usecases/GetUserById.js.ejs +36 -0
  35. package/templates/clean-architecture/js/src/usecases/GetUserById.spec.js.ejs +48 -0
  36. package/templates/clean-architecture/js/src/usecases/UpdateUser.js.ejs +28 -0
  37. package/templates/clean-architecture/js/src/utils/errorMessages.js +1 -0
  38. package/templates/clean-architecture/js/src/utils/httpCodes.js +2 -0
  39. package/templates/clean-architecture/ts/src/config/env.ts.ejs +12 -3
  40. package/templates/clean-architecture/ts/src/domain/user.ts +3 -1
  41. package/templates/clean-architecture/ts/src/index.ts.ejs +4 -0
  42. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +55 -9
  43. package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +32 -3
  44. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +26 -6
  45. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +57 -15
  46. package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +38 -23
  47. package/templates/clean-architecture/ts/src/interfaces/graphql/context.ts.ejs +14 -8
  48. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +33 -10
  49. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +15 -5
  50. package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/user.types.ts.ejs +1 -1
  51. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +9 -1
  52. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts.ejs +16 -0
  53. package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +3 -2
  54. package/templates/clean-architecture/ts/src/usecases/createUser.ts.ejs +35 -0
  55. package/templates/clean-architecture/ts/src/usecases/deleteUser.spec.ts.ejs +1 -0
  56. package/templates/clean-architecture/ts/src/usecases/deleteUser.ts.ejs +24 -0
  57. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts.ejs +21 -0
  58. package/templates/clean-architecture/ts/src/usecases/getUserById.spec.ts.ejs +47 -0
  59. package/templates/clean-architecture/ts/src/usecases/getUserById.ts.ejs +23 -0
  60. package/templates/clean-architecture/ts/src/usecases/updateUser.spec.ts.ejs +1 -0
  61. package/templates/clean-architecture/ts/src/usecases/updateUser.ts.ejs +25 -0
  62. package/templates/clean-architecture/ts/src/utils/errorMessages.ts +1 -0
  63. package/templates/clean-architecture/ts/src/utils/httpCodes.ts +1 -0
  64. package/templates/common/.cursorrules.ejs +9 -0
  65. package/templates/common/.env.example.ejs +17 -10
  66. package/templates/common/README.md.ejs +63 -18
  67. package/templates/common/auth/js/controllers/authController.js.ejs +168 -0
  68. package/templates/common/auth/js/controllers/authController.spec.js.ejs +148 -0
  69. package/templates/common/auth/js/middleware/authMiddleware.js.ejs +58 -0
  70. package/templates/common/auth/js/middleware/authMiddleware.spec.js.ejs +108 -0
  71. package/templates/common/auth/js/routes/authRoutes.js.ejs +16 -0
  72. package/templates/common/auth/js/services/jwtService.js.ejs +54 -0
  73. package/templates/common/auth/js/services/jwtService.spec.js.ejs +84 -0
  74. package/templates/common/auth/ts/controllers/authController.spec.ts.ejs +161 -0
  75. package/templates/common/auth/ts/controllers/authController.ts.ejs +165 -0
  76. package/templates/common/auth/ts/middleware/authMiddleware.spec.ts.ejs +128 -0
  77. package/templates/common/auth/ts/middleware/authMiddleware.ts.ejs +59 -0
  78. package/templates/common/auth/ts/routes/authRoutes.ts.ejs +20 -0
  79. package/templates/common/auth/ts/services/jwtService.spec.ts.ejs +89 -0
  80. package/templates/common/auth/ts/services/jwtService.ts.ejs +60 -0
  81. package/templates/common/caching/clean/js/CreateUser.js.ejs +14 -5
  82. package/templates/common/caching/clean/js/DeleteUser.js.ejs +2 -1
  83. package/templates/common/caching/clean/js/GetUserById.js.ejs +39 -0
  84. package/templates/common/caching/clean/js/UpdateUser.js.ejs +2 -1
  85. package/templates/common/caching/clean/ts/createUser.ts.ejs +14 -6
  86. package/templates/common/caching/clean/ts/deleteUser.ts.ejs +2 -1
  87. package/templates/common/caching/clean/ts/getUserById.ts.ejs +32 -0
  88. package/templates/common/caching/clean/ts/updateUser.ts.ejs +2 -1
  89. package/templates/common/database/js/models/User.js.ejs +14 -1
  90. package/templates/common/database/js/models/User.js.mongoose.ejs +7 -0
  91. package/templates/common/database/js/models/User.spec.js.ejs +12 -0
  92. package/templates/common/database/ts/models/User.spec.ts.ejs +10 -0
  93. package/templates/common/database/ts/models/User.ts.ejs +17 -0
  94. package/templates/common/database/ts/models/User.ts.mongoose.ejs +8 -0
  95. package/templates/common/docker-compose.yml.ejs +12 -0
  96. package/templates/common/ecosystem.config.js.ejs +9 -3
  97. package/templates/common/eslint.config.mjs.ejs +3 -0
  98. package/templates/common/jest.config.js.ejs +11 -9
  99. package/templates/common/kafka/js/messaging/baseConsumer.js.ejs +1 -1
  100. package/templates/common/kafka/js/services/kafkaService.js.ejs +1 -1
  101. package/templates/common/migrations/init.js.ejs +5 -4
  102. package/templates/common/package.json.ejs +8 -1
  103. package/templates/common/prompts/project-context.md.ejs +8 -1
  104. package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +149 -107
  105. package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +88 -47
  106. package/templates/common/swagger.yml.ejs +148 -0
  107. package/templates/common/tsconfig.eslint.json +15 -0
  108. package/templates/common/tsconfig.json +2 -1
  109. package/templates/common/views/ejs/index.ejs +264 -30
  110. package/templates/common/views/ejs/login.ejs.ejs +244 -0
  111. package/templates/common/views/ejs/signup.ejs.ejs +282 -0
  112. package/templates/common/views/pug/index.pug +269 -38
  113. package/templates/common/views/pug/login.pug.ejs +195 -0
  114. package/templates/common/views/pug/signup.pug.ejs +241 -0
  115. package/templates/db/mysql/V1__Initial_Setup.sql.ejs +6 -0
  116. package/templates/db/postgres/V1__Initial_Setup.sql.ejs +6 -0
  117. package/templates/mvc/js/src/config/env.js.ejs +12 -3
  118. package/templates/mvc/js/src/controllers/userController.js.ejs +29 -5
  119. package/templates/mvc/js/src/controllers/userController.spec.js.ejs +27 -12
  120. package/templates/mvc/js/src/graphql/context.js.ejs +14 -3
  121. package/templates/mvc/js/src/graphql/context.spec.js.ejs +36 -21
  122. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +10 -5
  123. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +32 -10
  124. package/templates/mvc/js/src/graphql/typeDefs/user.types.js.ejs +1 -1
  125. package/templates/mvc/js/src/index.js.ejs +16 -3
  126. package/templates/mvc/js/src/routes/api.js.ejs +14 -0
  127. package/templates/mvc/js/src/routes/api.spec.js.ejs +3 -0
  128. package/templates/mvc/js/src/utils/errorMessages.js +1 -0
  129. package/templates/mvc/js/src/utils/httpCodes.js +1 -0
  130. package/templates/mvc/ts/src/config/env.ts.ejs +12 -3
  131. package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +95 -7
  132. package/templates/mvc/ts/src/controllers/userController.ts.ejs +68 -11
  133. package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +36 -23
  134. package/templates/mvc/ts/src/graphql/context.ts.ejs +15 -6
  135. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +32 -10
  136. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +15 -5
  137. package/templates/mvc/ts/src/graphql/typeDefs/user.types.ts.ejs +1 -1
  138. package/templates/mvc/ts/src/index.ts.ejs +15 -3
  139. package/templates/mvc/ts/src/routes/api.spec.ts.ejs +6 -0
  140. package/templates/mvc/ts/src/routes/api.ts.ejs +15 -0
  141. package/templates/mvc/ts/src/utils/errorMessages.ts +1 -0
  142. package/templates/mvc/ts/src/utils/httpCodes.ts +1 -0
  143. package/templates/clean-architecture/js/src/interfaces/routes/api.js +0 -12
  144. package/templates/clean-architecture/js/src/usecases/CreateUser.js +0 -14
  145. package/templates/clean-architecture/js/src/usecases/DeleteUser.js +0 -11
  146. package/templates/clean-architecture/js/src/usecases/GetAllUsers.js +0 -12
  147. package/templates/clean-architecture/js/src/usecases/UpdateUser.js +0 -11
  148. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +0 -13
  149. package/templates/clean-architecture/ts/src/usecases/createUser.ts +0 -13
  150. package/templates/clean-architecture/ts/src/usecases/deleteUser.ts +0 -9
  151. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +0 -10
  152. package/templates/clean-architecture/ts/src/usecases/updateUser.ts +0 -9
  153. package/templates/mvc/js/src/routes/api.js +0 -10
  154. package/templates/mvc/ts/src/routes/api.ts +0 -12
@@ -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();
@@ -2,7 +2,9 @@
2
2
  import { Request, Response, NextFunction } from 'express';
3
3
  import { HTTP_STATUS } from '@/utils/httpCodes';
4
4
  <% } -%>
5
+ <% if (communication === 'GraphQL') { -%>
5
6
  import { ERROR_MESSAGES } from '@/utils/errorMessages';
7
+ <% } -%>
6
8
  import { UserController } from '@/interfaces/controllers/userController';
7
9
  import CreateUser from '@/usecases/createUser';
8
10
  import GetAllUsers from '@/usecases/getAllUsers';
@@ -118,7 +120,7 @@ describe('UserController (Clean Architecture)', () => {
118
120
  describe('createUser', () => {
119
121
  it('should successfully create a new user (Happy Path)', async () => {
120
122
  // Arrange
121
- const payload = { name: 'Alice', email: 'alice@example.com' };
123
+ const payload = { name: 'Alice', email: 'alice@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
122
124
  <% if (communication === 'GraphQL') { -%>
123
125
  const dataArg = payload;
124
126
  <% } else { -%>
@@ -146,13 +148,31 @@ describe('UserController (Clean Architecture)', () => {
146
148
 
147
149
  expect(mockResponse.json).toHaveBeenCalledWith(expectedUser);
148
150
  <% } -%>
149
- expect(mockCreateUserUseCase.execute).toHaveBeenCalledWith(payload.name, payload.email);
151
+ <% if (auth.includes('JWT')) { -%>
152
+ expect(mockCreateUserUseCase.execute).toHaveBeenCalledWith(payload.name, payload.email, payload.password);
153
+ <% } else { -%>
154
+ expect(mockCreateUserUseCase.execute).toHaveBeenCalledWith(payload.name, payload.email, undefined);
155
+ <% } -%>
156
+ });
157
+
158
+ <% if (auth.includes('JWT')) { %>
159
+ it('should fail to create a user when password is missing and JWT is enabled', async () => {
160
+ const payload = { name: 'Alice', email: 'alice@example.com' };
161
+ <% if (communication === 'GraphQL') { -%>
162
+ await expect(userController.createUser(payload)).rejects.toThrow('Password is required');
163
+ <% } else { -%>
164
+ mockRequest.body = payload;
165
+ await userController.createUser(mockRequest as Request, mockResponse as Response, mockNext);
166
+ expect(mockResponse.status).toHaveBeenCalledWith(HTTP_STATUS.BAD_REQUEST);
167
+ expect(mockResponse.json).toHaveBeenCalledWith({ error: 'Password is required' });
168
+ <% } -%>
150
169
  });
170
+ <% } %>
151
171
 
152
172
  it('should handle errors when creation fails (Error Handling)', async () => {
153
173
  // Arrange
154
174
  const error = new Error('Creation Error');
155
- const payload = { name: 'Bob', email: 'bob@example.com' };
175
+ const payload = { name: 'Bob', email: 'bob@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
156
176
  <% if (communication === 'GraphQL') { -%>
157
177
  const dataArg = payload;
158
178
  <% } else { -%>
@@ -173,7 +193,7 @@ describe('UserController (Clean Architecture)', () => {
173
193
  it('should handle non-Error objects in catch block when creation fails', async () => {
174
194
  // Arrange
175
195
  const error = 'Creation String Error';
176
- const payload = { name: 'Bob', email: 'bob@example.com' };
196
+ const payload = { name: 'Bob', email: 'bob@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
177
197
  <% if (communication === 'GraphQL') { -%>
178
198
  const dataArg = payload;
179
199
  <% } else { -%>
@@ -320,9 +340,9 @@ describe('UserController (Clean Architecture)', () => {
320
340
  const error = new Error('Database Error');
321
341
  mockCreateUserUseCase.execute.mockRejectedValue(error);
322
342
  <%_ if (communication === 'GraphQL') { -%>
323
- await expect(userController.createUser({ name: 'Alice', email: 'alice@example.com' })).rejects.toThrow(error);
343
+ await expect(userController.createUser({ name: 'Alice', email: 'alice@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> })).rejects.toThrow(error);
324
344
  <%_ } else { -%>
325
- mockRequest.body = { name: 'Alice', email: 'alice@example.com' };
345
+ mockRequest.body = { name: 'Alice', email: 'alice@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
326
346
  await userController.createUser(mockRequest as Request, mockResponse as Response, mockNext);
327
347
  expect(mockNext).toHaveBeenCalledWith(error);
328
348
  <%_ } -%>