nodejs-quickstart-structure 1.18.1 → 1.19.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 (235) hide show
  1. package/CHANGELOG.md +309 -294
  2. package/LICENSE +15 -15
  3. package/README.md +2 -1
  4. package/lib/generator.js +139 -139
  5. package/lib/modules/app-setup.js +401 -401
  6. package/lib/modules/caching-setup.js +76 -73
  7. package/lib/modules/config-files.js +151 -151
  8. package/lib/modules/database-setup.js +116 -116
  9. package/lib/modules/kafka-setup.js +249 -191
  10. package/lib/modules/project-setup.js +32 -31
  11. package/lib/prompts.js +100 -100
  12. package/package.json +78 -67
  13. package/templates/clean-architecture/js/src/domain/models/User.js +9 -9
  14. package/templates/clean-architecture/js/src/errors/ApiError.js +14 -14
  15. package/templates/clean-architecture/js/src/errors/BadRequestError.js +11 -10
  16. package/templates/clean-architecture/js/src/errors/BadRequestError.spec.js.ejs +22 -21
  17. package/templates/clean-architecture/js/src/errors/NotFoundError.js +11 -10
  18. package/templates/clean-architecture/js/src/errors/NotFoundError.spec.js.ejs +22 -21
  19. package/templates/clean-architecture/js/src/index.js.ejs +55 -55
  20. package/templates/clean-architecture/js/src/infrastructure/config/env.js.ejs +47 -47
  21. package/templates/clean-architecture/js/src/infrastructure/log/logger.js +36 -36
  22. package/templates/clean-architecture/js/src/infrastructure/log/logger.spec.js.ejs +63 -63
  23. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +69 -39
  24. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +142 -81
  25. package/templates/clean-architecture/js/src/infrastructure/webserver/middleware/errorMiddleware.js +30 -30
  26. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +89 -89
  27. package/templates/clean-architecture/js/src/infrastructure/webserver/swagger.js.ejs +6 -6
  28. package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +156 -75
  29. package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +234 -138
  30. package/templates/clean-architecture/js/src/interfaces/graphql/context.js.ejs +13 -13
  31. package/templates/clean-architecture/js/src/interfaces/graphql/context.spec.js.ejs +31 -31
  32. package/templates/clean-architecture/js/src/interfaces/graphql/index.js.ejs +5 -5
  33. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/index.js.ejs +6 -6
  34. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +27 -21
  35. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +66 -49
  36. package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/index.js.ejs +6 -6
  37. package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/user.types.js.ejs +19 -17
  38. package/templates/clean-architecture/js/src/interfaces/routes/api.js +12 -10
  39. package/templates/clean-architecture/js/src/interfaces/routes/api.spec.js.ejs +38 -38
  40. package/templates/clean-architecture/js/src/usecases/CreateUser.js +14 -14
  41. package/templates/clean-architecture/js/src/usecases/CreateUser.spec.js.ejs +51 -51
  42. package/templates/clean-architecture/js/src/usecases/DeleteUser.js +11 -0
  43. package/templates/clean-architecture/js/src/usecases/DeleteUser.spec.js.ejs +47 -0
  44. package/templates/clean-architecture/js/src/usecases/GetAllUsers.js +12 -12
  45. package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +61 -61
  46. package/templates/clean-architecture/js/src/usecases/UpdateUser.js +11 -0
  47. package/templates/clean-architecture/js/src/usecases/UpdateUser.spec.js.ejs +48 -0
  48. package/templates/clean-architecture/js/src/utils/errorMessages.js +14 -0
  49. package/templates/clean-architecture/js/src/utils/httpCodes.js +9 -9
  50. package/templates/clean-architecture/ts/src/config/env.ts.ejs +46 -46
  51. package/templates/clean-architecture/ts/src/config/swagger.ts.ejs +6 -6
  52. package/templates/clean-architecture/ts/src/domain/user.ts +7 -7
  53. package/templates/clean-architecture/ts/src/errors/ApiError.ts +15 -15
  54. package/templates/clean-architecture/ts/src/errors/BadRequestError.spec.ts.ejs +22 -21
  55. package/templates/clean-architecture/ts/src/errors/BadRequestError.ts +9 -8
  56. package/templates/clean-architecture/ts/src/errors/NotFoundError.spec.ts.ejs +22 -21
  57. package/templates/clean-architecture/ts/src/errors/NotFoundError.ts +9 -8
  58. package/templates/clean-architecture/ts/src/index.ts.ejs +139 -139
  59. package/templates/clean-architecture/ts/src/infrastructure/log/logger.spec.ts.ejs +63 -63
  60. package/templates/clean-architecture/ts/src/infrastructure/log/logger.ts +36 -36
  61. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +175 -85
  62. package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +74 -0
  63. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +331 -185
  64. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +173 -84
  65. package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +32 -32
  66. package/templates/clean-architecture/ts/src/interfaces/graphql/context.ts.ejs +17 -17
  67. package/templates/clean-architecture/ts/src/interfaces/graphql/index.ts.ejs +3 -3
  68. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/index.ts.ejs +4 -4
  69. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +68 -51
  70. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +29 -21
  71. package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/index.ts.ejs +4 -4
  72. package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/user.types.ts.ejs +17 -15
  73. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +40 -40
  74. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +13 -11
  75. package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +51 -51
  76. package/templates/clean-architecture/ts/src/usecases/createUser.ts +13 -13
  77. package/templates/clean-architecture/ts/src/usecases/deleteUser.spec.ts.ejs +47 -0
  78. package/templates/clean-architecture/ts/src/usecases/deleteUser.ts +9 -0
  79. package/templates/clean-architecture/ts/src/usecases/getAllUsers.spec.ts.ejs +63 -63
  80. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +10 -10
  81. package/templates/clean-architecture/ts/src/usecases/updateUser.spec.ts.ejs +48 -0
  82. package/templates/clean-architecture/ts/src/usecases/updateUser.ts +9 -0
  83. package/templates/clean-architecture/ts/src/utils/errorMessages.ts +12 -0
  84. package/templates/clean-architecture/ts/src/utils/errorMiddleware.ts.ejs +27 -27
  85. package/templates/clean-architecture/ts/src/utils/httpCodes.ts +7 -7
  86. package/templates/common/.cursorrules.ejs +60 -60
  87. package/templates/common/.dockerignore +12 -12
  88. package/templates/common/.env.example.ejs +41 -41
  89. package/templates/common/.gitattributes +46 -0
  90. package/templates/common/.gitlab-ci.yml.ejs +86 -86
  91. package/templates/common/.lintstagedrc +6 -6
  92. package/templates/common/.prettierrc +7 -7
  93. package/templates/common/Dockerfile +73 -73
  94. package/templates/common/Jenkinsfile.ejs +87 -87
  95. package/templates/common/README.md.ejs +294 -270
  96. package/templates/common/SECURITY.md +20 -20
  97. package/templates/common/_github/workflows/ci.yml.ejs +46 -46
  98. package/templates/common/_github/workflows/security.yml.ejs +36 -36
  99. package/templates/common/_gitignore +5 -5
  100. package/templates/common/_husky/pre-commit +4 -4
  101. package/templates/common/caching/clean/js/CreateUser.js.ejs +29 -29
  102. package/templates/common/caching/clean/js/DeleteUser.js.ejs +27 -0
  103. package/templates/common/caching/clean/js/GetAllUsers.js.ejs +37 -37
  104. package/templates/common/caching/clean/js/UpdateUser.js.ejs +27 -0
  105. package/templates/common/caching/clean/ts/createUser.ts.ejs +27 -27
  106. package/templates/common/caching/clean/ts/deleteUser.ts.ejs +24 -0
  107. package/templates/common/caching/clean/ts/getAllUsers.ts.ejs +34 -34
  108. package/templates/common/caching/clean/ts/updateUser.ts.ejs +25 -0
  109. package/templates/common/caching/js/memoryCache.js.ejs +60 -60
  110. package/templates/common/caching/js/memoryCache.spec.js.ejs +101 -101
  111. package/templates/common/caching/js/redisClient.js.ejs +75 -75
  112. package/templates/common/caching/js/redisClient.spec.js.ejs +147 -147
  113. package/templates/common/caching/ts/memoryCache.spec.ts.ejs +102 -102
  114. package/templates/common/caching/ts/memoryCache.ts.ejs +73 -64
  115. package/templates/common/caching/ts/redisClient.spec.ts.ejs +157 -157
  116. package/templates/common/caching/ts/redisClient.ts.ejs +89 -80
  117. package/templates/common/database/js/database.js.ejs +19 -19
  118. package/templates/common/database/js/database.spec.js.ejs +56 -56
  119. package/templates/common/database/js/models/User.js.ejs +79 -53
  120. package/templates/common/database/js/models/User.js.mongoose.ejs +23 -19
  121. package/templates/common/database/js/models/User.spec.js.ejs +94 -84
  122. package/templates/common/database/js/mongoose.js.ejs +33 -33
  123. package/templates/common/database/js/mongoose.spec.js.ejs +43 -43
  124. package/templates/common/database/ts/database.spec.ts.ejs +56 -56
  125. package/templates/common/database/ts/database.ts.ejs +21 -21
  126. package/templates/common/database/ts/models/User.spec.ts.ejs +100 -84
  127. package/templates/common/database/ts/models/User.ts.ejs +87 -61
  128. package/templates/common/database/ts/models/User.ts.mongoose.ejs +30 -25
  129. package/templates/common/database/ts/mongoose.spec.ts.ejs +42 -42
  130. package/templates/common/database/ts/mongoose.ts.ejs +28 -28
  131. package/templates/common/docker-compose.yml.ejs +159 -159
  132. package/templates/common/ecosystem.config.js.ejs +40 -40
  133. package/templates/common/eslint.config.mjs.ejs +77 -77
  134. package/templates/common/health/js/healthRoute.js.ejs +50 -47
  135. package/templates/common/health/js/healthRoute.spec.js.ejs +70 -70
  136. package/templates/common/health/ts/healthRoute.spec.ts.ejs +76 -76
  137. package/templates/common/health/ts/healthRoute.ts.ejs +49 -46
  138. package/templates/common/jest.config.js.ejs +32 -32
  139. package/templates/common/jest.e2e.config.js.ejs +8 -8
  140. package/templates/common/kafka/js/config/kafka.js +9 -9
  141. package/templates/common/kafka/js/config/kafka.spec.js.ejs +27 -27
  142. package/templates/common/kafka/js/messaging/baseConsumer.js.ejs +30 -30
  143. package/templates/common/kafka/js/messaging/baseConsumer.spec.js.ejs +58 -58
  144. package/templates/common/kafka/js/messaging/userEventSchema.js.ejs +12 -11
  145. package/templates/common/kafka/js/messaging/userEventSchema.spec.js.ejs +27 -27
  146. package/templates/common/kafka/js/messaging/welcomeEmailConsumer.js.ejs +44 -31
  147. package/templates/common/kafka/js/messaging/welcomeEmailConsumer.spec.js.ejs +86 -49
  148. package/templates/common/kafka/js/services/kafkaService.js.ejs +93 -93
  149. package/templates/common/kafka/js/services/kafkaService.spec.js.ejs +106 -106
  150. package/templates/common/kafka/js/utils/kafkaEvents.js.ejs +7 -0
  151. package/templates/common/kafka/ts/config/kafka.spec.ts.ejs +27 -27
  152. package/templates/common/kafka/ts/config/kafka.ts +7 -7
  153. package/templates/common/kafka/ts/messaging/baseConsumer.spec.ts.ejs +50 -50
  154. package/templates/common/kafka/ts/messaging/baseConsumer.ts.ejs +27 -27
  155. package/templates/common/kafka/ts/messaging/userEventSchema.spec.ts.ejs +51 -51
  156. package/templates/common/kafka/ts/messaging/userEventSchema.ts.ejs +12 -11
  157. package/templates/common/kafka/ts/messaging/welcomeEmailConsumer.spec.ts.ejs +86 -49
  158. package/templates/common/kafka/ts/messaging/welcomeEmailConsumer.ts.ejs +38 -25
  159. package/templates/common/kafka/ts/services/kafkaService.spec.ts.ejs +81 -81
  160. package/templates/common/kafka/ts/services/kafkaService.ts.ejs +95 -95
  161. package/templates/common/kafka/ts/utils/kafkaEvents.ts.ejs +5 -0
  162. package/templates/common/migrate-mongo-config.js.ejs +31 -31
  163. package/templates/common/migrations/init.js.ejs +23 -23
  164. package/templates/common/package.json.ejs +119 -118
  165. package/templates/common/prompts/add-feature.md.ejs +26 -26
  166. package/templates/common/prompts/project-context.md.ejs +43 -43
  167. package/templates/common/prompts/troubleshoot.md.ejs +28 -28
  168. package/templates/common/public/css/style.css +147 -147
  169. package/templates/common/scripts/run-e2e.js.ejs +63 -63
  170. package/templates/common/shutdown/js/gracefulShutdown.js.ejs +65 -61
  171. package/templates/common/shutdown/js/gracefulShutdown.spec.js.ejs +149 -160
  172. package/templates/common/shutdown/ts/gracefulShutdown.spec.ts.ejs +179 -158
  173. package/templates/common/shutdown/ts/gracefulShutdown.ts.ejs +59 -55
  174. package/templates/common/sonar-project.properties.ejs +27 -27
  175. package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +120 -49
  176. package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +120 -49
  177. package/templates/common/src/utils/errorMiddleware.spec.js.ejs +79 -79
  178. package/templates/common/src/utils/errorMiddleware.spec.ts.ejs +94 -94
  179. package/templates/common/swagger.yml.ejs +118 -66
  180. package/templates/common/tsconfig.json +22 -22
  181. package/templates/common/views/ejs/index.ejs +55 -55
  182. package/templates/common/views/pug/index.pug +40 -40
  183. package/templates/db/mysql/V1__Initial_Setup.sql.ejs +10 -9
  184. package/templates/db/postgres/V1__Initial_Setup.sql.ejs +10 -9
  185. package/templates/mvc/js/src/config/env.js.ejs +46 -46
  186. package/templates/mvc/js/src/config/swagger.js.ejs +6 -6
  187. package/templates/mvc/js/src/controllers/userController.js.ejs +246 -105
  188. package/templates/mvc/js/src/controllers/userController.spec.js.ejs +481 -209
  189. package/templates/mvc/js/src/errors/ApiError.js +14 -14
  190. package/templates/mvc/js/src/errors/BadRequestError.js +11 -10
  191. package/templates/mvc/js/src/errors/BadRequestError.spec.js.ejs +22 -21
  192. package/templates/mvc/js/src/errors/NotFoundError.js +11 -10
  193. package/templates/mvc/js/src/errors/NotFoundError.spec.js.ejs +22 -21
  194. package/templates/mvc/js/src/graphql/context.js.ejs +7 -7
  195. package/templates/mvc/js/src/graphql/context.spec.js.ejs +29 -29
  196. package/templates/mvc/js/src/graphql/index.js.ejs +5 -5
  197. package/templates/mvc/js/src/graphql/resolvers/index.js.ejs +6 -6
  198. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +25 -19
  199. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +64 -47
  200. package/templates/mvc/js/src/graphql/typeDefs/index.js.ejs +6 -6
  201. package/templates/mvc/js/src/graphql/typeDefs/user.types.js.ejs +19 -17
  202. package/templates/mvc/js/src/index.js.ejs +136 -136
  203. package/templates/mvc/js/src/routes/api.js +10 -8
  204. package/templates/mvc/js/src/routes/api.spec.js.ejs +41 -36
  205. package/templates/mvc/js/src/utils/errorMessages.js +14 -0
  206. package/templates/mvc/js/src/utils/errorMiddleware.js +29 -29
  207. package/templates/mvc/js/src/utils/httpCodes.js +9 -9
  208. package/templates/mvc/js/src/utils/logger.js +40 -40
  209. package/templates/mvc/js/src/utils/logger.spec.js.ejs +63 -63
  210. package/templates/mvc/ts/src/config/env.ts.ejs +45 -45
  211. package/templates/mvc/ts/src/config/swagger.ts.ejs +6 -6
  212. package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +481 -203
  213. package/templates/mvc/ts/src/controllers/userController.ts.ejs +248 -107
  214. package/templates/mvc/ts/src/errors/ApiError.ts +15 -15
  215. package/templates/mvc/ts/src/errors/BadRequestError.spec.ts.ejs +22 -21
  216. package/templates/mvc/ts/src/errors/BadRequestError.ts +9 -8
  217. package/templates/mvc/ts/src/errors/NotFoundError.spec.ts.ejs +27 -21
  218. package/templates/mvc/ts/src/errors/NotFoundError.ts +9 -8
  219. package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +30 -30
  220. package/templates/mvc/ts/src/graphql/context.ts.ejs +12 -12
  221. package/templates/mvc/ts/src/graphql/index.ts.ejs +3 -3
  222. package/templates/mvc/ts/src/graphql/resolvers/index.ts.ejs +4 -4
  223. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +68 -51
  224. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +29 -21
  225. package/templates/mvc/ts/src/graphql/typeDefs/index.ts.ejs +4 -4
  226. package/templates/mvc/ts/src/graphql/typeDefs/user.types.ts.ejs +17 -15
  227. package/templates/mvc/ts/src/index.ts.ejs +156 -153
  228. package/templates/mvc/ts/src/routes/api.spec.ts.ejs +59 -40
  229. package/templates/mvc/ts/src/routes/api.ts +12 -10
  230. package/templates/mvc/ts/src/utils/errorMessages.ts +12 -0
  231. package/templates/mvc/ts/src/utils/errorMiddleware.ts.ejs +27 -27
  232. package/templates/mvc/ts/src/utils/httpCodes.ts +7 -7
  233. package/templates/mvc/ts/src/utils/logger.spec.ts.ejs +63 -63
  234. package/templates/mvc/ts/src/utils/logger.ts +36 -36
  235. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.ts.ejs +0 -37
@@ -1,49 +1,86 @@
1
- import { WelcomeEmailConsumer } from '<% if (architecture === "Clean Architecture") { %>@/interfaces/messaging/consumers/instances/welcomeEmailConsumer<% } else { %>@/messaging/consumers/instances/welcomeEmailConsumer<% } %>';
2
- import logger from '<%= loggerPath %>';
3
-
4
- jest.mock('<%= loggerPath %>');
5
-
6
- describe('WelcomeEmailConsumer', () => {
7
- let consumer: WelcomeEmailConsumer;
8
-
9
- beforeEach(() => {
10
- jest.clearAllMocks();
11
- consumer = new WelcomeEmailConsumer();
12
- });
13
-
14
- it('should log welcome email simulation for USER_CREATED action', async () => {
15
- const data = {
16
- action: 'USER_CREATED',
17
- payload: {
18
- id: 1,
19
- email: 'test@example.com'
20
- }
21
- };
22
-
23
- await consumer.handle(data);
24
-
25
- expect(logger.info).toHaveBeenCalledWith(
26
- expect.stringContaining('[Kafka] Consumer: Received USER_CREATED.')
27
- );
28
- expect(logger.info).toHaveBeenCalledWith(
29
- expect.stringContaining('📧 Sending welcome email to \'test@example.com\'... Done!')
30
- );
31
- });
32
-
33
- it('should log error for invalid data', async () => {
34
- const data = {
35
- action: 'USER_CREATED',
36
- payload: {
37
- id: 1,
38
- email: 'invalid-email'
39
- }
40
- };
41
-
42
- await consumer.handle(data);
43
-
44
- expect(logger.error).toHaveBeenCalledWith(
45
- expect.stringContaining('[Kafka] Invalid user event data:'),
46
- expect.anything()
47
- );
48
- });
49
- });
1
+ import { WelcomeEmailConsumer } from '<% if (architecture === "Clean Architecture") { %>@/interfaces/messaging/consumers/instances/welcomeEmailConsumer<% } else { %>@/messaging/consumers/instances/welcomeEmailConsumer<% } %>';
2
+ import logger from '<%= loggerPath %>';
3
+
4
+ jest.mock('<%= loggerPath %>');
5
+
6
+ describe('WelcomeEmailConsumer', () => {
7
+ let consumer: WelcomeEmailConsumer;
8
+
9
+ beforeEach(() => {
10
+ jest.clearAllMocks();
11
+ consumer = new WelcomeEmailConsumer();
12
+ });
13
+
14
+ it('should log welcome email simulation for USER_CREATED action', async () => {
15
+ const data = {
16
+ action: 'USER_CREATED',
17
+ payload: {
18
+ id: 1,
19
+ email: 'test@example.com'
20
+ }
21
+ };
22
+
23
+ await consumer.handle(data);
24
+
25
+ expect(logger.info).toHaveBeenCalledWith(
26
+ expect.stringContaining('[Kafka] Consumer: Received USER_CREATED.')
27
+ );
28
+ expect(logger.info).toHaveBeenCalledWith(
29
+ expect.stringContaining('📧 Sending welcome email to \'test@example.com\'... Done!')
30
+ );
31
+ });
32
+
33
+ it('should log update simulation for USER_UPDATED action', async () => {
34
+ const data = {
35
+ action: 'USER_UPDATED',
36
+ payload: {
37
+ id: 1,
38
+ email: 'updated@example.com'
39
+ }
40
+ };
41
+
42
+ await consumer.handle(data);
43
+
44
+ expect(logger.info).toHaveBeenCalledWith(
45
+ expect.stringContaining('[Kafka] Consumer: Received USER_UPDATED.')
46
+ );
47
+ expect(logger.info).toHaveBeenCalledWith(
48
+ expect.stringContaining('🔄 Updating user records for \'1\' (Email: updated@example.com)... Done!')
49
+ );
50
+ });
51
+
52
+ it('should log deletion simulation for USER_DELETED action', async () => {
53
+ const data = {
54
+ action: 'USER_DELETED',
55
+ payload: {
56
+ id: 1
57
+ }
58
+ };
59
+
60
+ await consumer.handle(data);
61
+
62
+ expect(logger.info).toHaveBeenCalledWith(
63
+ expect.stringContaining('[Kafka] Consumer: Received USER_DELETED.')
64
+ );
65
+ expect(logger.info).toHaveBeenCalledWith(
66
+ expect.stringContaining('🗑️ Cleaning up data for user \'1\'... Done!')
67
+ );
68
+ });
69
+
70
+ it('should log error for invalid data', async () => {
71
+ const data = {
72
+ action: 'USER_CREATED',
73
+ payload: {
74
+ id: 1,
75
+ email: 'invalid-email'
76
+ }
77
+ };
78
+
79
+ await consumer.handle(data);
80
+
81
+ expect(logger.error).toHaveBeenCalledWith(
82
+ expect.stringContaining('[Kafka] Invalid user event data:'),
83
+ expect.anything()
84
+ );
85
+ });
86
+ });
@@ -1,25 +1,38 @@
1
- import { BaseConsumer } from '<% if (architecture === "Clean Architecture") { %>@/interfaces/messaging/baseConsumer<% } else { %>@/messaging/baseConsumer<% } %>';
2
- import logger from '<%= loggerPath %>';
3
- import { UserEventSchema } from '<% if (architecture === "Clean Architecture") { %>@/interfaces/messaging/schemas/userEventSchema<% } else { %>@/messaging/schemas/userEventSchema<% } %>';
4
-
5
- export class WelcomeEmailConsumer extends BaseConsumer {
6
- topic = 'user-topic';
7
- groupId = 'welcome-email-group';
8
-
9
- async handle(data: unknown) {
10
- const result = UserEventSchema.safeParse(data);
11
-
12
- if (!result.success) {
13
- logger.error('[Kafka] Invalid user event data:', result.error.format());
14
- return;
15
- }
16
-
17
- const { action, payload } = result.data;
18
-
19
- if (action === 'USER_CREATED') {
20
- logger.info(`[Kafka] Consumer: Received USER_CREATED.`);
21
- logger.info(`[Kafka] Consumer: 📧 Sending welcome email to '${payload.email}'... Done!`);
22
- // In a real app, you would call an EmailService here
23
- }
24
- }
25
- }
1
+ import { BaseConsumer } from '<% if (architecture === "Clean Architecture") { %>@/interfaces/messaging/baseConsumer<% } else { %>@/messaging/baseConsumer<% } %>';
2
+ import logger from '<%= loggerPath %>';
3
+ import { UserEventSchema } from '<% if (architecture === "Clean Architecture") { %>@/interfaces/messaging/schemas/userEventSchema<% } else { %>@/messaging/schemas/userEventSchema<% } %>';
4
+ import { ERROR_MESSAGES } from '@/utils/errorMessages';
5
+ import { KAFKA_ACTIONS } from '@/utils/kafkaEvents';
6
+
7
+ export class WelcomeEmailConsumer extends BaseConsumer {
8
+ topic = 'user-topic';
9
+ groupId = 'welcome-email-group';
10
+
11
+ async handle(data: unknown) {
12
+ const result = UserEventSchema.safeParse(data);
13
+
14
+ if (!result.success) {
15
+ logger.error(`[Kafka] ${ERROR_MESSAGES.INVALID_USER_DATA}:`, result.error.format());
16
+ return;
17
+ }
18
+
19
+ const { action, payload } = result.data;
20
+
21
+ switch (action) {
22
+ case KAFKA_ACTIONS.USER_CREATED:
23
+ logger.info(`[Kafka] Consumer: Received ${KAFKA_ACTIONS.USER_CREATED}.`);
24
+ logger.info(`[Kafka] Consumer: 📧 Sending welcome email to '${payload.email}'... Done!`);
25
+ break;
26
+ case KAFKA_ACTIONS.USER_UPDATED:
27
+ logger.info(`[Kafka] Consumer: Received ${KAFKA_ACTIONS.USER_UPDATED}.`);
28
+ logger.info(`[Kafka] Consumer: 🔄 Updating user records for '${payload.id}' (Email: ${payload.email})... Done!`);
29
+ break;
30
+ case KAFKA_ACTIONS.USER_DELETED:
31
+ logger.info(`[Kafka] Consumer: Received ${KAFKA_ACTIONS.USER_DELETED}.`);
32
+ logger.info(`[Kafka] Consumer: 🗑️ Cleaning up data for user '${payload.id}'... Done!`);
33
+ break;
34
+ default:
35
+ logger.warn(`[Kafka] Unknown action: ${action}`);
36
+ }
37
+ }
38
+ }
@@ -1,81 +1,81 @@
1
- import { KafkaService } from '<% if (architecture === "Clean Architecture") { %>@/infrastructure/messaging/kafkaClient<% } else { %>@/services/kafkaService<% } %>';
2
- import { kafka } from '<%= configPath %>';
3
-
4
- jest.mock('<%= configPath %>', () => ({
5
- kafka: {
6
- producer: jest.fn().mockReturnValue({
7
- connect: jest.fn().mockResolvedValue(undefined),
8
- send: jest.fn().mockResolvedValue(undefined),
9
- disconnect: jest.fn().mockResolvedValue(undefined),
10
- }),
11
- consumer: jest.fn().mockReturnValue({
12
- connect: jest.fn().mockResolvedValue(undefined),
13
- subscribe: jest.fn().mockResolvedValue(undefined),
14
- run: jest.fn().mockResolvedValue(undefined),
15
- disconnect: jest.fn().mockResolvedValue(undefined),
16
- }),
17
- },
18
- }));
19
-
20
- jest.mock('<%= loggerPath %>');
21
-
22
- describe('KafkaService', () => {
23
- let kafkaService: KafkaService;
24
-
25
- beforeEach(() => {
26
- jest.clearAllMocks();
27
- kafkaService = new KafkaService();
28
- });
29
-
30
- it('should connect producer and consumer', async () => {
31
- await kafkaService.connect();
32
- const producer = (kafka.producer as jest.Mock).mock.results[0].value;
33
- const consumer = (kafka.consumer as jest.Mock).mock.results[0].value;
34
-
35
- expect(producer.connect).toHaveBeenCalled();
36
- expect(consumer.connect).toHaveBeenCalled();
37
- expect(consumer.subscribe).toHaveBeenCalledWith(expect.objectContaining({ topic: 'user-topic', fromBeginning: true }));
38
- expect(consumer.run).toHaveBeenCalled();
39
- });
40
-
41
- it('should send a message', async () => {
42
- await kafkaService.connect();
43
- const topic = 'test-topic';
44
- const message = JSON.stringify({ action: 'TEST', payload: { email: 'test@example.com' } });
45
- await kafkaService.sendMessage(topic, message);
46
- const producer = (kafka.producer as jest.Mock).mock.results[0].value;
47
-
48
- expect(producer.send).toHaveBeenCalledWith({
49
- topic,
50
- messages: [{ value: message }],
51
- });
52
- });
53
-
54
- it('should retry connection on failure', async () => {
55
- const producer = (kafka.producer as jest.Mock).mock.results[0].value;
56
- producer.connect
57
- .mockRejectedValueOnce(new Error('Connection failed'))
58
- .mockResolvedValueOnce(undefined);
59
-
60
- jest.useFakeTimers();
61
- const connectPromise = kafkaService.connect(2);
62
-
63
- await jest.advanceTimersByTimeAsync(10000);
64
- await connectPromise;
65
-
66
- expect(producer.connect).toHaveBeenCalledTimes(2);
67
- });
68
-
69
- it('should throw error if producer not connected', async () => {
70
- await expect(kafkaService.sendMessage('topic', 'msg')).rejects.toThrow('[Kafka] Producer not connected');
71
- });
72
-
73
- it('should disconnect producer and consumer', async () => {
74
- await kafkaService.disconnect();
75
- const producer = (kafka.producer as jest.Mock).mock.results[0].value;
76
- const consumer = (kafka.consumer as jest.Mock).mock.results[0].value;
77
-
78
- expect(producer.disconnect).toHaveBeenCalled();
79
- expect(consumer.disconnect).toHaveBeenCalled();
80
- });
81
- });
1
+ import { KafkaService } from '<% if (architecture === "Clean Architecture") { %>@/infrastructure/messaging/kafkaClient<% } else { %>@/services/kafkaService<% } %>';
2
+ import { kafka } from '<%= configPath %>';
3
+
4
+ jest.mock('<%= configPath %>', () => ({
5
+ kafka: {
6
+ producer: jest.fn().mockReturnValue({
7
+ connect: jest.fn().mockResolvedValue(undefined),
8
+ send: jest.fn().mockResolvedValue(undefined),
9
+ disconnect: jest.fn().mockResolvedValue(undefined),
10
+ }),
11
+ consumer: jest.fn().mockReturnValue({
12
+ connect: jest.fn().mockResolvedValue(undefined),
13
+ subscribe: jest.fn().mockResolvedValue(undefined),
14
+ run: jest.fn().mockResolvedValue(undefined),
15
+ disconnect: jest.fn().mockResolvedValue(undefined),
16
+ }),
17
+ },
18
+ }));
19
+
20
+ jest.mock('<%= loggerPath %>');
21
+
22
+ describe('KafkaService', () => {
23
+ let kafkaService: KafkaService;
24
+
25
+ beforeEach(() => {
26
+ jest.clearAllMocks();
27
+ kafkaService = new KafkaService();
28
+ });
29
+
30
+ it('should connect producer and consumer', async () => {
31
+ await kafkaService.connect();
32
+ const producer = (kafka.producer as jest.Mock).mock.results[0].value;
33
+ const consumer = (kafka.consumer as jest.Mock).mock.results[0].value;
34
+
35
+ expect(producer.connect).toHaveBeenCalled();
36
+ expect(consumer.connect).toHaveBeenCalled();
37
+ expect(consumer.subscribe).toHaveBeenCalledWith(expect.objectContaining({ topic: 'user-topic', fromBeginning: true }));
38
+ expect(consumer.run).toHaveBeenCalled();
39
+ });
40
+
41
+ it('should send a message', async () => {
42
+ await kafkaService.connect();
43
+ const topic = 'test-topic';
44
+ const message = JSON.stringify({ action: 'TEST', payload: { email: 'test@example.com' } });
45
+ await kafkaService.sendMessage(topic, message);
46
+ const producer = (kafka.producer as jest.Mock).mock.results[0].value;
47
+
48
+ expect(producer.send).toHaveBeenCalledWith({
49
+ topic,
50
+ messages: [{ value: message }],
51
+ });
52
+ });
53
+
54
+ it('should retry connection on failure', async () => {
55
+ const producer = (kafka.producer as jest.Mock).mock.results[0].value;
56
+ producer.connect
57
+ .mockRejectedValueOnce(new Error('Connection failed'))
58
+ .mockResolvedValueOnce(undefined);
59
+
60
+ jest.useFakeTimers();
61
+ const connectPromise = kafkaService.connect(2);
62
+
63
+ await jest.advanceTimersByTimeAsync(10000);
64
+ await connectPromise;
65
+
66
+ expect(producer.connect).toHaveBeenCalledTimes(2);
67
+ });
68
+
69
+ it('should throw error if producer not connected', async () => {
70
+ await expect(kafkaService.sendMessage('topic', 'msg')).rejects.toThrow('[Kafka] Producer not connected');
71
+ });
72
+
73
+ it('should disconnect producer and consumer', async () => {
74
+ await kafkaService.disconnect();
75
+ const producer = (kafka.producer as jest.Mock).mock.results[0].value;
76
+ const consumer = (kafka.consumer as jest.Mock).mock.results[0].value;
77
+
78
+ expect(producer.disconnect).toHaveBeenCalled();
79
+ expect(consumer.disconnect).toHaveBeenCalled();
80
+ });
81
+ });
@@ -1,95 +1,95 @@
1
- import { kafka } from '<%= configPath %>';
2
- import { EachMessagePayload, Producer, Consumer } from 'kafkajs';
3
- import logger from '<%= loggerPath %>';
4
-
5
- export class KafkaService {
6
- private producer: Producer;
7
- private consumer: Consumer;
8
- private isConnected = false;
9
- private connectionPromise: Promise<void> | null = null;
10
-
11
- constructor() {
12
- this.producer = kafka.producer();
13
- this.consumer = kafka.consumer({ groupId: 'test-group' });
14
- }
15
-
16
- async connect(retries = 10) {
17
- if (this.connectionPromise) return this.connectionPromise;
18
-
19
- this.connectionPromise = (async () => {
20
- let attempt = 0;
21
- // Auto-register WelcomeEmailConsumer if it exists
22
- // Note: Dynamic import used here for simplicity and to avoid startup crashes.
23
- // In enterprise production, consider using Dependency Injection.
24
- const { WelcomeEmailConsumer } = await import('<% if (architecture === "Clean Architecture") { %>@/interfaces/messaging/consumers/instances/welcomeEmailConsumer<% } else { %>@/messaging/consumers/instances/welcomeEmailConsumer<% } %>');
25
- while (attempt < retries) {
26
- try {
27
- await this.producer.connect();
28
- await this.consumer.connect();
29
- logger.info('[Kafka] Producer connected successfully');
30
- logger.info('[Kafka] Consumer connected successfully');
31
- this.isConnected = true;
32
-
33
- try {
34
- const welcomeConsumer = new WelcomeEmailConsumer();
35
- await this.consumer.subscribe({ topic: welcomeConsumer.topic, fromBeginning: true });
36
- logger.info(`[Kafka] Registered consumer for topic: ${welcomeConsumer.topic}`);
37
-
38
- await this.consumer.run({
39
- eachMessage: async (payload) => welcomeConsumer.onMessage(payload),
40
- });
41
- } catch (e) {
42
- // Fallback or no consumers found
43
- logger.warn(`[Kafka] Could not load WelcomeEmailConsumer, using fallback: ${(e as Error).message}`);
44
- await this.consumer.subscribe({ topic: 'user-topic', fromBeginning: true });
45
- await this.consumer.run({
46
- eachMessage: async ({ message }: EachMessagePayload) => {
47
- logger.info(`[Kafka] Consumer: Received message on user-topic: ${message.value?.toString()}`);
48
- },
49
- });
50
- }
51
- return; // Success
52
- } catch (error) {
53
- attempt++;
54
- logger.error(`[Kafka] Connection attempt ${attempt} failed:`, (error as Error).message);
55
- if (attempt >= retries) {
56
- throw error;
57
- }
58
- await new Promise(res => setTimeout(res, 3000));
59
- }
60
- }
61
- })();
62
-
63
- return this.connectionPromise;
64
- }
65
-
66
- async sendMessage(topic: string, message: string) {
67
- if (this.connectionPromise) {
68
- await this.connectionPromise;
69
- }
70
-
71
- if (!this.isConnected) {
72
- throw new Error('[Kafka] Producer not connected. Check logs for connection errors.');
73
- }
74
-
75
- await this.producer.send({
76
- topic,
77
- messages: [
78
- { value: message },
79
- ],
80
- });
81
- try {
82
- const parsed = JSON.parse(message);
83
- logger.info(`[Kafka] Producer: Sent ${parsed.action} event for '${parsed.payload?.email || 'unknown'}'`);
84
- } catch {
85
- logger.info(`[Kafka] Producer: Sent message to ${topic}`);
86
- }
87
- }
88
-
89
- async disconnect() {
90
- await this.producer.disconnect();
91
- await this.consumer.disconnect();
92
- }
93
- }
94
-
95
- export const kafkaService = new KafkaService();
1
+ import { kafka } from '<%= configPath %>';
2
+ import { EachMessagePayload, Producer, Consumer } from 'kafkajs';
3
+ import logger from '<%= loggerPath %>';
4
+
5
+ export class KafkaService {
6
+ private producer: Producer;
7
+ private consumer: Consumer;
8
+ private isConnected = false;
9
+ private connectionPromise: Promise<void> | null = null;
10
+
11
+ constructor() {
12
+ this.producer = kafka.producer();
13
+ this.consumer = kafka.consumer({ groupId: 'test-group' });
14
+ }
15
+
16
+ async connect(retries = 10) {
17
+ if (this.connectionPromise) return this.connectionPromise;
18
+
19
+ this.connectionPromise = (async () => {
20
+ let attempt = 0;
21
+ // Auto-register WelcomeEmailConsumer if it exists
22
+ // Note: Dynamic import used here for simplicity and to avoid startup crashes.
23
+ // In enterprise production, consider using Dependency Injection.
24
+ const { WelcomeEmailConsumer } = await import('<% if (architecture === "Clean Architecture") { %>@/interfaces/messaging/consumers/instances/welcomeEmailConsumer<% } else { %>@/messaging/consumers/instances/welcomeEmailConsumer<% } %>');
25
+ while (attempt < retries) {
26
+ try {
27
+ await this.producer.connect();
28
+ await this.consumer.connect();
29
+ logger.info('[Kafka] Producer connected successfully');
30
+ logger.info('[Kafka] Consumer connected successfully');
31
+ this.isConnected = true;
32
+
33
+ try {
34
+ const welcomeConsumer = new WelcomeEmailConsumer();
35
+ await this.consumer.subscribe({ topic: welcomeConsumer.topic, fromBeginning: true });
36
+ logger.info(`[Kafka] Registered consumer for topic: ${welcomeConsumer.topic}`);
37
+
38
+ await this.consumer.run({
39
+ eachMessage: async (payload) => welcomeConsumer.onMessage(payload),
40
+ });
41
+ } catch (e) {
42
+ // Fallback or no consumers found
43
+ logger.warn(`[Kafka] Could not load WelcomeEmailConsumer, using fallback: ${(e as Error).message}`);
44
+ await this.consumer.subscribe({ topic: 'user-topic', fromBeginning: true });
45
+ await this.consumer.run({
46
+ eachMessage: async ({ message }: EachMessagePayload) => {
47
+ logger.info(`[Kafka] Consumer: Received message on user-topic: ${message.value?.toString()}`);
48
+ },
49
+ });
50
+ }
51
+ return; // Success
52
+ } catch (error) {
53
+ attempt++;
54
+ logger.error(`[Kafka] Connection attempt ${attempt} failed:`, (error as Error).message);
55
+ if (attempt >= retries) {
56
+ throw error;
57
+ }
58
+ await new Promise(res => setTimeout(res, 3000));
59
+ }
60
+ }
61
+ })();
62
+
63
+ return this.connectionPromise;
64
+ }
65
+
66
+ async sendMessage(topic: string, message: string, key?: string) {
67
+ if (this.connectionPromise) {
68
+ await this.connectionPromise;
69
+ }
70
+
71
+ if (!this.isConnected) {
72
+ throw new Error('[Kafka] Producer not connected. Check logs for connection errors.');
73
+ }
74
+
75
+ await this.producer.send({
76
+ topic,
77
+ messages: [
78
+ { key, value: message },
79
+ ],
80
+ });
81
+ try {
82
+ const parsed = JSON.parse(message);
83
+ logger.info(`[Kafka] Producer: Sent ${parsed.action} event for '${parsed.payload?.email || 'unknown'}'`);
84
+ } catch {
85
+ logger.info(`[Kafka] Producer: Sent message to ${topic}`);
86
+ }
87
+ }
88
+
89
+ async disconnect() {
90
+ await this.producer.disconnect();
91
+ await this.consumer.disconnect();
92
+ }
93
+ }
94
+
95
+ export const kafkaService = new KafkaService();
@@ -0,0 +1,5 @@
1
+ export enum KAFKA_ACTIONS {
2
+ USER_CREATED = 'USER_CREATED',
3
+ USER_UPDATED = 'USER_UPDATED',
4
+ USER_DELETED = 'USER_DELETED'
5
+ }
@@ -1,31 +1,31 @@
1
- const config = {
2
- mongodb: {
3
- url: process.env.MONGO_URI || `mongodb://${process.env.DB_HOST || 'localhost'}:27017`,
4
- databaseName: process.env.DB_NAME || '<%= dbName %>',
5
-
6
- options: {
7
- // useNewUrlParser: true, // No longer needed in Node.js driver v4+
8
- // useUnifiedTopology: true, // No longer needed in Node.js driver v4+
9
- // connectTimeoutMS: 3600000, // increase connection timeout to 1 hour
10
- // socketTimeoutMS: 3600000, // increase socket timeout to 1 hour
11
- }
12
- },
13
-
14
- // The migrations dir, can be an relative or absolute path. Only edit this when really necessary.
15
- migrationsDir: "migrations",
16
-
17
- // The mongodb collection where the applied changes are stored. Only edit this when really necessary.
18
- changelogCollectionName: "changelog",
19
-
20
- // The file extension to create migrations and search for in migration dir
21
- migrationFileExtension: ".js",
22
-
23
- // Enable the algorithm to create a checksum of the file contents and use that in the comparison to determine
24
- // if the file should be run. Requires that scripts are coded to be run multiple times.
25
- useFileHash: false,
26
-
27
- // Don't change this, unless you know what you are doing
28
- moduleSystem: 'commonjs',
29
- };
30
-
31
- module.exports = config;
1
+ const config = {
2
+ mongodb: {
3
+ url: process.env.MONGO_URI || `mongodb://${process.env.DB_HOST || 'localhost'}:27017`,
4
+ databaseName: process.env.DB_NAME || '<%= dbName %>',
5
+
6
+ options: {
7
+ // useNewUrlParser: true, // No longer needed in Node.js driver v4+
8
+ // useUnifiedTopology: true, // No longer needed in Node.js driver v4+
9
+ // connectTimeoutMS: 3600000, // increase connection timeout to 1 hour
10
+ // socketTimeoutMS: 3600000, // increase socket timeout to 1 hour
11
+ }
12
+ },
13
+
14
+ // The migrations dir, can be an relative or absolute path. Only edit this when really necessary.
15
+ migrationsDir: "migrations",
16
+
17
+ // The mongodb collection where the applied changes are stored. Only edit this when really necessary.
18
+ changelogCollectionName: "changelog",
19
+
20
+ // The file extension to create migrations and search for in migration dir
21
+ migrationFileExtension: ".js",
22
+
23
+ // Enable the algorithm to create a checksum of the file contents and use that in the comparison to determine
24
+ // if the file should be run. Requires that scripts are coded to be run multiple times.
25
+ useFileHash: false,
26
+
27
+ // Don't change this, unless you know what you are doing
28
+ moduleSystem: 'commonjs',
29
+ };
30
+
31
+ module.exports = config;