nodejs-quickstart-structure 1.14.0 → 1.16.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/CHANGELOG.md +32 -0
- package/bin/index.js +84 -80
- package/lib/generator.js +11 -1
- package/lib/modules/app-setup.js +3 -3
- package/lib/modules/config-files.js +27 -2
- package/lib/modules/kafka-setup.js +70 -24
- package/package.json +8 -3
- package/templates/clean-architecture/js/src/index.js.ejs +1 -3
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +11 -10
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +16 -1
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +36 -0
- package/templates/clean-architecture/ts/src/config/swagger.ts.ejs +1 -1
- package/templates/clean-architecture/ts/src/index.ts.ejs +12 -14
- package/templates/clean-architecture/ts/src/infrastructure/log/logger.spec.ts.ejs +0 -1
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +19 -0
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +16 -0
- package/templates/common/.cursorrules.ejs +60 -0
- package/templates/common/.dockerignore +2 -0
- package/templates/common/.gitlab-ci.yml.ejs +5 -5
- package/templates/common/Dockerfile +2 -0
- package/templates/common/Jenkinsfile.ejs +1 -1
- package/templates/common/README.md.ejs +34 -1
- package/templates/common/_github/workflows/ci.yml +7 -4
- package/templates/common/database/js/models/User.js.ejs +2 -1
- package/templates/common/database/ts/models/User.ts.ejs +4 -3
- package/templates/common/eslint.config.mjs.ejs +30 -3
- package/templates/common/jest.config.js.ejs +4 -1
- 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 +11 -0
- package/templates/common/kafka/js/messaging/userEventSchema.spec.js.ejs +27 -0
- package/templates/common/kafka/js/messaging/welcomeEmailConsumer.js.ejs +31 -0
- package/templates/common/kafka/js/messaging/welcomeEmailConsumer.spec.js.ejs +49 -0
- package/templates/common/kafka/js/services/kafkaService.js.ejs +75 -23
- package/templates/common/kafka/js/services/kafkaService.spec.js.ejs +53 -7
- 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 +11 -0
- package/templates/common/kafka/ts/messaging/welcomeEmailConsumer.spec.ts.ejs +49 -0
- package/templates/common/kafka/ts/messaging/welcomeEmailConsumer.ts.ejs +25 -0
- package/templates/common/kafka/ts/services/kafkaService.spec.ts.ejs +22 -2
- package/templates/common/kafka/ts/services/kafkaService.ts.ejs +72 -12
- package/templates/common/package.json.ejs +6 -4
- 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/mvc/js/src/controllers/userController.js.ejs +14 -0
- package/templates/mvc/js/src/controllers/userController.spec.js.ejs +39 -0
- package/templates/mvc/js/src/index.js.ejs +12 -11
- package/templates/mvc/ts/src/config/swagger.ts.ejs +1 -1
- package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +18 -0
- package/templates/mvc/ts/src/controllers/userController.ts.ejs +16 -0
- package/templates/mvc/ts/src/index.ts.ejs +13 -16
- package/templates/mvc/ts/src/utils/logger.spec.ts.ejs +0 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "<% if (language === 'TypeScript') { %>node dist/index.js<% } else { %>node src/index.js<% } %>",
|
|
8
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') { %> && cpx \"src/**/*.yml\" dist/<% } %>"<% } %>,
|
|
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
10
|
"deploy": "npx pm2 start ecosystem.config.js --env production",
|
|
11
11
|
"lint": "eslint .",
|
|
12
12
|
"lint:fix": "eslint . --fix",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"express-rate-limit": "^7.1.5",
|
|
48
48
|
"winston": "^3.11.0",
|
|
49
49
|
"winston-daily-rotate-file": "^5.0.0",
|
|
50
|
-
"morgan": "^1.10.0"<% if (communication === 'REST APIs') { %>,
|
|
50
|
+
"morgan": "^1.10.0"<% if (communication === 'REST APIs' || communication === 'Kafka') { %>,
|
|
51
51
|
"swagger-ui-express": "^5.0.0",
|
|
52
52
|
"yamljs": "^0.3.0"<% } %><% if (communication === 'GraphQL') { %>,
|
|
53
53
|
"@apollo/server": "^4.10.0",
|
|
@@ -72,16 +72,18 @@
|
|
|
72
72
|
"@types/sequelize": "^4.28.19",
|
|
73
73
|
<%_ } -%>
|
|
74
74
|
"@types/morgan": "^1.9.9",
|
|
75
|
-
"rimraf": "^6.0.1"<% if ((viewEngine && viewEngine !== 'None') || communication === 'REST APIs') { %>,
|
|
75
|
+
"rimraf": "^6.0.1"<% if ((viewEngine && viewEngine !== 'None') || communication === 'REST APIs' || communication === 'Kafka') { %>,
|
|
76
76
|
"cpx2": "^8.0.0"<% } %><% } %>,
|
|
77
77
|
"eslint": "^9.20.1",
|
|
78
78
|
"@eslint/js": "^9.20.0",
|
|
79
79
|
"globals": "^15.14.0",
|
|
80
80
|
"prettier": "^3.5.1",
|
|
81
81
|
"eslint-config-prettier": "^10.0.1",
|
|
82
|
+
"eslint-plugin-import-x": "^4.6.1",
|
|
83
|
+
"eslint-import-resolver-typescript": "^3.7.0",
|
|
82
84
|
"husky": "^8.0.3",
|
|
83
85
|
"lint-staged": "^15.4.3"<% if (language === 'TypeScript') { %>,
|
|
84
|
-
"typescript-eslint": "^8.24.1",<%_ if (communication === 'REST APIs') { %>
|
|
86
|
+
"typescript-eslint": "^8.24.1",<%_ if (communication === 'REST APIs' || communication === 'Kafka') { %>
|
|
85
87
|
"@types/swagger-ui-express": "^4.1.6",
|
|
86
88
|
"@types/yamljs": "^0.2.34",<%_ } %>
|
|
87
89
|
"jest": "^29.7.0",
|
|
@@ -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 > 70%!
|
|
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 > 70% 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.
|
|
@@ -40,6 +40,13 @@ const createUser = async (data) => {
|
|
|
40
40
|
const user = await User.create({ name, email });
|
|
41
41
|
<%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
|
|
42
42
|
await cacheService.del('users:all');
|
|
43
|
+
<%_ } -%>
|
|
44
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
45
|
+
const { sendMessage } = require('../services/kafkaService');
|
|
46
|
+
await sendMessage('user-topic', JSON.stringify({
|
|
47
|
+
action: 'USER_CREATED',
|
|
48
|
+
payload: { id: user.id || user._id, email: user.email }
|
|
49
|
+
}));
|
|
43
50
|
<%_ } -%>
|
|
44
51
|
return user;
|
|
45
52
|
} catch (error) {
|
|
@@ -78,6 +85,13 @@ const createUser = async (req, res, next) => {
|
|
|
78
85
|
const user = await User.create({ name, email });
|
|
79
86
|
<%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
|
|
80
87
|
await cacheService.del('users:all');
|
|
88
|
+
<%_ } -%>
|
|
89
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
90
|
+
const { sendMessage } = require('../services/kafkaService');
|
|
91
|
+
await sendMessage('user-topic', JSON.stringify({
|
|
92
|
+
action: 'USER_CREATED',
|
|
93
|
+
payload: { id: user.id || user._id, email: user.email }
|
|
94
|
+
}));
|
|
81
95
|
<%_ } -%>
|
|
82
96
|
res.status(HTTP_STATUS.CREATED).json(user);
|
|
83
97
|
} catch (error) {
|
|
@@ -23,6 +23,14 @@ jest.mock('@/config/memoryCache', () => ({
|
|
|
23
23
|
}));
|
|
24
24
|
<%_ } -%>
|
|
25
25
|
jest.mock('@/utils/logger');
|
|
26
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
27
|
+
const { sendMessage } = require('@/services/kafkaService');
|
|
28
|
+
jest.mock('@/services/kafkaService', () => ({
|
|
29
|
+
sendMessage: jest.fn().mockResolvedValue(undefined),
|
|
30
|
+
connectKafka: jest.fn().mockResolvedValue(undefined)
|
|
31
|
+
}));
|
|
32
|
+
<%_ } -%>
|
|
33
|
+
|
|
26
34
|
|
|
27
35
|
describe('UserController', () => {
|
|
28
36
|
<% if (communication !== 'GraphQL') { -%>
|
|
@@ -143,6 +151,9 @@ describe('UserController', () => {
|
|
|
143
151
|
<% } -%>
|
|
144
152
|
<%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
|
|
145
153
|
expect(cacheService.del).toHaveBeenCalledWith('users:all');
|
|
154
|
+
<%_ } -%>
|
|
155
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
156
|
+
expect(sendMessage).toHaveBeenCalled();
|
|
146
157
|
<%_ } -%>
|
|
147
158
|
});
|
|
148
159
|
|
|
@@ -166,5 +177,33 @@ describe('UserController', () => {
|
|
|
166
177
|
expect(mockNext).toHaveBeenCalledWith(error);
|
|
167
178
|
<% } -%>
|
|
168
179
|
});
|
|
180
|
+
|
|
181
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
182
|
+
it('should successfully create a new user with _id for Kafka (Happy Path)', async () => {
|
|
183
|
+
// Arrange
|
|
184
|
+
const payload = { name: 'Bob', email: 'bob@example.com' };
|
|
185
|
+
<% if (communication === 'GraphQL') { -%>
|
|
186
|
+
const dataArg = payload;
|
|
187
|
+
<% } else { -%>
|
|
188
|
+
mockRequest.body = payload;
|
|
189
|
+
<% } -%>
|
|
190
|
+
|
|
191
|
+
const expectedUser = { _id: '2', ...payload };
|
|
192
|
+
User.create.mockResolvedValue(expectedUser);
|
|
193
|
+
|
|
194
|
+
// Act
|
|
195
|
+
<% if (communication === 'GraphQL') { -%>
|
|
196
|
+
await createUser(dataArg);
|
|
197
|
+
<% } else { -%>
|
|
198
|
+
await createUser(mockRequest, mockResponse, mockNext);
|
|
199
|
+
<% } -%>
|
|
200
|
+
|
|
201
|
+
// Assert
|
|
202
|
+
expect(sendMessage).toHaveBeenCalledWith(
|
|
203
|
+
'user-topic',
|
|
204
|
+
expect.stringContaining('"id":"2"')
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
<%_ } -%>
|
|
169
208
|
});
|
|
170
209
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const cors = require('cors');
|
|
3
|
-
<%_ if (communication === 'REST APIs') { -%>const apiRoutes = require('./routes/api');<%_ } %>
|
|
3
|
+
<%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>const apiRoutes = require('./routes/api');<%_ } %>
|
|
4
4
|
const healthRoutes = require('./routes/healthRoute');
|
|
5
5
|
<%_ if (communication === 'Kafka') { -%>const { connectKafka, sendMessage } = require('./services/kafkaService');<%_ } -%>
|
|
6
6
|
<%_ if (communication === 'GraphQL') { -%>
|
|
@@ -11,8 +11,8 @@ const { unwrapResolverError } = require('@apollo/server/errors');
|
|
|
11
11
|
const { ApiError } = require('./errors/ApiError');
|
|
12
12
|
const { typeDefs, resolvers } = require('./graphql');
|
|
13
13
|
const { gqlContext } = require('./graphql/context');
|
|
14
|
-
<% }
|
|
15
|
-
<%_ if (communication === 'REST APIs') { -%>
|
|
14
|
+
<% } %>
|
|
15
|
+
<%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>
|
|
16
16
|
const swaggerUi = require('swagger-ui-express');
|
|
17
17
|
const swaggerSpecs = require('./config/swagger');
|
|
18
18
|
<%_ } -%>
|
|
@@ -28,7 +28,7 @@ app.use(cors());
|
|
|
28
28
|
app.use(express.json());
|
|
29
29
|
app.use(morgan('combined', { stream: { write: message => logger.info(message.trim()) } }));
|
|
30
30
|
|
|
31
|
-
<%_ if (communication === 'REST APIs') { -%>
|
|
31
|
+
<%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>
|
|
32
32
|
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
|
|
33
33
|
<%_ } -%>
|
|
34
34
|
<%_ if (viewEngine === 'EJS' || viewEngine === 'Pug') { -%>
|
|
@@ -37,7 +37,7 @@ const path = require('path');
|
|
|
37
37
|
app.set('views', path.join(__dirname, 'views'));
|
|
38
38
|
app.set('view engine', '<%= viewEngine.toLowerCase() %>');
|
|
39
39
|
app.use(express.static(path.join(__dirname, '../public')));<%_ } %>
|
|
40
|
-
<%_ if (communication === 'REST APIs') { -%>
|
|
40
|
+
<%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>
|
|
41
41
|
app.use('/api', apiRoutes);
|
|
42
42
|
<%_ } -%><% if (viewEngine && viewEngine !== 'None') { -%>
|
|
43
43
|
app.get('/', (req, res) => {
|
|
@@ -86,12 +86,13 @@ const startServer = async () => {
|
|
|
86
86
|
const server = app.listen(PORT, () => {
|
|
87
87
|
logger.info(`Server running on port ${PORT}`);
|
|
88
88
|
<%_ if (communication === 'Kafka') { -%>
|
|
89
|
-
connectKafka()
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
connectKafka()
|
|
90
|
+
.then(async () => {
|
|
91
|
+
logger.info('Kafka connected');
|
|
92
|
+
})
|
|
93
|
+
.catch(err => {
|
|
94
|
+
logger.error('Failed to connect to Kafka after retries:', err.message);
|
|
95
|
+
});
|
|
95
96
|
<%_ } -%>
|
|
96
97
|
});
|
|
97
98
|
|
|
@@ -24,6 +24,20 @@ jest.mock('@/config/memoryCache', () => ({
|
|
|
24
24
|
}));
|
|
25
25
|
<%_ } -%>
|
|
26
26
|
jest.mock('@/utils/logger');
|
|
27
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
28
|
+
jest.mock('@/services/kafkaService', () => {
|
|
29
|
+
const mockSendMessage = jest.fn().mockResolvedValue(undefined);
|
|
30
|
+
return {
|
|
31
|
+
kafkaService: {
|
|
32
|
+
sendMessage: mockSendMessage
|
|
33
|
+
},
|
|
34
|
+
KafkaService: jest.fn().mockImplementation(() => ({
|
|
35
|
+
sendMessage: mockSendMessage
|
|
36
|
+
}))
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
<%_ } -%>
|
|
40
|
+
|
|
27
41
|
|
|
28
42
|
describe('UserController', () => {
|
|
29
43
|
let userController: UserController;
|
|
@@ -158,6 +172,10 @@ describe('UserController', () => {
|
|
|
158
172
|
<% } -%>
|
|
159
173
|
<%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
|
|
160
174
|
expect(cacheService.del).toHaveBeenCalledWith('users:all');
|
|
175
|
+
<%_ } -%>
|
|
176
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
177
|
+
const { kafkaService } = require('@/services/kafkaService');
|
|
178
|
+
expect(kafkaService.sendMessage).toHaveBeenCalled();
|
|
161
179
|
<%_ } -%>
|
|
162
180
|
});
|
|
163
181
|
|
|
@@ -42,6 +42,14 @@ export class UserController {
|
|
|
42
42
|
const user = await User.create({ name, email });
|
|
43
43
|
<%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
|
|
44
44
|
await cacheService.del('users:all');
|
|
45
|
+
<%_ } -%>
|
|
46
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
47
|
+
const { kafkaService } = await import('@/services/kafkaService');
|
|
48
|
+
await kafkaService.sendMessage('user-topic', JSON.stringify({
|
|
49
|
+
action: 'USER_CREATED',
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
+
payload: { id: (user as any).id || (user as any)._id, email: user.email }
|
|
52
|
+
}));
|
|
45
53
|
<%_ } -%>
|
|
46
54
|
return user;
|
|
47
55
|
} catch (error) {
|
|
@@ -80,6 +88,14 @@ export class UserController {
|
|
|
80
88
|
const user = await User.create({ name, email });
|
|
81
89
|
<%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
|
|
82
90
|
await cacheService.del('users:all');
|
|
91
|
+
<%_ } -%>
|
|
92
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
93
|
+
const { kafkaService } = await import('@/services/kafkaService');
|
|
94
|
+
await kafkaService.sendMessage('user-topic', JSON.stringify({
|
|
95
|
+
action: 'USER_CREATED',
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
97
|
+
payload: { id: (user as any).id || (user as any)._id, email: user.email }
|
|
98
|
+
}));
|
|
83
99
|
<%_ } -%>
|
|
84
100
|
res.status(HTTP_STATUS.CREATED).json(user);
|
|
85
101
|
} catch (error) {
|
|
@@ -9,12 +9,11 @@ import morgan from 'morgan';
|
|
|
9
9
|
import { errorMiddleware } from '@/utils/errorMiddleware';
|
|
10
10
|
import { setupGracefulShutdown } from '@/utils/gracefulShutdown';
|
|
11
11
|
import healthRoutes from '@/routes/healthRoute';
|
|
12
|
-
<%_ if (communication === 'REST APIs') { -%>
|
|
13
|
-
import apiRoutes from '@/routes/api'
|
|
14
|
-
<% if (communication === 'REST APIs') { %>
|
|
12
|
+
<%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>
|
|
13
|
+
import apiRoutes from '@/routes/api';
|
|
15
14
|
import swaggerUi from 'swagger-ui-express';
|
|
16
|
-
import swaggerSpecs from '@/config/swagger';<% }
|
|
17
|
-
<%_ if (communication === 'Kafka') { -%>import {
|
|
15
|
+
import swaggerSpecs from '@/config/swagger';<%_ } %>
|
|
16
|
+
<%_ if (communication === 'Kafka') { -%>import { kafkaService } from '@/services/kafkaService';<%_ } -%>
|
|
18
17
|
<%_ if (communication === 'GraphQL') { -%>
|
|
19
18
|
import { ApolloServer } from '@apollo/server';
|
|
20
19
|
import { expressMiddleware } from '@apollo/server/express4';
|
|
@@ -51,7 +50,7 @@ app.use(limiter);
|
|
|
51
50
|
|
|
52
51
|
app.use(express.json());
|
|
53
52
|
app.use(morgan('combined', { stream: { write: (message) => logger.info(message.trim()) } }));
|
|
54
|
-
<%_ if (communication === 'REST APIs') { -%>
|
|
53
|
+
<%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>
|
|
55
54
|
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
|
|
56
55
|
<%_ } -%>
|
|
57
56
|
<%_ if (viewEngine === 'EJS' || viewEngine === 'Pug') { -%>
|
|
@@ -60,7 +59,7 @@ import path from 'path';
|
|
|
60
59
|
app.set('views', path.join(__dirname, 'views'));
|
|
61
60
|
app.set('view engine', '<%= viewEngine.toLowerCase() %>');
|
|
62
61
|
app.use(express.static(path.join(__dirname, '../public')));<%_ } %>
|
|
63
|
-
<%_ if (communication === 'REST APIs') { -%>
|
|
62
|
+
<%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>
|
|
64
63
|
app.use('/api', apiRoutes);
|
|
65
64
|
<%_ } -%><% if (viewEngine && viewEngine !== 'None') { -%>
|
|
66
65
|
app.get('/', (req: Request, res: Response) => {
|
|
@@ -106,18 +105,16 @@ const startServer = async () => {
|
|
|
106
105
|
app.use('/graphql', expressMiddleware(apolloServer, { context: gqlContext }));
|
|
107
106
|
<%_ } -%>
|
|
108
107
|
app.use(errorMiddleware);
|
|
109
|
-
<%_ if (communication === 'Kafka') { -%>
|
|
110
|
-
const kafkaService = new KafkaService();
|
|
111
|
-
<%_ } -%>
|
|
112
108
|
const server = app.listen(port, () => {
|
|
113
109
|
logger.info(`Server running on port ${port}`);
|
|
114
110
|
<%_ if (communication === 'Kafka') { -%>
|
|
115
|
-
kafkaService.connect()
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
111
|
+
kafkaService.connect()
|
|
112
|
+
.then(async () => {
|
|
113
|
+
logger.info('Kafka connected');
|
|
114
|
+
})
|
|
115
|
+
.catch(err => {
|
|
116
|
+
logger.error('Failed to connect to Kafka after retries:', (err as Error).message);
|
|
117
|
+
});
|
|
121
118
|
<%_ } -%>
|
|
122
119
|
});
|
|
123
120
|
|
|
@@ -56,7 +56,6 @@ describe('Logger', () => {
|
|
|
56
56
|
const winston = require('winston');
|
|
57
57
|
jest.resetModules();
|
|
58
58
|
process.env.NODE_ENV = 'production';
|
|
59
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
60
59
|
require('<% if (architecture === "MVC") { %>@/utils/logger<% } else { %>@/infrastructure/log/logger<% } %>');
|
|
61
60
|
expect(winston.format.json).toHaveBeenCalled();
|
|
62
61
|
process.env.NODE_ENV = 'test';
|