nodejs-quickstart-structure 2.0.1 → 2.1.1

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 (165) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +64 -66
  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/config-files.js +6 -0
  9. package/lib/modules/database-setup.js +2 -1
  10. package/lib/modules/project-setup.js +1 -0
  11. package/lib/prompts.js +39 -0
  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 +38 -1
  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 +51 -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 +70 -5
  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 +55 -22
  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 +12 -3
  32. package/templates/clean-architecture/js/src/usecases/DeleteUser.js.ejs +27 -0
  33. package/templates/clean-architecture/js/src/usecases/DeleteUser.spec.js.ejs +9 -1
  34. package/templates/clean-architecture/js/src/usecases/GetAllUsers.js.ejs +36 -0
  35. package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +23 -1
  36. package/templates/clean-architecture/js/src/usecases/GetUserById.js.ejs +36 -0
  37. package/templates/clean-architecture/js/src/usecases/GetUserById.spec.js.ejs +48 -0
  38. package/templates/clean-architecture/js/src/usecases/UpdateUser.js.ejs +28 -0
  39. package/templates/clean-architecture/js/src/usecases/UpdateUser.spec.js.ejs +9 -1
  40. package/templates/clean-architecture/js/src/utils/errorMessages.js +1 -0
  41. package/templates/clean-architecture/js/src/utils/httpCodes.js +2 -0
  42. package/templates/clean-architecture/ts/src/config/env.ts.ejs +12 -3
  43. package/templates/clean-architecture/ts/src/domain/user.ts +3 -1
  44. package/templates/clean-architecture/ts/src/index.ts.ejs +4 -0
  45. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +71 -10
  46. package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +32 -3
  47. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +43 -9
  48. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +57 -15
  49. package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +57 -24
  50. package/templates/clean-architecture/ts/src/interfaces/graphql/context.ts.ejs +14 -8
  51. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +33 -10
  52. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +15 -5
  53. package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/user.types.ts.ejs +1 -1
  54. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +9 -1
  55. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts.ejs +16 -0
  56. package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +12 -3
  57. package/templates/clean-architecture/ts/src/usecases/createUser.ts.ejs +35 -0
  58. package/templates/clean-architecture/ts/src/usecases/deleteUser.spec.ts.ejs +10 -1
  59. package/templates/clean-architecture/ts/src/usecases/deleteUser.ts.ejs +24 -0
  60. package/templates/clean-architecture/ts/src/usecases/getAllUsers.spec.ts.ejs +9 -1
  61. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts.ejs +21 -0
  62. package/templates/clean-architecture/ts/src/usecases/getUserById.spec.ts.ejs +55 -0
  63. package/templates/clean-architecture/ts/src/usecases/getUserById.ts.ejs +23 -0
  64. package/templates/clean-architecture/ts/src/usecases/updateUser.spec.ts.ejs +10 -1
  65. package/templates/clean-architecture/ts/src/usecases/updateUser.ts.ejs +25 -0
  66. package/templates/clean-architecture/ts/src/utils/errorMessages.ts +1 -0
  67. package/templates/clean-architecture/ts/src/utils/httpCodes.ts +1 -0
  68. package/templates/common/.cursorrules.ejs +9 -0
  69. package/templates/common/.env.example.ejs +17 -10
  70. package/templates/common/README.md.ejs +63 -18
  71. package/templates/common/auth/js/controllers/authController.js.ejs +170 -0
  72. package/templates/common/auth/js/controllers/authController.spec.js.ejs +148 -0
  73. package/templates/common/auth/js/middleware/authMiddleware.js.ejs +58 -0
  74. package/templates/common/auth/js/middleware/authMiddleware.spec.js.ejs +108 -0
  75. package/templates/common/auth/js/routes/authRoutes.js.ejs +16 -0
  76. package/templates/common/auth/js/services/jwtService.js.ejs +54 -0
  77. package/templates/common/auth/js/services/jwtService.spec.js.ejs +84 -0
  78. package/templates/common/auth/ts/controllers/authController.spec.ts.ejs +161 -0
  79. package/templates/common/auth/ts/controllers/authController.ts.ejs +167 -0
  80. package/templates/common/auth/ts/middleware/authMiddleware.spec.ts.ejs +128 -0
  81. package/templates/common/auth/ts/middleware/authMiddleware.ts.ejs +59 -0
  82. package/templates/common/auth/ts/routes/authRoutes.ts.ejs +20 -0
  83. package/templates/common/auth/ts/services/jwtService.spec.ts.ejs +89 -0
  84. package/templates/common/auth/ts/services/jwtService.ts.ejs +60 -0
  85. package/templates/common/babel.config.js.ejs +5 -0
  86. package/templates/common/caching/clean/js/CreateUser.js.ejs +14 -5
  87. package/templates/common/caching/clean/js/DeleteUser.js.ejs +2 -1
  88. package/templates/common/caching/clean/js/GetUserById.js.ejs +39 -0
  89. package/templates/common/caching/clean/js/UpdateUser.js.ejs +2 -1
  90. package/templates/common/caching/clean/ts/createUser.ts.ejs +14 -6
  91. package/templates/common/caching/clean/ts/deleteUser.ts.ejs +2 -1
  92. package/templates/common/caching/clean/ts/getUserById.ts.ejs +32 -0
  93. package/templates/common/caching/clean/ts/updateUser.ts.ejs +2 -1
  94. package/templates/common/caching/js/memoryCache.spec.js.ejs +2 -0
  95. package/templates/common/caching/js/redisClient.spec.js.ejs +2 -0
  96. package/templates/common/caching/ts/memoryCache.spec.ts.ejs +2 -0
  97. package/templates/common/caching/ts/redisClient.spec.ts.ejs +2 -0
  98. package/templates/common/database/js/models/User.js.ejs +14 -1
  99. package/templates/common/database/js/models/User.js.mongoose.ejs +7 -0
  100. package/templates/common/database/js/models/User.spec.js.ejs +12 -0
  101. package/templates/common/database/js/mongoose.spec.js.ejs +2 -0
  102. package/templates/common/database/ts/models/User.spec.ts.ejs +10 -0
  103. package/templates/common/database/ts/models/User.ts.ejs +17 -0
  104. package/templates/common/database/ts/models/User.ts.mongoose.ejs +8 -0
  105. package/templates/common/database/ts/mongoose.spec.ts.ejs +2 -0
  106. package/templates/common/docker-compose.yml.ejs +12 -0
  107. package/templates/common/ecosystem.config.js.ejs +9 -3
  108. package/templates/common/eslint.config.mjs.ejs +3 -0
  109. package/templates/common/jest.config.js.ejs +13 -9
  110. package/templates/common/kafka/js/messaging/baseConsumer.js.ejs +1 -1
  111. package/templates/common/kafka/js/services/kafkaService.js.ejs +1 -1
  112. package/templates/common/migrations/init.js.ejs +5 -4
  113. package/templates/common/package.json.ejs +11 -2
  114. package/templates/common/prompts/project-context.md.ejs +8 -1
  115. package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +149 -107
  116. package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +88 -47
  117. package/templates/common/swagger.yml.ejs +148 -0
  118. package/templates/common/tsconfig.eslint.json +15 -0
  119. package/templates/common/tsconfig.json +3 -1
  120. package/templates/common/views/ejs/index.ejs +264 -30
  121. package/templates/common/views/ejs/login.ejs.ejs +244 -0
  122. package/templates/common/views/ejs/signup.ejs.ejs +282 -0
  123. package/templates/common/views/pug/index.pug +269 -38
  124. package/templates/common/views/pug/login.pug.ejs +195 -0
  125. package/templates/common/views/pug/signup.pug.ejs +241 -0
  126. package/templates/db/mysql/V1__Initial_Setup.sql.ejs +6 -0
  127. package/templates/db/postgres/V1__Initial_Setup.sql.ejs +6 -0
  128. package/templates/mvc/js/src/config/env.js.ejs +12 -3
  129. package/templates/mvc/js/src/controllers/userController.js.ejs +29 -5
  130. package/templates/mvc/js/src/controllers/userController.spec.js.ejs +27 -12
  131. package/templates/mvc/js/src/graphql/context.js.ejs +14 -3
  132. package/templates/mvc/js/src/graphql/context.spec.js.ejs +36 -21
  133. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +10 -5
  134. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +32 -10
  135. package/templates/mvc/js/src/graphql/typeDefs/user.types.js.ejs +1 -1
  136. package/templates/mvc/js/src/index.js.ejs +16 -3
  137. package/templates/mvc/js/src/routes/api.js.ejs +14 -0
  138. package/templates/mvc/js/src/routes/api.spec.js.ejs +3 -0
  139. package/templates/mvc/js/src/utils/errorMessages.js +1 -0
  140. package/templates/mvc/js/src/utils/httpCodes.js +1 -0
  141. package/templates/mvc/ts/src/config/env.ts.ejs +12 -3
  142. package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +95 -7
  143. package/templates/mvc/ts/src/controllers/userController.ts.ejs +68 -11
  144. package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +36 -23
  145. package/templates/mvc/ts/src/graphql/context.ts.ejs +15 -6
  146. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +32 -10
  147. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +15 -5
  148. package/templates/mvc/ts/src/graphql/typeDefs/user.types.ts.ejs +1 -1
  149. package/templates/mvc/ts/src/index.ts.ejs +15 -3
  150. package/templates/mvc/ts/src/routes/api.spec.ts.ejs +6 -0
  151. package/templates/mvc/ts/src/routes/api.ts.ejs +15 -0
  152. package/templates/mvc/ts/src/utils/errorMessages.ts +1 -0
  153. package/templates/mvc/ts/src/utils/httpCodes.ts +1 -0
  154. package/templates/clean-architecture/js/src/interfaces/routes/api.js +0 -12
  155. package/templates/clean-architecture/js/src/usecases/CreateUser.js +0 -14
  156. package/templates/clean-architecture/js/src/usecases/DeleteUser.js +0 -11
  157. package/templates/clean-architecture/js/src/usecases/GetAllUsers.js +0 -12
  158. package/templates/clean-architecture/js/src/usecases/UpdateUser.js +0 -11
  159. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +0 -13
  160. package/templates/clean-architecture/ts/src/usecases/createUser.ts +0 -13
  161. package/templates/clean-architecture/ts/src/usecases/deleteUser.ts +0 -9
  162. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +0 -10
  163. package/templates/clean-architecture/ts/src/usecases/updateUser.ts +0 -9
  164. package/templates/mvc/js/src/routes/api.js +0 -10
  165. package/templates/mvc/ts/src/routes/api.ts +0 -12
@@ -2,7 +2,22 @@ import { UserRepository } from '@/infrastructure/repositories/UserRepository';
2
2
  import UserModel from '@/infrastructure/database/models/User';
3
3
 
4
4
  // Mock DB Model Database Layer
5
- jest.mock('@/infrastructure/database/models/User');
5
+ jest.mock('@/infrastructure/database/models/User', () => {
6
+ return {
7
+ __esModule: true,
8
+ default: {
9
+ create: jest.fn(),
10
+ findAll: jest.fn(),
11
+ findByPk: jest.fn(),
12
+ update: jest.fn(),
13
+ destroy: jest.fn(),
14
+ find: jest.fn(),
15
+ findById: jest.fn(),
16
+ findByIdAndUpdate: jest.fn(),
17
+ findByIdAndDelete: jest.fn(),
18
+ }
19
+ };
20
+ });
6
21
 
7
22
  describe('UserRepository', () => {
8
23
  let userRepository: UserRepository;
@@ -15,7 +30,7 @@ describe('UserRepository', () => {
15
30
  describe('save', () => {
16
31
  it('should save and return a newly created user (Happy Path)', async () => {
17
32
  // Arrange
18
- const payload = { id: '', name: 'TestUser', email: 'test@example.com' };
33
+ const payload = { id: '', name: 'TestUser', email: 'test@example.com', password: 'password123' };
19
34
  <%_ if (database === 'MongoDB') { -%>
20
35
  const mockDbRecord = { _id: { toString: () => '1' }, name: 'TestUser', email: 'test@example.com' };
21
36
  (UserModel.create as jest.Mock).mockResolvedValue(mockDbRecord);
@@ -34,7 +49,11 @@ describe('UserRepository', () => {
34
49
  expect(result.name).toEqual(payload.name)
35
50
  <%_ } else { -%>
36
51
  expect(result).toEqual({ id: '1', name: 'TestUser', email: 'test@example.com' });
37
- expect(UserModel.create).toHaveBeenCalledWith({ name: payload.name, email: payload.email });
52
+ expect(UserModel.create).toHaveBeenCalledWith({
53
+ name: payload.name,
54
+ email: payload.email,
55
+ <% if (auth.includes('JWT')) { %>password: payload.password<% } %>
56
+ });
38
57
  <%_ } -%>
39
58
  });
40
59
 
@@ -43,7 +62,7 @@ describe('UserRepository', () => {
43
62
  // Mocks do not naturally fail
44
63
  <%_ } else { -%>
45
64
  // Arrange
46
- const payload = { id: '', name: 'FailUser', email: 'fail@example.com' };
65
+ const payload = { id: '', name: 'FailUser', email: 'fail@example.com', password: 'password123' };
47
66
  const error = new Error('DB Connection Refused');
48
67
  (UserModel.create as jest.Mock).mockRejectedValue(error);
49
68
 
@@ -53,6 +72,49 @@ describe('UserRepository', () => {
53
72
  });
54
73
  });
55
74
 
75
+ describe('findById', () => {
76
+ it('should find and return a user by id (Happy Path)', async () => {
77
+ // Arrange
78
+ const id = '1';
79
+ <%_ if (database === 'MongoDB') { -%>
80
+ const mockDbRecord = { _id: { toString: () => '1' }, name: 'TestUser', email: 'test@example.com' };
81
+ (UserModel.findById as jest.Mock).mockResolvedValue(mockDbRecord);
82
+ <%_ } else if (database === 'None') { -%>
83
+ const mockDbRecord = { id: '1', name: 'TestUser', email: 'test@example.com' };
84
+ (UserModel.findByPk as jest.Mock).mockResolvedValue(mockDbRecord);
85
+ <%_ } else { -%>
86
+ const mockDbRecord = { id: '1', name: 'TestUser', email: 'test@example.com' };
87
+ (UserModel.findByPk as jest.Mock).mockResolvedValue(mockDbRecord);
88
+ <%_ } -%>
89
+
90
+ // Act
91
+ const result = await userRepository.findById(id);
92
+
93
+ // Assert
94
+ expect(result).toEqual({ id: '1', name: 'TestUser', email: 'test@example.com' });
95
+ <%_ if (database === 'MongoDB') { -%>
96
+ expect(UserModel.findById).toHaveBeenCalledWith(id);
97
+ <%_ } else { -%>
98
+ expect(UserModel.findByPk).toHaveBeenCalledWith(id);
99
+ <%_ } -%>
100
+ });
101
+
102
+ it('should return null if user not found', async () => {
103
+ // Arrange
104
+ <%_ if (database === 'MongoDB') { -%>
105
+ (UserModel.findById as jest.Mock).mockResolvedValue(null);
106
+ <%_ } else { -%>
107
+ (UserModel.findByPk as jest.Mock).mockResolvedValue(null);
108
+ <%_ } -%>
109
+
110
+ // Act
111
+ const result = await userRepository.findById('999');
112
+
113
+ // Assert
114
+ expect(result).toBeNull();
115
+ });
116
+ });
117
+
56
118
  describe('getUsers', () => {
57
119
  it('should return a list of mapped UserEntities (Happy Path)', async () => {
58
120
  // Arrange
@@ -88,17 +150,16 @@ describe('UserRepository', () => {
88
150
  // Arrange
89
151
  const id = '1';
90
152
  const data = { name: 'Updated' };
91
- const expectedUser = { id: '1', name: 'Updated', email: 'test@example.com' };
92
-
93
153
  <%_ if (database === 'MongoDB') { -%>
94
154
  const mockDbRecord = { _id: { toString: () => '1' }, name: 'Updated', email: 'test@example.com' };
95
155
  (UserModel.findByIdAndUpdate as jest.Mock).mockResolvedValue(mockDbRecord);
96
- <%_ } else if (database === 'None') { -%>
97
- (UserModel.update as jest.Mock).mockResolvedValue(expectedUser);
98
- <%_ } else { -%>
156
+ <% } else if (database === 'None') { -%>
157
+ const mockDbRecord = { id: '1', name: 'Updated', email: 'test@example.com' };
158
+ (UserModel.update as jest.Mock).mockResolvedValue(mockDbRecord);
159
+ <% } else { -%>
99
160
  const mockDbRecord = { id: '1', name: 'Updated', email: 'test@example.com', update: jest.fn().mockResolvedValue(true) };
100
161
  (UserModel.findByPk as jest.Mock).mockResolvedValue(mockDbRecord);
101
- <%_ } -%>
162
+ <% } -%>
102
163
 
103
164
  // Act
104
165
  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';
@@ -10,12 +12,26 @@ import UpdateUser from '@/usecases/updateUser';
10
12
  import DeleteUser from '@/usecases/deleteUser';
11
13
 
12
14
  // Mock dependencies
13
- jest.mock('@/infrastructure/repositories/UserRepository');
15
+ jest.mock('@/infrastructure/repositories/UserRepository', () => ({
16
+ UserRepository: jest.fn().mockImplementation(() => ({
17
+ save: jest.fn(),
18
+ findById: jest.fn(),
19
+ getUsers: jest.fn(),
20
+ update: jest.fn(),
21
+ delete: jest.fn()
22
+ }))
23
+ }));
14
24
  jest.mock('@/usecases/createUser');
15
25
  jest.mock('@/usecases/getAllUsers');
16
26
  jest.mock('@/usecases/updateUser');
17
27
  jest.mock('@/usecases/deleteUser');
18
- jest.mock('@/infrastructure/log/logger');
28
+ jest.mock('@/usecases/getUserById');
29
+ jest.mock('@/infrastructure/log/logger', () => ({
30
+ info: jest.fn(),
31
+ error: jest.fn(),
32
+ warn: jest.fn(),
33
+ debug: jest.fn()
34
+ }));
19
35
  <%_ if (communication === 'Kafka') { -%>
20
36
  jest.mock('@/infrastructure/messaging/kafkaClient', () => ({
21
37
  kafkaService: {
@@ -43,7 +59,7 @@ describe('UserController (Clean Architecture)', () => {
43
59
 
44
60
  beforeEach(() => {
45
61
  // Clear all mocks
46
- jest.resetAllMocks();
62
+ jest.clearAllMocks();
47
63
 
48
64
  userController = new UserController();
49
65
 
@@ -118,7 +134,7 @@ describe('UserController (Clean Architecture)', () => {
118
134
  describe('createUser', () => {
119
135
  it('should successfully create a new user (Happy Path)', async () => {
120
136
  // Arrange
121
- const payload = { name: 'Alice', email: 'alice@example.com' };
137
+ const payload = { name: 'Alice', email: 'alice@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
122
138
  <% if (communication === 'GraphQL') { -%>
123
139
  const dataArg = payload;
124
140
  <% } else { -%>
@@ -146,13 +162,31 @@ describe('UserController (Clean Architecture)', () => {
146
162
 
147
163
  expect(mockResponse.json).toHaveBeenCalledWith(expectedUser);
148
164
  <% } -%>
149
- expect(mockCreateUserUseCase.execute).toHaveBeenCalledWith(payload.name, payload.email);
165
+ <% if (auth.includes('JWT')) { -%>
166
+ expect(mockCreateUserUseCase.execute).toHaveBeenCalledWith(payload.name, payload.email, payload.password);
167
+ <% } else { -%>
168
+ expect(mockCreateUserUseCase.execute).toHaveBeenCalledWith(payload.name, payload.email, undefined);
169
+ <% } -%>
170
+ });
171
+
172
+ <% if (auth.includes('JWT')) { %>
173
+ it('should fail to create a user when password is missing and JWT is enabled', async () => {
174
+ const payload = { name: 'Alice', email: 'alice@example.com' };
175
+ <% if (communication === 'GraphQL') { -%>
176
+ await expect(userController.createUser(payload)).rejects.toThrow('Password is required');
177
+ <% } else { -%>
178
+ mockRequest.body = payload;
179
+ await userController.createUser(mockRequest as Request, mockResponse as Response, mockNext);
180
+ expect(mockResponse.status).toHaveBeenCalledWith(HTTP_STATUS.BAD_REQUEST);
181
+ expect(mockResponse.json).toHaveBeenCalledWith({ error: 'Password is required' });
182
+ <% } -%>
150
183
  });
184
+ <% } %>
151
185
 
152
186
  it('should handle errors when creation fails (Error Handling)', async () => {
153
187
  // Arrange
154
188
  const error = new Error('Creation Error');
155
- const payload = { name: 'Bob', email: 'bob@example.com' };
189
+ const payload = { name: 'Bob', email: 'bob@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
156
190
  <% if (communication === 'GraphQL') { -%>
157
191
  const dataArg = payload;
158
192
  <% } else { -%>
@@ -173,7 +207,7 @@ describe('UserController (Clean Architecture)', () => {
173
207
  it('should handle non-Error objects in catch block when creation fails', async () => {
174
208
  // Arrange
175
209
  const error = 'Creation String Error';
176
- const payload = { name: 'Bob', email: 'bob@example.com' };
210
+ const payload = { name: 'Bob', email: 'bob@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
177
211
  <% if (communication === 'GraphQL') { -%>
178
212
  const dataArg = payload;
179
213
  <% } else { -%>
@@ -320,9 +354,9 @@ describe('UserController (Clean Architecture)', () => {
320
354
  const error = new Error('Database Error');
321
355
  mockCreateUserUseCase.execute.mockRejectedValue(error);
322
356
  <%_ if (communication === 'GraphQL') { -%>
323
- await expect(userController.createUser({ name: 'Alice', email: 'alice@example.com' })).rejects.toThrow(error);
357
+ await expect(userController.createUser({ name: 'Alice', email: 'alice@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> })).rejects.toThrow(error);
324
358
  <%_ } else { -%>
325
- mockRequest.body = { name: 'Alice', email: 'alice@example.com' };
359
+ mockRequest.body = { name: 'Alice', email: 'alice@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
326
360
  await userController.createUser(mockRequest as Request, mockResponse as Response, mockNext);
327
361
  expect(mockNext).toHaveBeenCalledWith(error);
328
362
  <%_ } -%>
@@ -6,17 +6,18 @@ import { HTTP_STATUS } from '@/utils/httpCodes';
6
6
  import { UserRepository } from '@/infrastructure/repositories/UserRepository';
7
7
  import CreateUser from '@/usecases/createUser';
8
8
  import GetAllUsers from '@/usecases/getAllUsers';
9
+ import GetUserById from '@/usecases/getUserById';
9
10
  import UpdateUser from '@/usecases/updateUser';
10
11
  import DeleteUser from '@/usecases/deleteUser';
11
12
  import logger from '@/infrastructure/log/logger';
12
13
  <%_ if (communication === 'Kafka') { -%>
13
14
  import { kafkaService } from '@/infrastructure/messaging/kafkaClient';
14
15
  import { KAFKA_ACTIONS } from '@/utils/kafkaEvents';
15
- <%_ } -%>
16
-
16
+ <%_ } %>
17
17
  export class UserController {
18
18
  private createUserUseCase: CreateUser;
19
19
  private getAllUsersUseCase: GetAllUsers;
20
+ private getUserByIdUseCase: GetUserById;
20
21
  private updateUserUseCase: UpdateUser;
21
22
  private deleteUserUseCase: DeleteUser;
22
23
 
@@ -24,24 +25,31 @@ export class UserController {
24
25
  const userRepository = new UserRepository();
25
26
  this.createUserUseCase = new CreateUser(userRepository);
26
27
  this.getAllUsersUseCase = new GetAllUsers(userRepository);
28
+ this.getUserByIdUseCase = new GetUserById(userRepository);
27
29
  this.updateUserUseCase = new UpdateUser(userRepository);
28
30
  this.deleteUserUseCase = new DeleteUser(userRepository);
29
31
  }
30
32
 
31
33
  <% if (communication === 'GraphQL') { -%>
32
- async createUser(data: { name: string, email: string }) {
34
+ async createUser(data: { name: string, email: string, password?: string }) {
33
35
  try {
34
- const { name, email } = data;
35
- const user = await this.createUserUseCase.execute(name, email);
36
+ const { name, email, password } = data;
37
+ <% if (auth.includes('JWT')) { -%>
38
+ if (!password) {
39
+ throw new Error('Password is required');
40
+ }
41
+ <% } -%>
42
+ const user = await this.createUserUseCase.execute(name, email, password);
36
43
  <%_ if (communication === 'Kafka') { -%>
37
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
- const userId = (user as any).id || (user as any)._id;
44
+ const rawUserForKafka = user as unknown as { id?: string | number; _id?: string | number; email: string };
45
+ const userId = rawUserForKafka.id || rawUserForKafka._id;
39
46
  await kafkaService.sendMessage('user-topic', JSON.stringify({
40
47
  action: KAFKA_ACTIONS.USER_CREATED,
41
- payload: { id: userId, email: user.email }
48
+ payload: { id: userId, email: rawUserForKafka.email }
42
49
  }), userId?.toString() || '');
43
50
  <%_ } -%>
44
- return user;
51
+ const rawUser = user as unknown as { toJSON?: () => Record<string, unknown> };
52
+ return typeof rawUser.toJSON === 'function' ? rawUser.toJSON() : (user as unknown as Record<string, unknown>);
45
53
  } catch (error: unknown) {
46
54
  const message = error instanceof Error ? error.message : 'Unknown error';
47
55
  logger.error(`${ERROR_MESSAGES.CREATE_USER_ERROR}:`, message);
@@ -49,6 +57,18 @@ export class UserController {
49
57
  }
50
58
  }
51
59
 
60
+ async getUserById(id: string) {
61
+ try {
62
+ const user = await this.getUserByIdUseCase.execute(id);
63
+ if (!user) throw new Error(ERROR_MESSAGES.USER_NOT_FOUND);
64
+ return user;
65
+ } catch (error: unknown) {
66
+ const message = error instanceof Error ? error.message : 'Unknown error';
67
+ logger.error(`${ERROR_MESSAGES.FETCH_USER_ERROR}:`, message);
68
+ throw error;
69
+ }
70
+ }
71
+
52
72
  async getUsers() {
53
73
  try {
54
74
  const users = await this.getAllUsersUseCase.execute();
@@ -98,17 +118,24 @@ export class UserController {
98
118
  <% } else { -%>
99
119
  async createUser(req: Request, res: Response, next: NextFunction) {
100
120
  try {
101
- const { name, email } = req.body || {};
102
- const user = await this.createUserUseCase.execute(name, email);
121
+ const { name, email, password } = req.body || {};
122
+ <% if (auth.includes('JWT')) { -%>
123
+ if (!password) {
124
+ return res.status(HTTP_STATUS.BAD_REQUEST).json({ error: 'Password is required' });
125
+ }
126
+ <% } -%>
127
+ const user = await this.createUserUseCase.execute(name, email, password);
103
128
  <%_ if (communication === 'Kafka') { -%>
104
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
- const userId = (user as any).id || (user as any)._id;
129
+ const rawUserForKafka = user as unknown as { id?: string | number; _id?: string | number; email: string };
130
+ const userId = rawUserForKafka.id || rawUserForKafka._id;
106
131
  await kafkaService.sendMessage('user-topic', JSON.stringify({
107
132
  action: KAFKA_ACTIONS.USER_CREATED,
108
- payload: { id: userId, email: user.email }
133
+ payload: { id: userId, email: rawUserForKafka.email }
109
134
  }), userId?.toString() || '');
110
135
  <%_ } -%>
111
- res.status(HTTP_STATUS.CREATED).json(user);
136
+ const rawUser = user as unknown as { toJSON?: () => Record<string, unknown> };
137
+ const userJson = typeof rawUser.toJSON === 'function' ? rawUser.toJSON() : (user as unknown as Record<string, unknown>);
138
+ res.status(HTTP_STATUS.CREATED).json(userJson);
112
139
  } catch (error: unknown) {
113
140
  const message = error instanceof Error ? error.message : 'Unknown error';
114
141
  logger.error(`${ERROR_MESSAGES.CREATE_USER_ERROR}:`, message);
@@ -116,6 +143,21 @@ export class UserController {
116
143
  }
117
144
  }
118
145
 
146
+ async getUserById(req: Request, res: Response, next: NextFunction) {
147
+ try {
148
+ const { id } = req.params;
149
+ const user = await this.getUserByIdUseCase.execute(id);
150
+ if (!user) {
151
+ return res.status(HTTP_STATUS.NOT_FOUND).json({ error: ERROR_MESSAGES.USER_NOT_FOUND });
152
+ }
153
+ res.json(user);
154
+ } catch (error: unknown) {
155
+ const message = error instanceof Error ? error.message : 'Unknown error';
156
+ logger.error(`${ERROR_MESSAGES.FETCH_USER_ERROR}:`, message);
157
+ next(error);
158
+ }
159
+ }
160
+
119
161
  async getUsers(req: Request, res: Response, next: NextFunction) {
120
162
  try {
121
163
  const users = await this.getAllUsersUseCase.execute();
@@ -1,32 +1,65 @@
1
- import { gqlContext } from '@/interfaces/graphql/context';
2
1
  import { Request } from 'express';
3
- import { resolvers } from '@/interfaces/graphql/resolvers';
4
- import { typeDefs } from '@/interfaces/graphql/typeDefs';
2
+ <% if (auth.includes('JWT')) { %>
3
+ import { JwtService } from '@/infrastructure/auth/jwtService';
4
+ jest.mock('@/infrastructure/auth/jwtService', () => ({
5
+ JwtService: {
6
+ generateToken: jest.fn(),
7
+ verifyToken: jest.fn(),
8
+ generateRefreshToken: jest.fn(),
9
+ verifyRefreshToken: jest.fn(),
10
+ decodeToken: jest.fn(),
11
+ }
12
+ }));
13
+ <% } %>
14
+ import { gqlContext } from '@/interfaces/graphql/context';
5
15
 
6
- jest.mock('@/infrastructure/repositories/UserRepository');
16
+ jest.mock('@/infrastructure/repositories/UserRepository', () => ({
17
+ UserRepository: jest.fn().mockImplementation(() => ({
18
+ save: jest.fn(),
19
+ findById: jest.fn(),
20
+ getUsers: jest.fn(),
21
+ update: jest.fn(),
22
+ delete: jest.fn()
23
+ }))
24
+ }));
7
25
 
8
26
  describe('GraphQL Context', () => {
9
- it('should exercise GraphQL index entry points', () => {
10
- expect(resolvers).toBeDefined();
11
- expect(typeDefs).toBeDefined();
12
- });
13
- it('should return context with token when authorization header is present', async () => {
14
- const mockRequest = {
15
- headers: {
16
- authorization: 'Bearer token123',
17
- },
18
- } as Request;
27
+ afterEach(() => {
28
+ jest.clearAllMocks();
29
+ });
30
+
31
+ it('should return context with user when authorization header is present and valid', async () => {
32
+ <% if (auth.includes('JWT')) { %>
33
+ const mockUser = { id: '1', email: 'test@test.com' };
34
+ (JwtService.verifyToken as jest.Mock).mockReturnValue(mockUser);
35
+ const mockRequest = {
36
+ headers: {
37
+ authorization: 'Bearer valid-token',
38
+ },
39
+ } as Request;
19
40
 
20
- const context = await gqlContext({ req: mockRequest });
21
- expect(context.token).toBe('Bearer token123');
22
- });
41
+ const context = await gqlContext({ req: mockRequest });
42
+ expect(context.user).toEqual(mockUser);
43
+ expect(JwtService.verifyToken).toHaveBeenCalledWith('valid-token');
44
+ <% } else { %>
45
+ const mockRequest = {
46
+ headers: {
47
+ authorization: 'Bearer token123',
48
+ },
49
+ } as Request;
50
+ const context = await gqlContext({ req: mockRequest });
51
+ expect(context.user).toBeUndefined();
52
+ <% } %>
53
+ expect(context.userRepository).toBeDefined();
54
+ });
23
55
 
24
- it('should return context with empty token when authorization header is missing', async () => {
25
- const mockRequest = {
26
- headers: {},
27
- } as Request;
56
+ it('should return context without user when authorization header is missing', async () => {
57
+ const mockRequest = {
58
+ headers: {},
59
+ } as Request;
28
60
 
29
- const context = await gqlContext({ req: mockRequest });
30
- expect(context.token).toBe('');
31
- });
61
+ const context = await gqlContext({ req: mockRequest });
62
+ expect(context.user).toBeUndefined();
63
+ expect(context.userRepository).toBeDefined();
64
+ });
32
65
  });
@@ -1,17 +1,23 @@
1
1
  import { Request } from 'express';
2
2
  import { UserRepository } from '@/infrastructure/repositories/UserRepository';
3
+ <% if (auth.includes('JWT')) { %>import { JwtService } from '@/infrastructure/auth/jwtService';<% } %>
3
4
 
4
5
  export interface MyContext {
5
- token?: string;
6
6
  userRepository: UserRepository;
7
+ user?: unknown;
7
8
  }
8
9
 
9
- export const gqlContext = async ({ req }: { req: Request }): Promise<MyContext> => {
10
- const token = req.headers.authorization || '';
10
+ export const gqlContext = async ({ req: <% if (auth.includes('JWT')) { %>req<% } else { %>_req<% } %> }: { req: Request }): Promise<MyContext> => {
11
11
  const userRepository = new UserRepository();
12
-
13
- return {
14
- token,
15
- userRepository
16
- };
12
+ const context: MyContext = { userRepository };
13
+ <% if (auth.includes('JWT')) { %>
14
+ const authHeader = req.headers.authorization || '';
15
+ if (authHeader.startsWith('Bearer ')) {
16
+ const token = authHeader.split(' ')[1];
17
+ const user = JwtService.verifyToken(token);
18
+ if (user) {
19
+ context.user = user;
20
+ }
21
+ }<% } %>
22
+ return context;
17
23
  };
@@ -15,40 +15,63 @@ jest.mock('@/interfaces/controllers/userController', () => {
15
15
  });
16
16
 
17
17
  describe('User Resolvers', () => {
18
+ const mockContext = {
19
+ userRepository: {} as any,
20
+ <% if (auth.includes('JWT')) { %>user: { id: 'admin', email: 'admin@test.com' }<% } %>
21
+ };
22
+
18
23
  afterEach(() => {
19
24
  jest.clearAllMocks();
20
25
  });
21
26
 
22
27
  describe('Query.getAllUsers', () => {
23
- it('should return all users', async () => {
24
- const result = await userResolvers.Query.getAllUsers();
28
+ it('should return all users when authorized', async () => {
29
+ const result = await userResolvers.Query.getAllUsers(null, null, mockContext as any);
25
30
  expect(result).toEqual([{ id: '1', name: 'John Doe', email: 'john@example.com' }]);
26
31
  expect(mockGetUsers).toHaveBeenCalledTimes(1);
27
32
  });
33
+
34
+ <% if (auth.includes('JWT')) { %>
35
+ it('should throw error when unauthorized', async () => {
36
+ await expect(userResolvers.Query.getAllUsers(null, null, { userRepository: {} } as any)).rejects.toThrow('Unauthorized');
37
+ });
38
+ <% } %>
28
39
  });
29
40
 
30
41
  describe('Mutation.createUser', () => {
31
- it('should create and return a new user', async () => {
32
- const result = await userResolvers.Mutation.createUser(null, { name: 'Jane', email: 'jane@example.com' });
42
+ it('should create and return a new user (Public)', async () => {
43
+ const payload = { name: 'Jane', email: 'jane@example.com'<% if (auth.some(a => a !=='None')) { %>, password: 'password123'<% } %> };
44
+ const result = await userResolvers.Mutation.createUser(null, payload);
33
45
  expect(result).toEqual({ id: '1', name: 'Jane', email: 'jane@example.com' });
34
- expect(mockCreateUser).toHaveBeenCalledWith({ name: 'Jane', email: 'jane@example.com' });
35
- expect(mockCreateUser).toHaveBeenCalledTimes(1);
46
+ expect(mockCreateUser).toHaveBeenCalledWith(payload);
36
47
  });
37
48
  });
38
49
 
39
50
  describe('Mutation.updateUser', () => {
40
- it('should update and return the user', async () => {
51
+ it('should update and return the user when authorized', async () => {
41
52
  const payload = { name: 'Updated' };
42
- const result = await userResolvers.Mutation.updateUser(null, { id: '1', ...payload });
53
+ const result = await userResolvers.Mutation.updateUser(null, { id: '1', ...payload }, mockContext as any);
43
54
  expect(result).toMatchObject(payload);
44
55
  });
56
+
57
+ <% if (auth.includes('JWT')) { %>
58
+ it('should throw error when unauthorized', async () => {
59
+ await expect(userResolvers.Mutation.updateUser(null, { id: '1', name: 'N' }, { userRepository: {} } as any)).rejects.toThrow('Unauthorized');
60
+ });
61
+ <% } %>
45
62
  });
46
63
 
47
64
  describe('Mutation.deleteUser', () => {
48
- it('should delete and return true', async () => {
49
- const result = await userResolvers.Mutation.deleteUser(null, { id: '1' });
65
+ it('should delete and return true when authorized', async () => {
66
+ const result = await userResolvers.Mutation.deleteUser(null, { id: '1' }, mockContext as any);
50
67
  expect(result).toBe(true);
51
68
  });
69
+
70
+ <% if (auth.includes('JWT')) { %>
71
+ it('should throw error when unauthorized', async () => {
72
+ await expect(userResolvers.Mutation.deleteUser(null, { id: '1' }, { userRepository: {} } as any)).rejects.toThrow('Unauthorized');
73
+ });
74
+ <% } %>
52
75
  });
53
76
  <%_ if (database === 'MongoDB') { -%>
54
77
  describe('User.id', () => {
@@ -1,24 +1,34 @@
1
1
  import { UserController } from '@/interfaces/controllers/userController';
2
+ import { MyContext } from '@/interfaces/graphql/context';
2
3
 
3
4
  const userController = new UserController();
4
5
 
5
6
  export const userResolvers = {
6
7
  Query: {
7
- getAllUsers: async () => {
8
+ getAllUsers: async (_: unknown, __: unknown, <% if (auth.includes('JWT')) { %>context<% } else { %>_context<% } %>: MyContext) => {
9
+ <%_ if (auth.includes('JWT')) { -%>
10
+ if (!context.user) throw new Error('Unauthorized');
11
+ <% } %>
8
12
  const users = await userController.getUsers();
9
13
  return users;
10
14
  }
11
15
  },
12
16
  Mutation: {
13
- createUser: async (_: unknown, { name, email }: { name: string, email: string }) => {
14
- const user = await userController.createUser({ name, email });
17
+ createUser: async (_: unknown, { name, email<% if (auth.some(a => a !=='None')) { %>, password<% } %> }: { name: string, email: string<% if (auth.some(a => a !=='None')) { %>, password: string<% } %> }) => {
18
+ const user = await userController.createUser({ name, email<% if (auth.some(a => a !=='None')) { %>, password<% } %> });
15
19
  return user;
16
20
  },
17
- updateUser: async (_: unknown, { id, name, email }: { id: string, name?: string, email?: string }) => {
21
+ updateUser: async (_: unknown, { id, name, email }: { id: string, name?: string, email?: string }, <% if (auth.includes('JWT')) { %>context<% } else { %>_context<% } %>: MyContext) => {
22
+ <%_ if (auth.includes('JWT')) { -%>
23
+ if (!context.user) throw new Error('Unauthorized');
24
+ <% } %>
18
25
  const user = await userController.updateUser(id, { name, email });
19
26
  return user;
20
27
  },
21
- deleteUser: async (_: unknown, { id }: { id: string }) => {
28
+ deleteUser: async (_: unknown, { id }: { id: string }, <% if (auth.includes('JWT')) { %>context<% } else { %>_context<% } %>: MyContext) => {
29
+ <%_ if (auth.includes('JWT')) { -%>
30
+ if (!context.user) throw new Error('Unauthorized');
31
+ <% } %>
22
32
  const result = await userController.deleteUser(id);
23
33
  return result;
24
34
  }