nodejs-quickstart-structure 2.0.1 → 2.1.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 +14 -0
- package/README.md +43 -39
- package/bin/index.js +5 -2
- package/lib/generator.js +10 -4
- package/lib/modules/app-setup.js +76 -6
- package/lib/modules/auth-setup.js +143 -0
- package/lib/modules/caching-setup.js +8 -1
- package/lib/modules/database-setup.js +2 -1
- package/lib/modules/project-setup.js +1 -0
- package/lib/prompts.js +39 -0
- package/package.json +5 -4
- package/templates/clean-architecture/js/src/domain/models/User.js +3 -1
- package/templates/clean-architecture/js/src/index.js.ejs +2 -0
- package/templates/clean-architecture/js/src/infrastructure/config/env.js.ejs +12 -3
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +25 -2
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +27 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +3 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.spec.js.ejs +49 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/swagger.spec.js.ejs +14 -0
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +41 -4
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +69 -4
- package/templates/clean-architecture/js/src/interfaces/graphql/context.js.ejs +13 -6
- package/templates/clean-architecture/js/src/interfaces/graphql/context.spec.js.ejs +38 -21
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +10 -5
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +32 -10
- package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/user.types.js.ejs +1 -1
- package/templates/clean-architecture/js/src/interfaces/routes/api.js.ejs +15 -0
- package/templates/clean-architecture/js/src/interfaces/routes/api.spec.js.ejs +4 -0
- package/templates/clean-architecture/js/src/usecases/CreateUser.js.ejs +34 -0
- package/templates/clean-architecture/js/src/usecases/CreateUser.spec.js.ejs +3 -2
- package/templates/clean-architecture/js/src/usecases/DeleteUser.js.ejs +27 -0
- package/templates/clean-architecture/js/src/usecases/GetAllUsers.js.ejs +36 -0
- package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +14 -0
- package/templates/clean-architecture/js/src/usecases/GetUserById.js.ejs +36 -0
- package/templates/clean-architecture/js/src/usecases/GetUserById.spec.js.ejs +48 -0
- package/templates/clean-architecture/js/src/usecases/UpdateUser.js.ejs +28 -0
- package/templates/clean-architecture/js/src/utils/errorMessages.js +1 -0
- package/templates/clean-architecture/js/src/utils/httpCodes.js +2 -0
- package/templates/clean-architecture/ts/src/config/env.ts.ejs +12 -3
- package/templates/clean-architecture/ts/src/domain/user.ts +3 -1
- package/templates/clean-architecture/ts/src/index.ts.ejs +4 -0
- package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +55 -9
- package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +32 -3
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +26 -6
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +57 -15
- package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +38 -23
- package/templates/clean-architecture/ts/src/interfaces/graphql/context.ts.ejs +14 -8
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +33 -10
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +15 -5
- package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/user.types.ts.ejs +1 -1
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +9 -1
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts.ejs +16 -0
- package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +3 -2
- package/templates/clean-architecture/ts/src/usecases/createUser.ts.ejs +35 -0
- package/templates/clean-architecture/ts/src/usecases/deleteUser.spec.ts.ejs +1 -0
- package/templates/clean-architecture/ts/src/usecases/deleteUser.ts.ejs +24 -0
- package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts.ejs +21 -0
- package/templates/clean-architecture/ts/src/usecases/getUserById.spec.ts.ejs +47 -0
- package/templates/clean-architecture/ts/src/usecases/getUserById.ts.ejs +23 -0
- package/templates/clean-architecture/ts/src/usecases/updateUser.spec.ts.ejs +1 -0
- package/templates/clean-architecture/ts/src/usecases/updateUser.ts.ejs +25 -0
- package/templates/clean-architecture/ts/src/utils/errorMessages.ts +1 -0
- package/templates/clean-architecture/ts/src/utils/httpCodes.ts +1 -0
- package/templates/common/.cursorrules.ejs +9 -0
- package/templates/common/.env.example.ejs +17 -10
- package/templates/common/README.md.ejs +63 -18
- package/templates/common/auth/js/controllers/authController.js.ejs +168 -0
- package/templates/common/auth/js/controllers/authController.spec.js.ejs +148 -0
- package/templates/common/auth/js/middleware/authMiddleware.js.ejs +58 -0
- package/templates/common/auth/js/middleware/authMiddleware.spec.js.ejs +108 -0
- package/templates/common/auth/js/routes/authRoutes.js.ejs +16 -0
- package/templates/common/auth/js/services/jwtService.js.ejs +54 -0
- package/templates/common/auth/js/services/jwtService.spec.js.ejs +84 -0
- package/templates/common/auth/ts/controllers/authController.spec.ts.ejs +161 -0
- package/templates/common/auth/ts/controllers/authController.ts.ejs +165 -0
- package/templates/common/auth/ts/middleware/authMiddleware.spec.ts.ejs +128 -0
- package/templates/common/auth/ts/middleware/authMiddleware.ts.ejs +59 -0
- package/templates/common/auth/ts/routes/authRoutes.ts.ejs +20 -0
- package/templates/common/auth/ts/services/jwtService.spec.ts.ejs +89 -0
- package/templates/common/auth/ts/services/jwtService.ts.ejs +60 -0
- package/templates/common/caching/clean/js/CreateUser.js.ejs +14 -5
- package/templates/common/caching/clean/js/DeleteUser.js.ejs +2 -1
- package/templates/common/caching/clean/js/GetUserById.js.ejs +39 -0
- package/templates/common/caching/clean/js/UpdateUser.js.ejs +2 -1
- package/templates/common/caching/clean/ts/createUser.ts.ejs +14 -6
- package/templates/common/caching/clean/ts/deleteUser.ts.ejs +2 -1
- package/templates/common/caching/clean/ts/getUserById.ts.ejs +32 -0
- package/templates/common/caching/clean/ts/updateUser.ts.ejs +2 -1
- package/templates/common/database/js/models/User.js.ejs +14 -1
- package/templates/common/database/js/models/User.js.mongoose.ejs +7 -0
- package/templates/common/database/js/models/User.spec.js.ejs +12 -0
- package/templates/common/database/ts/models/User.spec.ts.ejs +10 -0
- package/templates/common/database/ts/models/User.ts.ejs +17 -0
- package/templates/common/database/ts/models/User.ts.mongoose.ejs +8 -0
- package/templates/common/docker-compose.yml.ejs +12 -0
- package/templates/common/ecosystem.config.js.ejs +9 -3
- package/templates/common/eslint.config.mjs.ejs +3 -0
- package/templates/common/jest.config.js.ejs +11 -9
- package/templates/common/kafka/js/messaging/baseConsumer.js.ejs +1 -1
- package/templates/common/kafka/js/services/kafkaService.js.ejs +1 -1
- package/templates/common/migrations/init.js.ejs +5 -4
- package/templates/common/package.json.ejs +8 -1
- package/templates/common/prompts/project-context.md.ejs +8 -1
- package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +149 -107
- package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +88 -47
- package/templates/common/swagger.yml.ejs +148 -0
- package/templates/common/tsconfig.eslint.json +15 -0
- package/templates/common/tsconfig.json +2 -1
- package/templates/common/views/ejs/index.ejs +264 -30
- package/templates/common/views/ejs/login.ejs.ejs +244 -0
- package/templates/common/views/ejs/signup.ejs.ejs +282 -0
- package/templates/common/views/pug/index.pug +269 -38
- package/templates/common/views/pug/login.pug.ejs +195 -0
- package/templates/common/views/pug/signup.pug.ejs +241 -0
- package/templates/db/mysql/V1__Initial_Setup.sql.ejs +6 -0
- package/templates/db/postgres/V1__Initial_Setup.sql.ejs +6 -0
- package/templates/mvc/js/src/config/env.js.ejs +12 -3
- package/templates/mvc/js/src/controllers/userController.js.ejs +29 -5
- package/templates/mvc/js/src/controllers/userController.spec.js.ejs +27 -12
- package/templates/mvc/js/src/graphql/context.js.ejs +14 -3
- package/templates/mvc/js/src/graphql/context.spec.js.ejs +36 -21
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +10 -5
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +32 -10
- package/templates/mvc/js/src/graphql/typeDefs/user.types.js.ejs +1 -1
- package/templates/mvc/js/src/index.js.ejs +16 -3
- package/templates/mvc/js/src/routes/api.js.ejs +14 -0
- package/templates/mvc/js/src/routes/api.spec.js.ejs +3 -0
- package/templates/mvc/js/src/utils/errorMessages.js +1 -0
- package/templates/mvc/js/src/utils/httpCodes.js +1 -0
- package/templates/mvc/ts/src/config/env.ts.ejs +12 -3
- package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +95 -7
- package/templates/mvc/ts/src/controllers/userController.ts.ejs +68 -11
- package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +36 -23
- package/templates/mvc/ts/src/graphql/context.ts.ejs +15 -6
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +32 -10
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +15 -5
- package/templates/mvc/ts/src/graphql/typeDefs/user.types.ts.ejs +1 -1
- package/templates/mvc/ts/src/index.ts.ejs +15 -3
- package/templates/mvc/ts/src/routes/api.spec.ts.ejs +6 -0
- package/templates/mvc/ts/src/routes/api.ts.ejs +15 -0
- package/templates/mvc/ts/src/utils/errorMessages.ts +1 -0
- package/templates/mvc/ts/src/utils/httpCodes.ts +1 -0
- package/templates/clean-architecture/js/src/interfaces/routes/api.js +0 -12
- package/templates/clean-architecture/js/src/usecases/CreateUser.js +0 -14
- package/templates/clean-architecture/js/src/usecases/DeleteUser.js +0 -11
- package/templates/clean-architecture/js/src/usecases/GetAllUsers.js +0 -12
- package/templates/clean-architecture/js/src/usecases/UpdateUser.js +0 -11
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +0 -13
- package/templates/clean-architecture/ts/src/usecases/createUser.ts +0 -13
- package/templates/clean-architecture/ts/src/usecases/deleteUser.ts +0 -9
- package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +0 -10
- package/templates/clean-architecture/ts/src/usecases/updateUser.ts +0 -9
- package/templates/mvc/js/src/routes/api.js +0 -10
- package/templates/mvc/ts/src/routes/api.ts +0 -12
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const UserController = require('../controllers/userController');
|
|
3
|
+
<%_ if (auth.includes('JWT')) { _%>
|
|
4
|
+
const authMiddleware = require('../../infrastructure/webserver/middleware/authMiddleware');
|
|
5
|
+
<%_ } _%>
|
|
6
|
+
const router = express.Router();
|
|
7
|
+
const userController = new UserController();
|
|
8
|
+
|
|
9
|
+
router.get('/users', <% if (auth.includes('JWT')) { %>authMiddleware, <% } %>(req, res, next) => userController.getUsers(req, res, next));
|
|
10
|
+
router.get('/users/:id', <% if (auth.includes('JWT')) { %>authMiddleware, <% } %>(req, res, next) => userController.getUserById(req, res, next));
|
|
11
|
+
router.post('/users', (req, res, next) => userController.createUser(req, res, next));
|
|
12
|
+
router.patch('/users/:id', <% if (auth.includes('JWT')) { %>authMiddleware, <% } %>(req, res, next) => userController.updateUser(req, res, next));
|
|
13
|
+
router.delete('/users/:id', <% if (auth.includes('JWT')) { %>authMiddleware, <% } %>(req, res, next) => userController.deleteUser(req, res, next));
|
|
14
|
+
|
|
15
|
+
module.exports = router;
|
|
@@ -12,6 +12,10 @@ jest.mock('@/interfaces/controllers/userController', () => {
|
|
|
12
12
|
}));
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
+
<% if (auth.includes('JWT')) { %>
|
|
16
|
+
jest.mock('@/infrastructure/webserver/middleware/authMiddleware', () => (req, res, next) => next());
|
|
17
|
+
<% } %>
|
|
18
|
+
|
|
15
19
|
describe('ApiRoutes', () => {
|
|
16
20
|
let app;
|
|
17
21
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const User = require('../domain/models/User');
|
|
2
|
+
<%_ if (auth.includes('JWT')) { _%>
|
|
3
|
+
const bcrypt = require('bcryptjs');
|
|
4
|
+
<%_ } _%>
|
|
5
|
+
<%_ if (caching !== 'None') { _%>
|
|
6
|
+
const cacheService = require('<% if (caching === "Redis") { %>../infrastructure/caching/redisClient<% } else { %>../infrastructure/caching/memoryCache<% } %>');
|
|
7
|
+
const logger = require('../infrastructure/log/logger');
|
|
8
|
+
<%_ } _%>
|
|
9
|
+
|
|
10
|
+
class CreateUser {
|
|
11
|
+
constructor(userRepository) {
|
|
12
|
+
this.userRepository = userRepository;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async execute(name, email, password) {
|
|
16
|
+
let finalPassword = password;
|
|
17
|
+
<%_ if (auth.includes('JWT')) { -%>
|
|
18
|
+
if (password) {
|
|
19
|
+
finalPassword = await bcrypt.hash(password, 10);
|
|
20
|
+
}<%_ } -%>
|
|
21
|
+
const user = new User(null, name, email, finalPassword);
|
|
22
|
+
const savedUser = await this.userRepository.save(user);
|
|
23
|
+
<%_ if (caching !== 'None') { -%>
|
|
24
|
+
try {
|
|
25
|
+
await cacheService.del('users:all');
|
|
26
|
+
logger.info('Invalidated users:all cache');
|
|
27
|
+
} catch (error) {
|
|
28
|
+
logger.error('Cache error (del):', error);
|
|
29
|
+
}<%_ } _%>
|
|
30
|
+
return savedUser;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = CreateUser;
|
|
@@ -26,11 +26,12 @@ describe('CreateUser UseCase', () => {
|
|
|
26
26
|
it('should create and save a new user', async () => {
|
|
27
27
|
const name = 'Test User';
|
|
28
28
|
const email = 'test@example.com';
|
|
29
|
+
const password = 'password123';
|
|
29
30
|
const expectedResult = { id: 1, name, email };
|
|
30
31
|
|
|
31
32
|
mockUserRepository.save.mockResolvedValue(expectedResult);
|
|
32
33
|
|
|
33
|
-
const result = await createUser.execute(name, email);
|
|
34
|
+
const result = await createUser.execute(name, email, password);
|
|
34
35
|
|
|
35
36
|
expect(mockUserRepository.save).toHaveBeenCalledTimes(1);
|
|
36
37
|
const savedUser = mockUserRepository.save.mock.calls[0][0];
|
|
@@ -46,6 +47,6 @@ describe('CreateUser UseCase', () => {
|
|
|
46
47
|
const error = new Error('Database error');
|
|
47
48
|
mockUserRepository.save.mockRejectedValue(error);
|
|
48
49
|
|
|
49
|
-
await expect(createUser.execute('Test', 'test@test.com')).rejects.toThrow(error);
|
|
50
|
+
await expect(createUser.execute('Test', 'test@test.com', 'pwd')).rejects.toThrow(error);
|
|
50
51
|
});
|
|
51
52
|
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<%_ if (caching !== 'None') { _%>
|
|
2
|
+
const cacheService = require('<% if (caching === "Redis") { %>../infrastructure/caching/redisClient<% } else { %>../infrastructure/caching/memoryCache<% } %>');
|
|
3
|
+
const logger = require('../infrastructure/log/logger');
|
|
4
|
+
<%_ } _%>
|
|
5
|
+
|
|
6
|
+
class DeleteUser {
|
|
7
|
+
constructor(userRepository) {
|
|
8
|
+
this.userRepository = userRepository;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async execute(id) {
|
|
12
|
+
const result = await this.userRepository.delete(id);
|
|
13
|
+
<%_ if (caching !== 'None') { -%>
|
|
14
|
+
if (result) {
|
|
15
|
+
try {
|
|
16
|
+
await cacheService.del('users:all');
|
|
17
|
+
await cacheService.del(`user:${id}`);
|
|
18
|
+
logger.info(`Invalidated cache for user ${id}`);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
logger.error('Cache error (del):', error);
|
|
21
|
+
}
|
|
22
|
+
}<%_ } -%>
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = DeleteUser;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<%_ if (caching !== 'None') { _%>
|
|
2
|
+
const cacheService = require('<% if (caching === "Redis") { %>../infrastructure/caching/redisClient<% } else { %>../infrastructure/caching/memoryCache<% } %>');
|
|
3
|
+
const logger = require('../infrastructure/log/logger');
|
|
4
|
+
<%_ } _%>
|
|
5
|
+
|
|
6
|
+
class GetAllUsers {
|
|
7
|
+
constructor(userRepository) {
|
|
8
|
+
this.userRepository = userRepository;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async execute() {
|
|
12
|
+
<%_ if (caching !== 'None') { -%>
|
|
13
|
+
const cacheKey = 'users:all';
|
|
14
|
+
try {
|
|
15
|
+
const cachedUsers = await cacheService.get(cacheKey);
|
|
16
|
+
if (cachedUsers) {
|
|
17
|
+
logger.info('Serving users from cache');
|
|
18
|
+
return cachedUsers;
|
|
19
|
+
}
|
|
20
|
+
} catch (error) {
|
|
21
|
+
logger.error('Cache error (get):', error);
|
|
22
|
+
}<%_ } -%>
|
|
23
|
+
const users = await this.userRepository.getUsers();
|
|
24
|
+
<%_ if (caching !== 'None') { -%>
|
|
25
|
+
if (users) {
|
|
26
|
+
try {
|
|
27
|
+
await cacheService.set(cacheKey, users, 3600); // Cache for 1 hour
|
|
28
|
+
} catch (error) {
|
|
29
|
+
logger.error('Cache error (set):', error);
|
|
30
|
+
}
|
|
31
|
+
}<%_ } -%>
|
|
32
|
+
return users;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = GetAllUsers;
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
const GetAllUsers = require('@/usecases/GetAllUsers');
|
|
2
|
+
|
|
3
|
+
<% if (caching === 'Redis') { -%>
|
|
4
|
+
jest.mock('@/infrastructure/caching/redisClient', () => ({
|
|
5
|
+
get: jest.fn(),
|
|
6
|
+
set: jest.fn(),
|
|
7
|
+
del: jest.fn()
|
|
8
|
+
}));
|
|
9
|
+
<% } else if (caching === 'Memory Cache') { -%>
|
|
10
|
+
jest.mock('@/infrastructure/caching/memoryCache', () => ({
|
|
11
|
+
get: jest.fn(),
|
|
12
|
+
set: jest.fn(),
|
|
13
|
+
del: jest.fn()
|
|
14
|
+
}));
|
|
15
|
+
<% } -%>
|
|
2
16
|
const UserRepository = require('@/infrastructure/repositories/UserRepository');
|
|
3
17
|
<%_ if (caching !== 'None') { -%>
|
|
4
18
|
const cacheService = require('<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>');
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<%_ if (caching !== 'None') { _%>
|
|
2
|
+
const cacheService = require('<% if (caching === "Redis") { %>../infrastructure/caching/redisClient<% } else { %>../infrastructure/caching/memoryCache<% } %>');
|
|
3
|
+
const logger = require('../infrastructure/log/logger');
|
|
4
|
+
<%_ } _%>
|
|
5
|
+
|
|
6
|
+
class GetUserById {
|
|
7
|
+
constructor(userRepository) {
|
|
8
|
+
this.userRepository = userRepository;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async execute(id) {
|
|
12
|
+
<%_ if (caching !== 'None') { -%>
|
|
13
|
+
const cacheKey = `user:${id}`;
|
|
14
|
+
try {
|
|
15
|
+
const cachedUser = await cacheService.get(cacheKey);
|
|
16
|
+
if (cachedUser) {
|
|
17
|
+
logger.info(`Serving user ${id} from cache`);
|
|
18
|
+
return cachedUser;
|
|
19
|
+
}
|
|
20
|
+
} catch (error) {
|
|
21
|
+
logger.error('Cache error (get):', error);
|
|
22
|
+
}<%_ } -%>
|
|
23
|
+
const user = await this.userRepository.findById(id);
|
|
24
|
+
<%_ if (caching !== 'None') { -%>
|
|
25
|
+
if (user) {
|
|
26
|
+
try {
|
|
27
|
+
await cacheService.set(cacheKey, user, 3600); // Cache for 1 hour
|
|
28
|
+
} catch (error) {
|
|
29
|
+
logger.error('Cache error (set):', error);
|
|
30
|
+
}
|
|
31
|
+
}<%_ } -%>
|
|
32
|
+
return user;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = GetUserById;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const GetUserById = require('@/usecases/GetUserById');
|
|
2
|
+
|
|
3
|
+
<% if (caching === 'Redis') { -%>
|
|
4
|
+
jest.mock('@/infrastructure/caching/redisClient', () => ({
|
|
5
|
+
get: jest.fn(),
|
|
6
|
+
set: jest.fn(),
|
|
7
|
+
del: jest.fn()
|
|
8
|
+
}));
|
|
9
|
+
<% } else if (caching === 'Memory Cache') { -%>
|
|
10
|
+
jest.mock('@/infrastructure/caching/memoryCache', () => ({
|
|
11
|
+
get: jest.fn(),
|
|
12
|
+
set: jest.fn(),
|
|
13
|
+
del: jest.fn()
|
|
14
|
+
}));
|
|
15
|
+
<% } -%>
|
|
16
|
+
|
|
17
|
+
describe('GetUserById Use Case', () => {
|
|
18
|
+
let userRepository;
|
|
19
|
+
let getUserById;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
userRepository = {
|
|
23
|
+
findById: jest.fn(),
|
|
24
|
+
};
|
|
25
|
+
getUserById = new GetUserById(userRepository);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return a user if found (Happy Path)', async () => {
|
|
29
|
+
const id = '1';
|
|
30
|
+
const user = { id, name: 'Test User', email: 'test@example.com' };
|
|
31
|
+
userRepository.findById.mockResolvedValue(user);
|
|
32
|
+
|
|
33
|
+
const result = await getUserById.execute(id);
|
|
34
|
+
|
|
35
|
+
expect(result).toEqual(user);
|
|
36
|
+
expect(userRepository.findById).toHaveBeenCalledWith(id);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should return null if user not found (Error Handling)', async () => {
|
|
40
|
+
const id = '999';
|
|
41
|
+
userRepository.findById.mockResolvedValue(null);
|
|
42
|
+
|
|
43
|
+
const result = await getUserById.execute(id);
|
|
44
|
+
|
|
45
|
+
expect(result).toBeNull();
|
|
46
|
+
expect(userRepository.findById).toHaveBeenCalledWith(id);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<%_ if (caching !== 'None') { _%>
|
|
2
|
+
const cacheService = require('<% if (caching === "Redis") { %>../infrastructure/caching/redisClient<% } else { %>../infrastructure/caching/memoryCache<% } %>');
|
|
3
|
+
const logger = require('../infrastructure/log/logger');
|
|
4
|
+
<%_ } _%>
|
|
5
|
+
|
|
6
|
+
class UpdateUser {
|
|
7
|
+
constructor(userRepository) {
|
|
8
|
+
this.userRepository = userRepository;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async execute(id, data) {
|
|
12
|
+
const user = await this.userRepository.update(id, data);
|
|
13
|
+
<%_ if (caching !== 'None') { -%>
|
|
14
|
+
if (user) {
|
|
15
|
+
try {
|
|
16
|
+
await cacheService.del('users:all');
|
|
17
|
+
await cacheService.del(`user:${id}`);
|
|
18
|
+
logger.info(`Invalidated cache for user ${id}`);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
logger.error('Cache error (del):', error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
<%_ } -%>
|
|
24
|
+
return user;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = UpdateUser;
|
|
@@ -5,6 +5,7 @@ const ERROR_MESSAGES = {
|
|
|
5
5
|
INTERNAL_SERVER_ERROR: 'Internal Server Error',
|
|
6
6
|
BAD_REQUEST: 'Bad Request',
|
|
7
7
|
FETCH_USERS_ERROR: 'Error fetching users',
|
|
8
|
+
FETCH_USER_ERROR: 'Error fetching user',
|
|
8
9
|
CREATE_USER_ERROR: 'Error creating user',
|
|
9
10
|
UPDATE_USER_ERROR: 'Error updating user',
|
|
10
11
|
DELETE_USER_ERROR: 'Error deleting user',
|
|
@@ -34,13 +34,22 @@ const envSchema = z.object({
|
|
|
34
34
|
<%_ if (communication === 'Kafka') { -%>
|
|
35
35
|
KAFKA_BROKER: z.string(),
|
|
36
36
|
<%_ } -%>
|
|
37
|
+
<%_ if (auth.includes('JWT')) { -%>
|
|
38
|
+
JWT_SECRET: z.string(),
|
|
39
|
+
JWT_EXPIRES_IN: z.string().default('15m'),
|
|
40
|
+
JWT_REFRESH_SECRET: z.string().default('your-secret-refresh-key'),
|
|
41
|
+
JWT_REFRESH_EXPIRES_IN: z.string().default('7d'),
|
|
42
|
+
<%_ } -%>
|
|
37
43
|
});
|
|
38
44
|
|
|
39
45
|
const _env = envSchema.safeParse(process.env);
|
|
40
46
|
|
|
41
47
|
if (!_env.success) {
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
49
|
+
logger.error('❌ Invalid environment variables:', _env.error.format());
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
logger.warn('⚠️ Environment validation failed. Continuing in test mode.');
|
|
44
53
|
}
|
|
45
54
|
|
|
46
|
-
export const env = _env.data
|
|
55
|
+
export const env = (_env.success ? _env.data : process.env) as unknown as z.infer<typeof envSchema>;
|
|
@@ -13,6 +13,8 @@ import healthRoutes from '@/interfaces/routes/healthRoute';
|
|
|
13
13
|
import userRoutes from '@/interfaces/routes/userRoutes';
|
|
14
14
|
import swaggerUi from 'swagger-ui-express';
|
|
15
15
|
import swaggerSpecs from '@/config/swagger';<% } %>
|
|
16
|
+
<%_ if (auth.includes('JWT')) { -%>import authRoutes from '@/interfaces/routes/authRoutes';<%_ } -%>
|
|
17
|
+
|
|
16
18
|
<%_ if (communication === 'Kafka') { -%>import { kafkaService } from '@/infrastructure/messaging/kafkaClient';<%_ } -%>
|
|
17
19
|
<%_ if (communication === 'GraphQL') { -%>
|
|
18
20
|
import { ApolloServer } from '@apollo/server';
|
|
@@ -54,6 +56,8 @@ app.use(morgan('combined', { stream: { write: (message) => logger.info(message.t
|
|
|
54
56
|
<%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>
|
|
55
57
|
app.use('/api/users', userRoutes);
|
|
56
58
|
<%_ } -%>
|
|
59
|
+
<%_ if (auth.includes('JWT')) { -%>app.use('/api/auth', authRoutes);<%_ } -%>
|
|
60
|
+
|
|
57
61
|
<%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>
|
|
58
62
|
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
|
|
59
63
|
<%_ } -%>
|
package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs
CHANGED
|
@@ -15,7 +15,7 @@ describe('UserRepository', () => {
|
|
|
15
15
|
describe('save', () => {
|
|
16
16
|
it('should save and return a newly created user (Happy Path)', async () => {
|
|
17
17
|
// Arrange
|
|
18
|
-
const payload = { id: '', name: 'TestUser', email: 'test@example.com' };
|
|
18
|
+
const payload = { id: '', name: 'TestUser', email: 'test@example.com', password: 'password123' };
|
|
19
19
|
<%_ if (database === 'MongoDB') { -%>
|
|
20
20
|
const mockDbRecord = { _id: { toString: () => '1' }, name: 'TestUser', email: 'test@example.com' };
|
|
21
21
|
(UserModel.create as jest.Mock).mockResolvedValue(mockDbRecord);
|
|
@@ -34,7 +34,11 @@ describe('UserRepository', () => {
|
|
|
34
34
|
expect(result.name).toEqual(payload.name)
|
|
35
35
|
<%_ } else { -%>
|
|
36
36
|
expect(result).toEqual({ id: '1', name: 'TestUser', email: 'test@example.com' });
|
|
37
|
-
expect(UserModel.create).toHaveBeenCalledWith({
|
|
37
|
+
expect(UserModel.create).toHaveBeenCalledWith({
|
|
38
|
+
name: payload.name,
|
|
39
|
+
email: payload.email,
|
|
40
|
+
<% if (auth.includes('JWT')) { %>password: payload.password<% } %>
|
|
41
|
+
});
|
|
38
42
|
<%_ } -%>
|
|
39
43
|
});
|
|
40
44
|
|
|
@@ -43,7 +47,7 @@ describe('UserRepository', () => {
|
|
|
43
47
|
// Mocks do not naturally fail
|
|
44
48
|
<%_ } else { -%>
|
|
45
49
|
// Arrange
|
|
46
|
-
const payload = { id: '', name: 'FailUser', email: 'fail@example.com' };
|
|
50
|
+
const payload = { id: '', name: 'FailUser', email: 'fail@example.com', password: 'password123' };
|
|
47
51
|
const error = new Error('DB Connection Refused');
|
|
48
52
|
(UserModel.create as jest.Mock).mockRejectedValue(error);
|
|
49
53
|
|
|
@@ -53,6 +57,49 @@ describe('UserRepository', () => {
|
|
|
53
57
|
});
|
|
54
58
|
});
|
|
55
59
|
|
|
60
|
+
describe('findById', () => {
|
|
61
|
+
it('should find and return a user by id (Happy Path)', async () => {
|
|
62
|
+
// Arrange
|
|
63
|
+
const id = '1';
|
|
64
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
65
|
+
const mockDbRecord = { _id: { toString: () => '1' }, name: 'TestUser', email: 'test@example.com' };
|
|
66
|
+
(UserModel.findById as jest.Mock).mockResolvedValue(mockDbRecord);
|
|
67
|
+
<%_ } else if (database === 'None') { -%>
|
|
68
|
+
const mockDbRecord = { id: '1', name: 'TestUser', email: 'test@example.com' };
|
|
69
|
+
(UserModel.findByPk as jest.Mock).mockResolvedValue(mockDbRecord);
|
|
70
|
+
<%_ } else { -%>
|
|
71
|
+
const mockDbRecord = { id: '1', name: 'TestUser', email: 'test@example.com' };
|
|
72
|
+
(UserModel.findByPk as jest.Mock).mockResolvedValue(mockDbRecord);
|
|
73
|
+
<%_ } -%>
|
|
74
|
+
|
|
75
|
+
// Act
|
|
76
|
+
const result = await userRepository.findById(id);
|
|
77
|
+
|
|
78
|
+
// Assert
|
|
79
|
+
expect(result).toEqual({ id: '1', name: 'TestUser', email: 'test@example.com' });
|
|
80
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
81
|
+
expect(UserModel.findById).toHaveBeenCalledWith(id);
|
|
82
|
+
<%_ } else { -%>
|
|
83
|
+
expect(UserModel.findByPk).toHaveBeenCalledWith(id);
|
|
84
|
+
<%_ } -%>
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should return null if user not found', async () => {
|
|
88
|
+
// Arrange
|
|
89
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
90
|
+
(UserModel.findById as jest.Mock).mockResolvedValue(null);
|
|
91
|
+
<%_ } else { -%>
|
|
92
|
+
(UserModel.findByPk as jest.Mock).mockResolvedValue(null);
|
|
93
|
+
<%_ } -%>
|
|
94
|
+
|
|
95
|
+
// Act
|
|
96
|
+
const result = await userRepository.findById('999');
|
|
97
|
+
|
|
98
|
+
// Assert
|
|
99
|
+
expect(result).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
56
103
|
describe('getUsers', () => {
|
|
57
104
|
it('should return a list of mapped UserEntities (Happy Path)', async () => {
|
|
58
105
|
// Arrange
|
|
@@ -88,17 +135,16 @@ describe('UserRepository', () => {
|
|
|
88
135
|
// Arrange
|
|
89
136
|
const id = '1';
|
|
90
137
|
const data = { name: 'Updated' };
|
|
91
|
-
const expectedUser = { id: '1', name: 'Updated', email: 'test@example.com' };
|
|
92
|
-
|
|
93
138
|
<%_ if (database === 'MongoDB') { -%>
|
|
94
139
|
const mockDbRecord = { _id: { toString: () => '1' }, name: 'Updated', email: 'test@example.com' };
|
|
95
140
|
(UserModel.findByIdAndUpdate as jest.Mock).mockResolvedValue(mockDbRecord);
|
|
96
|
-
<%
|
|
97
|
-
|
|
98
|
-
|
|
141
|
+
<% } else if (database === 'None') { -%>
|
|
142
|
+
const mockDbRecord = { id: '1', name: 'Updated', email: 'test@example.com' };
|
|
143
|
+
(UserModel.update as jest.Mock).mockResolvedValue(mockDbRecord);
|
|
144
|
+
<% } else { -%>
|
|
99
145
|
const mockDbRecord = { id: '1', name: 'Updated', email: 'test@example.com', update: jest.fn().mockResolvedValue(true) };
|
|
100
146
|
(UserModel.findByPk as jest.Mock).mockResolvedValue(mockDbRecord);
|
|
101
|
-
<%
|
|
147
|
+
<% } -%>
|
|
102
148
|
|
|
103
149
|
// Act
|
|
104
150
|
const result = await userRepository.update(id, data);
|
package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs
CHANGED
|
@@ -4,17 +4,46 @@ import UserModel from '@/infrastructure/database/models/User';
|
|
|
4
4
|
export class UserRepository {
|
|
5
5
|
async save(user: UserEntity): Promise<UserEntity> {
|
|
6
6
|
<%_ if (database === 'MongoDB') { -%>
|
|
7
|
-
const newUser = await UserModel.create({
|
|
7
|
+
const newUser = await UserModel.create({
|
|
8
|
+
name: user.name,
|
|
9
|
+
email: user.email,
|
|
10
|
+
<% if (auth.includes('JWT')) { %>password: user.password<% } %>
|
|
11
|
+
});
|
|
8
12
|
return { id: newUser._id.toString(), name: newUser.name, email: newUser.email };
|
|
9
13
|
<%_ } else if (database === 'None') { -%>
|
|
10
|
-
const newUser = await UserModel.create({
|
|
14
|
+
const newUser = await UserModel.create({
|
|
15
|
+
name: user.name,
|
|
16
|
+
email: user.email,
|
|
17
|
+
<% if (auth.includes('JWT')) { %>password: user.password<% } %>
|
|
18
|
+
});
|
|
11
19
|
return { id: newUser.id, name: newUser.name, email: newUser.email };
|
|
12
20
|
<%_ } else { -%>
|
|
13
|
-
const newUser = await UserModel.create({
|
|
21
|
+
const newUser = await UserModel.create({
|
|
22
|
+
name: user.name,
|
|
23
|
+
email: user.email,
|
|
24
|
+
<% if (auth.includes('JWT')) { %>password: user.password<% } %>
|
|
25
|
+
});
|
|
14
26
|
return { id: newUser.id, name: newUser.name, email: newUser.email };
|
|
15
27
|
<%_ } -%>
|
|
16
28
|
}
|
|
17
29
|
|
|
30
|
+
async findById(id: number | string): Promise<UserEntity | null> {
|
|
31
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
32
|
+
const user = await UserModel.findById(id);
|
|
33
|
+
if (!user) return null;
|
|
34
|
+
return { id: user._id.toString(), name: user.name, email: user.email };
|
|
35
|
+
<%_ } else if (database === 'None') { -%>
|
|
36
|
+
const user = await UserModel.findByPk(id);
|
|
37
|
+
if (!user) return null;
|
|
38
|
+
return { id: user.id, name: user.name, email: user.email };
|
|
39
|
+
<%_ } else { -%>
|
|
40
|
+
const user = await UserModel.findByPk(id);
|
|
41
|
+
if (!user) return null;
|
|
42
|
+
return { id: user.id, name: user.name, email: user.email };
|
|
43
|
+
<%_ } -%>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
18
47
|
async getUsers(): Promise<UserEntity[]> {
|
|
19
48
|
<%_ if (database === 'MongoDB') { -%>
|
|
20
49
|
const users = await UserModel.find();
|
package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { Request, Response, NextFunction } from 'express';
|
|
3
3
|
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
4
4
|
<% } -%>
|
|
5
|
+
<% if (communication === 'GraphQL') { -%>
|
|
5
6
|
import { ERROR_MESSAGES } from '@/utils/errorMessages';
|
|
7
|
+
<% } -%>
|
|
6
8
|
import { UserController } from '@/interfaces/controllers/userController';
|
|
7
9
|
import CreateUser from '@/usecases/createUser';
|
|
8
10
|
import GetAllUsers from '@/usecases/getAllUsers';
|
|
@@ -118,7 +120,7 @@ describe('UserController (Clean Architecture)', () => {
|
|
|
118
120
|
describe('createUser', () => {
|
|
119
121
|
it('should successfully create a new user (Happy Path)', async () => {
|
|
120
122
|
// Arrange
|
|
121
|
-
const payload = { name: 'Alice', email: 'alice@example.com' };
|
|
123
|
+
const payload = { name: 'Alice', email: 'alice@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
|
|
122
124
|
<% if (communication === 'GraphQL') { -%>
|
|
123
125
|
const dataArg = payload;
|
|
124
126
|
<% } else { -%>
|
|
@@ -146,13 +148,31 @@ describe('UserController (Clean Architecture)', () => {
|
|
|
146
148
|
|
|
147
149
|
expect(mockResponse.json).toHaveBeenCalledWith(expectedUser);
|
|
148
150
|
<% } -%>
|
|
149
|
-
|
|
151
|
+
<% if (auth.includes('JWT')) { -%>
|
|
152
|
+
expect(mockCreateUserUseCase.execute).toHaveBeenCalledWith(payload.name, payload.email, payload.password);
|
|
153
|
+
<% } else { -%>
|
|
154
|
+
expect(mockCreateUserUseCase.execute).toHaveBeenCalledWith(payload.name, payload.email, undefined);
|
|
155
|
+
<% } -%>
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
<% if (auth.includes('JWT')) { %>
|
|
159
|
+
it('should fail to create a user when password is missing and JWT is enabled', async () => {
|
|
160
|
+
const payload = { name: 'Alice', email: 'alice@example.com' };
|
|
161
|
+
<% if (communication === 'GraphQL') { -%>
|
|
162
|
+
await expect(userController.createUser(payload)).rejects.toThrow('Password is required');
|
|
163
|
+
<% } else { -%>
|
|
164
|
+
mockRequest.body = payload;
|
|
165
|
+
await userController.createUser(mockRequest as Request, mockResponse as Response, mockNext);
|
|
166
|
+
expect(mockResponse.status).toHaveBeenCalledWith(HTTP_STATUS.BAD_REQUEST);
|
|
167
|
+
expect(mockResponse.json).toHaveBeenCalledWith({ error: 'Password is required' });
|
|
168
|
+
<% } -%>
|
|
150
169
|
});
|
|
170
|
+
<% } %>
|
|
151
171
|
|
|
152
172
|
it('should handle errors when creation fails (Error Handling)', async () => {
|
|
153
173
|
// Arrange
|
|
154
174
|
const error = new Error('Creation Error');
|
|
155
|
-
const payload = { name: 'Bob', email: 'bob@example.com' };
|
|
175
|
+
const payload = { name: 'Bob', email: 'bob@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
|
|
156
176
|
<% if (communication === 'GraphQL') { -%>
|
|
157
177
|
const dataArg = payload;
|
|
158
178
|
<% } else { -%>
|
|
@@ -173,7 +193,7 @@ describe('UserController (Clean Architecture)', () => {
|
|
|
173
193
|
it('should handle non-Error objects in catch block when creation fails', async () => {
|
|
174
194
|
// Arrange
|
|
175
195
|
const error = 'Creation String Error';
|
|
176
|
-
const payload = { name: 'Bob', email: 'bob@example.com' };
|
|
196
|
+
const payload = { name: 'Bob', email: 'bob@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
|
|
177
197
|
<% if (communication === 'GraphQL') { -%>
|
|
178
198
|
const dataArg = payload;
|
|
179
199
|
<% } else { -%>
|
|
@@ -320,9 +340,9 @@ describe('UserController (Clean Architecture)', () => {
|
|
|
320
340
|
const error = new Error('Database Error');
|
|
321
341
|
mockCreateUserUseCase.execute.mockRejectedValue(error);
|
|
322
342
|
<%_ if (communication === 'GraphQL') { -%>
|
|
323
|
-
await expect(userController.createUser({ name: 'Alice', email: 'alice@example.com' })).rejects.toThrow(error);
|
|
343
|
+
await expect(userController.createUser({ name: 'Alice', email: 'alice@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> })).rejects.toThrow(error);
|
|
324
344
|
<%_ } else { -%>
|
|
325
|
-
mockRequest.body = { name: 'Alice', email: 'alice@example.com' };
|
|
345
|
+
mockRequest.body = { name: 'Alice', email: 'alice@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
|
|
326
346
|
await userController.createUser(mockRequest as Request, mockResponse as Response, mockNext);
|
|
327
347
|
expect(mockNext).toHaveBeenCalledWith(error);
|
|
328
348
|
<%_ } -%>
|