nodejs-structure-cli 1.0.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.
- package/README.md +32 -0
- package/bin/index.js +143 -0
- package/lib/generator.js +145 -0
- package/lib/modules/app-setup.js +479 -0
- package/lib/modules/caching-setup.js +76 -0
- package/lib/modules/config-files.js +151 -0
- package/lib/modules/database-setup.js +116 -0
- package/lib/modules/kafka-setup.js +249 -0
- package/lib/modules/project-setup.js +32 -0
- package/lib/prompts.js +128 -0
- package/package.json +66 -0
- package/templates/clean-architecture/js/src/domain/models/User.js.ejs +11 -0
- package/templates/clean-architecture/js/src/errors/ApiError.js +14 -0
- package/templates/clean-architecture/js/src/errors/BadRequestError.js +11 -0
- package/templates/clean-architecture/js/src/errors/BadRequestError.spec.js.ejs +22 -0
- package/templates/clean-architecture/js/src/errors/NotFoundError.js +11 -0
- package/templates/clean-architecture/js/src/errors/NotFoundError.spec.js.ejs +22 -0
- package/templates/clean-architecture/js/src/index.js.ejs +56 -0
- package/templates/clean-architecture/js/src/infrastructure/config/env.js.ejs +47 -0
- package/templates/clean-architecture/js/src/infrastructure/log/logger.js +36 -0
- package/templates/clean-architecture/js/src/infrastructure/log/logger.spec.js.ejs +63 -0
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +88 -0
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +142 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/middleware/errorMiddleware.js +30 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +93 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/swagger.js.ejs +6 -0
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +190 -0
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +234 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/context.js.ejs +13 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/context.spec.js.ejs +31 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/index.js.ejs +5 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/index.js.ejs +6 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +27 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +66 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/index.js.ejs +6 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/user.types.js.ejs +19 -0
- package/templates/clean-architecture/js/src/interfaces/routes/api.js.ejs +17 -0
- package/templates/clean-architecture/js/src/interfaces/routes/api.spec.js.ejs +38 -0
- package/templates/clean-architecture/js/src/usecases/CreateUser.js.ejs +14 -0
- package/templates/clean-architecture/js/src/usecases/CreateUser.spec.js.ejs +51 -0
- package/templates/clean-architecture/js/src/usecases/DeleteUser.js +11 -0
- package/templates/clean-architecture/js/src/usecases/DeleteUser.spec.js.ejs +47 -0
- package/templates/clean-architecture/js/src/usecases/GetAllUsers.js +12 -0
- package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +61 -0
- package/templates/clean-architecture/js/src/usecases/UpdateUser.js.ejs +11 -0
- package/templates/clean-architecture/js/src/usecases/UpdateUser.spec.js.ejs +48 -0
- package/templates/clean-architecture/js/src/utils/errorMessages.js +14 -0
- package/templates/clean-architecture/js/src/utils/httpCodes.js +9 -0
- package/templates/clean-architecture/ts/src/config/env.ts.ejs +46 -0
- package/templates/clean-architecture/ts/src/config/swagger.ts.ejs +6 -0
- package/templates/clean-architecture/ts/src/domain/user.ts.ejs +9 -0
- package/templates/clean-architecture/ts/src/errors/ApiError.ts +15 -0
- package/templates/clean-architecture/ts/src/errors/BadRequestError.spec.ts.ejs +22 -0
- package/templates/clean-architecture/ts/src/errors/BadRequestError.ts +9 -0
- package/templates/clean-architecture/ts/src/errors/NotFoundError.spec.ts.ejs +22 -0
- package/templates/clean-architecture/ts/src/errors/NotFoundError.ts +9 -0
- package/templates/clean-architecture/ts/src/index.ts.ejs +144 -0
- package/templates/clean-architecture/ts/src/infrastructure/log/logger.spec.ts.ejs +63 -0
- package/templates/clean-architecture/ts/src/infrastructure/log/logger.ts +36 -0
- package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +175 -0
- package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +125 -0
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +331 -0
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +208 -0
- package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +32 -0
- package/templates/clean-architecture/ts/src/interfaces/graphql/context.ts.ejs +17 -0
- package/templates/clean-architecture/ts/src/interfaces/graphql/index.ts.ejs +3 -0
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/index.ts.ejs +4 -0
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +68 -0
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +29 -0
- package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/index.ts.ejs +4 -0
- package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/user.types.ts.ejs +17 -0
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +40 -0
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts.ejs +18 -0
- package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +51 -0
- package/templates/clean-architecture/ts/src/usecases/createUser.ts.ejs +11 -0
- package/templates/clean-architecture/ts/src/usecases/deleteUser.spec.ts.ejs +47 -0
- package/templates/clean-architecture/ts/src/usecases/deleteUser.ts +9 -0
- package/templates/clean-architecture/ts/src/usecases/getAllUsers.spec.ts.ejs +63 -0
- package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +10 -0
- package/templates/clean-architecture/ts/src/usecases/updateUser.spec.ts.ejs +48 -0
- package/templates/clean-architecture/ts/src/usecases/updateUser.ts.ejs +10 -0
- package/templates/clean-architecture/ts/src/utils/errorMessages.ts +12 -0
- package/templates/clean-architecture/ts/src/utils/errorMiddleware.ts.ejs +27 -0
- package/templates/clean-architecture/ts/src/utils/httpCodes.ts +7 -0
- package/templates/common/.cursorrules.ejs +60 -0
- package/templates/common/.dockerignore +12 -0
- package/templates/common/.env.example.ejs +60 -0
- package/templates/common/.gitattributes +46 -0
- package/templates/common/.gitlab-ci.yml.ejs +86 -0
- package/templates/common/.lintstagedrc +6 -0
- package/templates/common/.prettierrc +7 -0
- package/templates/common/.snyk.ejs +45 -0
- package/templates/common/Dockerfile +73 -0
- package/templates/common/Jenkinsfile.ejs +87 -0
- package/templates/common/README.md.ejs +148 -0
- package/templates/common/_github/workflows/ci.yml.ejs +46 -0
- package/templates/common/_github/workflows/security.yml.ejs +36 -0
- package/templates/common/_gitignore +5 -0
- package/templates/common/_husky/pre-commit +4 -0
- package/templates/common/caching/clean/js/CreateUser.js.ejs +29 -0
- package/templates/common/caching/clean/js/DeleteUser.js.ejs +27 -0
- package/templates/common/caching/clean/js/GetAllUsers.js.ejs +37 -0
- package/templates/common/caching/clean/js/UpdateUser.js.ejs +27 -0
- package/templates/common/caching/clean/ts/createUser.ts.ejs +27 -0
- package/templates/common/caching/clean/ts/deleteUser.ts.ejs +24 -0
- package/templates/common/caching/clean/ts/getAllUsers.ts.ejs +34 -0
- package/templates/common/caching/clean/ts/updateUser.ts.ejs +25 -0
- package/templates/common/caching/js/memoryCache.js.ejs +60 -0
- package/templates/common/caching/js/memoryCache.spec.js.ejs +101 -0
- package/templates/common/caching/js/redisClient.js.ejs +75 -0
- package/templates/common/caching/js/redisClient.spec.js.ejs +147 -0
- package/templates/common/caching/ts/memoryCache.spec.ts.ejs +102 -0
- package/templates/common/caching/ts/memoryCache.ts.ejs +73 -0
- package/templates/common/caching/ts/redisClient.spec.ts.ejs +157 -0
- package/templates/common/caching/ts/redisClient.ts.ejs +89 -0
- package/templates/common/database/js/database.js.ejs +19 -0
- package/templates/common/database/js/database.spec.js.ejs +56 -0
- package/templates/common/database/js/models/User.js.ejs +91 -0
- package/templates/common/database/js/models/User.js.mongoose.ejs +35 -0
- package/templates/common/database/js/models/User.spec.js.ejs +94 -0
- package/templates/common/database/js/mongoose.js.ejs +33 -0
- package/templates/common/database/js/mongoose.spec.js.ejs +43 -0
- package/templates/common/database/ts/database.spec.ts.ejs +56 -0
- package/templates/common/database/ts/database.ts.ejs +21 -0
- package/templates/common/database/ts/models/User.spec.ts.ejs +100 -0
- package/templates/common/database/ts/models/User.ts.ejs +102 -0
- package/templates/common/database/ts/models/User.ts.mongoose.ejs +34 -0
- package/templates/common/database/ts/mongoose.spec.ts.ejs +42 -0
- package/templates/common/database/ts/mongoose.ts.ejs +28 -0
- package/templates/common/docker-compose.yml.ejs +159 -0
- package/templates/common/ecosystem.config.js.ejs +40 -0
- package/templates/common/eslint.config.mjs.ejs +77 -0
- package/templates/common/health/js/healthRoute.js.ejs +50 -0
- package/templates/common/health/js/healthRoute.spec.js.ejs +70 -0
- package/templates/common/health/ts/healthRoute.spec.ts.ejs +76 -0
- package/templates/common/health/ts/healthRoute.ts.ejs +49 -0
- package/templates/common/jest.config.js.ejs +32 -0
- package/templates/common/jest.e2e.config.js.ejs +8 -0
- package/templates/common/kafka/js/config/kafka.js +9 -0
- package/templates/common/kafka/js/config/kafka.spec.js.ejs +27 -0
- package/templates/common/kafka/js/messaging/baseConsumer.js.ejs +30 -0
- package/templates/common/kafka/js/messaging/baseConsumer.spec.js.ejs +58 -0
- package/templates/common/kafka/js/messaging/userEventSchema.js.ejs +12 -0
- package/templates/common/kafka/js/messaging/userEventSchema.spec.js.ejs +27 -0
- package/templates/common/kafka/js/messaging/welcomeEmailConsumer.js.ejs +44 -0
- package/templates/common/kafka/js/messaging/welcomeEmailConsumer.spec.js.ejs +86 -0
- package/templates/common/kafka/js/services/kafkaService.js.ejs +93 -0
- package/templates/common/kafka/js/services/kafkaService.spec.js.ejs +106 -0
- package/templates/common/kafka/js/utils/kafkaEvents.js.ejs +7 -0
- package/templates/common/kafka/ts/config/kafka.spec.ts.ejs +27 -0
- package/templates/common/kafka/ts/config/kafka.ts +7 -0
- package/templates/common/kafka/ts/messaging/baseConsumer.spec.ts.ejs +50 -0
- package/templates/common/kafka/ts/messaging/baseConsumer.ts.ejs +27 -0
- package/templates/common/kafka/ts/messaging/userEventSchema.spec.ts.ejs +51 -0
- package/templates/common/kafka/ts/messaging/userEventSchema.ts.ejs +12 -0
- package/templates/common/kafka/ts/messaging/welcomeEmailConsumer.spec.ts.ejs +86 -0
- package/templates/common/kafka/ts/messaging/welcomeEmailConsumer.ts.ejs +38 -0
- package/templates/common/kafka/ts/services/kafkaService.spec.ts.ejs +81 -0
- package/templates/common/kafka/ts/services/kafkaService.ts.ejs +95 -0
- package/templates/common/kafka/ts/utils/kafkaEvents.ts.ejs +5 -0
- package/templates/common/migrate-mongo-config.js.ejs +31 -0
- package/templates/common/migrations/init.js.ejs +23 -0
- package/templates/common/package.json.ejs +137 -0
- package/templates/common/prompts/add-feature.md.ejs +26 -0
- package/templates/common/prompts/project-context.md.ejs +43 -0
- package/templates/common/prompts/troubleshoot.md.ejs +28 -0
- package/templates/common/public/css/style.css +147 -0
- package/templates/common/scripts/run-e2e.js.ejs +63 -0
- package/templates/common/shutdown/js/gracefulShutdown.js.ejs +65 -0
- package/templates/common/shutdown/js/gracefulShutdown.spec.js.ejs +149 -0
- package/templates/common/shutdown/ts/gracefulShutdown.spec.ts.ejs +179 -0
- package/templates/common/shutdown/ts/gracefulShutdown.ts.ejs +59 -0
- package/templates/common/sonar-project.properties.ejs +27 -0
- package/templates/common/src/config/auth.js.ejs +19 -0
- package/templates/common/src/config/auth.ts.ejs +19 -0
- package/templates/common/src/controllers/authController.js.ejs +101 -0
- package/templates/common/src/controllers/authController.ts.ejs +101 -0
- package/templates/common/src/middleware/auth.js.ejs +20 -0
- package/templates/common/src/middleware/auth.ts.ejs +25 -0
- package/templates/common/src/middleware/upload.js.ejs +31 -0
- package/templates/common/src/middleware/upload.ts.ejs +32 -0
- package/templates/common/src/routes/authRoutes.js.ejs +20 -0
- package/templates/common/src/routes/authRoutes.ts.ejs +20 -0
- package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +120 -0
- package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +120 -0
- package/templates/common/src/utils/errorMiddleware.spec.js.ejs +79 -0
- package/templates/common/src/utils/errorMiddleware.spec.ts.ejs +94 -0
- package/templates/common/swagger.yml.ejs +118 -0
- package/templates/common/tsconfig.json +23 -0
- package/templates/common/views/ejs/index.ejs +55 -0
- package/templates/common/views/pug/index.pug +40 -0
- package/templates/db/mysql/V1__Initial_Setup.sql.ejs +10 -0
- package/templates/db/postgres/V1__Initial_Setup.sql.ejs +10 -0
- package/templates/mvc/js/src/config/env.js.ejs +46 -0
- package/templates/mvc/js/src/config/swagger.js.ejs +6 -0
- package/templates/mvc/js/src/controllers/userController.js.ejs +288 -0
- package/templates/mvc/js/src/controllers/userController.spec.js.ejs +481 -0
- package/templates/mvc/js/src/errors/ApiError.js +14 -0
- package/templates/mvc/js/src/errors/BadRequestError.js +11 -0
- package/templates/mvc/js/src/errors/BadRequestError.spec.js.ejs +22 -0
- package/templates/mvc/js/src/errors/NotFoundError.js +11 -0
- package/templates/mvc/js/src/errors/NotFoundError.spec.js.ejs +22 -0
- package/templates/mvc/js/src/graphql/context.js.ejs +7 -0
- package/templates/mvc/js/src/graphql/context.spec.js.ejs +29 -0
- package/templates/mvc/js/src/graphql/index.js.ejs +5 -0
- package/templates/mvc/js/src/graphql/resolvers/index.js.ejs +6 -0
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +25 -0
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +64 -0
- package/templates/mvc/js/src/graphql/typeDefs/index.js.ejs +6 -0
- package/templates/mvc/js/src/graphql/typeDefs/user.types.js.ejs +19 -0
- package/templates/mvc/js/src/index.js.ejs +141 -0
- package/templates/mvc/js/src/routes/api.js.ejs +15 -0
- package/templates/mvc/js/src/routes/api.spec.js.ejs +41 -0
- package/templates/mvc/js/src/utils/errorMessages.js +14 -0
- package/templates/mvc/js/src/utils/errorMiddleware.js +29 -0
- package/templates/mvc/js/src/utils/httpCodes.js +9 -0
- package/templates/mvc/js/src/utils/logger.js +40 -0
- package/templates/mvc/js/src/utils/logger.spec.js.ejs +63 -0
- package/templates/mvc/ts/src/config/env.ts.ejs +45 -0
- package/templates/mvc/ts/src/config/swagger.ts.ejs +6 -0
- package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +481 -0
- package/templates/mvc/ts/src/controllers/userController.ts.ejs +292 -0
- package/templates/mvc/ts/src/errors/ApiError.ts +15 -0
- package/templates/mvc/ts/src/errors/BadRequestError.spec.ts.ejs +22 -0
- package/templates/mvc/ts/src/errors/BadRequestError.ts +9 -0
- package/templates/mvc/ts/src/errors/NotFoundError.spec.ts.ejs +27 -0
- package/templates/mvc/ts/src/errors/NotFoundError.ts +9 -0
- package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +30 -0
- package/templates/mvc/ts/src/graphql/context.ts.ejs +12 -0
- package/templates/mvc/ts/src/graphql/index.ts.ejs +3 -0
- package/templates/mvc/ts/src/graphql/resolvers/index.ts.ejs +4 -0
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +68 -0
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +29 -0
- package/templates/mvc/ts/src/graphql/typeDefs/index.ts.ejs +4 -0
- package/templates/mvc/ts/src/graphql/typeDefs/user.types.ts.ejs +17 -0
- package/templates/mvc/ts/src/index.ts.ejs +157 -0
- package/templates/mvc/ts/src/routes/api.spec.ts.ejs +59 -0
- package/templates/mvc/ts/src/routes/api.ts.ejs +17 -0
- package/templates/mvc/ts/src/utils/errorMessages.ts +12 -0
- package/templates/mvc/ts/src/utils/errorMiddleware.ts.ejs +27 -0
- package/templates/mvc/ts/src/utils/httpCodes.ts +7 -0
- package/templates/mvc/ts/src/utils/logger.spec.ts.ejs +63 -0
- package/templates/mvc/ts/src/utils/logger.ts +36 -0
|
@@ -0,0 +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 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
|
+
});
|
|
@@ -0,0 +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
|
+
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
|
+
}
|
|
@@ -0,0 +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
|
+
});
|
|
@@ -0,0 +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, 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,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;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
async up(db, client) {
|
|
3
|
+
const adminEmail = 'admin@example.com';
|
|
4
|
+
const existingAdmin = await db.collection('users').findOne({ email: adminEmail });
|
|
5
|
+
|
|
6
|
+
if (!existingAdmin) {
|
|
7
|
+
await db.collection('users').insertOne({
|
|
8
|
+
name: 'Admin User',
|
|
9
|
+
email: adminEmail,
|
|
10
|
+
createdAt: new Date(),
|
|
11
|
+
updatedAt: new Date()
|
|
12
|
+
});
|
|
13
|
+
console.log('Admin User seeded successfully');
|
|
14
|
+
} else {
|
|
15
|
+
console.log('Admin User already exists, skipping seed');
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
async down(db, client) {
|
|
20
|
+
// Optional: Undo the seed. Usually for seeds we might want to keep data, but strictly speaking 'down' should reverse 'up'.
|
|
21
|
+
// await db.collection('users').deleteOne({ email: 'admin@example.com' });
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "<%= projectName %>",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Generated by nodejs-quickstart",
|
|
5
|
+
"main": "<% if (language === 'TypeScript') { %>dist/index.js<% } else { %>src/index.js<% } %>",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "<% if (language === 'TypeScript') { %>node dist/index.js<% } else { %>node src/index.js<% } %>",
|
|
8
|
+
"dev": "<% if (language === 'TypeScript') { %>nodemon --exec ts-node -r tsconfig-paths/register src/index.ts<% } else { %>nodemon src/index.js<% } %>"<% if (language === 'TypeScript') { %>,
|
|
9
|
+
"build": "rimraf dist && tsc && tsc-alias<% if (viewEngine && viewEngine !== 'None') { %> && cpx \"src/views/**/*\" dist/views<% } %><% if (communication === 'REST APIs' || communication === 'Kafka') { %> && cpx \"src/**/*.yml\" dist/<% } %>"<% } %>,
|
|
10
|
+
"deploy": "npx pm2 start ecosystem.config.js --env production",
|
|
11
|
+
"lint": "eslint .",
|
|
12
|
+
"lint:fix": "eslint . --fix",
|
|
13
|
+
"format": "prettier --write .",
|
|
14
|
+
"prepare": "node -e \"if (require('fs').existsSync('.git')) { try { require('child_process').execSync('husky install', { stdio: 'inherit' }); } catch (e) { console.error('Husky installation failed:', e.message); } }\"",
|
|
15
|
+
<% if (database === 'MongoDB') { %> "migrate": "migrate-mongo up",
|
|
16
|
+
<% } -%>
|
|
17
|
+
"test": "jest",
|
|
18
|
+
"test:watch": "jest --watch",
|
|
19
|
+
"test:coverage": "jest --coverage",
|
|
20
|
+
"test:e2e:run": "jest --config ./jest.e2e.config.js --passWithNoTests",
|
|
21
|
+
"test:e2e": "node scripts/run-e2e.js"<% if (includeSecurity) { %>,
|
|
22
|
+
"security:check": "npm audit && npm run snyk:test",
|
|
23
|
+
"snyk:test": "snyk test"<% } %>
|
|
24
|
+
},
|
|
25
|
+
"overrides": {
|
|
26
|
+
"brace-expansion": "^5.0.5",
|
|
27
|
+
"jake": "^11.9.5",
|
|
28
|
+
"micromatch": "^4.0.8",
|
|
29
|
+
"braces": "^3.0.3",
|
|
30
|
+
"picomatch": "^4.0.4",
|
|
31
|
+
"lodash": "^4.17.23"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"express": "^4.18.2",
|
|
35
|
+
"dotenv": "^16.3.1",
|
|
36
|
+
"zod": "^3.22.4",
|
|
37
|
+
<% if (database === 'MySQL') { %> "mysql2": "^3.6.5",
|
|
38
|
+
"sequelize": "^6.35.2",
|
|
39
|
+
<% } -%>
|
|
40
|
+
<% if (database === 'PostgreSQL') { %> "pg": "^8.11.3",
|
|
41
|
+
"sequelize": "^6.35.2",
|
|
42
|
+
<% } -%>
|
|
43
|
+
<% if (database === 'MongoDB') { %> "mongoose": "^8.0.3",
|
|
44
|
+
"migrate-mongo": "^11.0.0",
|
|
45
|
+
<% } -%>
|
|
46
|
+
<% if (communication === 'Kafka') { %> "kafkajs": "^2.2.4",
|
|
47
|
+
<% } -%>
|
|
48
|
+
<% if (caching === 'Redis') { %> "ioredis": "^5.3.2",
|
|
49
|
+
<% } -%>
|
|
50
|
+
<% if (caching === 'Memory Cache') { %> "node-cache": "^5.1.2",
|
|
51
|
+
<% } -%>
|
|
52
|
+
<% if (viewEngine === 'EJS') { %> "ejs": "^3.1.10",
|
|
53
|
+
<% } -%>
|
|
54
|
+
<% if (viewEngine === 'Pug') { %> "pug": "^3.0.2",
|
|
55
|
+
<% } -%>
|
|
56
|
+
"cors": "^2.8.5",
|
|
57
|
+
"helmet": "^7.1.0",
|
|
58
|
+
"hpp": "^0.2.3",
|
|
59
|
+
"express-rate-limit": "^7.1.5",
|
|
60
|
+
"winston": "^3.11.0",
|
|
61
|
+
"winston-daily-rotate-file": "^5.0.0",
|
|
62
|
+
"morgan": "^1.10.0"<% if (communication === 'REST APIs' || communication === 'Kafka') { %>,
|
|
63
|
+
"swagger-ui-express": "^5.0.0",
|
|
64
|
+
"yamljs": "^0.3.0"<% } %><% if (communication === 'GraphQL') { %>,
|
|
65
|
+
"@apollo/server": "^5.5.0",
|
|
66
|
+
"@as-integrations/express4": "^1.1.2",
|
|
67
|
+
"graphql": "^16.8.1",
|
|
68
|
+
"@graphql-tools/merge": "^9.0.3"<% } -%>
|
|
69
|
+
<% if (includeMulter) { %>,
|
|
70
|
+
"multer": "^1.4.5-lts.1"<% } -%>
|
|
71
|
+
<% if (auth === 'JWT') { %>,
|
|
72
|
+
"jsonwebtoken": "^9.0.2",
|
|
73
|
+
"bcryptjs": "^2.4.3"<% } -%>
|
|
74
|
+
<% if (auth === 'Better-Auth') { %>,
|
|
75
|
+
"better-auth": "^1.1.0"<% } -%>
|
|
76
|
+
<% if (auth === 'OAuth' || googleLogin === 'Google Login') { %>,
|
|
77
|
+
"passport": "^0.7.0",
|
|
78
|
+
"passport-google-oauth20": "^2.0.0"<% } %>
|
|
79
|
+
},
|
|
80
|
+
"devDependencies": {
|
|
81
|
+
"nodemon": "^3.0.2"<% if (language === 'TypeScript') { %>,
|
|
82
|
+
"typescript": "^5.3.3",
|
|
83
|
+
"ts-node": "^10.9.2",
|
|
84
|
+
"@types/node": "^20.10.5",
|
|
85
|
+
"@types/express": "^4.17.21",
|
|
86
|
+
"@types/cors": "^2.8.17",
|
|
87
|
+
"@types/hpp": "^0.2.3",
|
|
88
|
+
<% if (includeMulter) { -%> "@types/multer": "^1.4.11",
|
|
89
|
+
<% } -%>
|
|
90
|
+
<% if (auth === 'JWT') { -%> "@types/jsonwebtoken": "^9.0.5",
|
|
91
|
+
"@types/bcryptjs": "^2.4.6",
|
|
92
|
+
<% } -%>
|
|
93
|
+
<% if (auth === 'OAuth' || googleLogin === 'Google Login') { -%> "@types/passport": "^1.0.16",
|
|
94
|
+
"@types/passport-google-oauth20": "^2.0.14",
|
|
95
|
+
<% } -%>
|
|
96
|
+
<% if (caching === 'Memory Cache') { %> "@types/node-cache": "^4.2.5",
|
|
97
|
+
<% } -%>
|
|
98
|
+
<% if (database === 'PostgreSQL') { %> "@types/pg": "^8.10.9",
|
|
99
|
+
<% } -%>
|
|
100
|
+
<% if (database === 'MySQL' || database === 'PostgreSQL') { -%>
|
|
101
|
+
"@types/sequelize": "^4.28.19",
|
|
102
|
+
<% } -%>
|
|
103
|
+
"@types/morgan": "^1.9.9",
|
|
104
|
+
"rimraf": "^6.0.1"<% if ((viewEngine && viewEngine !== 'None') || communication === 'REST APIs' || communication === 'Kafka') { %>,
|
|
105
|
+
"cpx2": "^8.0.0"<% } %><% } %>,
|
|
106
|
+
"eslint": "^10.1.0",
|
|
107
|
+
"@eslint/js": "^9.20.0",
|
|
108
|
+
"globals": "^15.14.0",
|
|
109
|
+
"prettier": "^3.5.1",
|
|
110
|
+
"eslint-config-prettier": "^10.0.1",
|
|
111
|
+
"eslint-plugin-import-x": "^4.6.1",
|
|
112
|
+
"eslint-import-resolver-typescript": "^3.7.0",
|
|
113
|
+
"husky": "^8.0.3",
|
|
114
|
+
"lint-staged": "^15.4.3",
|
|
115
|
+
"snyk": "^1.1295.0"<% if (language === 'TypeScript') { %>,
|
|
116
|
+
"typescript-eslint": "^8.24.1",<%_ if (communication === 'REST APIs' || communication === 'Kafka') { %>
|
|
117
|
+
"@types/swagger-ui-express": "^4.1.6",
|
|
118
|
+
"@types/yamljs": "^0.2.34",<%_ } %>
|
|
119
|
+
"jest": "^29.7.0",
|
|
120
|
+
"ts-jest": "^29.2.5",
|
|
121
|
+
"@types/jest": "^29.5.14",
|
|
122
|
+
"wait-on": "^7.2.0",
|
|
123
|
+
"supertest": "^7.1.3",
|
|
124
|
+
"tsconfig-paths": "^4.2.0",
|
|
125
|
+
"tsc-alias": "^1.8.10",
|
|
126
|
+
"@types/supertest": "^6.0.2"<% } else { %>,
|
|
127
|
+
"jest": "^29.7.0",
|
|
128
|
+
"wait-on": "^7.2.0",
|
|
129
|
+
"supertest": "^7.1.3"<% } %>
|
|
130
|
+
},
|
|
131
|
+
"lint-staged": {
|
|
132
|
+
"*.{js,ts}": [
|
|
133
|
+
"eslint --fix",
|
|
134
|
+
"prettier --write"
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Add New Feature
|
|
2
|
+
|
|
3
|
+
I want to add a new feature to the existing application.
|
|
4
|
+
Please follow the strict standards defined in the project context.
|
|
5
|
+
|
|
6
|
+
## Feature Description
|
|
7
|
+
[INSERT EXPLANATION OF WHAT YOU WANT TO ADD HERE]
|
|
8
|
+
|
|
9
|
+
## Implementation Guidelines
|
|
10
|
+
|
|
11
|
+
Please provide the code implementation following these steps:
|
|
12
|
+
|
|
13
|
+
1. **Plan first**: Outline the files you need to create/modify and the logic they'll contain.
|
|
14
|
+
<% if (architecture === 'Clean Architecture') { -%>
|
|
15
|
+
2. **Domain/Entity**: Define the core entity structure or interfaces if applicable.
|
|
16
|
+
3. **Use Case**: Implement the business logic handling the feature.
|
|
17
|
+
4. **Adapter (Controller & Route)**: Create the necessary endpoints and validate input.
|
|
18
|
+
5. **Infrastructure (Repository)**: Implement database queries or external service calls.
|
|
19
|
+
<% } else { -%>
|
|
20
|
+
2. **Model**: Define the database schema/model if applicable.
|
|
21
|
+
3. **Controller**: Implement the business logic and request handling.
|
|
22
|
+
4. **Route**: Create the API endpoints and wire them to the controller.
|
|
23
|
+
<% } -%>
|
|
24
|
+
6. **Testing**: Write comprehensive Jest unit tests covering the "Happy Path" and "Edge Cases/Errors" (AAA pattern). Remember, our coverage requirement is > 80%!
|
|
25
|
+
|
|
26
|
+
Please provide the plan first so I can review it before we write the code.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Project Context
|
|
2
|
+
|
|
3
|
+
Hello AI! I am working on a Node.js project. Here is the context to help you understand the architecture, domain, and standards.
|
|
4
|
+
|
|
5
|
+
## Domain Overview
|
|
6
|
+
**Project Name**: <%= projectName %>
|
|
7
|
+
You are an expert working on **<%= projectName %>**.
|
|
8
|
+
**Project Goal**: [Replace this with your business logic, e.g., E-commerce API]
|
|
9
|
+
*(Keep this goal in mind when writing business logic, proposing data schemas, or considering edge cases like security and performance.)*
|
|
10
|
+
|
|
11
|
+
## Tech Stack
|
|
12
|
+
- **Language**: <%= language %>
|
|
13
|
+
- **Architecture**: <%= architecture %>
|
|
14
|
+
- **Database**: <%= database %>
|
|
15
|
+
- **Communication Protocol**: <%= communication %>
|
|
16
|
+
<% if (caching !== 'None') { -%>
|
|
17
|
+
- **Caching**: <%= caching %>
|
|
18
|
+
<% } -%>
|
|
19
|
+
|
|
20
|
+
## High-Level Architecture
|
|
21
|
+
<% if (architecture === 'Clean Architecture') { -%>
|
|
22
|
+
We use Clean Architecture. The project separates concerns into:
|
|
23
|
+
- `src/domain`: Core entities and rules. No external dependencies.
|
|
24
|
+
- `src/usecases`: Application business logic.
|
|
25
|
+
- `src/interfaces`: Adapters (Controllers, Routes) that mediate between the outside world and use cases.
|
|
26
|
+
- `src/infrastructure`: External tools (Database, Web Server, Config, Caching).
|
|
27
|
+
<% } else { -%>
|
|
28
|
+
We use the MVC (Model-View-Controller) pattern.
|
|
29
|
+
- `src/models`: Database schemas/models.
|
|
30
|
+
- `src/controllers`: Handling incoming requests and implementing business logic.
|
|
31
|
+
- `src/routes`: API endpoints mapped to controllers.
|
|
32
|
+
<% } -%>
|
|
33
|
+
|
|
34
|
+
## Core Standards
|
|
35
|
+
1. **Testing**: We enforce > 80% coverage. Tests use Jest and the AAA (Arrange, Act, Assert) pattern.
|
|
36
|
+
2. **Error Handling**: We use centralized custom errors (e.g., `ApiError`) and global error middleware. Status codes come from standard constants, not hardcoded numbers.
|
|
37
|
+
3. **Paths & Naming**:
|
|
38
|
+
<% if (language === 'TypeScript') { -%>
|
|
39
|
+
- We use `@/` path aliases for internal imports.
|
|
40
|
+
<% } -%>
|
|
41
|
+
- Files are mostly `camelCase`.
|
|
42
|
+
|
|
43
|
+
Please acknowledge you understand this context by saying "Context loaded successfully! How can I help you build the <%= projectName %>?"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Troubleshoot Error
|
|
2
|
+
|
|
3
|
+
I am encountering an error in the <%= projectName %> application. Please help me diagnose and fix it based on our architectural standards.
|
|
4
|
+
|
|
5
|
+
## The Error Log / Issue Description
|
|
6
|
+
\`\`\`
|
|
7
|
+
[PASTE YOUR ERROR LOG OR DESCRIBE THE ISSUE HERE]
|
|
8
|
+
\`\`\`
|
|
9
|
+
|
|
10
|
+
## Context Variables
|
|
11
|
+
- **Architecture**: <%= architecture %>
|
|
12
|
+
- **Language**: <%= language %>
|
|
13
|
+
|
|
14
|
+
## Guidelines for Fixing
|
|
15
|
+
|
|
16
|
+
When analyzing this error, please keep these project standards in mind:
|
|
17
|
+
|
|
18
|
+
1. **Centralized Error Handling**:
|
|
19
|
+
- Ensure the error uses the standard custom error classes from `src/errors/` (e.g., `ApiError`, `NotFoundError`, `BadRequestError`).
|
|
20
|
+
- If an error occurs in a controller, it should be passed to the global error middleware via `throw` (for async handlers, or `next(error)` in MVC).
|
|
21
|
+
2. **Standard Status Codes**:
|
|
22
|
+
- Verify that appropriate status codes from `httpCodes` are being used correctly, rather than generic 500s unless unexpected.
|
|
23
|
+
3. **Dependencies**:
|
|
24
|
+
- Check if this is a connection issue (e.g., Database, Kafka, Redis) and see if our standard configuration or health checks provide hints.
|
|
25
|
+
4. **Fix Suggestion**:
|
|
26
|
+
- Explain *why* the error happened.
|
|
27
|
+
- Provide a targeted code fix matching our coding style (<%= language %>, <%= architecture %>).
|
|
28
|
+
- Only modify what is strictly necessary to solve the issue.
|