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
|
@@ -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:
|
|
@@ -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
|
}
|
|
@@ -53,8 +53,12 @@
|
|
|
53
53
|
<% if (viewEngine === 'EJS') { %> "ejs": "^3.1.10",
|
|
54
54
|
<% } -%>
|
|
55
55
|
<% if (viewEngine === 'Pug') { %> "pug": "^3.0.2",
|
|
56
|
+
<% } -%>
|
|
57
|
+
<% if (auth.includes('JWT')) { %> "jsonwebtoken": "^9.0.2",
|
|
58
|
+
"bcryptjs": "^2.4.3",
|
|
56
59
|
<% } -%>
|
|
57
60
|
"cors": "^2.8.5",
|
|
61
|
+
|
|
58
62
|
"helmet": "^7.1.0",
|
|
59
63
|
"hpp": "^0.2.3",
|
|
60
64
|
"express-rate-limit": "^7.1.5",
|
|
@@ -85,7 +89,10 @@
|
|
|
85
89
|
<% } -%>
|
|
86
90
|
"@types/morgan": "^1.9.9",
|
|
87
91
|
"rimraf": "^6.0.1"<% if ((viewEngine && viewEngine !== 'None') || communication === 'REST APIs' || communication === 'Kafka') { %>,
|
|
88
|
-
"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
|
+
|
|
89
96
|
"eslint": "^10.1.0",
|
|
90
97
|
"@eslint/js": "^9.20.0",
|
|
91
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,118 +3,160 @@ const request = require('supertest');
|
|
|
3
3
|
const SERVER_URL = process.env.TEST_URL || `http://127.0.0.1:${process.env.PORT || 3001}`;
|
|
4
4
|
|
|
5
5
|
describe('E2E User Tests', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
let userId;
|
|
7
|
+
<%_ if (auth.includes('JWT')) { _%>
|
|
8
|
+
let authToken;
|
|
9
|
+
<%_ } _%>
|
|
10
|
+
const uniqueEmail = `test_${Date.now()}@example.com`;
|
|
11
|
+
<%_ if (auth.includes('JWT')) { _%>
|
|
12
|
+
const testPassword = 'password123';
|
|
13
|
+
<%_ } _%>
|
|
14
|
+
|
|
15
|
+
<%_ if (auth.includes('JWT')) { _%>
|
|
16
|
+
it('should fail to fetch users without token (Protected)', async () => {
|
|
17
|
+
<%_ if (communication === 'GraphQL') { _%>
|
|
18
|
+
const query = `{ getAllUsers { id name } }`;
|
|
19
|
+
const response = await request(SERVER_URL).post('/graphql').send({ query });
|
|
20
|
+
// In GraphQL errors are usually in the body with 200/400 status
|
|
21
|
+
if (response.statusCode === 200 && response.body.errors) {
|
|
22
|
+
expect(response.body.errors[0].message.toLowerCase()).toContain('unauthorized');
|
|
23
|
+
} else {
|
|
24
|
+
expect([401, 400]).toContain(response.statusCode);
|
|
25
|
+
}
|
|
26
|
+
<%_ } else { _%>
|
|
27
|
+
const response = await request(SERVER_URL).get('/api/users');
|
|
28
|
+
expect(response.statusCode).toBe(401);
|
|
29
|
+
<%_ } _%>
|
|
30
|
+
});
|
|
31
|
+
<%_ } _%>
|
|
10
32
|
|
|
11
33
|
<%_ if (communication === 'GraphQL') { -%>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
it('should update a user via GraphQL', async () => {
|
|
32
|
-
const query = `
|
|
33
|
-
mutation {
|
|
34
|
-
updateUser(id: "${userId}", name: "Updated User") {
|
|
35
|
-
id
|
|
36
|
-
name
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
`;
|
|
40
|
-
const response = await request(SERVER_URL)
|
|
41
|
-
.post('/graphql')
|
|
42
|
-
.send({ query });
|
|
43
|
-
|
|
44
|
-
expect(response.statusCode).toBe(200);
|
|
45
|
-
expect(response.body.data.updateUser.name).toBe("Updated User");
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should delete a user via GraphQL', async () => {
|
|
49
|
-
const query = `
|
|
50
|
-
mutation {
|
|
51
|
-
deleteUser(id: "${userId}")
|
|
52
|
-
}
|
|
53
|
-
`;
|
|
54
|
-
const response = await request(SERVER_URL)
|
|
55
|
-
.post('/graphql')
|
|
56
|
-
.send({ query });
|
|
57
|
-
|
|
58
|
-
expect(response.statusCode).toBe(200);
|
|
59
|
-
expect(response.body.data.deleteUser).toBe(true);
|
|
60
|
-
});
|
|
61
|
-
<%_ } else if (communication === 'Kafka') { -%>
|
|
62
|
-
it('should trigger Kafka event for user creation', async () => {
|
|
63
|
-
const response = await request(SERVER_URL)
|
|
64
|
-
.post('/api/users')
|
|
65
|
-
.send({ name: 'Test User', email: uniqueEmail });
|
|
66
|
-
|
|
67
|
-
expect([201, 202]).toContain(response.statusCode);
|
|
68
|
-
userId = response.body.id || response.body._id;
|
|
69
|
-
expect(userId).toBeDefined();
|
|
70
|
-
|
|
71
|
-
// Wait for Kafka to process...
|
|
72
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should trigger Kafka event for user update', async () => {
|
|
76
|
-
const response = await request(SERVER_URL)
|
|
77
|
-
.patch(`/api/users/${userId}`)
|
|
78
|
-
.send({ name: 'Updated User' });
|
|
79
|
-
|
|
80
|
-
expect([200, 202, 204]).toContain(response.statusCode);
|
|
81
|
-
|
|
82
|
-
// Wait for Kafka to process...
|
|
83
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('should trigger Kafka event for user deletion', async () => {
|
|
87
|
-
const response = await request(SERVER_URL)
|
|
88
|
-
.delete(`/api/users/${userId}`);
|
|
89
|
-
|
|
90
|
-
expect([200, 202, 204]).toContain(response.statusCode);
|
|
91
|
-
|
|
92
|
-
// Wait for Kafka to process...
|
|
93
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
94
|
-
});
|
|
34
|
+
it('should create a user via GraphQL', async () => {
|
|
35
|
+
const query = `
|
|
36
|
+
mutation {
|
|
37
|
+
createUser(name: "Test User", email: "${uniqueEmail}"<%_ if (auth.includes('JWT')) { _%>, password: "${testPassword}"<%_ } _%>) {
|
|
38
|
+
id
|
|
39
|
+
name
|
|
40
|
+
email
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
const response = await request(SERVER_URL)
|
|
45
|
+
.post('/graphql')
|
|
46
|
+
.send({ query });
|
|
47
|
+
|
|
48
|
+
expect(response.statusCode).toBe(200);
|
|
49
|
+
expect(response.body.errors).toBeUndefined();
|
|
50
|
+
userId = response.body.data.createUser.id;
|
|
51
|
+
expect(userId).toBeDefined();
|
|
52
|
+
});
|
|
95
53
|
<%_ } else { -%>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
it('should update a user successfully via REST', async () => {
|
|
106
|
-
const response = await request(SERVER_URL)
|
|
107
|
-
.patch(`/api/users/${userId}`)
|
|
108
|
-
.send({ name: 'Updated User' });
|
|
54
|
+
it('should create a user successfully (Signup)', async () => {
|
|
55
|
+
const response = await request(SERVER_URL)
|
|
56
|
+
.post('/api/users')
|
|
57
|
+
.send({ name: 'Test User', email: uniqueEmail<%_ if (auth.includes('JWT')) { _%>, password: testPassword <%_ } _%>});
|
|
58
|
+
|
|
59
|
+
expect([201, 202]).toContain(response.statusCode);
|
|
60
|
+
userId = response.body.id || response.body._id;
|
|
61
|
+
});
|
|
62
|
+
<%_ } -%>
|
|
109
63
|
|
|
110
|
-
|
|
111
|
-
|
|
64
|
+
<%_ if (auth.includes('JWT')) { _%>
|
|
65
|
+
it('should login and obtain a JWT token', async () => {
|
|
66
|
+
const response = await request(SERVER_URL)
|
|
67
|
+
.post('/api/auth/login')
|
|
68
|
+
.send({ email: uniqueEmail, password: testPassword });
|
|
112
69
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
70
|
+
expect(response.statusCode).toBe(200);
|
|
71
|
+
expect(response.body.accessToken || response.body.token).toBeDefined();
|
|
72
|
+
authToken = response.body.accessToken || response.body.token;
|
|
73
|
+
});
|
|
74
|
+
<%_ } _%>
|
|
116
75
|
|
|
117
|
-
|
|
118
|
-
|
|
76
|
+
<%_ if (communication === 'GraphQL') { -%>
|
|
77
|
+
it('should fetch all users via GraphQL', async () => {
|
|
78
|
+
const query = `{ getAllUsers { id name email } }`;
|
|
79
|
+
const response = await request(SERVER_URL)
|
|
80
|
+
.post('/graphql')
|
|
81
|
+
<%_ if (auth.includes('JWT')) { _%>
|
|
82
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
83
|
+
<%_ } _%>
|
|
84
|
+
.send({ query });
|
|
85
|
+
|
|
86
|
+
expect(response.statusCode).toBe(200);
|
|
87
|
+
expect(Array.isArray(response.body.data.getAllUsers)).toBe(true);
|
|
88
|
+
const user = response.body.data.getAllUsers.find(u => u.id === userId);
|
|
89
|
+
expect(user).toBeDefined();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should update a user via GraphQL', async () => {
|
|
93
|
+
const query = `
|
|
94
|
+
mutation {
|
|
95
|
+
updateUser(id: "${userId}", name: "Updated User") {
|
|
96
|
+
id
|
|
97
|
+
name
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
`;
|
|
101
|
+
const response = await request(SERVER_URL)
|
|
102
|
+
.post('/graphql')
|
|
103
|
+
<%_ if (auth.includes('JWT')) { _%>
|
|
104
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
105
|
+
<%_ } _%>
|
|
106
|
+
.set('Content-Type', 'application/json')
|
|
107
|
+
.send({ query });
|
|
108
|
+
|
|
109
|
+
expect(response.statusCode).toBe(200);
|
|
110
|
+
expect(response.body.data.updateUser.name).toBe("Updated User");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should delete a user via GraphQL', async () => {
|
|
114
|
+
const query = `
|
|
115
|
+
mutation {
|
|
116
|
+
deleteUser(id: "${userId}")
|
|
117
|
+
}
|
|
118
|
+
`;
|
|
119
|
+
const response = await request(SERVER_URL)
|
|
120
|
+
.post('/graphql')
|
|
121
|
+
<%_ if (auth.includes('JWT')) { _%>
|
|
122
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
123
|
+
<%_ } _%>
|
|
124
|
+
.set('Content-Type', 'application/json')
|
|
125
|
+
.send({ query });
|
|
126
|
+
|
|
127
|
+
expect(response.statusCode).toBe(200);
|
|
128
|
+
expect(response.body.data.deleteUser).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
<%_ } else { -%>
|
|
131
|
+
it('should fetch users successfully', async () => {
|
|
132
|
+
const response = await request(SERVER_URL)
|
|
133
|
+
.get('/api/users')
|
|
134
|
+
<%_ if (auth.includes('JWT')) { _%>
|
|
135
|
+
.set('Authorization', `Bearer ${authToken}`);
|
|
136
|
+
<%_ } _%>
|
|
137
|
+
expect(response.statusCode).toBe(200);
|
|
138
|
+
expect(Array.isArray(response.body)).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should update a user successfully', async () => {
|
|
142
|
+
const response = await request(SERVER_URL)
|
|
143
|
+
.patch(`/api/users/${userId}`)
|
|
144
|
+
<%_ if (auth.includes('JWT')) { _%>
|
|
145
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
146
|
+
<%_ } _%>
|
|
147
|
+
.send({ name: 'Updated User' });
|
|
148
|
+
|
|
149
|
+
expect([200, 202, 204]).toContain(response.statusCode);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should delete a user successfully', async () => {
|
|
153
|
+
const response = await request(SERVER_URL)
|
|
154
|
+
.delete(`/api/users/${userId}`)
|
|
155
|
+
<%_ if (auth.includes('JWT')) { _%>
|
|
156
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
157
|
+
<%_ } _%>;
|
|
158
|
+
|
|
159
|
+
expect([200, 202, 204]).toContain(response.statusCode);
|
|
160
|
+
});
|
|
119
161
|
<%_ } -%>
|
|
120
162
|
});
|