express-genix 2.0.1 → 3.0.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.
Files changed (33) hide show
  1. package/README.md +13 -1
  2. package/index.js +22 -1
  3. package/lib/features.js +8 -0
  4. package/lib/generator.js +77 -2
  5. package/package.json +2 -2
  6. package/templates/config/queue.js.ejs +29 -0
  7. package/templates/config/schema.prisma.ejs +2 -0
  8. package/templates/controllers/adminController.js.ejs +109 -0
  9. package/templates/controllers/authController.js.ejs +9 -4
  10. package/templates/controllers/userController.js.ejs +1 -0
  11. package/templates/core/app.js.ejs +50 -1
  12. package/templates/core/env.ejs +17 -0
  13. package/templates/core/env.example.ejs +17 -0
  14. package/templates/core/package.json.ejs +8 -1
  15. package/templates/core/server.js.ejs +5 -2
  16. package/templates/graphql/resolvers.js.ejs +61 -0
  17. package/templates/graphql/typeDefs.js.ejs +53 -0
  18. package/templates/jobs/worker.js.ejs +60 -0
  19. package/templates/middleware/auditLog.js.ejs +62 -0
  20. package/templates/middleware/metrics.js.ejs +65 -0
  21. package/templates/middleware/rbac.js.ejs +86 -0
  22. package/templates/middleware/upload.js.ejs +50 -0
  23. package/templates/models/User.mongo.js.ejs +29 -0
  24. package/templates/models/User.postgres.js.ejs +7 -1
  25. package/templates/routes/adminRoutes.js.ejs +150 -0
  26. package/templates/routes/index.js.ejs +6 -0
  27. package/templates/routes/jobRoutes.js.ejs +85 -0
  28. package/templates/routes/uploadRoutes.js.ejs +100 -0
  29. package/templates/services/authService.js.ejs +1 -0
  30. package/templates/services/emailService.js.ejs +88 -0
  31. package/templates/services/userService.mongodb.js.ejs +32 -2
  32. package/templates/services/userService.postgres.js.ejs +33 -1
  33. package/templates/services/userService.prisma.js.ejs +50 -6
@@ -0,0 +1,100 @@
1
+ const express = require('express');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const { uploadImage, uploadAny } = require('../middleware/upload');
5
+ const { success } = require('../utils/response');
6
+ const { AppError } = require('../utils/errors');
7
+ <% if (hasAuth) { %>const { authenticateToken } = require('../middleware/auth');<% } %>
8
+
9
+ const router = express.Router();
10
+
11
+ /**
12
+ * @swagger
13
+ * /uploads/single:
14
+ * post:
15
+ * summary: Upload a single image
16
+ * tags: [Uploads]<% if (hasAuth) { %>
17
+ * security:
18
+ * - bearerAuth: []<% } %>
19
+ * requestBody:
20
+ * required: true
21
+ * content:
22
+ * multipart/form-data:
23
+ * schema:
24
+ * type: object
25
+ * properties:
26
+ * file:
27
+ * type: string
28
+ * format: binary
29
+ * responses:
30
+ * 200:
31
+ * description: File uploaded
32
+ * 400:
33
+ * description: Invalid file
34
+ */
35
+ router.post('/single',
36
+ <% if (hasAuth) { %>authenticateToken,<% } %>
37
+ uploadImage.single('file'),
38
+ (req, res) => {
39
+ if (!req.file) {
40
+ throw new AppError('No file uploaded', 400);
41
+ }
42
+
43
+ return success(res, {
44
+ message: 'File uploaded successfully',
45
+ file: {
46
+ filename: req.file.filename,
47
+ originalName: req.file.originalname,
48
+ size: req.file.size,
49
+ mimetype: req.file.mimetype,
50
+ path: `/uploads/${req.file.filename}`,
51
+ },
52
+ });
53
+ }
54
+ );
55
+
56
+ /**
57
+ * @swagger
58
+ * /uploads/multiple:
59
+ * post:
60
+ * summary: Upload multiple files (max 5)
61
+ * tags: [Uploads]<% if (hasAuth) { %>
62
+ * security:
63
+ * - bearerAuth: []<% } %>
64
+ * requestBody:
65
+ * required: true
66
+ * content:
67
+ * multipart/form-data:
68
+ * schema:
69
+ * type: object
70
+ * properties:
71
+ * files:
72
+ * type: array
73
+ * items:
74
+ * type: string
75
+ * format: binary
76
+ * responses:
77
+ * 200:
78
+ * description: Files uploaded
79
+ */
80
+ router.post('/multiple',
81
+ <% if (hasAuth) { %>authenticateToken,<% } %>
82
+ uploadAny.array('files', 5),
83
+ (req, res) => {
84
+ if (!req.files || req.files.length === 0) {
85
+ throw new AppError('No files uploaded', 400);
86
+ }
87
+
88
+ const files = req.files.map((file) => ({
89
+ filename: file.filename,
90
+ originalName: file.originalname,
91
+ size: file.size,
92
+ mimetype: file.mimetype,
93
+ path: `/uploads/${file.filename}`,
94
+ }));
95
+
96
+ return success(res, { message: `${files.length} file(s) uploaded`, files });
97
+ }
98
+ );
99
+
100
+ module.exports = router;
@@ -12,6 +12,7 @@ const generateTokens = (user) => {
12
12
  userId: user.id,
13
13
  username: user.username,
14
14
  email: user.email,
15
+ role: user.role || 'user',
15
16
  };
16
17
 
17
18
  const accessToken = jwt.sign(payload, process.env.JWT_SECRET, {
@@ -0,0 +1,88 @@
1
+ const nodemailer = require('nodemailer');
2
+ const logger = require('../utils/logger');
3
+
4
+ let transporter;
5
+
6
+ const getTransporter = () => {
7
+ if (transporter) return transporter;
8
+
9
+ if (process.env.NODE_ENV === 'production') {
10
+ transporter = nodemailer.createTransport({
11
+ host: process.env.SMTP_HOST,
12
+ port: parseInt(process.env.SMTP_PORT, 10) || 587,
13
+ secure: process.env.SMTP_SECURE === 'true',
14
+ auth: {
15
+ user: process.env.SMTP_USER,
16
+ pass: process.env.SMTP_PASS,
17
+ },
18
+ });
19
+ } else {
20
+ // Use Ethereal for development (fake SMTP)
21
+ transporter = nodemailer.createTransport({
22
+ host: 'smtp.ethereal.email',
23
+ port: 587,
24
+ auth: {
25
+ user: process.env.SMTP_USER || '',
26
+ pass: process.env.SMTP_PASS || '',
27
+ },
28
+ });
29
+ }
30
+
31
+ return transporter;
32
+ };
33
+
34
+ const sendMail = async ({ to, subject, html, text }) => {
35
+ try {
36
+ const transport = getTransporter();
37
+ const info = await transport.sendMail({
38
+ from: process.env.SMTP_FROM || '"App" <noreply@example.com>',
39
+ to,
40
+ subject,
41
+ html,
42
+ text: text || subject,
43
+ });
44
+
45
+ logger.info(`Email sent: ${info.messageId}`);
46
+
47
+ if (process.env.NODE_ENV !== 'production') {
48
+ const previewUrl = nodemailer.getTestMessageUrl(info);
49
+ if (previewUrl) {
50
+ logger.info(`Preview URL: ${previewUrl}`);
51
+ }
52
+ }
53
+
54
+ return info;
55
+ } catch (error) {
56
+ logger.error('Failed to send email:', error);
57
+ throw error;
58
+ }
59
+ };
60
+
61
+ const sendWelcomeEmail = async (email, username) => {
62
+ return sendMail({
63
+ to: email,
64
+ subject: 'Welcome!',
65
+ html: `
66
+ <h1>Welcome, ${username}!</h1>
67
+ <p>Thank you for creating an account. We're glad to have you.</p>
68
+ `,
69
+ });
70
+ };
71
+
72
+ const sendPasswordResetEmail = async (email, resetToken) => {
73
+ const resetUrl = `${process.env.FRONTEND_URL || 'http://localhost:3000'}/reset-password?token=${resetToken}`;
74
+
75
+ return sendMail({
76
+ to: email,
77
+ subject: 'Password Reset Request',
78
+ html: `
79
+ <h1>Password Reset</h1>
80
+ <p>You requested a password reset. Click the link below to set a new password:</p>
81
+ <p><a href="${resetUrl}">Reset Password</a></p>
82
+ <p>This link expires in 1 hour.</p>
83
+ <p>If you did not request this, please ignore this email.</p>
84
+ `,
85
+ });
86
+ };
87
+
88
+ module.exports = { sendMail, sendWelcomeEmail, sendPasswordResetEmail, getTransporter };
@@ -22,7 +22,34 @@ const updateById = async (id, updateData) => {
22
22
  };
23
23
 
24
24
  const deleteById = async (id) => {
25
- return await User.findByIdAndDelete(id);
25
+ <% if (hasSoftDelete) { %> const user = await User.findById(id);
26
+ if (user) return user.softDelete();
27
+ return null;
28
+ <% } else { %> return await User.findByIdAndDelete(id);
29
+ <% } %>};
30
+ <% if (hasSoftDelete) { %>
31
+ const restoreById = async (id) => {
32
+ const user = await User.findOne({ _id: id, includeDeleted: true });
33
+ if (user) return user.restore();
34
+ return null;
35
+ };
36
+
37
+ const findDeleted = async ({ page = 1, limit = 20 } = {}) => {
38
+ const skip = (page - 1) * limit;
39
+ const [users, total] = await Promise.all([
40
+ User.find({ deletedAt: { $ne: null }, includeDeleted: true }).select('-password').skip(skip).limit(limit),
41
+ User.countDocuments({ deletedAt: { $ne: null } }),
42
+ ]);
43
+ return { users, total, page, totalPages: Math.ceil(total / limit) };
44
+ };
45
+ <% } %>
46
+ const findAll = async ({ page = 1, limit = 20 } = {}) => {
47
+ const skip = (page - 1) * limit;
48
+ const [users, total] = await Promise.all([
49
+ User.find().select('-password').skip(skip).limit(limit).sort({ createdAt: -1 }),
50
+ User.countDocuments(),
51
+ ]);
52
+ return { users, total, page, totalPages: Math.ceil(total / limit) };
26
53
  };
27
54
 
28
55
  module.exports = {
@@ -31,4 +58,7 @@ module.exports = {
31
58
  create,
32
59
  updateById,
33
60
  deleteById,
34
- };
61
+ findAll,
62
+ <% if (hasSoftDelete) { %> restoreById,
63
+ findDeleted,
64
+ <% } %>};
@@ -21,6 +21,35 @@ const updateById = async (id, updateData) => {
21
21
  const deleteById = async (id) => {
22
22
  return await User.destroy({ where: { id } });
23
23
  };
24
+ <% if (hasSoftDelete) { %>
25
+ const restoreById = async (id) => {
26
+ return await User.restore({ where: { id } });
27
+ };
28
+
29
+ const findDeleted = async ({ page = 1, limit = 20 } = {}) => {
30
+ const offset = (page - 1) * limit;
31
+ const { rows: users, count: total } = await User.findAndCountAll({
32
+ where: { deletedAt: { [require('sequelize').Op.ne]: null } },
33
+ attributes: { exclude: ['password'] },
34
+ order: [['deletedAt', 'DESC']],
35
+ limit,
36
+ offset,
37
+ paranoid: false,
38
+ });
39
+ return { users, total, page, totalPages: Math.ceil(total / limit) };
40
+ };
41
+ <% } %>
42
+
43
+ const findAll = async ({ page = 1, limit = 20 } = {}) => {
44
+ const offset = (page - 1) * limit;
45
+ const { rows: users, count: total } = await User.findAndCountAll({
46
+ attributes: { exclude: ['password'] },
47
+ order: [['createdAt', 'DESC']],
48
+ limit,
49
+ offset,
50
+ });
51
+ return { users, total, page, totalPages: Math.ceil(total / limit) };
52
+ };
24
53
 
25
54
  module.exports = {
26
55
  findById,
@@ -28,4 +57,7 @@ module.exports = {
28
57
  create,
29
58
  updateById,
30
59
  deleteById,
31
- };
60
+ findAll,
61
+ <% if (hasSoftDelete) { %> restoreById,
62
+ findDeleted,
63
+ <% } %>};
@@ -1,14 +1,14 @@
1
1
  const { prisma } = require('../config/database');
2
2
 
3
3
  const findById = async (id) => {
4
- return await prisma.user.findUnique({
5
- where: { id },
6
- select: { id: true, username: true, email: true, createdAt: true, updatedAt: true },
4
+ return await prisma.user.findFirst({
5
+ where: { id<% if (hasSoftDelete) { %>, deletedAt: null<% } %> },
6
+ select: { id: true, username: true, email: true, role: true, createdAt: true, updatedAt: true },
7
7
  });
8
8
  };
9
9
 
10
10
  const findByEmail = async (email) => {
11
- return await prisma.user.findUnique({ where: { email } });
11
+ return await prisma.user.findFirst({ where: { email<% if (hasSoftDelete) { %>, deletedAt: null<% } %> } });
12
12
  };
13
13
 
14
14
  const create = async (userData) => {
@@ -24,7 +24,48 @@ const updateById = async (id, updateData) => {
24
24
  };
25
25
 
26
26
  const deleteById = async (id) => {
27
- return await prisma.user.delete({ where: { id } });
27
+ <% if (hasSoftDelete) { %> return await prisma.user.update({
28
+ where: { id },
29
+ data: { deletedAt: new Date() },
30
+ });
31
+ <% } else { %> return await prisma.user.delete({ where: { id } });
32
+ <% } %>};
33
+ <% if (hasSoftDelete) { %>
34
+ const restoreById = async (id) => {
35
+ return await prisma.user.update({
36
+ where: { id },
37
+ data: { deletedAt: null },
38
+ });
39
+ };
40
+
41
+ const findDeleted = async ({ page = 1, limit = 20 } = {}) => {
42
+ const skip = (page - 1) * limit;
43
+ const [users, total] = await Promise.all([
44
+ prisma.user.findMany({
45
+ where: { deletedAt: { not: null } },
46
+ select: { id: true, username: true, email: true, role: true, deletedAt: true, createdAt: true },
47
+ orderBy: { deletedAt: 'desc' },
48
+ skip,
49
+ take: limit,
50
+ }),
51
+ prisma.user.count({ where: { deletedAt: { not: null } } }),
52
+ ]);
53
+ return { users, total, page, totalPages: Math.ceil(total / limit) };
54
+ };
55
+ <% } %>
56
+
57
+ const findAll = async ({ page = 1, limit = 20 } = {}) => {
58
+ const skip = (page - 1) * limit;
59
+ const [users, total] = await Promise.all([
60
+ prisma.user.findMany({
61
+ select: { id: true, username: true, email: true, role: true, createdAt: true, updatedAt: true },
62
+ orderBy: { createdAt: 'desc' },
63
+ skip,
64
+ take: limit,
65
+ }),
66
+ prisma.user.count(),
67
+ ]);
68
+ return { users, total, page, totalPages: Math.ceil(total / limit) };
28
69
  };
29
70
 
30
71
  module.exports = {
@@ -33,4 +74,7 @@ module.exports = {
33
74
  create,
34
75
  updateById,
35
76
  deleteById,
36
- };
77
+ findAll,
78
+ <% if (hasSoftDelete) { %> restoreById,
79
+ findDeleted,
80
+ <% } %>};