nodejs-quickstart-structure 1.18.0 → 1.19.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 +17 -4
- package/README.md +2 -1
- package/bin/index.js +93 -92
- package/lib/generator.js +1 -1
- package/lib/modules/caching-setup.js +76 -73
- package/lib/modules/config-files.js +4 -0
- package/lib/modules/kafka-setup.js +249 -191
- package/lib/modules/project-setup.js +1 -0
- package/package.json +13 -2
- package/templates/clean-architecture/js/src/errors/BadRequestError.js +11 -10
- package/templates/clean-architecture/js/src/errors/BadRequestError.spec.js.ejs +22 -21
- package/templates/clean-architecture/js/src/errors/NotFoundError.js +11 -10
- package/templates/clean-architecture/js/src/errors/NotFoundError.spec.js.ejs +22 -21
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +69 -39
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +142 -81
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +1 -1
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +156 -75
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +234 -138
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +27 -21
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +66 -49
- package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/user.types.js.ejs +19 -17
- package/templates/clean-architecture/js/src/interfaces/routes/api.js +12 -10
- 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/UpdateUser.js +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/ts/src/errors/BadRequestError.spec.ts.ejs +22 -21
- package/templates/clean-architecture/ts/src/errors/BadRequestError.ts +9 -8
- package/templates/clean-architecture/ts/src/errors/NotFoundError.spec.ts.ejs +22 -21
- package/templates/clean-architecture/ts/src/errors/NotFoundError.ts +9 -8
- package/templates/clean-architecture/ts/src/index.ts.ejs +1 -1
- package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +175 -85
- package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +74 -0
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +331 -185
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +173 -84
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +68 -51
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +29 -21
- package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/user.types.ts.ejs +17 -15
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +13 -11
- 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/updateUser.spec.ts.ejs +48 -0
- package/templates/clean-architecture/ts/src/usecases/updateUser.ts +9 -0
- package/templates/clean-architecture/ts/src/utils/errorMessages.ts +12 -0
- package/templates/common/.gitattributes +46 -0
- package/templates/common/.snyk.ejs +45 -0
- package/templates/common/Dockerfile +17 -9
- package/templates/common/README.md.ejs +295 -263
- package/templates/common/caching/clean/js/DeleteUser.js.ejs +27 -0
- package/templates/common/caching/clean/js/UpdateUser.js.ejs +27 -0
- package/templates/common/caching/clean/ts/deleteUser.ts.ejs +24 -0
- package/templates/common/caching/clean/ts/updateUser.ts.ejs +25 -0
- package/templates/common/caching/ts/memoryCache.ts.ejs +73 -64
- package/templates/common/caching/ts/redisClient.ts.ejs +89 -80
- package/templates/common/database/js/models/User.js.ejs +79 -53
- package/templates/common/database/js/models/User.js.mongoose.ejs +23 -19
- package/templates/common/database/js/models/User.spec.js.ejs +94 -84
- package/templates/common/database/ts/models/User.spec.ts.ejs +100 -84
- package/templates/common/database/ts/models/User.ts.ejs +87 -61
- package/templates/common/database/ts/models/User.ts.mongoose.ejs +30 -25
- package/templates/common/health/js/healthRoute.js.ejs +50 -47
- package/templates/common/health/ts/healthRoute.ts.ejs +49 -46
- package/templates/common/jest.e2e.config.js.ejs +8 -8
- package/templates/common/kafka/js/messaging/baseConsumer.js.ejs +30 -30
- package/templates/common/kafka/js/messaging/userEventSchema.js.ejs +12 -11
- package/templates/common/kafka/js/messaging/welcomeEmailConsumer.js.ejs +44 -31
- package/templates/common/kafka/js/messaging/welcomeEmailConsumer.spec.js.ejs +86 -49
- package/templates/common/kafka/js/services/kafkaService.js.ejs +93 -93
- package/templates/common/kafka/js/utils/kafkaEvents.js.ejs +7 -0
- package/templates/common/kafka/ts/messaging/userEventSchema.spec.ts.ejs +51 -51
- package/templates/common/kafka/ts/messaging/userEventSchema.ts.ejs +12 -11
- package/templates/common/kafka/ts/messaging/welcomeEmailConsumer.spec.ts.ejs +86 -49
- package/templates/common/kafka/ts/messaging/welcomeEmailConsumer.ts.ejs +38 -25
- package/templates/common/kafka/ts/services/kafkaService.ts.ejs +95 -95
- package/templates/common/kafka/ts/utils/kafkaEvents.ts.ejs +5 -0
- package/templates/common/package.json.ejs +10 -2
- package/templates/common/shutdown/js/gracefulShutdown.js.ejs +65 -61
- package/templates/common/shutdown/js/gracefulShutdown.spec.js.ejs +149 -160
- package/templates/common/shutdown/ts/gracefulShutdown.spec.ts.ejs +179 -158
- package/templates/common/shutdown/ts/gracefulShutdown.ts.ejs +59 -55
- package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +120 -49
- package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +120 -49
- package/templates/common/swagger.yml.ejs +118 -66
- package/templates/db/mysql/V1__Initial_Setup.sql.ejs +10 -9
- package/templates/db/postgres/V1__Initial_Setup.sql.ejs +10 -9
- package/templates/mvc/js/src/controllers/userController.js.ejs +246 -105
- package/templates/mvc/js/src/controllers/userController.spec.js.ejs +481 -209
- package/templates/mvc/js/src/errors/BadRequestError.js +11 -10
- package/templates/mvc/js/src/errors/BadRequestError.spec.js.ejs +22 -21
- package/templates/mvc/js/src/errors/NotFoundError.js +11 -10
- package/templates/mvc/js/src/errors/NotFoundError.spec.js.ejs +22 -21
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +25 -19
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +64 -47
- package/templates/mvc/js/src/graphql/typeDefs/user.types.js.ejs +19 -17
- package/templates/mvc/js/src/index.js.ejs +1 -1
- package/templates/mvc/js/src/routes/api.js +10 -8
- package/templates/mvc/js/src/routes/api.spec.js.ejs +41 -36
- package/templates/mvc/js/src/utils/errorMessages.js +14 -0
- package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +481 -203
- package/templates/mvc/ts/src/controllers/userController.ts.ejs +248 -107
- package/templates/mvc/ts/src/errors/BadRequestError.spec.ts.ejs +22 -21
- package/templates/mvc/ts/src/errors/BadRequestError.ts +9 -8
- package/templates/mvc/ts/src/errors/NotFoundError.spec.ts.ejs +27 -21
- package/templates/mvc/ts/src/errors/NotFoundError.ts +9 -8
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +68 -51
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +29 -21
- package/templates/mvc/ts/src/graphql/typeDefs/user.types.ts.ejs +17 -15
- package/templates/mvc/ts/src/index.ts.ejs +156 -153
- package/templates/mvc/ts/src/routes/api.spec.ts.ejs +59 -40
- package/templates/mvc/ts/src/routes/api.ts +12 -10
- package/templates/mvc/ts/src/utils/errorMessages.ts +12 -0
- package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.ts.ejs +0 -37
|
@@ -1,47 +1,50 @@
|
|
|
1
|
-
const express = require('express');
|
|
2
|
-
const router = express.Router();
|
|
3
|
-
const logger = require('<% if (architecture === "MVC") { %>../utils/logger<% } else { %>../../infrastructure/log/logger<% } %>');
|
|
4
|
-
const HTTP_STATUS = require('<% if (architecture === "MVC") { %>../utils/httpCodes<% } else { %>../../utils/httpCodes<% } %>');
|
|
5
|
-
<%_ if (database
|
|
6
|
-
const
|
|
7
|
-
<%_ }
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
<%_ } -%>
|
|
34
|
-
|
|
35
|
-
healthData.database = '
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const logger = require('<% if (architecture === "MVC") { %>../utils/logger<% } else { %>../../infrastructure/log/logger<% } %>');
|
|
4
|
+
const HTTP_STATUS = require('<% if (architecture === "MVC") { %>../utils/httpCodes<% } else { %>../../utils/httpCodes<% } %>');
|
|
5
|
+
<%_ if (database !== 'None') { -%>
|
|
6
|
+
const ERROR_MESSAGES = require('<% if (architecture === "MVC") { %>../utils/errorMessages<% } else { %>../../utils/errorMessages<% } %>');
|
|
7
|
+
<%_ } -%>
|
|
8
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
9
|
+
const mongoose = require('mongoose');
|
|
10
|
+
<%_ } else if (database !== 'None') { -%>
|
|
11
|
+
const sequelize = require('<% if (architecture === "MVC") { %>../config/database<% } else { %>../../infrastructure/database/database<% } %>');
|
|
12
|
+
<%_ } -%>
|
|
13
|
+
|
|
14
|
+
router.get('/', async (req, res) => {
|
|
15
|
+
const healthData = {
|
|
16
|
+
status: 'UP',
|
|
17
|
+
uptime: process.uptime(),
|
|
18
|
+
memory: process.memoryUsage(),
|
|
19
|
+
database: 'disconnected',
|
|
20
|
+
timestamp: Date.now()
|
|
21
|
+
};
|
|
22
|
+
logger.info('Health Check');
|
|
23
|
+
|
|
24
|
+
<%_ if (database !== 'None') { -%>
|
|
25
|
+
try {
|
|
26
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
27
|
+
if (mongoose.connection.readyState === 1) {
|
|
28
|
+
if (mongoose.connection.db && mongoose.connection.db.admin) {
|
|
29
|
+
await mongoose.connection.db.admin().ping();
|
|
30
|
+
}
|
|
31
|
+
healthData.database = 'connected';
|
|
32
|
+
}
|
|
33
|
+
<%_ } else { -%>
|
|
34
|
+
await sequelize.authenticate();
|
|
35
|
+
healthData.database = 'connected';
|
|
36
|
+
<%_ } -%>
|
|
37
|
+
} catch (err) {
|
|
38
|
+
healthData.database = 'error';
|
|
39
|
+
healthData.status = 'DOWN';
|
|
40
|
+
logger.error(`${ERROR_MESSAGES.DATABASE_PING_FAILED}:`, err);
|
|
41
|
+
return res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json(healthData);
|
|
42
|
+
}
|
|
43
|
+
<%_ } else { -%>
|
|
44
|
+
healthData.database = 'None';
|
|
45
|
+
<%_ } -%>
|
|
46
|
+
|
|
47
|
+
res.status(HTTP_STATUS.OK).json(healthData);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
module.exports = router;
|
|
@@ -1,46 +1,49 @@
|
|
|
1
|
-
import { Router, Request, Response } from 'express';
|
|
2
|
-
import logger from '<% if (architecture === "MVC") { %>@/utils/logger<% } else { %>@/infrastructure/log/logger<% } %>';
|
|
3
|
-
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
4
|
-
<%_ if (database
|
|
5
|
-
import
|
|
6
|
-
<%_ }
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<%_ } -%>
|
|
33
|
-
|
|
34
|
-
healthData.database = '
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
1
|
+
import { Router, Request, Response } from 'express';
|
|
2
|
+
import logger from '<% if (architecture === "MVC") { %>@/utils/logger<% } else { %>@/infrastructure/log/logger<% } %>';
|
|
3
|
+
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
4
|
+
<%_ if (database !== 'None') { -%>
|
|
5
|
+
import { ERROR_MESSAGES } from '@/utils/errorMessages';
|
|
6
|
+
<%_ } -%>
|
|
7
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
8
|
+
import mongoose from 'mongoose';
|
|
9
|
+
<%_ } else if (database !== 'None') { -%>
|
|
10
|
+
import sequelize from '<% if (architecture === "MVC") { %>@/config/database<% } else { %>@/infrastructure/database/database<% } %>';
|
|
11
|
+
<%_ } -%>
|
|
12
|
+
|
|
13
|
+
const router = Router();
|
|
14
|
+
|
|
15
|
+
router.get('/', async (req: Request, res: Response) => {
|
|
16
|
+
const healthData: Record<string, unknown> = {
|
|
17
|
+
status: 'UP',
|
|
18
|
+
uptime: process.uptime(),
|
|
19
|
+
memory: process.memoryUsage(),
|
|
20
|
+
database: 'disconnected',
|
|
21
|
+
timestamp: Date.now()
|
|
22
|
+
};
|
|
23
|
+
logger.info('Health Check');
|
|
24
|
+
|
|
25
|
+
<%_ if (database !== 'None') { -%>
|
|
26
|
+
try {
|
|
27
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
28
|
+
if (mongoose.connection.readyState === 1) {
|
|
29
|
+
await mongoose.connection.db?.admin().ping();
|
|
30
|
+
healthData.database = 'connected';
|
|
31
|
+
}
|
|
32
|
+
<%_ } else { -%>
|
|
33
|
+
await sequelize.authenticate();
|
|
34
|
+
healthData.database = 'connected';
|
|
35
|
+
<%_ } -%>
|
|
36
|
+
} catch (err) {
|
|
37
|
+
healthData.database = 'error';
|
|
38
|
+
healthData.status = 'DOWN';
|
|
39
|
+
logger.error(`${ERROR_MESSAGES.DATABASE_PING_FAILED}:`, err);
|
|
40
|
+
return res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json(healthData);
|
|
41
|
+
}
|
|
42
|
+
<%_ } else { -%>
|
|
43
|
+
healthData.database = 'None';
|
|
44
|
+
<%_ } -%>
|
|
45
|
+
|
|
46
|
+
res.status(HTTP_STATUS.OK).json(healthData);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export default router;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
<%_ if (language === 'TypeScript') {
|
|
2
|
-
module.exports = {
|
|
3
|
-
...require('./jest.config'),
|
|
4
|
-
testMatch: ['<rootDir>/tests/e2e/**/*.test.ts', '<rootDir>/tests/e2e/**/*.test.js'],
|
|
5
|
-
testPathIgnorePatterns: ['/node_modules/'],
|
|
6
|
-
testTimeout: 30000,
|
|
7
|
-
clearMocks: true
|
|
8
|
-
};
|
|
1
|
+
<%_ if (language === 'TypeScript') { -%>/* eslint-disable @typescript-eslint/no-require-imports */<%_ } -%>
|
|
2
|
+
module.exports = {
|
|
3
|
+
...require('./jest.config'),
|
|
4
|
+
testMatch: ['<rootDir>/tests/e2e/**/*.test.ts', '<rootDir>/tests/e2e/**/*.test.js'],
|
|
5
|
+
testPathIgnorePatterns: ['/node_modules/'],
|
|
6
|
+
testTimeout: 30000,
|
|
7
|
+
clearMocks: true
|
|
8
|
+
};
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
const logger = require('
|
|
2
|
-
|
|
3
|
-
class BaseConsumer {
|
|
4
|
-
constructor() {
|
|
5
|
-
if (this.constructor === BaseConsumer) {
|
|
6
|
-
throw new Error("Abstract class 'BaseConsumer' cannot be instantiated.");
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
get topic() { throw new Error("Property 'topic' must be implemented"); }
|
|
11
|
-
get groupId() { throw new Error("Property 'groupId' must be implemented"); }
|
|
12
|
-
|
|
13
|
-
async onMessage({ message }) {
|
|
14
|
-
try {
|
|
15
|
-
const rawValue = message.value?.toString();
|
|
16
|
-
if (!rawValue) return;
|
|
17
|
-
|
|
18
|
-
const data = JSON.parse(rawValue);
|
|
19
|
-
await this.handle(data);
|
|
20
|
-
} catch (error) {
|
|
21
|
-
logger.error(`[Kafka] Error processing message on topic ${this.topic}:`, error);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async handle(data) {
|
|
26
|
-
throw new Error("Method 'handle()' must be implemented");
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
module.exports = BaseConsumer;
|
|
1
|
+
const logger = require('<%- loggerPath %>');
|
|
2
|
+
|
|
3
|
+
class BaseConsumer {
|
|
4
|
+
constructor() {
|
|
5
|
+
if (this.constructor === BaseConsumer) {
|
|
6
|
+
throw new Error("Abstract class 'BaseConsumer' cannot be instantiated.");
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get topic() { throw new Error("Property 'topic' must be implemented"); }
|
|
11
|
+
get groupId() { throw new Error("Property 'groupId' must be implemented"); }
|
|
12
|
+
|
|
13
|
+
async onMessage({ message }) {
|
|
14
|
+
try {
|
|
15
|
+
const rawValue = message.value?.toString();
|
|
16
|
+
if (!rawValue) return;
|
|
17
|
+
|
|
18
|
+
const data = JSON.parse(rawValue);
|
|
19
|
+
await this.handle(data);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
logger.error(`[Kafka] Error processing message on topic ${this.topic}:`, error);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async handle(data) {
|
|
26
|
+
throw new Error("Method 'handle()' must be implemented");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = BaseConsumer;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
const { z } = require('zod');
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { KAFKA_ACTIONS } = require('<%- kafkaEventsPath %>');
|
|
3
|
+
|
|
4
|
+
const UserEventSchema = z.object({
|
|
5
|
+
action: z.enum(Object.values(KAFKA_ACTIONS)),
|
|
6
|
+
payload: z.object({
|
|
7
|
+
id: z.union([z.string(), z.number()]),
|
|
8
|
+
email: z.string().email().optional(),
|
|
9
|
+
}),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
module.exports = { UserEventSchema };
|
|
@@ -1,31 +1,44 @@
|
|
|
1
|
-
const BaseConsumer = require('
|
|
2
|
-
const logger = require('
|
|
3
|
-
const { UserEventSchema } = require('
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
1
|
+
const BaseConsumer = require('<%- baseConsumerPath %>');
|
|
2
|
+
const logger = require('<%- loggerPath %>');
|
|
3
|
+
const { UserEventSchema } = require('<%- userEventSchemaPath %>');
|
|
4
|
+
const ERROR_MESSAGES = require('<%- errorMessagesPath %>');
|
|
5
|
+
const { KAFKA_ACTIONS } = require('<%- kafkaEventsPath %>');
|
|
6
|
+
|
|
7
|
+
class WelcomeEmailConsumer extends BaseConsumer {
|
|
8
|
+
constructor() {
|
|
9
|
+
super();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get topic() { return 'user-topic'; }
|
|
13
|
+
get groupId() { return 'welcome-email-group'; }
|
|
14
|
+
|
|
15
|
+
async handle(data) {
|
|
16
|
+
const result = UserEventSchema.safeParse(data);
|
|
17
|
+
|
|
18
|
+
if (!result.success) {
|
|
19
|
+
logger.error(`[Kafka] ${ERROR_MESSAGES.INVALID_USER_DATA}:`, result.error.format());
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { action, payload } = result.data;
|
|
24
|
+
|
|
25
|
+
switch (action) {
|
|
26
|
+
case KAFKA_ACTIONS.USER_CREATED:
|
|
27
|
+
logger.info(`[Kafka] Consumer: Received ${KAFKA_ACTIONS.USER_CREATED}.`);
|
|
28
|
+
logger.info(`[Kafka] Consumer: 📧 Sending welcome email to '${payload.email}'... Done!`);
|
|
29
|
+
break;
|
|
30
|
+
case KAFKA_ACTIONS.USER_UPDATED:
|
|
31
|
+
logger.info(`[Kafka] Consumer: Received ${KAFKA_ACTIONS.USER_UPDATED}.`);
|
|
32
|
+
logger.info(`[Kafka] Consumer: 🔄 Updating user records for '${payload.id}' (Email: ${payload.email})... Done!`);
|
|
33
|
+
break;
|
|
34
|
+
case KAFKA_ACTIONS.USER_DELETED:
|
|
35
|
+
logger.info(`[Kafka] Consumer: Received ${KAFKA_ACTIONS.USER_DELETED}.`);
|
|
36
|
+
logger.info(`[Kafka] Consumer: 🗑️ Cleaning up data for user '${payload.id}'... Done!`);
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
logger.warn(`[Kafka] Unknown action: ${action}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = WelcomeEmailConsumer;
|
|
@@ -1,49 +1,86 @@
|
|
|
1
|
-
const WelcomeEmailConsumer = require('<% if (architecture === "Clean Architecture") { %>@/interfaces/messaging/consumers/instances/welcomeEmailConsumer<% } else { %>@/messaging/consumers/instances/welcomeEmailConsumer<% } %>');
|
|
2
|
-
const logger = require('<% if (architecture === "Clean Architecture") { %>@/infrastructure/log/logger<% } else { %>@/utils/logger<% } %>');
|
|
3
|
-
|
|
4
|
-
jest.mock('<% if (architecture === "Clean Architecture") { %>@/infrastructure/log/logger<% } else { %>@/utils/logger<% } %>');
|
|
5
|
-
|
|
6
|
-
describe('WelcomeEmailConsumer', () => {
|
|
7
|
-
let consumer;
|
|
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
|
|
34
|
-
const data = {
|
|
35
|
-
action: '
|
|
36
|
-
payload: {
|
|
37
|
-
id: 1,
|
|
38
|
-
email: '
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
await consumer.handle(data);
|
|
43
|
-
|
|
44
|
-
expect(logger.
|
|
45
|
-
expect.stringContaining('[Kafka]
|
|
46
|
-
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
|
|
1
|
+
const WelcomeEmailConsumer = require('<% if (architecture === "Clean Architecture") { %>@/interfaces/messaging/consumers/instances/welcomeEmailConsumer<% } else { %>@/messaging/consumers/instances/welcomeEmailConsumer<% } %>');
|
|
2
|
+
const logger = require('<% if (architecture === "Clean Architecture") { %>@/infrastructure/log/logger<% } else { %>@/utils/logger<% } %>');
|
|
3
|
+
|
|
4
|
+
jest.mock('<% if (architecture === "Clean Architecture") { %>@/infrastructure/log/logger<% } else { %>@/utils/logger<% } %>');
|
|
5
|
+
|
|
6
|
+
describe('WelcomeEmailConsumer', () => {
|
|
7
|
+
let consumer;
|
|
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
|
+
});
|