nodejs-quickstart-structure 2.0.0 → 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 +26 -0
- package/README.md +44 -40
- package/bin/index.js +6 -3
- 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/config-files.js +10 -0
- package/lib/modules/database-setup.js +2 -1
- package/lib/modules/project-setup.js +1 -0
- package/lib/prompts.js +40 -1
- 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/.gitlab-ci.yml.ejs +3 -1
- package/templates/common/Jenkinsfile.ejs +10 -1
- package/templates/common/README.md.ejs +64 -19
- package/templates/common/_circleci/config.yml.ejs +96 -0
- package/templates/common/_github/workflows/ci.yml.ejs +1 -1
- 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/bitbucket-pipelines.yml.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 -2
- 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 +14 -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 +10 -2
- package/templates/common/prompts/project-context.md.ejs +8 -1
- package/templates/common/scripts/run-e2e.js.ejs +26 -10
- 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
package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs
CHANGED
|
@@ -13,40 +13,62 @@ jest.mock('@/interfaces/controllers/userController', () => {
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
describe('User Resolvers', () => {
|
|
16
|
+
const mockContext = {
|
|
17
|
+
<% if (auth.includes('JWT')) { %>user: { id: 'admin', email: 'admin@test.com' }<% } %>
|
|
18
|
+
};
|
|
19
|
+
|
|
16
20
|
afterEach(() => {
|
|
17
21
|
jest.clearAllMocks();
|
|
18
22
|
});
|
|
19
23
|
|
|
20
24
|
describe('Query.getAllUsers', () => {
|
|
21
|
-
it('should return all users', async () => {
|
|
22
|
-
const result = await userResolvers.Query.getAllUsers();
|
|
25
|
+
it('should return all users when authorized', async () => {
|
|
26
|
+
const result = await userResolvers.Query.getAllUsers(null, null, mockContext);
|
|
23
27
|
expect(result).toEqual([{ id: '1', name: 'John Doe', email: 'john@example.com' }]);
|
|
24
28
|
expect(mockGetUsers).toHaveBeenCalledTimes(1);
|
|
25
29
|
});
|
|
30
|
+
|
|
31
|
+
<% if (auth.includes('JWT')) { %>
|
|
32
|
+
it('should throw error when unauthorized', async () => {
|
|
33
|
+
await expect(userResolvers.Query.getAllUsers(null, null, {})).rejects.toThrow('Unauthorized');
|
|
34
|
+
});
|
|
35
|
+
<% } %>
|
|
26
36
|
});
|
|
27
37
|
|
|
28
38
|
describe('Mutation.createUser', () => {
|
|
29
|
-
it('should create and return a new user', async () => {
|
|
30
|
-
const
|
|
39
|
+
it('should create and return a new user (Public)', async () => {
|
|
40
|
+
const payload = { name: 'Jane', email: 'jane@example.com'<% if (auth.some(a => a !=='None')) { %>, password: 'password123'<% } %> };
|
|
41
|
+
const result = await userResolvers.Mutation.createUser(null, payload);
|
|
31
42
|
expect(result).toEqual({ id: '1', name: 'Jane', email: 'jane@example.com' });
|
|
32
|
-
expect(mockCreateUser).toHaveBeenCalledWith(
|
|
33
|
-
expect(mockCreateUser).toHaveBeenCalledTimes(1);
|
|
43
|
+
expect(mockCreateUser).toHaveBeenCalledWith(payload);
|
|
34
44
|
});
|
|
35
45
|
});
|
|
36
46
|
|
|
37
47
|
describe('Mutation.updateUser', () => {
|
|
38
|
-
it('should update and return the user', async () => {
|
|
48
|
+
it('should update and return the user when authorized', async () => {
|
|
39
49
|
const payload = { name: 'Updated' };
|
|
40
|
-
const result = await userResolvers.Mutation.updateUser(null, { id: '1', ...payload });
|
|
50
|
+
const result = await userResolvers.Mutation.updateUser(null, { id: '1', ...payload }, mockContext);
|
|
41
51
|
expect(result).toMatchObject(payload);
|
|
42
52
|
});
|
|
53
|
+
|
|
54
|
+
<% if (auth.includes('JWT')) { %>
|
|
55
|
+
it('should throw error when unauthorized', async () => {
|
|
56
|
+
await expect(userResolvers.Mutation.updateUser(null, { id: '1', name: 'N' }, {})).rejects.toThrow('Unauthorized');
|
|
57
|
+
});
|
|
58
|
+
<% } %>
|
|
43
59
|
});
|
|
44
60
|
|
|
45
61
|
describe('Mutation.deleteUser', () => {
|
|
46
|
-
it('should delete and return true', async () => {
|
|
47
|
-
const result = await userResolvers.Mutation.deleteUser(null, { id: '1' });
|
|
62
|
+
it('should delete and return true when authorized', async () => {
|
|
63
|
+
const result = await userResolvers.Mutation.deleteUser(null, { id: '1' }, mockContext);
|
|
48
64
|
expect(result).toBe(true);
|
|
49
65
|
});
|
|
66
|
+
|
|
67
|
+
<% if (auth.includes('JWT')) { %>
|
|
68
|
+
it('should throw error when unauthorized', async () => {
|
|
69
|
+
await expect(userResolvers.Mutation.deleteUser(null, { id: '1' }, {})).rejects.toThrow('Unauthorized');
|
|
70
|
+
});
|
|
71
|
+
<% } %>
|
|
50
72
|
});
|
|
51
73
|
<%_ if (database === 'MongoDB') { -%>
|
|
52
74
|
describe('User.id', () => {
|
|
@@ -10,7 +10,7 @@ const userTypes = `#graphql
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
type Mutation {
|
|
13
|
-
createUser(name: String!, email: String
|
|
13
|
+
createUser(name: String!, email: String!<%_ if (auth.some(a => a !=='None')) { _%>, password: String!<%_ } _%>): User
|
|
14
14
|
updateUser(id: ID!, name: String, email: String): User
|
|
15
15
|
deleteUser(id: ID!): Boolean
|
|
16
16
|
}
|
|
@@ -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();
|