nodejs-quickstart-structure 1.18.1 → 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 +7 -0
- package/README.md +2 -1
- package/lib/modules/caching-setup.js +76 -73
- 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/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/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/README.md.ejs +294 -270
- 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/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/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,191 +1,249 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import ejs from 'ejs';
|
|
4
|
-
|
|
5
|
-
export const setupKafka = async (templatesDir, targetDir, config) => {
|
|
6
|
-
const { communication, architecture, language } = config;
|
|
7
|
-
if (communication !== 'Kafka') return;
|
|
8
|
-
|
|
9
|
-
const langExt = language === 'TypeScript' ? 'ts' : 'js';
|
|
10
|
-
const kafkaSource = path.join(templatesDir, 'common', 'kafka', langExt);
|
|
11
|
-
|
|
12
|
-
// 1. Copy necessary directories individually (to avoid orphaned templates in src)
|
|
13
|
-
if (await fs.pathExists(path.join(kafkaSource, 'services'))) {
|
|
14
|
-
await fs.copy(path.join(kafkaSource, 'services'), path.join(targetDir, 'src/services'));
|
|
15
|
-
}
|
|
16
|
-
if (await fs.pathExists(path.join(kafkaSource, 'config'))) {
|
|
17
|
-
await fs.copy(path.join(kafkaSource, 'config'), path.join(targetDir, 'src/config'));
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Render Kafka Service with dynamic logger path
|
|
21
|
-
const kafkaServiceFileName = `kafkaService.${langExt}`;
|
|
22
|
-
const kafkaServiceTemplate = path.join(targetDir, 'src', 'services', `${kafkaServiceFileName}.ejs`);
|
|
23
|
-
// Render Kafka Service Spec
|
|
24
|
-
const kafkaSpecFileName = `kafkaService.spec.${langExt}`;
|
|
25
|
-
const kafkaSpecTemplate = path.join(targetDir, 'src', 'services', `${kafkaSpecFileName}.ejs`);
|
|
26
|
-
|
|
27
|
-
if (await fs.pathExists(kafkaServiceTemplate)) {
|
|
28
|
-
let serviceLoggerPath, serviceConfigPath;
|
|
29
|
-
if (language === 'TypeScript') {
|
|
30
|
-
serviceLoggerPath = architecture === 'Clean Architecture' ? '@/infrastructure/log/logger' : '@/utils/logger';
|
|
31
|
-
serviceConfigPath = architecture === 'Clean Architecture' ? '@/infrastructure/config/kafka' : '@/config/kafka';
|
|
32
|
-
} else {
|
|
33
|
-
serviceLoggerPath = architecture === 'Clean Architecture' ? '../../infrastructure/log/logger' : '../utils/logger';
|
|
34
|
-
serviceConfigPath = architecture === 'Clean Architecture' ? '../../infrastructure/config/kafka' : '../config/kafka';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const content = ejs.render(await fs.readFile(kafkaServiceTemplate, 'utf-8'), { ...config, loggerPath: serviceLoggerPath, configPath: serviceConfigPath });
|
|
38
|
-
await fs.writeFile(path.join(targetDir, 'src', 'services', kafkaServiceFileName), content);
|
|
39
|
-
await fs.remove(kafkaServiceTemplate);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (await fs.pathExists(kafkaSpecTemplate)) {
|
|
43
|
-
let specLoggerPath, specConfigPath, specServicePath;
|
|
44
|
-
if (language === 'TypeScript') {
|
|
45
|
-
specLoggerPath = architecture === 'Clean Architecture' ? '@/infrastructure/log/logger' : '@/utils/logger';
|
|
46
|
-
specConfigPath = architecture === 'Clean Architecture' ? '@/infrastructure/config/kafka' : '@/config/kafka';
|
|
47
|
-
specServicePath = architecture === 'Clean Architecture' ? '@/infrastructure/messaging/kafkaClient' : '@/services/kafkaService';
|
|
48
|
-
} else {
|
|
49
|
-
specLoggerPath = architecture === 'Clean Architecture' ? '../../infrastructure/log/logger' : '../utils/logger';
|
|
50
|
-
specConfigPath = architecture === 'Clean Architecture' ? '../../infrastructure/config/kafka' : '../config/kafka';
|
|
51
|
-
specServicePath = architecture === 'Clean Architecture' ? '../../infrastructure/messaging/kafkaClient' : '../services/kafkaService';
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const specContent = ejs.render(await fs.readFile(kafkaSpecTemplate, 'utf-8'), { ...config, loggerPath: specLoggerPath, configPath: specConfigPath, servicePath: specServicePath });
|
|
55
|
-
await fs.writeFile(path.join(targetDir, 'src', 'services', kafkaSpecFileName), specContent);
|
|
56
|
-
await fs.remove(kafkaSpecTemplate);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Render Kafka Config Spec
|
|
60
|
-
const kafkaConfigSpecFileName = `kafka.spec.${langExt}`;
|
|
61
|
-
const kafkaConfigSpecTemplate = path.join(templatesDir, 'common', 'kafka', langExt, 'config', `${kafkaConfigSpecFileName}.ejs`);
|
|
62
|
-
if (await fs.pathExists(kafkaConfigSpecTemplate)) {
|
|
63
|
-
const specContent = ejs.render(await fs.readFile(kafkaConfigSpecTemplate, 'utf-8'), { ...config });
|
|
64
|
-
let specTarget;
|
|
65
|
-
if (architecture === 'MVC') {
|
|
66
|
-
specTarget = path.join(targetDir, 'tests', 'unit', 'config', kafkaConfigSpecFileName);
|
|
67
|
-
} else {
|
|
68
|
-
specTarget = path.join(targetDir, 'tests', 'unit', 'infrastructure', 'config', kafkaConfigSpecFileName);
|
|
69
|
-
}
|
|
70
|
-
await fs.ensureDir(path.dirname(specTarget));
|
|
71
|
-
await fs.writeFile(specTarget, specContent);
|
|
72
|
-
|
|
73
|
-
const targetSpecTemplate = path.join(targetDir, 'src', 'config', `${kafkaConfigSpecFileName}.ejs`);
|
|
74
|
-
if (await fs.pathExists(targetSpecTemplate)) {
|
|
75
|
-
await fs.remove(targetSpecTemplate);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (architecture === 'Clean Architecture') {
|
|
80
|
-
// Clean Architecture Restructuring
|
|
81
|
-
await fs.ensureDir(path.join(targetDir, 'src/infrastructure/messaging'));
|
|
82
|
-
await fs.ensureDir(path.join(targetDir, 'tests/unit/infrastructure/messaging'));
|
|
83
|
-
await fs.ensureDir(path.join(targetDir, 'src/infrastructure/config'));
|
|
84
|
-
|
|
85
|
-
const serviceExt = language === 'TypeScript' ? 'ts' : 'js';
|
|
86
|
-
|
|
87
|
-
await fs.move(
|
|
88
|
-
path.join(targetDir, `src/services/kafkaService.${serviceExt}`),
|
|
89
|
-
path.join(targetDir, `src/infrastructure/messaging/kafkaClient.${serviceExt}`),
|
|
90
|
-
{ overwrite: true }
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
if (await fs.pathExists(path.join(targetDir, `src/services/kafkaService.spec.${serviceExt}`))) {
|
|
94
|
-
await fs.move(
|
|
95
|
-
path.join(targetDir, `src/services/kafkaService.spec.${serviceExt}`),
|
|
96
|
-
path.join(targetDir, `tests/unit/infrastructure/messaging/kafkaClient.spec.${serviceExt}`),
|
|
97
|
-
{ overwrite: true }
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (await fs.pathExists(path.join(targetDir, `src/config/kafka.${serviceExt}`))) {
|
|
102
|
-
await fs.move(
|
|
103
|
-
path.join(targetDir, `src/config/kafka.${serviceExt}`),
|
|
104
|
-
path.join(targetDir, `src/infrastructure/config/kafka.${serviceExt}`),
|
|
105
|
-
{ overwrite: true }
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
await fs.remove(path.join(targetDir, 'src/services'));
|
|
110
|
-
|
|
111
|
-
// Messaging Infrastructure Enhancement
|
|
112
|
-
const messagingDir = path.join(targetDir, 'src/interfaces/messaging');
|
|
113
|
-
await fs.ensureDir(path.join(messagingDir, 'consumers/instances'));
|
|
114
|
-
await fs.ensureDir(path.join(messagingDir, 'schemas'));
|
|
115
|
-
|
|
116
|
-
const loggerPath = language === 'TypeScript' ? '@/infrastructure/log/logger' : '
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
{ src: '
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import ejs from 'ejs';
|
|
4
|
+
|
|
5
|
+
export const setupKafka = async (templatesDir, targetDir, config) => {
|
|
6
|
+
const { communication, architecture, language } = config;
|
|
7
|
+
if (communication !== 'Kafka') return;
|
|
8
|
+
|
|
9
|
+
const langExt = language === 'TypeScript' ? 'ts' : 'js';
|
|
10
|
+
const kafkaSource = path.join(templatesDir, 'common', 'kafka', langExt);
|
|
11
|
+
|
|
12
|
+
// 1. Copy necessary directories individually (to avoid orphaned templates in src)
|
|
13
|
+
if (await fs.pathExists(path.join(kafkaSource, 'services'))) {
|
|
14
|
+
await fs.copy(path.join(kafkaSource, 'services'), path.join(targetDir, 'src/services'));
|
|
15
|
+
}
|
|
16
|
+
if (await fs.pathExists(path.join(kafkaSource, 'config'))) {
|
|
17
|
+
await fs.copy(path.join(kafkaSource, 'config'), path.join(targetDir, 'src/config'));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Render Kafka Service with dynamic logger path
|
|
21
|
+
const kafkaServiceFileName = `kafkaService.${langExt}`;
|
|
22
|
+
const kafkaServiceTemplate = path.join(targetDir, 'src', 'services', `${kafkaServiceFileName}.ejs`);
|
|
23
|
+
// Render Kafka Service Spec
|
|
24
|
+
const kafkaSpecFileName = `kafkaService.spec.${langExt}`;
|
|
25
|
+
const kafkaSpecTemplate = path.join(targetDir, 'src', 'services', `${kafkaSpecFileName}.ejs`);
|
|
26
|
+
|
|
27
|
+
if (await fs.pathExists(kafkaServiceTemplate)) {
|
|
28
|
+
let serviceLoggerPath, serviceConfigPath;
|
|
29
|
+
if (language === 'TypeScript') {
|
|
30
|
+
serviceLoggerPath = architecture === 'Clean Architecture' ? '@/infrastructure/log/logger' : '@/utils/logger';
|
|
31
|
+
serviceConfigPath = architecture === 'Clean Architecture' ? '@/infrastructure/config/kafka' : '@/config/kafka';
|
|
32
|
+
} else {
|
|
33
|
+
serviceLoggerPath = architecture === 'Clean Architecture' ? '../../infrastructure/log/logger' : '../utils/logger';
|
|
34
|
+
serviceConfigPath = architecture === 'Clean Architecture' ? '../../infrastructure/config/kafka' : '../config/kafka';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const content = ejs.render(await fs.readFile(kafkaServiceTemplate, 'utf-8'), { ...config, loggerPath: serviceLoggerPath, configPath: serviceConfigPath });
|
|
38
|
+
await fs.writeFile(path.join(targetDir, 'src', 'services', kafkaServiceFileName), content);
|
|
39
|
+
await fs.remove(kafkaServiceTemplate);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (await fs.pathExists(kafkaSpecTemplate)) {
|
|
43
|
+
let specLoggerPath, specConfigPath, specServicePath;
|
|
44
|
+
if (language === 'TypeScript') {
|
|
45
|
+
specLoggerPath = architecture === 'Clean Architecture' ? '@/infrastructure/log/logger' : '@/utils/logger';
|
|
46
|
+
specConfigPath = architecture === 'Clean Architecture' ? '@/infrastructure/config/kafka' : '@/config/kafka';
|
|
47
|
+
specServicePath = architecture === 'Clean Architecture' ? '@/infrastructure/messaging/kafkaClient' : '@/services/kafkaService';
|
|
48
|
+
} else {
|
|
49
|
+
specLoggerPath = architecture === 'Clean Architecture' ? '../../infrastructure/log/logger' : '../utils/logger';
|
|
50
|
+
specConfigPath = architecture === 'Clean Architecture' ? '../../infrastructure/config/kafka' : '../config/kafka';
|
|
51
|
+
specServicePath = architecture === 'Clean Architecture' ? '../../infrastructure/messaging/kafkaClient' : '../services/kafkaService';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const specContent = ejs.render(await fs.readFile(kafkaSpecTemplate, 'utf-8'), { ...config, loggerPath: specLoggerPath, configPath: specConfigPath, servicePath: specServicePath });
|
|
55
|
+
await fs.writeFile(path.join(targetDir, 'src', 'services', kafkaSpecFileName), specContent);
|
|
56
|
+
await fs.remove(kafkaSpecTemplate);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Render Kafka Config Spec
|
|
60
|
+
const kafkaConfigSpecFileName = `kafka.spec.${langExt}`;
|
|
61
|
+
const kafkaConfigSpecTemplate = path.join(templatesDir, 'common', 'kafka', langExt, 'config', `${kafkaConfigSpecFileName}.ejs`);
|
|
62
|
+
if (await fs.pathExists(kafkaConfigSpecTemplate)) {
|
|
63
|
+
const specContent = ejs.render(await fs.readFile(kafkaConfigSpecTemplate, 'utf-8'), { ...config });
|
|
64
|
+
let specTarget;
|
|
65
|
+
if (architecture === 'MVC') {
|
|
66
|
+
specTarget = path.join(targetDir, 'tests', 'unit', 'config', kafkaConfigSpecFileName);
|
|
67
|
+
} else {
|
|
68
|
+
specTarget = path.join(targetDir, 'tests', 'unit', 'infrastructure', 'config', kafkaConfigSpecFileName);
|
|
69
|
+
}
|
|
70
|
+
await fs.ensureDir(path.dirname(specTarget));
|
|
71
|
+
await fs.writeFile(specTarget, specContent);
|
|
72
|
+
|
|
73
|
+
const targetSpecTemplate = path.join(targetDir, 'src', 'config', `${kafkaConfigSpecFileName}.ejs`);
|
|
74
|
+
if (await fs.pathExists(targetSpecTemplate)) {
|
|
75
|
+
await fs.remove(targetSpecTemplate);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (architecture === 'Clean Architecture') {
|
|
80
|
+
// Clean Architecture Restructuring
|
|
81
|
+
await fs.ensureDir(path.join(targetDir, 'src/infrastructure/messaging'));
|
|
82
|
+
await fs.ensureDir(path.join(targetDir, 'tests/unit/infrastructure/messaging'));
|
|
83
|
+
await fs.ensureDir(path.join(targetDir, 'src/infrastructure/config'));
|
|
84
|
+
|
|
85
|
+
const serviceExt = language === 'TypeScript' ? 'ts' : 'js';
|
|
86
|
+
|
|
87
|
+
await fs.move(
|
|
88
|
+
path.join(targetDir, `src/services/kafkaService.${serviceExt}`),
|
|
89
|
+
path.join(targetDir, `src/infrastructure/messaging/kafkaClient.${serviceExt}`),
|
|
90
|
+
{ overwrite: true }
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (await fs.pathExists(path.join(targetDir, `src/services/kafkaService.spec.${serviceExt}`))) {
|
|
94
|
+
await fs.move(
|
|
95
|
+
path.join(targetDir, `src/services/kafkaService.spec.${serviceExt}`),
|
|
96
|
+
path.join(targetDir, `tests/unit/infrastructure/messaging/kafkaClient.spec.${serviceExt}`),
|
|
97
|
+
{ overwrite: true }
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (await fs.pathExists(path.join(targetDir, `src/config/kafka.${serviceExt}`))) {
|
|
102
|
+
await fs.move(
|
|
103
|
+
path.join(targetDir, `src/config/kafka.${serviceExt}`),
|
|
104
|
+
path.join(targetDir, `src/infrastructure/config/kafka.${serviceExt}`),
|
|
105
|
+
{ overwrite: true }
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
await fs.remove(path.join(targetDir, 'src/services'));
|
|
110
|
+
|
|
111
|
+
// Messaging Infrastructure Enhancement
|
|
112
|
+
const messagingDir = path.join(targetDir, 'src/interfaces/messaging');
|
|
113
|
+
await fs.ensureDir(path.join(messagingDir, 'consumers/instances'));
|
|
114
|
+
await fs.ensureDir(path.join(messagingDir, 'schemas'));
|
|
115
|
+
|
|
116
|
+
const loggerPath = language === 'TypeScript' ? '@/infrastructure/log/logger' : '../../../../infrastructure/log/logger';
|
|
117
|
+
const errorMessagesPath = language === 'TypeScript' ? '@/utils/errorMessages' : '../../../../utils/errorMessages';
|
|
118
|
+
const userEventSchemaPath = language === 'TypeScript' ? '@/messaging/schemas/userEventSchema' : '../../schemas/userEventSchema';
|
|
119
|
+
const messagingTemplates = [
|
|
120
|
+
{ src: 'baseConsumer', subDir: 'messaging', dest: 'interfaces/messaging/baseConsumer' },
|
|
121
|
+
{ src: 'userEventSchema', subDir: 'messaging', dest: 'interfaces/messaging/schemas/userEventSchema' },
|
|
122
|
+
{ src: 'welcomeEmailConsumer', subDir: 'messaging', dest: 'interfaces/messaging/consumers/instances/welcomeEmailConsumer' },
|
|
123
|
+
{ src: 'kafkaEvents', subDir: 'utils', dest: 'utils/kafkaEvents' }
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
for (const t of messagingTemplates) {
|
|
127
|
+
const templateSubDir = t.subDir || 'messaging';
|
|
128
|
+
const templateSource = path.join(templatesDir, 'common', 'kafka', langExt, templateSubDir, `${t.src}.${langExt}.ejs`);
|
|
129
|
+
if (await fs.pathExists(templateSource)) {
|
|
130
|
+
// Calculate dynamic relative paths for JS
|
|
131
|
+
let dynamicLoggerPath = loggerPath;
|
|
132
|
+
let dynamicErrorMessagesPath = errorMessagesPath;
|
|
133
|
+
let dynamicUserEventSchemaPath = userEventSchemaPath;
|
|
134
|
+
let dynamicKafkaEventsPath = language === 'TypeScript' ? '@/utils/kafkaEvents' : '../../utils/kafkaEvents';
|
|
135
|
+
let dynamicBaseConsumerPath = language === 'TypeScript' ? '@/interfaces/messaging/baseConsumer' : '../../baseConsumer';
|
|
136
|
+
|
|
137
|
+
if (language === 'JavaScript') {
|
|
138
|
+
const destDir = path.dirname(path.join('src', t.dest));
|
|
139
|
+
dynamicLoggerPath = path.relative(destDir, 'src/infrastructure/log/logger').replace(/\\/g, '/');
|
|
140
|
+
dynamicErrorMessagesPath = path.relative(destDir, 'src/utils/errorMessages').replace(/\\/g, '/');
|
|
141
|
+
dynamicUserEventSchemaPath = path.relative(destDir, 'src/interfaces/messaging/schemas/userEventSchema').replace(/\\/g, '/');
|
|
142
|
+
dynamicKafkaEventsPath = path.relative(destDir, 'src/utils/kafkaEvents').replace(/\\/g, '/');
|
|
143
|
+
dynamicBaseConsumerPath = path.relative(destDir, 'src/interfaces/messaging/baseConsumer').replace(/\\/g, '/');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const content = ejs.render(await fs.readFile(templateSource, 'utf-8'), {
|
|
147
|
+
...config,
|
|
148
|
+
loggerPath: dynamicLoggerPath,
|
|
149
|
+
errorMessagesPath: dynamicErrorMessagesPath,
|
|
150
|
+
userEventSchemaPath: dynamicUserEventSchemaPath,
|
|
151
|
+
kafkaEventsPath: dynamicKafkaEventsPath,
|
|
152
|
+
baseConsumerPath: dynamicBaseConsumerPath
|
|
153
|
+
});
|
|
154
|
+
const destPath = path.join(targetDir, 'src', `${t.dest}.${langExt}`);
|
|
155
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
156
|
+
await fs.writeFile(destPath, content);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Render Specs for messaging components
|
|
160
|
+
const specTemplateSource = path.join(templatesDir, 'common', 'kafka', langExt, 'messaging', `${t.src}.spec.${langExt}.ejs`);
|
|
161
|
+
if (await fs.pathExists(specTemplateSource)) {
|
|
162
|
+
const specContent = ejs.render(await fs.readFile(specTemplateSource, 'utf-8'), { ...config, loggerPath });
|
|
163
|
+
const specDest = path.join(targetDir, 'tests', 'unit', `${t.dest}.spec.${langExt}`);
|
|
164
|
+
await fs.ensureDir(path.dirname(specDest));
|
|
165
|
+
await fs.writeFile(specDest, specContent);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
} else if (architecture === 'MVC') {
|
|
170
|
+
const serviceExt = language === 'TypeScript' ? 'ts' : 'js';
|
|
171
|
+
|
|
172
|
+
const messagingDir = path.join(targetDir, 'src/messaging');
|
|
173
|
+
await fs.ensureDir(path.join(messagingDir, 'consumers/instances'));
|
|
174
|
+
await fs.ensureDir(path.join(messagingDir, 'schemas'));
|
|
175
|
+
|
|
176
|
+
const loggerPath = language === 'TypeScript' ? '@/utils/logger' : '../../../utils/logger';
|
|
177
|
+
const errorMessagesPath = language === 'TypeScript' ? '@/utils/errorMessages' : '../../../utils/errorMessages';
|
|
178
|
+
const userEventSchemaPath = language === 'TypeScript' ? '@/messaging/schemas/userEventSchema' : '../../schemas/userEventSchema';
|
|
179
|
+
const messagingTemplates = [
|
|
180
|
+
{ src: 'baseConsumer', subDir: 'messaging', dest: 'messaging/baseConsumer' },
|
|
181
|
+
{ src: 'userEventSchema', subDir: 'messaging', dest: 'messaging/schemas/userEventSchema' },
|
|
182
|
+
{ src: 'welcomeEmailConsumer', subDir: 'messaging', dest: 'messaging/consumers/instances/welcomeEmailConsumer' },
|
|
183
|
+
{ src: 'kafkaEvents', subDir: 'utils', dest: 'utils/kafkaEvents' }
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
for (const t of messagingTemplates) {
|
|
187
|
+
const templateSubDir = t.subDir || 'messaging';
|
|
188
|
+
const templateSource = path.join(templatesDir, 'common', 'kafka', langExt, templateSubDir, `${t.src}.${langExt}.ejs`);
|
|
189
|
+
if (await fs.pathExists(templateSource)) {
|
|
190
|
+
// Calculate dynamic relative paths for JS
|
|
191
|
+
let dynamicLoggerPath = loggerPath;
|
|
192
|
+
let dynamicErrorMessagesPath = errorMessagesPath;
|
|
193
|
+
let dynamicUserEventSchemaPath = userEventSchemaPath;
|
|
194
|
+
let dynamicKafkaEventsPath = language === 'TypeScript' ? '@/utils/kafkaEvents' : '../../utils/kafkaEvents';
|
|
195
|
+
let dynamicBaseConsumerPath = language === 'TypeScript' ? '@/messaging/baseConsumer' : '../../baseConsumer';
|
|
196
|
+
|
|
197
|
+
if (language === 'JavaScript') {
|
|
198
|
+
const destDir = path.dirname(path.join('src', t.dest));
|
|
199
|
+
dynamicLoggerPath = path.relative(destDir, 'src/utils/logger').replace(/\\/g, '/');
|
|
200
|
+
dynamicErrorMessagesPath = path.relative(destDir, 'src/utils/errorMessages').replace(/\\/g, '/');
|
|
201
|
+
dynamicUserEventSchemaPath = path.relative(destDir, 'src/messaging/schemas/userEventSchema').replace(/\\/g, '/');
|
|
202
|
+
dynamicKafkaEventsPath = path.relative(destDir, 'src/utils/kafkaEvents').replace(/\\/g, '/');
|
|
203
|
+
dynamicBaseConsumerPath = path.relative(destDir, 'src/messaging/baseConsumer').replace(/\\/g, '/');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const content = ejs.render(await fs.readFile(templateSource, 'utf-8'), {
|
|
207
|
+
...config,
|
|
208
|
+
loggerPath: dynamicLoggerPath,
|
|
209
|
+
errorMessagesPath: dynamicErrorMessagesPath,
|
|
210
|
+
userEventSchemaPath: dynamicUserEventSchemaPath,
|
|
211
|
+
kafkaEventsPath: dynamicKafkaEventsPath,
|
|
212
|
+
baseConsumerPath: dynamicBaseConsumerPath
|
|
213
|
+
});
|
|
214
|
+
const destPath = path.join(targetDir, 'src', `${t.dest}.${langExt}`);
|
|
215
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
216
|
+
await fs.writeFile(destPath, content);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Render Specs for messaging components
|
|
220
|
+
const specTemplateSource = path.join(templatesDir, 'common', 'kafka', langExt, 'messaging', `${t.src}.spec.${langExt}.ejs`);
|
|
221
|
+
if (await fs.pathExists(specTemplateSource)) {
|
|
222
|
+
const specContent = ejs.render(await fs.readFile(specTemplateSource, 'utf-8'), { ...config, loggerPath });
|
|
223
|
+
const specDest = path.join(targetDir, 'tests', 'unit', `${t.dest}.spec.${langExt}`);
|
|
224
|
+
await fs.ensureDir(path.dirname(specDest));
|
|
225
|
+
await fs.writeFile(specDest, specContent);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (await fs.pathExists(path.join(targetDir, `src/services/kafkaService.spec.${serviceExt}`))) {
|
|
230
|
+
await fs.ensureDir(path.join(targetDir, 'tests/unit/services'));
|
|
231
|
+
await fs.move(
|
|
232
|
+
path.join(targetDir, `src/services/kafkaService.spec.${serviceExt}`),
|
|
233
|
+
path.join(targetDir, `tests/unit/services/kafkaService.spec.${serviceExt}`),
|
|
234
|
+
{ overwrite: true }
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
export const setupViews = async (templatesDir, targetDir, config) => {
|
|
242
|
+
const { architecture, viewEngine } = config;
|
|
243
|
+
if (architecture === 'MVC' && viewEngine && viewEngine !== 'None') {
|
|
244
|
+
const publicDir = path.join(templatesDir, 'common', 'public');
|
|
245
|
+
if (await fs.pathExists(publicDir)) {
|
|
246
|
+
await fs.copy(publicDir, path.join(targetDir, 'public'));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
};
|
|
@@ -24,6 +24,7 @@ export const copyBaseStructure = async (templatesDir, targetDir, architecture, l
|
|
|
24
24
|
export const copyCommonFiles = async (templatesDir, targetDir, language) => {
|
|
25
25
|
await fs.copy(path.join(templatesDir, 'common', '_gitignore'), path.join(targetDir, '.gitignore'));
|
|
26
26
|
await fs.copy(path.join(templatesDir, 'common', '.dockerignore'), path.join(targetDir, '.dockerignore'));
|
|
27
|
+
await fs.copy(path.join(templatesDir, 'common', '.gitattributes'), path.join(targetDir, '.gitattributes'));
|
|
27
28
|
|
|
28
29
|
if (language === 'TypeScript') {
|
|
29
30
|
await fs.copy(path.join(templatesDir, 'common', 'tsconfig.json'), path.join(targetDir, 'tsconfig.json'));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodejs-quickstart-structure",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The ultimate nodejs quickstart structure CLI to scaffold Node.js microservices with MVC or Clean Architecture",
|
|
6
6
|
"main": "bin/index.js",
|
|
@@ -33,7 +33,18 @@
|
|
|
33
33
|
"generator",
|
|
34
34
|
"boilerplate"
|
|
35
35
|
],
|
|
36
|
-
"author": "Pau Dang <[
|
|
36
|
+
"author": "Pau Dang <[phucdangb1400718@gmail.com]>",
|
|
37
|
+
"contributors": [
|
|
38
|
+
{
|
|
39
|
+
"name": "Pau Dang",
|
|
40
|
+
"url": "https://github.com/paudang"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "Gemini AI",
|
|
44
|
+
"url": "https://deepmind.google/technologies/gemini/",
|
|
45
|
+
"role": "AI Architectural Assistant"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
37
48
|
"repository": {
|
|
38
49
|
"type": "git",
|
|
39
50
|
"url": "git+https://github.com/paudang/nodejs-quickstart-structure.git"
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
const { ApiError } = require('./ApiError');
|
|
2
|
-
const HTTP_STATUS = require('../utils/httpCodes');
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
const { ApiError } = require('./ApiError');
|
|
2
|
+
const HTTP_STATUS = require('../utils/httpCodes');
|
|
3
|
+
const ERROR_MESSAGES = require('../utils/errorMessages');
|
|
4
|
+
|
|
5
|
+
class BadRequestError extends ApiError {
|
|
6
|
+
constructor(message = ERROR_MESSAGES.BAD_REQUEST) {
|
|
7
|
+
super(HTTP_STATUS.BAD_REQUEST, message);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = { BadRequestError };
|
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
const { BadRequestError } = require('@/errors/BadRequestError');
|
|
2
|
-
const { ApiError } = require('@/errors/ApiError');
|
|
3
|
-
const HTTP_STATUS = require('@/utils/httpCodes');
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
expect(error.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
});
|
|
1
|
+
const { BadRequestError } = require('@/errors/BadRequestError');
|
|
2
|
+
const { ApiError } = require('@/errors/ApiError');
|
|
3
|
+
const HTTP_STATUS = require('@/utils/httpCodes');
|
|
4
|
+
const ERROR_MESSAGES = require('@/utils/errorMessages');
|
|
5
|
+
|
|
6
|
+
describe('BadRequestError', () => {
|
|
7
|
+
it('should extend ApiError', () => {
|
|
8
|
+
const error = new BadRequestError();
|
|
9
|
+
expect(error).toBeInstanceOf(ApiError);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should have default message "Bad Request"', () => {
|
|
13
|
+
const error = new BadRequestError();
|
|
14
|
+
expect(error.message).toBe(ERROR_MESSAGES.BAD_REQUEST);
|
|
15
|
+
expect(error.statusCode).toBe(HTTP_STATUS.BAD_REQUEST);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should accept a custom message', () => {
|
|
19
|
+
const error = new BadRequestError('Custom bad request');
|
|
20
|
+
expect(error.message).toBe('Custom bad request');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
const { ApiError } = require('./ApiError');
|
|
2
|
-
const HTTP_STATUS = require('../utils/httpCodes');
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
const { ApiError } = require('./ApiError');
|
|
2
|
+
const HTTP_STATUS = require('../utils/httpCodes');
|
|
3
|
+
const ERROR_MESSAGES = require('../utils/errorMessages');
|
|
4
|
+
|
|
5
|
+
class NotFoundError extends ApiError {
|
|
6
|
+
constructor(message = ERROR_MESSAGES.RESOURCE_NOT_FOUND) {
|
|
7
|
+
super(HTTP_STATUS.NOT_FOUND, message);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = { NotFoundError };
|
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
const { NotFoundError } = require('@/errors/NotFoundError');
|
|
2
|
-
const { ApiError } = require('@/errors/ApiError');
|
|
3
|
-
const HTTP_STATUS = require('@/utils/httpCodes');
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
expect(error.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
});
|
|
1
|
+
const { NotFoundError } = require('@/errors/NotFoundError');
|
|
2
|
+
const { ApiError } = require('@/errors/ApiError');
|
|
3
|
+
const HTTP_STATUS = require('@/utils/httpCodes');
|
|
4
|
+
const ERROR_MESSAGES = require('@/utils/errorMessages');
|
|
5
|
+
|
|
6
|
+
describe('NotFoundError', () => {
|
|
7
|
+
it('should extend ApiError', () => {
|
|
8
|
+
const error = new NotFoundError();
|
|
9
|
+
expect(error).toBeInstanceOf(ApiError);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should have default message "Resource not found"', () => {
|
|
13
|
+
const error = new NotFoundError();
|
|
14
|
+
expect(error.message).toBe(ERROR_MESSAGES.RESOURCE_NOT_FOUND);
|
|
15
|
+
expect(error.statusCode).toBe(HTTP_STATUS.NOT_FOUND);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should accept a custom message', () => {
|
|
19
|
+
const error = new NotFoundError(ERROR_MESSAGES.USER_NOT_FOUND);
|
|
20
|
+
expect(error.message).toBe(ERROR_MESSAGES.USER_NOT_FOUND);
|
|
21
|
+
});
|
|
22
|
+
});
|