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
@@ -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,47 @@
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')) { %>import { JwtService } from '@/infrastructure/auth/jwtService';
3
+ jest.mock('@/infrastructure/auth/jwtService');<% } %>
4
+ import { gqlContext } from '@/interfaces/graphql/context';
5
5
 
6
6
  jest.mock('@/infrastructure/repositories/UserRepository');
7
7
 
8
8
  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;
9
+ afterEach(() => {
10
+ jest.clearAllMocks();
11
+ });
12
+
13
+ it('should return context with user when authorization header is present and valid', async () => {
14
+ <% if (auth.includes('JWT')) { %>
15
+ const mockUser = { id: '1', email: 'test@test.com' };
16
+ (JwtService.verifyToken as jest.Mock).mockReturnValue(mockUser);
17
+ const mockRequest = {
18
+ headers: {
19
+ authorization: 'Bearer valid-token',
20
+ },
21
+ } as Request;
19
22
 
20
- const context = await gqlContext({ req: mockRequest });
21
- expect(context.token).toBe('Bearer token123');
22
- });
23
+ const context = await gqlContext({ req: mockRequest });
24
+ expect(context.user).toEqual(mockUser);
25
+ expect(JwtService.verifyToken).toHaveBeenCalledWith('valid-token');
26
+ <% } else { %>
27
+ const mockRequest = {
28
+ headers: {
29
+ authorization: 'Bearer token123',
30
+ },
31
+ } as Request;
32
+ const context = await gqlContext({ req: mockRequest });
33
+ expect(context.user).toBeUndefined();
34
+ <% } %>
35
+ expect(context.userRepository).toBeDefined();
36
+ });
23
37
 
24
- it('should return context with empty token when authorization header is missing', async () => {
25
- const mockRequest = {
26
- headers: {},
27
- } as Request;
38
+ it('should return context without user when authorization header is missing', async () => {
39
+ const mockRequest = {
40
+ headers: {},
41
+ } as Request;
28
42
 
29
- const context = await gqlContext({ req: mockRequest });
30
- expect(context.token).toBe('');
31
- });
43
+ const context = await gqlContext({ req: mockRequest });
44
+ expect(context.user).toBeUndefined();
45
+ expect(context.userRepository).toBeDefined();
46
+ });
32
47
  });
@@ -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
  }
@@ -10,7 +10,7 @@ export 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
  }
@@ -4,12 +4,20 @@ import router from '@/interfaces/routes/userRoutes';
4
4
 
5
5
  const mockGetUsers = jest.fn().mockImplementation((req, res) => res.status(200).json([{ id: '1', name: 'John Doe' }]));
6
6
  const mockCreateUser = jest.fn().mockImplementation((req, res) => res.status(201).json({ id: '1', name: 'Test' }));
7
+ const mockGetUserById = jest.fn().mockImplementation((req, res) => res.status(200).json({ id: '1', name: 'Test' }));
8
+
9
+ <% if (auth.includes('JWT')) { -%>
10
+ jest.mock('@/infrastructure/webserver/middleware/authMiddleware', () => ({
11
+ authMiddleware: (req: any, res: any, next: any) => next()
12
+ }));
13
+ <% } -%>
7
14
 
8
15
  jest.mock('@/interfaces/controllers/userController', () => {
9
16
  return {
10
17
  UserController: jest.fn().mockImplementation(() => ({
11
18
  getUsers: (...args: unknown[]) => mockGetUsers(...args),
12
- createUser: (...args: unknown[]) => mockCreateUser(...args)
19
+ createUser: (...args: unknown[]) => mockCreateUser(...args),
20
+ getUserById: (...args: unknown[]) => mockGetUserById(...args)
13
21
  }))
14
22
  };
15
23
  });
@@ -0,0 +1,16 @@
1
+ import { Router, Request, Response, NextFunction } from 'express';
2
+ import { UserController } from '@/interfaces/controllers/userController';
3
+ <%_ if (auth.includes('JWT')) { _%>
4
+ import { authMiddleware } from '@/infrastructure/webserver/middleware/authMiddleware';
5
+ <%_ } _%>
6
+
7
+ const router = Router();
8
+ const userController = new UserController();
9
+
10
+ router.post('/', (req: Request, res: Response, next: NextFunction) => userController.createUser(req, res, next));
11
+ router.get('/', <% if (auth.includes('JWT')) { %>authMiddleware, <% } %>(req: Request, res: Response, next: NextFunction) => userController.getUsers(req, res, next));
12
+ router.get('/:id', <% if (auth.includes('JWT')) { %>authMiddleware, <% } %>(req: Request, res: Response, next: NextFunction) => userController.getUserById(req, res, next));
13
+ router.patch('/:id', <% if (auth.includes('JWT')) { %>authMiddleware, <% } %>(req: Request, res: Response, next: NextFunction) => userController.updateUser(req, res, next));
14
+ router.delete('/:id', <% if (auth.includes('JWT')) { %>authMiddleware, <% } %>(req: Request, res: Response, next: NextFunction) => userController.deleteUser(req, res, next));
15
+
16
+ export default router;
@@ -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 as any);
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,35 @@
1
+ import { User } from '@/domain/user';
2
+ import { UserRepository } from '@/infrastructure/repositories/UserRepository';
3
+ <% if (auth.includes('JWT')) { -%>
4
+ import bcrypt from 'bcryptjs';
5
+ <% } -%>
6
+ <% if (caching !== 'None') { -%>
7
+ import cacheService from '<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>';
8
+ <% } -%>
9
+ <% if (caching !== 'None') { -%>
10
+ import logger from '@/infrastructure/log/logger';
11
+ <% } -%>
12
+
13
+ export default class CreateUser {
14
+ constructor(private userRepository: UserRepository) {}
15
+
16
+ async execute(name: string, email: string, password?: string) {
17
+ let finalPassword = password;
18
+ <% if (auth.includes('JWT')) { -%>
19
+ if (password) {
20
+ finalPassword = await bcrypt.hash(password, 10);
21
+ }
22
+ <% } -%>
23
+ const user = new User(null, name, email, finalPassword);
24
+ const savedUser = await this.userRepository.save(user);
25
+ <% if (caching !== 'None') { -%>
26
+ try {
27
+ await cacheService.del('users:all');
28
+ } catch (error) {
29
+ logger.warn('Failed to clear cache after user creation:', error);
30
+ }
31
+ <% } -%>
32
+
33
+ return savedUser;
34
+ }
35
+ }
@@ -35,6 +35,7 @@ describe('DeleteUser UseCase', () => {
35
35
  expect(result).toEqual(expectedResult);
36
36
  <%_ if (caching !== 'None') { -%>
37
37
  expect(cacheService.del).toHaveBeenCalledWith('users:all');
38
+ expect(cacheService.del).toHaveBeenCalledWith(`user:${id}`);
38
39
  <%_ } %>
39
40
  });
40
41
 
@@ -0,0 +1,24 @@
1
+ import { UserRepository } from '@/infrastructure/repositories/UserRepository';
2
+ <%_ if (caching !== 'None') { _%>
3
+ import cacheService from '<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>';
4
+ import logger from '@/infrastructure/log/logger';
5
+ <%_ } _%>
6
+
7
+ export default class DeleteUser {
8
+ constructor(private userRepository: UserRepository) {}
9
+
10
+ async execute(id: number | string) {
11
+ const result = await this.userRepository.delete(id);
12
+ <%_ if (caching !== 'None') { -%>
13
+ if (result) {
14
+ try {
15
+ await cacheService.del('users:all');
16
+ await cacheService.del(`user:${id}`);
17
+ } catch (error) {
18
+ logger.warn('Failed to clear cache after user deletion:', error);
19
+ }
20
+ }<%_ } -%>
21
+
22
+ return result;
23
+ }
24
+ }
@@ -0,0 +1,21 @@
1
+ import { UserRepository } from '@/infrastructure/repositories/UserRepository';
2
+ <%_ if (caching !== 'None') { _%>
3
+ import cacheService from '<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>';
4
+ <%_ } _%>
5
+
6
+ export default class GetAllUsers {
7
+ constructor(private userRepository: UserRepository) {}
8
+
9
+ async execute() {
10
+ <%_ if (caching !== 'None') { -%>
11
+ const cachedUsers = await cacheService.get('users:all');
12
+ if (cachedUsers) return cachedUsers;
13
+ <%_ } -%>
14
+ const users = await this.userRepository.getUsers();
15
+ <%_ if (caching !== 'None') { -%>
16
+ await cacheService.set('users:all', users, 3600); // Cache for 1 hour
17
+ <%_ } -%>
18
+
19
+ return users;
20
+ }
21
+ }
@@ -0,0 +1,47 @@
1
+ import GetUserById from '@/usecases/getUserById';
2
+ import { UserRepository } from '@/infrastructure/repositories/UserRepository';
3
+
4
+ jest.mock('@/infrastructure/repositories/UserRepository');
5
+
6
+ <% if (caching === 'Redis') { -%>
7
+ jest.mock('@/infrastructure/caching/redisClient', () => ({
8
+ get: jest.fn(),
9
+ set: jest.fn(),
10
+ del: jest.fn()
11
+ }));
12
+ <% } else if (caching === 'Memory Cache') { -%>
13
+ jest.mock('@/infrastructure/caching/memoryCache', () => ({
14
+ get: jest.fn(),
15
+ set: jest.fn(),
16
+ del: jest.fn()
17
+ }));
18
+ <% } -%>
19
+
20
+ describe('GetUserById Use Case', () => {
21
+ let getUserById: GetUserById;
22
+ let userRepository: jest.Mocked<UserRepository>;
23
+
24
+ beforeEach(() => {
25
+ userRepository = new UserRepository() as jest.Mocked<UserRepository>;
26
+ getUserById = new GetUserById(userRepository);
27
+ });
28
+
29
+ it('should return a user if found', async () => {
30
+ const mockUser = { id: '1', name: 'John Doe', email: 'john@example.com' };
31
+ userRepository.findById.mockResolvedValue(mockUser);
32
+
33
+ const result = await getUserById.execute('1');
34
+
35
+ expect(result).toEqual(mockUser);
36
+ expect(userRepository.findById).toHaveBeenCalledWith('1');
37
+ });
38
+
39
+ it('should return null if user is not found', async () => {
40
+ userRepository.findById.mockResolvedValue(null);
41
+
42
+ const result = await getUserById.execute('999');
43
+
44
+ expect(result).toBeNull();
45
+ expect(userRepository.findById).toHaveBeenCalledWith('999');
46
+ });
47
+ });
@@ -0,0 +1,23 @@
1
+ import { UserRepository } from '@/infrastructure/repositories/UserRepository';
2
+ <%_ if (caching !== 'None') { _%>
3
+ import cacheService from '<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>';
4
+ <%_ } _%>
5
+
6
+ export default class GetUserById {
7
+ constructor(private userRepository: UserRepository) {}
8
+
9
+ async execute(id: string | number) {
10
+ <%_ if (caching !== 'None') { -%>
11
+ const cacheKey = `user:${id}`;
12
+ const cachedUser = await cacheService.get(cacheKey);
13
+ if (cachedUser) return cachedUser;
14
+ <%_ } -%>
15
+ const user = await this.userRepository.findById(id);
16
+ <%_ if (caching !== 'None') { -%>
17
+ if (user) {
18
+ await cacheService.set(`user:${id}`, user, 3600); // Cache for 1 hour
19
+ }<%_ } -%>
20
+
21
+ return user;
22
+ }
23
+ }
@@ -36,6 +36,7 @@ describe('UpdateUser UseCase', () => {
36
36
  expect(result).toEqual(expectedUser);
37
37
  <%_ if (caching !== 'None') { -%>
38
38
  expect(cacheService.del).toHaveBeenCalledWith('users:all');
39
+ expect(cacheService.del).toHaveBeenCalledWith(`user:${id}`);
39
40
  <%_ } %>
40
41
  });
41
42
 
@@ -0,0 +1,25 @@
1
+ import { User } from '@/domain/user';
2
+ import { UserRepository } from '@/infrastructure/repositories/UserRepository';
3
+ <%_ if (caching !== 'None') { _%>
4
+ import cacheService from '<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>';
5
+ import logger from '@/infrastructure/log/logger';
6
+ <%_ } _%>
7
+
8
+ export default class UpdateUser {
9
+ constructor(private userRepository: UserRepository) {}
10
+
11
+ async execute(id: number | string, data: Partial<User>) {
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
+ } catch (error) {
19
+ logger.warn('Failed to clear cache after user update:', error);
20
+ }
21
+ }<%_ } -%>
22
+
23
+ return user;
24
+ }
25
+ }
@@ -5,6 +5,7 @@ export 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,7 @@ export const HTTP_STATUS = {
2
2
  OK: 200,
3
3
  CREATED: 201,
4
4
  BAD_REQUEST: 400,
5
+ UNAUTHORIZED: 401,
5
6
  NOT_FOUND: 404,
6
7
  INTERNAL_SERVER_ERROR: 500
7
8
  } as const;