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
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import { User } from '@/domain/user';
|
|
2
|
-
|
|
3
2
|
import { UserRepository } from '@/infrastructure/repositories/UserRepository';
|
|
4
|
-
<%
|
|
3
|
+
<% if (auth.includes('JWT')) { -%>
|
|
4
|
+
import bcrypt from 'bcryptjs';
|
|
5
|
+
<% } -%>
|
|
6
|
+
<% if (caching === 'Redis') { -%>
|
|
5
7
|
import cacheService from '@/infrastructure/caching/redisClient';
|
|
6
|
-
<%
|
|
8
|
+
<% } else if (caching === 'Memory Cache') { -%>
|
|
7
9
|
import cacheService from '@/infrastructure/caching/memoryCache';
|
|
8
|
-
<%
|
|
10
|
+
<% } -%>
|
|
9
11
|
import logger from '@/infrastructure/log/logger';
|
|
10
12
|
|
|
11
13
|
export default class CreateUser {
|
|
12
14
|
constructor(private userRepository: UserRepository) {}
|
|
13
15
|
|
|
14
|
-
async execute(name: string, email: string) {
|
|
15
|
-
|
|
16
|
+
async execute(name: string, email: string, password?: string) {
|
|
17
|
+
let finalPassword = password;
|
|
18
|
+
<% if (auth.includes('JWT')) { -%>
|
|
19
|
+
if (password) {
|
|
20
|
+
finalPassword = await bcrypt.hash(password, 10);
|
|
21
|
+
}
|
|
22
|
+
<% } -%>
|
|
23
|
+
const user = new User(null, name, email, finalPassword);
|
|
16
24
|
const savedUser = await this.userRepository.save(user);
|
|
17
25
|
|
|
18
26
|
try {
|
|
@@ -14,7 +14,8 @@ export default class DeleteUser {
|
|
|
14
14
|
|
|
15
15
|
try {
|
|
16
16
|
await cacheService.del('users:all');
|
|
17
|
-
|
|
17
|
+
await cacheService.del(`user:${id}`);
|
|
18
|
+
logger.info(`Invalidated cache for user:${id} and all users`);
|
|
18
19
|
} catch (error) {
|
|
19
20
|
logger.error('Cache error (del):', error);
|
|
20
21
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { UserRepository } from '@/infrastructure/repositories/UserRepository';
|
|
2
|
+
import cacheService from '<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>';
|
|
3
|
+
import logger from '@/infrastructure/log/logger';
|
|
4
|
+
|
|
5
|
+
export default class GetUserById {
|
|
6
|
+
constructor(private userRepository: UserRepository) {}
|
|
7
|
+
|
|
8
|
+
async execute(id: string | number) {
|
|
9
|
+
const cacheKey = `user:${id}`;
|
|
10
|
+
try {
|
|
11
|
+
const cachedUser = await cacheService.get(cacheKey);
|
|
12
|
+
if (cachedUser) {
|
|
13
|
+
logger.info(`Serving user ${id} from cache`);
|
|
14
|
+
return cachedUser;
|
|
15
|
+
}
|
|
16
|
+
} catch (error) {
|
|
17
|
+
logger.error('Cache error (get):', error);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const user = await this.userRepository.findById(id);
|
|
21
|
+
|
|
22
|
+
if (user) {
|
|
23
|
+
try {
|
|
24
|
+
await cacheService.set(cacheKey, user, 3600); // Cache for 1 hour
|
|
25
|
+
} catch (error) {
|
|
26
|
+
logger.error('Cache error (set):', error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return user;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { User } from '@/domain/user';
|
|
2
1
|
import { UserRepository } from '@/infrastructure/repositories/UserRepository';
|
|
3
2
|
<%_ if (caching === 'Redis') { -%>
|
|
4
3
|
import cacheService from '@/infrastructure/caching/redisClient';
|
|
@@ -15,7 +14,8 @@ export default class UpdateUser {
|
|
|
15
14
|
|
|
16
15
|
try {
|
|
17
16
|
await cacheService.del('users:all');
|
|
18
|
-
|
|
17
|
+
await cacheService.del(`user:${id}`);
|
|
18
|
+
logger.info(`Invalidated cache for user:${id} and all users`);
|
|
19
19
|
} catch (error) {
|
|
20
20
|
logger.error('Cache error (del):', error);
|
|
21
21
|
}
|
|
@@ -6,12 +6,18 @@ class UserModel {
|
|
|
6
6
|
return this.mockData;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
static async findOne(query) {
|
|
10
|
+
return this.mockData.find((u) => {
|
|
11
|
+
return Object.entries(query).every(([key, value]) => u[key] === value);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
static async findAll() {
|
|
10
16
|
return this.mockData;
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
static async create(data) {
|
|
14
|
-
const { id, ...rest } = data;
|
|
20
|
+
const { id: _, ...rest } = data;
|
|
15
21
|
const newUser = { id: String(this.mockData.length + 1), ...rest };
|
|
16
22
|
this.mockData.push(newUser);
|
|
17
23
|
return newUser;
|
|
@@ -62,6 +68,12 @@ User.init(
|
|
|
62
68
|
allowNull: false,
|
|
63
69
|
unique: true,
|
|
64
70
|
},
|
|
71
|
+
<% if (auth.includes('JWT')) { %>
|
|
72
|
+
password: {
|
|
73
|
+
type: DataTypes.STRING,
|
|
74
|
+
allowNull: false,
|
|
75
|
+
},
|
|
76
|
+
<% } %>
|
|
65
77
|
deletedAt: {
|
|
66
78
|
type: DataTypes.DATE,
|
|
67
79
|
allowNull: true,
|
|
@@ -75,5 +87,6 @@ User.init(
|
|
|
75
87
|
}
|
|
76
88
|
);
|
|
77
89
|
|
|
90
|
+
|
|
78
91
|
module.exports = User;
|
|
79
92
|
<% } -%>
|
|
@@ -10,6 +10,12 @@ const UserSchema = new mongoose.Schema({
|
|
|
10
10
|
required: true,
|
|
11
11
|
unique: true
|
|
12
12
|
},
|
|
13
|
+
<% if (auth.includes('JWT')) { %>
|
|
14
|
+
password: {
|
|
15
|
+
type: String,
|
|
16
|
+
required: true
|
|
17
|
+
},
|
|
18
|
+
<% } %>
|
|
13
19
|
createdAt: {
|
|
14
20
|
type: Date,
|
|
15
21
|
default: Date.now
|
|
@@ -20,4 +26,5 @@ const UserSchema = new mongoose.Schema({
|
|
|
20
26
|
}
|
|
21
27
|
});
|
|
22
28
|
|
|
29
|
+
|
|
23
30
|
module.exports = mongoose.model('User', UserSchema);
|
|
@@ -73,15 +73,27 @@ describe('User Model', () => {
|
|
|
73
73
|
expect(await User.find()).toBeDefined();
|
|
74
74
|
expect(await User.findAll()).toBeDefined();
|
|
75
75
|
|
|
76
|
+
// Test findOne
|
|
77
|
+
const oneUser = await User.findOne({ email: data.email });
|
|
78
|
+
expect(oneUser.email).toBe(data.email);
|
|
79
|
+
|
|
76
80
|
const found = await User.findByPk(user.id);
|
|
77
81
|
expect(found.id).toBe(user.id);
|
|
78
82
|
|
|
79
83
|
const updated = await User.update(user.id, { name: 'New Name' });
|
|
80
84
|
expect(updated.name).toBe('New Name');
|
|
81
85
|
|
|
86
|
+
// Test update non-existent
|
|
87
|
+
const nonExistentUpdate = await User.update('999', { name: 'Fail' });
|
|
88
|
+
expect(nonExistentUpdate).toBeUndefined();
|
|
89
|
+
|
|
82
90
|
const deleted = await User.destroy(user.id);
|
|
83
91
|
expect(deleted).toBe(true);
|
|
84
92
|
expect(await User.findByPk(user.id)).toBeUndefined();
|
|
93
|
+
|
|
94
|
+
// Test destroy non-existent
|
|
95
|
+
const notDeleted = await User.destroy('999');
|
|
96
|
+
expect(notDeleted).toBe(false);
|
|
85
97
|
<% } else { %>
|
|
86
98
|
User.create.mockResolvedValue({ id: 1, ...data });
|
|
87
99
|
User.findAll.mockResolvedValue([{ id: 1, ...data }]);
|
|
@@ -40,6 +40,11 @@ jest.mock('@/infrastructure/database/database', () => ({}));
|
|
|
40
40
|
describe('User Model', () => {
|
|
41
41
|
beforeEach(() => {
|
|
42
42
|
jest.clearAllMocks();
|
|
43
|
+
<% if (database === 'None') { %>
|
|
44
|
+
const UserPath = '<% if (architecture === "MVC") { %>@/models/User<% } else { %>@/infrastructure/database/models/User<% } %>';
|
|
45
|
+
const User = require(UserPath).default;
|
|
46
|
+
User.mockData = [];
|
|
47
|
+
<% } %>
|
|
43
48
|
});
|
|
44
49
|
|
|
45
50
|
it('should be defined and initialized', () => {
|
|
@@ -78,6 +83,11 @@ describe('User Model', () => {
|
|
|
78
83
|
expect(found).toEqual(user);
|
|
79
84
|
expect(await User.findByPk('invalid')).toBeUndefined();
|
|
80
85
|
|
|
86
|
+
// findOne
|
|
87
|
+
const foundOne = await User.findOne({ email: data.email });
|
|
88
|
+
expect(foundOne).toEqual(user);
|
|
89
|
+
expect(await User.findOne({ email: 'nonexistent' })).toBeUndefined();
|
|
90
|
+
|
|
81
91
|
// update
|
|
82
92
|
const updated = await User.update(user.id, { name: 'Updated' });
|
|
83
93
|
expect(updated.name).toBe('Updated');
|
|
@@ -3,6 +3,9 @@ export interface User {
|
|
|
3
3
|
id: string;
|
|
4
4
|
name: string;
|
|
5
5
|
email: string;
|
|
6
|
+
<% if (auth.includes('JWT')) { -%>
|
|
7
|
+
password?: string;
|
|
8
|
+
<% } -%>
|
|
6
9
|
}
|
|
7
10
|
|
|
8
11
|
export default class UserModel {
|
|
@@ -12,6 +15,12 @@ export default class UserModel {
|
|
|
12
15
|
return this.mockData;
|
|
13
16
|
}
|
|
14
17
|
|
|
18
|
+
static async findOne(query: Partial<User>) {
|
|
19
|
+
return this.mockData.find((u) => {
|
|
20
|
+
return Object.entries(query).every(([key, value]) => (u as unknown as Record<string, unknown>)[key] === value);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
static async findAll() {
|
|
16
25
|
return this.mockData;
|
|
17
26
|
}
|
|
@@ -52,6 +61,7 @@ class User extends Model {
|
|
|
52
61
|
public id!: number;
|
|
53
62
|
public name!: string;
|
|
54
63
|
public email!: string;
|
|
64
|
+
<% if (auth.includes('JWT')) { %>public password!: string;<% } %>
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
User.init(
|
|
@@ -70,6 +80,12 @@ User.init(
|
|
|
70
80
|
allowNull: false,
|
|
71
81
|
unique: true,
|
|
72
82
|
},
|
|
83
|
+
<% if (auth.includes('JWT')) { %>
|
|
84
|
+
password: {
|
|
85
|
+
type: DataTypes.STRING,
|
|
86
|
+
allowNull: false,
|
|
87
|
+
},
|
|
88
|
+
<% } %>
|
|
73
89
|
deletedAt: {
|
|
74
90
|
type: DataTypes.DATE,
|
|
75
91
|
allowNull: true,
|
|
@@ -83,5 +99,6 @@ User.init(
|
|
|
83
99
|
}
|
|
84
100
|
);
|
|
85
101
|
|
|
102
|
+
|
|
86
103
|
export default User;
|
|
87
104
|
<% } -%>
|
|
@@ -3,6 +3,7 @@ import mongoose, { Schema, Document } from 'mongoose';
|
|
|
3
3
|
export interface IUser extends Document {
|
|
4
4
|
name: string;
|
|
5
5
|
email: string;
|
|
6
|
+
<% if (auth.includes('JWT')) { %>password: string;<% } %>
|
|
6
7
|
createdAt: Date;
|
|
7
8
|
deletedAt?: Date | null;
|
|
8
9
|
}
|
|
@@ -17,6 +18,12 @@ const UserSchema: Schema = new Schema({
|
|
|
17
18
|
required: true,
|
|
18
19
|
unique: true
|
|
19
20
|
},
|
|
21
|
+
<% if (auth.includes('JWT')) { %>
|
|
22
|
+
password: {
|
|
23
|
+
type: String,
|
|
24
|
+
required: true
|
|
25
|
+
},
|
|
26
|
+
<% } %>
|
|
20
27
|
createdAt: {
|
|
21
28
|
type: Date,
|
|
22
29
|
default: Date.now
|
|
@@ -27,4 +34,5 @@ const UserSchema: Schema = new Schema({
|
|
|
27
34
|
}
|
|
28
35
|
});
|
|
29
36
|
|
|
37
|
+
|
|
30
38
|
export default mongoose.model<IUser>('User', UserSchema);
|
|
@@ -39,6 +39,12 @@ services:
|
|
|
39
39
|
- DB_PORT=27017
|
|
40
40
|
<%_ } -%>
|
|
41
41
|
<%_ } -%>
|
|
42
|
+
<%_ if (auth.includes('JWT')) { -%>
|
|
43
|
+
- JWT_SECRET=your_jwt_secret_key_here_change_it
|
|
44
|
+
- JWT_REFRESH_SECRET=your_jwt_refresh_secret_key_here_change_it
|
|
45
|
+
- JWT_EXPIRES_IN=1d
|
|
46
|
+
- JWT_REFRESH_EXPIRES_IN=7d
|
|
47
|
+
<%_ } -%>
|
|
42
48
|
<%_ } else { -%>
|
|
43
49
|
environment:
|
|
44
50
|
- PORT=3000
|
|
@@ -64,6 +70,12 @@ services:
|
|
|
64
70
|
- DB_PORT=27017
|
|
65
71
|
<%_ } -%>
|
|
66
72
|
<%_ } -%>
|
|
73
|
+
<%_ if (auth.includes('JWT')) { -%>
|
|
74
|
+
- JWT_SECRET=your_jwt_secret_key_here_change_it
|
|
75
|
+
- JWT_REFRESH_SECRET=your_jwt_refresh_secret_key_here_change_it
|
|
76
|
+
- JWT_EXPIRES_IN=1d
|
|
77
|
+
- JWT_REFRESH_EXPIRES_IN=7d
|
|
78
|
+
<%_ } -%>
|
|
67
79
|
<%_ } -%>
|
|
68
80
|
<%_ if (database !== 'None') { -%>
|
|
69
81
|
db:
|
|
@@ -122,10 +134,12 @@ services:
|
|
|
122
134
|
FLYWAY_URL: jdbc:mysql://db:3306/<%= dbName %>
|
|
123
135
|
FLYWAY_USER: root
|
|
124
136
|
FLYWAY_PASSWORD: root
|
|
137
|
+
FLYWAY_BASELINE_ON_MIGRATE: "true"
|
|
125
138
|
<%_ } -%><%_ if (database === 'PostgreSQL') { -%>
|
|
126
139
|
FLYWAY_URL: jdbc:postgresql://db:5432/<%= dbName %>
|
|
127
140
|
FLYWAY_USER: postgres
|
|
128
141
|
FLYWAY_PASSWORD: root
|
|
142
|
+
FLYWAY_BASELINE_ON_MIGRATE: "true"
|
|
129
143
|
<%_ } -%>
|
|
130
144
|
depends_on:
|
|
131
145
|
- db
|
|
@@ -24,16 +24,22 @@ module.exports = {
|
|
|
24
24
|
DB_USER: "root",
|
|
25
25
|
DB_PASSWORD: "root",
|
|
26
26
|
DB_NAME: "<%= dbName %>",
|
|
27
|
-
DB_PORT: 3306
|
|
27
|
+
DB_PORT: 3306,
|
|
28
28
|
<%_ } else if (database === 'PostgreSQL') { -%>
|
|
29
29
|
DB_USER: "postgres",
|
|
30
30
|
DB_PASSWORD: "root",
|
|
31
31
|
DB_NAME: "<%= dbName %>",
|
|
32
|
-
DB_PORT: 5432
|
|
32
|
+
DB_PORT: 5432,
|
|
33
33
|
<%_ } else if (database === 'MongoDB') { -%>
|
|
34
34
|
DB_NAME: "<%= dbName %>",
|
|
35
|
-
DB_PORT: 27017
|
|
35
|
+
DB_PORT: 27017,
|
|
36
36
|
<%_ } -%>
|
|
37
|
+
<%_ } -%>
|
|
38
|
+
<%_ if (auth.includes('JWT')) { -%>
|
|
39
|
+
JWT_SECRET: "your_jwt_secret_here",
|
|
40
|
+
JWT_REFRESH_SECRET: "your_jwt_refresh_secret_here",
|
|
41
|
+
JWT_EXPIRES_IN: "1h",
|
|
42
|
+
JWT_REFRESH_EXPIRES_IN: "7d",
|
|
37
43
|
<%_ } -%>
|
|
38
44
|
}
|
|
39
45
|
}]
|
|
@@ -11,15 +11,17 @@ module.exports = {
|
|
|
11
11
|
coveragePathIgnorePatterns: [
|
|
12
12
|
"/node_modules/",
|
|
13
13
|
"/dist/",
|
|
14
|
-
"src/index",
|
|
15
|
-
"src/app",
|
|
16
|
-
"src/config
|
|
17
|
-
"src/infrastructure/config
|
|
18
|
-
"src/
|
|
19
|
-
"src/infrastructure/
|
|
20
|
-
"src/
|
|
21
|
-
"src/
|
|
22
|
-
"src/
|
|
14
|
+
"src/index.(js|ts)",
|
|
15
|
+
"src/app.(js|ts)",
|
|
16
|
+
"src/config",
|
|
17
|
+
"src/infrastructure/config",
|
|
18
|
+
"src/routes",
|
|
19
|
+
"src/infrastructure/routes",
|
|
20
|
+
"src/interfaces/routes",
|
|
21
|
+
"src/graphql",
|
|
22
|
+
"src/interfaces/graphql",
|
|
23
|
+
"src/utils",
|
|
24
|
+
"src/models"
|
|
23
25
|
],
|
|
24
26
|
coverageThreshold: {
|
|
25
27
|
global: {
|
|
@@ -34,7 +34,7 @@ const connectKafka = async (retries = 10) => {
|
|
|
34
34
|
await consumer.run({
|
|
35
35
|
eachMessage: async (payload) => welcomeConsumer.onMessage(payload),
|
|
36
36
|
});
|
|
37
|
-
} catch
|
|
37
|
+
} catch {
|
|
38
38
|
// Fallback or no consumers found
|
|
39
39
|
await consumer.subscribe({ topic: 'user-topic', fromBeginning: true });
|
|
40
40
|
await consumer.run({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
-
async up(db,
|
|
2
|
+
async up(db, _client) {
|
|
3
3
|
const adminEmail = 'admin@example.com';
|
|
4
4
|
const existingAdmin = await db.collection('users').findOne({ email: adminEmail });
|
|
5
5
|
|
|
@@ -7,16 +7,17 @@ module.exports = {
|
|
|
7
7
|
await db.collection('users').insertOne({
|
|
8
8
|
name: 'Admin User',
|
|
9
9
|
email: adminEmail,
|
|
10
|
+
<% if (auth.includes('JWT')) { %>password: '$2a$10$X.fO9PeyF0Lq0lF8uV6G9u4Vb4e5T0rF8l/JzM6S7X9u4Vb4e5T0r', // password: password123<% } %>
|
|
10
11
|
createdAt: new Date(),
|
|
11
12
|
updatedAt: new Date()
|
|
12
13
|
});
|
|
13
|
-
console.log('Admin User seeded successfully');
|
|
14
|
+
console.log('Admin User seeded successfully'); // eslint-disable-line no-console
|
|
14
15
|
} else {
|
|
15
|
-
console.log('Admin User already exists, skipping seed');
|
|
16
|
+
console.log('Admin User already exists, skipping seed'); // eslint-disable-line no-console
|
|
16
17
|
}
|
|
17
18
|
},
|
|
18
19
|
|
|
19
|
-
async down(
|
|
20
|
+
async down(_db, _client) {
|
|
20
21
|
// Optional: Undo the seed. Usually for seeds we might want to keep data, but strictly speaking 'down' should reverse 'up'.
|
|
21
22
|
// await db.collection('users').deleteOne({ email: 'admin@example.com' });
|
|
22
23
|
}
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"micromatch": "^4.0.8",
|
|
29
29
|
"braces": "^3.0.3",
|
|
30
30
|
"picomatch": "^4.0.4",
|
|
31
|
-
"lodash": "^4.17.23"
|
|
31
|
+
"lodash": "^4.17.23",
|
|
32
|
+
"debounce": "^1.2.1"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
35
|
"express": "^4.18.2",
|
|
@@ -52,8 +53,12 @@
|
|
|
52
53
|
<% if (viewEngine === 'EJS') { %> "ejs": "^3.1.10",
|
|
53
54
|
<% } -%>
|
|
54
55
|
<% if (viewEngine === 'Pug') { %> "pug": "^3.0.2",
|
|
56
|
+
<% } -%>
|
|
57
|
+
<% if (auth.includes('JWT')) { %> "jsonwebtoken": "^9.0.2",
|
|
58
|
+
"bcryptjs": "^2.4.3",
|
|
55
59
|
<% } -%>
|
|
56
60
|
"cors": "^2.8.5",
|
|
61
|
+
|
|
57
62
|
"helmet": "^7.1.0",
|
|
58
63
|
"hpp": "^0.2.3",
|
|
59
64
|
"express-rate-limit": "^7.1.5",
|
|
@@ -84,7 +89,10 @@
|
|
|
84
89
|
<% } -%>
|
|
85
90
|
"@types/morgan": "^1.9.9",
|
|
86
91
|
"rimraf": "^6.0.1"<% if ((viewEngine && viewEngine !== 'None') || communication === 'REST APIs' || communication === 'Kafka') { %>,
|
|
87
|
-
"cpx2": "^8.0.0"<% } %><%
|
|
92
|
+
"cpx2": "^8.0.0"<% } %><% if (auth.includes('JWT')) { %>,
|
|
93
|
+
"@types/jsonwebtoken": "^9.0.6",
|
|
94
|
+
"@types/bcryptjs": "^2.4.6"<% } %><% } %>,
|
|
95
|
+
|
|
88
96
|
"eslint": "^10.1.0",
|
|
89
97
|
"@eslint/js": "^9.20.0",
|
|
90
98
|
"globals": "^15.14.0",
|
|
@@ -16,6 +16,9 @@ You are an expert working on **<%= projectName %>**.
|
|
|
16
16
|
<% if (caching !== 'None') { -%>
|
|
17
17
|
- **Caching**: <%= caching %>
|
|
18
18
|
<% } -%>
|
|
19
|
+
<% if (auth.includes('JWT')) { -%>
|
|
20
|
+
- **Authentication**: JWT (Access & Refresh Tokens)
|
|
21
|
+
<% } -%>
|
|
19
22
|
|
|
20
23
|
## High-Level Architecture
|
|
21
24
|
<% if (architecture === 'Clean Architecture') { -%>
|
|
@@ -34,7 +37,11 @@ We use the MVC (Model-View-Controller) pattern.
|
|
|
34
37
|
## Core Standards
|
|
35
38
|
1. **Testing**: We enforce > 80% coverage. Tests use Jest and the AAA (Arrange, Act, Assert) pattern.
|
|
36
39
|
2. **Error Handling**: We use centralized custom errors (e.g., `ApiError`) and global error middleware. Status codes come from standard constants, not hardcoded numbers.
|
|
37
|
-
3. **
|
|
40
|
+
3. **Security**:
|
|
41
|
+
<% if (auth.includes('JWT')) { %>- Use `authMiddleware` for protected routes.<% } %>
|
|
42
|
+
- Validate and sanitize all inputs to prevent injection and XSS.
|
|
43
|
+
- Never expose sensitive data (passwords, inner keys) in API responses.
|
|
44
|
+
4. **Paths & Naming**:
|
|
38
45
|
<% if (language === 'TypeScript') { -%>
|
|
39
46
|
- We use `@/` path aliases for internal imports.
|
|
40
47
|
<% } -%>
|
|
@@ -3,13 +3,21 @@ const { execSync } = require('child_process');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
|
|
5
5
|
// Set a specific port for E2E tests to avoid collisions with local development
|
|
6
|
-
process.env.PORT
|
|
7
|
-
const
|
|
6
|
+
const TEST_PORT = <% if (database === 'MySQL') { %>process.env.PORT || '3001'<% } else { %>process.env.PORT || '3001'<% } %>;
|
|
7
|
+
const WAIT_ON_HOST = process.env.WAIT_ON_HOST || '127.0.0.1';
|
|
8
8
|
|
|
9
9
|
const execute = (command) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
console.log(`\n> ${command}`);
|
|
11
|
+
// Run commands from the project root instead of the scripts folder
|
|
12
|
+
execSync(command, {
|
|
13
|
+
stdio: 'inherit',
|
|
14
|
+
cwd: path.resolve(__dirname, '../'),
|
|
15
|
+
env: {
|
|
16
|
+
...process.env,
|
|
17
|
+
PORT: TEST_PORT,
|
|
18
|
+
DB_PORT: process.env.DB_PORT || '3306'
|
|
19
|
+
}
|
|
20
|
+
});
|
|
13
21
|
};
|
|
14
22
|
|
|
15
23
|
let composeCmd = 'docker-compose';
|
|
@@ -26,7 +34,7 @@ try {
|
|
|
26
34
|
let isAlreadyUp = false;
|
|
27
35
|
try {
|
|
28
36
|
// Silently check if the endpoint is already live (1.5-second timeout)
|
|
29
|
-
execSync(`npx wait-on http-get
|
|
37
|
+
execSync(`npx wait-on http-get://${WAIT_ON_HOST}:${TEST_PORT}/health -t 1500`, {
|
|
30
38
|
stdio: 'ignore',
|
|
31
39
|
cwd: path.resolve(__dirname, '../')
|
|
32
40
|
});
|
|
@@ -42,10 +50,18 @@ try {
|
|
|
42
50
|
execute(`${composeCmd} up -d --build`);
|
|
43
51
|
currentProcessStartedDocker = true;
|
|
44
52
|
|
|
45
|
-
console.log(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
console.log(`Waiting for application healthcheck to turn green (420s timeout)...`);
|
|
54
|
+
try {
|
|
55
|
+
// Using WAIT_ON_HOST to allow containerized CI to hit host ports (e.g. host.docker.internal)
|
|
56
|
+
execute(`npx wait-on http-get://${WAIT_ON_HOST}:${TEST_PORT}/health -t 420000`);
|
|
57
|
+
console.log('Infrastructure is healthy!');
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.error('\n❌ Healthcheck timed out! Printing infrastructure logs for debugging:');
|
|
60
|
+
console.error('------------------------------------------------------------');
|
|
61
|
+
execute(`${composeCmd} logs --tail=100`);
|
|
62
|
+
console.error('------------------------------------------------------------');
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
49
65
|
}
|
|
50
66
|
|
|
51
67
|
console.log('Running E2E tests...');
|