express-genix 2.0.0 → 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 +20 -3
  2. package/index.js +22 -1
  3. package/lib/features.js +8 -0
  4. package/lib/generator.js +77 -2
  5. package/package.json +3 -3
  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,61 @@
1
+ <% if (hasAuth) { %>const bcrypt = require('bcryptjs');
2
+ const authService = require('../services/authService');
3
+ const userService = require('../services/userService');
4
+ <% } %>
5
+ const resolvers = {
6
+ Query: {
7
+ hello: () => 'Hello from GraphQL!',
8
+ <% if (hasAuth) { %>
9
+ me: async (_, __, { user }) => {
10
+ if (!user) throw new Error('Authentication required');
11
+ return userService.findById(user.userId);
12
+ },
13
+
14
+ users: async (_, { page = 1, limit = 20 }, { user }) => {
15
+ if (!user || user.role !== 'admin') throw new Error('Admin access required');
16
+ return userService.findAll({ page, limit });
17
+ },
18
+ <% } %> },
19
+ <% if (hasAuth) { %>
20
+ Mutation: {
21
+ register: async (_, { input }) => {
22
+ const { username, email, password } = input;
23
+
24
+ const existing = await userService.findByEmail(email);
25
+ if (existing) throw new Error('User already exists with this email');
26
+
27
+ const hashedPassword = await bcrypt.hash(password, 12);
28
+ const user = await userService.create({ username, email, password: hashedPassword });
29
+ const tokens = authService.generateTokens(user);
30
+
31
+ return {
32
+ user: { id: user.id, username: user.username, email: user.email, role: user.role || 'user', createdAt: user.createdAt },
33
+ ...tokens,
34
+ };
35
+ },
36
+
37
+ login: async (_, { input }) => {
38
+ const { email, password } = input;
39
+
40
+ const user = await userService.findByEmail(email);
41
+ if (!user) throw new Error('Invalid credentials');
42
+
43
+ const valid = await bcrypt.compare(password, user.password);
44
+ if (!valid) throw new Error('Invalid credentials');
45
+
46
+ const tokens = authService.generateTokens(user);
47
+
48
+ return {
49
+ user: { id: user.id, username: user.username, email: user.email, role: user.role || 'user', createdAt: user.createdAt },
50
+ ...tokens,
51
+ };
52
+ },
53
+
54
+ updateProfile: async (_, { input }, { user }) => {
55
+ if (!user) throw new Error('Authentication required');
56
+ return userService.updateById(user.userId, input);
57
+ },
58
+ },
59
+ <% } %>};
60
+
61
+ module.exports = resolvers;
@@ -0,0 +1,53 @@
1
+ const gql = require('graphql-tag');
2
+
3
+ const typeDefs = gql`
4
+ type Query {
5
+ hello: String
6
+ <% if (hasAuth) { %> me: User
7
+ users(page: Int, limit: Int): UserList
8
+ <% } %> }
9
+
10
+ <% if (hasAuth) { %> type Mutation {
11
+ register(input: RegisterInput!): AuthPayload
12
+ login(input: LoginInput!): AuthPayload
13
+ updateProfile(input: UpdateProfileInput!): User
14
+ }
15
+
16
+ type User {
17
+ id: ID!
18
+ username: String!
19
+ email: String!
20
+ role: String!
21
+ createdAt: String!
22
+ }
23
+
24
+ type UserList {
25
+ users: [User!]!
26
+ total: Int!
27
+ page: Int!
28
+ totalPages: Int!
29
+ }
30
+
31
+ type AuthPayload {
32
+ user: User!
33
+ accessToken: String!
34
+ refreshToken: String!
35
+ }
36
+
37
+ input RegisterInput {
38
+ username: String!
39
+ email: String!
40
+ password: String!
41
+ }
42
+
43
+ input LoginInput {
44
+ email: String!
45
+ password: String!
46
+ }
47
+
48
+ input UpdateProfileInput {
49
+ username: String
50
+ }
51
+ <% } %>`;
52
+
53
+ module.exports = typeDefs;
@@ -0,0 +1,60 @@
1
+ const { Worker } = require('bullmq');
2
+ const { redisConnection } = require('../config/queue');
3
+ const logger = require('../utils/logger');
4
+
5
+ // Email job processor
6
+ const emailProcessor = async (job) => {
7
+ logger.info(`Processing email job: ${job.name} (${job.id})`);
8
+
9
+ switch (job.name) {
10
+ case 'send-welcome':
11
+ // TODO: Integrate with your email service
12
+ logger.info(`Sending welcome email to ${job.data.email}`);
13
+ break;
14
+ case 'send-reset':
15
+ logger.info(`Sending password reset email to ${job.data.email}`);
16
+ break;
17
+ default:
18
+ logger.warn(`Unknown email job: ${job.name}`);
19
+ }
20
+ };
21
+
22
+ // Default job processor
23
+ const defaultProcessor = async (job) => {
24
+ logger.info(`Processing job: ${job.name} (${job.id})`, job.data);
25
+
26
+ switch (job.name) {
27
+ case 'cleanup':
28
+ logger.info('Running cleanup task...');
29
+ break;
30
+ default:
31
+ logger.warn(`Unknown job: ${job.name}`);
32
+ }
33
+ };
34
+
35
+ const startWorkers = () => {
36
+ const emailWorker = new Worker('email', emailProcessor, {
37
+ connection: redisConnection,
38
+ concurrency: 5,
39
+ });
40
+
41
+ const defaultWorker = new Worker('default', defaultProcessor, {
42
+ connection: redisConnection,
43
+ concurrency: 3,
44
+ });
45
+
46
+ [emailWorker, defaultWorker].forEach((worker) => {
47
+ worker.on('completed', (job) => {
48
+ logger.info(`Job completed: ${job.name} (${job.id})`);
49
+ });
50
+
51
+ worker.on('failed', (job, err) => {
52
+ logger.error(`Job failed: ${job.name} (${job.id}) — ${err.message}`);
53
+ });
54
+ });
55
+
56
+ logger.info('Background workers started');
57
+ return { emailWorker, defaultWorker };
58
+ };
59
+
60
+ module.exports = { startWorkers };
@@ -0,0 +1,62 @@
1
+ const logger = require('../utils/logger');
2
+
3
+ /**
4
+ * Audit Logging Middleware
5
+ *
6
+ * Tracks API requests with user context, action, and resource details.
7
+ * Logs are written via the configured logger (Winston/Pino).
8
+ *
9
+ * Usage:
10
+ * router.post('/users', authenticateToken, auditLog('CREATE_USER'), handler);
11
+ * app.use(auditLog()); // log all requests
12
+ */
13
+
14
+ const auditLog = (action) => (req, res, next) => {
15
+ const startTime = Date.now();
16
+
17
+ // Capture response on finish
18
+ const originalEnd = res.end;
19
+ res.end = function (...args) {
20
+ const duration = Date.now() - startTime;
21
+
22
+ const auditEntry = {
23
+ type: 'AUDIT',
24
+ action: action || `${req.method} ${req.route ? req.route.path : req.path}`,
25
+ method: req.method,
26
+ path: req.originalUrl,
27
+ statusCode: res.statusCode,
28
+ duration: `${duration}ms`,
29
+ ip: req.ip,
30
+ userAgent: req.get('user-agent'),
31
+ userId: req.user ? req.user.userId : 'anonymous',
32
+ username: req.user ? req.user.username : 'anonymous',
33
+ requestId: req.id || req.headers['x-request-id'] || null,
34
+ timestamp: new Date().toISOString(),
35
+ };
36
+
37
+ // Don't log request/response bodies in production for security
38
+ if (process.env.NODE_ENV !== 'production') {
39
+ if (req.body && Object.keys(req.body).length > 0) {
40
+ // Redact sensitive fields
41
+ const safeBody = { ...req.body };
42
+ const sensitiveFields = ['password', 'token', 'refreshToken', 'secret', 'creditCard'];
43
+ sensitiveFields.forEach((field) => {
44
+ if (safeBody[field]) safeBody[field] = '[REDACTED]';
45
+ });
46
+ auditEntry.requestBody = safeBody;
47
+ }
48
+ }
49
+
50
+ if (res.statusCode >= 400) {
51
+ logger.warn(auditEntry);
52
+ } else {
53
+ logger.info(auditEntry);
54
+ }
55
+
56
+ originalEnd.apply(res, args);
57
+ };
58
+
59
+ next();
60
+ };
61
+
62
+ module.exports = { auditLog };
@@ -0,0 +1,65 @@
1
+ const client = require('prom-client');
2
+
3
+ // Create a Registry
4
+ const register = new client.Registry();
5
+
6
+ // Add default metrics (event loop lag, heap size, etc.)
7
+ client.collectDefaultMetrics({ register });
8
+
9
+ // Custom metrics
10
+ const httpRequestDuration = new client.Histogram({
11
+ name: 'http_request_duration_seconds',
12
+ help: 'Duration of HTTP requests in seconds',
13
+ labelNames: ['method', 'route', 'status_code'],
14
+ buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
15
+ });
16
+ register.registerMetric(httpRequestDuration);
17
+
18
+ const httpRequestTotal = new client.Counter({
19
+ name: 'http_requests_total',
20
+ help: 'Total number of HTTP requests',
21
+ labelNames: ['method', 'route', 'status_code'],
22
+ });
23
+ register.registerMetric(httpRequestTotal);
24
+
25
+ const activeConnections = new client.Gauge({
26
+ name: 'http_active_connections',
27
+ help: 'Number of active HTTP connections',
28
+ });
29
+ register.registerMetric(activeConnections);
30
+
31
+ /**
32
+ * Metrics middleware — tracks request duration, count, and active connections.
33
+ */
34
+ const metricsMiddleware = (req, res, next) => {
35
+ // Skip metrics endpoint itself
36
+ if (req.path === '/metrics') return next();
37
+
38
+ activeConnections.inc();
39
+ const end = httpRequestDuration.startTimer();
40
+
41
+ res.on('finish', () => {
42
+ const route = req.route ? req.route.path : req.path;
43
+ const labels = {
44
+ method: req.method,
45
+ route,
46
+ status_code: res.statusCode,
47
+ };
48
+
49
+ end(labels);
50
+ httpRequestTotal.inc(labels);
51
+ activeConnections.dec();
52
+ });
53
+
54
+ next();
55
+ };
56
+
57
+ /**
58
+ * GET /metrics handler — returns Prometheus-formatted metrics.
59
+ */
60
+ const metricsEndpoint = async (req, res) => {
61
+ res.set('Content-Type', register.contentType);
62
+ res.end(await register.metrics());
63
+ };
64
+
65
+ module.exports = { metricsMiddleware, metricsEndpoint, register };
@@ -0,0 +1,86 @@
1
+ const { AppError } = require('../utils/errors');
2
+
3
+ /**
4
+ * Role-Based Access Control (RBAC) Middleware
5
+ *
6
+ * Roles hierarchy: admin > moderator > user
7
+ * Usage:
8
+ * router.get('/admin', authenticateToken, requireRole('admin'), handler);
9
+ * router.put('/posts/:id', authenticateToken, requirePermission('posts:write'), handler);
10
+ */
11
+
12
+ const ROLES = {
13
+ admin: { level: 3, permissions: ['*'] },
14
+ moderator: { level: 2, permissions: [
15
+ 'users:read', 'users:list',
16
+ 'posts:read', 'posts:write', 'posts:delete',
17
+ 'comments:read', 'comments:write', 'comments:delete',
18
+ ]},
19
+ user: { level: 1, permissions: [
20
+ 'users:read',
21
+ 'posts:read', 'posts:write',
22
+ 'comments:read', 'comments:write',
23
+ ]},
24
+ };
25
+
26
+ const hasPermission = (role, permission) => {
27
+ const config = ROLES[role];
28
+ if (!config) return false;
29
+ if (config.permissions.includes('*')) return true;
30
+ return config.permissions.includes(permission);
31
+ };
32
+
33
+ /**
34
+ * Require a minimum role level.
35
+ * @param {...string} roles - Allowed roles (e.g., 'admin', 'moderator')
36
+ */
37
+ const requireRole = (...roles) => (req, res, next) => {
38
+ if (!req.user) {
39
+ return next(new AppError('Authentication required', 401));
40
+ }
41
+
42
+ const userRole = req.user.role || 'user';
43
+ if (!roles.includes(userRole)) {
44
+ return next(new AppError(`Role '${userRole}' is not authorized. Required: ${roles.join(' or ')}`, 403));
45
+ }
46
+
47
+ next();
48
+ };
49
+
50
+ /**
51
+ * Require a specific permission.
52
+ * @param {string} permission - e.g., 'users:write', 'posts:delete'
53
+ */
54
+ const requirePermission = (permission) => (req, res, next) => {
55
+ if (!req.user) {
56
+ return next(new AppError('Authentication required', 401));
57
+ }
58
+
59
+ const userRole = req.user.role || 'user';
60
+ if (!hasPermission(userRole, permission)) {
61
+ return next(new AppError(`Permission '${permission}' denied for role '${userRole}'`, 403));
62
+ }
63
+
64
+ next();
65
+ };
66
+
67
+ /**
68
+ * Require ownership OR a specific role.
69
+ * Checks req.params[paramName] === req.user.userId, or falls back to role check.
70
+ */
71
+ const requireOwnerOrRole = (paramName = 'id', ...roles) => (req, res, next) => {
72
+ if (!req.user) {
73
+ return next(new AppError('Authentication required', 401));
74
+ }
75
+
76
+ const isOwner = req.params[paramName] === req.user.userId;
77
+ const userRole = req.user.role || 'user';
78
+
79
+ if (isOwner || roles.includes(userRole)) {
80
+ return next();
81
+ }
82
+
83
+ return next(new AppError('Not authorized to access this resource', 403));
84
+ };
85
+
86
+ module.exports = { requireRole, requirePermission, requireOwnerOrRole, hasPermission, ROLES };
@@ -0,0 +1,50 @@
1
+ const multer = require('multer');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+ const { AppError } = require('../utils/errors');
5
+
6
+ // Allowed MIME types
7
+ const ALLOWED_TYPES = {
8
+ image: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
9
+ document: ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
10
+ any: null, // allow all
11
+ };
12
+
13
+ // Storage configuration
14
+ const storage = multer.diskStorage({
15
+ destination: (req, file, cb) => {
16
+ cb(null, path.join(process.cwd(), process.env.UPLOAD_DIR || 'uploads'));
17
+ },
18
+ filename: (req, file, cb) => {
19
+ const uniqueSuffix = crypto.randomBytes(16).toString('hex');
20
+ const ext = path.extname(file.originalname).toLowerCase();
21
+ cb(null, `${uniqueSuffix}${ext}`);
22
+ },
23
+ });
24
+
25
+ const createUpload = (options = {}) => {
26
+ const {
27
+ maxSize = parseInt(process.env.UPLOAD_MAX_SIZE, 10) || 5 * 1024 * 1024, // 5MB
28
+ allowedTypes = 'image',
29
+ } = options;
30
+
31
+ const allowedMimes = ALLOWED_TYPES[allowedTypes];
32
+
33
+ return multer({
34
+ storage,
35
+ limits: { fileSize: maxSize },
36
+ fileFilter: (req, file, cb) => {
37
+ if (allowedMimes && !allowedMimes.includes(file.mimetype)) {
38
+ return cb(new AppError(`File type '${file.mimetype}' not allowed. Allowed: ${allowedMimes.join(', ')}`, 400));
39
+ }
40
+ cb(null, true);
41
+ },
42
+ });
43
+ };
44
+
45
+ // Pre-configured upload instances
46
+ const uploadImage = createUpload({ allowedTypes: 'image' });
47
+ const uploadDocument = createUpload({ allowedTypes: 'document', maxSize: 10 * 1024 * 1024 });
48
+ const uploadAny = createUpload({ allowedTypes: 'any', maxSize: 10 * 1024 * 1024 });
49
+
50
+ module.exports = { createUpload, uploadImage, uploadDocument, uploadAny };
@@ -22,9 +22,38 @@ const userSchema = new mongoose.Schema({
22
22
  required: [true, 'Password is required'],
23
23
  minlength: [8, 'Password must be at least 8 characters long'],
24
24
  },
25
+ role: {
26
+ type: String,
27
+ enum: ['user', 'moderator', 'admin'],
28
+ default: 'user',
29
+ },<% if (hasSoftDelete) { %>
30
+ deletedAt: {
31
+ type: Date,
32
+ default: null,
33
+ index: true,
34
+ },<% } %>
25
35
  }, {
26
36
  timestamps: true,
27
37
  });
38
+ <% if (hasSoftDelete) { %>
39
+ // Soft delete: exclude deleted documents by default
40
+ userSchema.pre(/^find/, function (next) {
41
+ if (!this.getQuery().includeDeleted) {
42
+ this.where({ deletedAt: null });
43
+ } else {
44
+ delete this.getQuery().includeDeleted;
45
+ }
46
+ next();
47
+ });
28
48
 
49
+ userSchema.methods.softDelete = function () {
50
+ this.deletedAt = new Date();
51
+ return this.save();
52
+ };
29
53
 
54
+ userSchema.methods.restore = function () {
55
+ this.deletedAt = null;
56
+ return this.save();
57
+ };
58
+ <% } %>
30
59
  module.exports = mongoose.model('User', userSchema);
@@ -33,8 +33,14 @@ const User = sequelize.define('User', {
33
33
  notEmpty: true,
34
34
  },
35
35
  },
36
+ role: {
37
+ type: DataTypes.ENUM('user', 'moderator', 'admin'),
38
+ defaultValue: 'user',
39
+ allowNull: false,
40
+ },
36
41
  }, {
37
- timestamps: true,
42
+ timestamps: true,<% if (hasSoftDelete) { %>
43
+ paranoid: true, // Enables soft deletes (adds deletedAt column)<% } %>
38
44
  tableName: 'users',
39
45
  });
40
46
 
@@ -0,0 +1,150 @@
1
+ const express = require('express');
2
+ const adminController = require('../controllers/adminController');
3
+ const { authenticateToken } = require('../middleware/auth');
4
+ const { requireRole } = require('../middleware/rbac');
5
+
6
+ const router = express.Router();
7
+
8
+ /**
9
+ * @swagger
10
+ * /admin/users:
11
+ * get:
12
+ * summary: List all users (admin only)
13
+ * tags: [Admin]
14
+ * security:
15
+ * - bearerAuth: []
16
+ * parameters:
17
+ * - in: query
18
+ * name: page
19
+ * schema:
20
+ * type: integer
21
+ * default: 1
22
+ * - in: query
23
+ * name: limit
24
+ * schema:
25
+ * type: integer
26
+ * default: 20
27
+ * responses:
28
+ * 200:
29
+ * description: List of users
30
+ * 403:
31
+ * description: Forbidden — admin role required
32
+ */
33
+ router.get('/users', authenticateToken, requireRole('admin'), adminController.listUsers);
34
+
35
+ /**
36
+ * @swagger
37
+ * /admin/users/{id}:
38
+ * get:
39
+ * summary: Get user by ID (admin only)
40
+ * tags: [Admin]
41
+ * security:
42
+ * - bearerAuth: []
43
+ * parameters:
44
+ * - in: path
45
+ * name: id
46
+ * required: true
47
+ * schema:
48
+ * type: string
49
+ * responses:
50
+ * 200:
51
+ * description: User details
52
+ * 404:
53
+ * description: User not found
54
+ */
55
+ router.get('/users/:id', authenticateToken, requireRole('admin', 'moderator'), adminController.getUserById);
56
+
57
+ /**
58
+ * @swagger
59
+ * /admin/users/{id}/role:
60
+ * patch:
61
+ * summary: Update user role (admin only)
62
+ * tags: [Admin]
63
+ * security:
64
+ * - bearerAuth: []
65
+ * parameters:
66
+ * - in: path
67
+ * name: id
68
+ * required: true
69
+ * schema:
70
+ * type: string
71
+ * requestBody:
72
+ * required: true
73
+ * content:
74
+ * application/json:
75
+ * schema:
76
+ * type: object
77
+ * required:
78
+ * - role
79
+ * properties:
80
+ * role:
81
+ * type: string
82
+ * enum: [user, moderator, admin]
83
+ * example: moderator
84
+ * responses:
85
+ * 200:
86
+ * description: Role updated
87
+ * 400:
88
+ * description: Invalid role
89
+ */
90
+ router.patch('/users/:id/role', authenticateToken, requireRole('admin'), adminController.updateUserRole);
91
+
92
+ /**
93
+ * @swagger
94
+ * /admin/users/{id}:
95
+ * delete:
96
+ * summary: Delete a user (admin only)
97
+ * tags: [Admin]
98
+ * security:
99
+ * - bearerAuth: []
100
+ * parameters:
101
+ * - in: path
102
+ * name: id
103
+ * required: true
104
+ * schema:
105
+ * type: string
106
+ * responses:
107
+ * 200:
108
+ * description: User deleted
109
+ * 400:
110
+ * description: Cannot delete own account
111
+ */
112
+ router.delete('/users/:id', authenticateToken, requireRole('admin'), adminController.deleteUser);
113
+ <% if (hasSoftDelete) { %>
114
+ /**
115
+ * @swagger
116
+ * /admin/users/{id}/restore:
117
+ * post:
118
+ * summary: Restore a soft-deleted user (admin only)
119
+ * tags: [Admin]
120
+ * security:
121
+ * - bearerAuth: []
122
+ * parameters:
123
+ * - in: path
124
+ * name: id
125
+ * required: true
126
+ * schema:
127
+ * type: string
128
+ * responses:
129
+ * 200:
130
+ * description: User restored
131
+ * 404:
132
+ * description: User not found or not deleted
133
+ */
134
+ router.post('/users/:id/restore', authenticateToken, requireRole('admin'), adminController.restoreUser);
135
+
136
+ /**
137
+ * @swagger
138
+ * /admin/users/deleted:
139
+ * get:
140
+ * summary: List soft-deleted users (admin only)
141
+ * tags: [Admin]
142
+ * security:
143
+ * - bearerAuth: []
144
+ * responses:
145
+ * 200:
146
+ * description: List of deleted users
147
+ */
148
+ router.get('/users/deleted', authenticateToken, requireRole('admin'), adminController.listDeletedUsers);
149
+ <% } %>
150
+ module.exports = router;
@@ -1,14 +1,20 @@
1
1
  const express = require('express');
2
2
  <% if (hasAuth) { %>const authRoutes = require('./authRoutes');
3
3
  const userRoutes = require('./userRoutes');
4
+ const adminRoutes = require('./adminRoutes');
4
5
  <% } else { %>const exampleRoutes = require('./exampleRoutes');
6
+ <% } %><% if (hasFileUpload) { %>const uploadRoutes = require('./uploadRoutes');
7
+ <% } %><% if (hasBackgroundJobs) { %>const jobRoutes = require('./jobRoutes');
5
8
  <% } %>
6
9
 
7
10
  const router = express.Router();
8
11
 
9
12
  <% if (hasAuth) { %>router.use('/auth', authRoutes);
10
13
  router.use('/users', userRoutes);
14
+ router.use('/admin', adminRoutes);
11
15
  <% } else { %>router.use('/examples', exampleRoutes);
16
+ <% } %><% if (hasFileUpload) { %>router.use('/uploads', uploadRoutes);
17
+ <% } %><% if (hasBackgroundJobs) { %>router.use('/jobs', jobRoutes);
12
18
  <% } %>
13
19
 
14
20
  /**