offbyt 1.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 (103) hide show
  1. package/README.md +2 -0
  2. package/cli/index.js +2 -0
  3. package/cli.js +206 -0
  4. package/core/detector/detectAxios.js +107 -0
  5. package/core/detector/detectFetch.js +148 -0
  6. package/core/detector/detectForms.js +55 -0
  7. package/core/detector/detectSocket.js +341 -0
  8. package/core/generator/generateControllers.js +17 -0
  9. package/core/generator/generateModels.js +25 -0
  10. package/core/generator/generateRoutes.js +17 -0
  11. package/core/generator/generateServer.js +18 -0
  12. package/core/generator/generateSocket.js +160 -0
  13. package/core/index.js +14 -0
  14. package/core/ir/IRTypes.js +25 -0
  15. package/core/ir/buildIR.js +83 -0
  16. package/core/parser/parseJS.js +26 -0
  17. package/core/parser/parseTS.js +27 -0
  18. package/core/rules/relationRules.js +38 -0
  19. package/core/rules/resourceRules.js +32 -0
  20. package/core/rules/schemaInference.js +26 -0
  21. package/core/scanner/scanProject.js +58 -0
  22. package/deploy/cloudflare.js +41 -0
  23. package/deploy/cloudflareWorker.js +122 -0
  24. package/deploy/connect.js +198 -0
  25. package/deploy/flyio.js +51 -0
  26. package/deploy/index.js +322 -0
  27. package/deploy/netlify.js +29 -0
  28. package/deploy/railway.js +215 -0
  29. package/deploy/render.js +195 -0
  30. package/deploy/utils.js +383 -0
  31. package/deploy/vercel.js +29 -0
  32. package/index.js +18 -0
  33. package/lib/generator/advancedCrudGenerator.js +475 -0
  34. package/lib/generator/crudCodeGenerator.js +486 -0
  35. package/lib/generator/irBasedGenerator.js +360 -0
  36. package/lib/ir-builder/index.js +16 -0
  37. package/lib/ir-builder/irBuilder.js +330 -0
  38. package/lib/ir-builder/rulesEngine.js +353 -0
  39. package/lib/ir-builder/templateEngine.js +193 -0
  40. package/lib/ir-builder/templates/index.js +14 -0
  41. package/lib/ir-builder/templates/model.template.js +47 -0
  42. package/lib/ir-builder/templates/routes-generic.template.js +66 -0
  43. package/lib/ir-builder/templates/routes-user.template.js +105 -0
  44. package/lib/ir-builder/templates/routes.template.js +102 -0
  45. package/lib/ir-builder/templates/validation.template.js +15 -0
  46. package/lib/ir-integration.js +349 -0
  47. package/lib/modes/benchmark.js +162 -0
  48. package/lib/modes/configBasedGenerator.js +2258 -0
  49. package/lib/modes/connect.js +1125 -0
  50. package/lib/modes/doctorAi.js +172 -0
  51. package/lib/modes/generateApi.js +435 -0
  52. package/lib/modes/interactiveSetup.js +548 -0
  53. package/lib/modes/offline.clean.js +14 -0
  54. package/lib/modes/offline.enhanced.js +787 -0
  55. package/lib/modes/offline.js +295 -0
  56. package/lib/modes/offline.v2.js +13 -0
  57. package/lib/modes/sync.js +629 -0
  58. package/lib/scanner/apiEndpointExtractor.js +387 -0
  59. package/lib/scanner/authPatternDetector.js +54 -0
  60. package/lib/scanner/frontendScanner.js +642 -0
  61. package/lib/utils/apiClientGenerator.js +242 -0
  62. package/lib/utils/apiScanner.js +95 -0
  63. package/lib/utils/codeInjector.js +350 -0
  64. package/lib/utils/doctor.js +381 -0
  65. package/lib/utils/envGenerator.js +36 -0
  66. package/lib/utils/loadTester.js +61 -0
  67. package/lib/utils/performanceAnalyzer.js +298 -0
  68. package/lib/utils/resourceDetector.js +281 -0
  69. package/package.json +20 -0
  70. package/templates/.env.template +31 -0
  71. package/templates/advanced.model.template.js +201 -0
  72. package/templates/advanced.route.template.js +341 -0
  73. package/templates/auth.middleware.template.js +87 -0
  74. package/templates/auth.routes.template.js +238 -0
  75. package/templates/auth.user.model.template.js +78 -0
  76. package/templates/cache.middleware.js +34 -0
  77. package/templates/chat.models.template.js +260 -0
  78. package/templates/chat.routes.template.js +478 -0
  79. package/templates/compression.middleware.js +19 -0
  80. package/templates/database.config.js +74 -0
  81. package/templates/errorHandler.middleware.js +54 -0
  82. package/templates/express/controller.ejs +26 -0
  83. package/templates/express/model.ejs +9 -0
  84. package/templates/express/route.ejs +18 -0
  85. package/templates/express/server.ejs +16 -0
  86. package/templates/frontend.env.template +14 -0
  87. package/templates/model.template.js +86 -0
  88. package/templates/package.production.json +51 -0
  89. package/templates/package.template.json +41 -0
  90. package/templates/pagination.utility.js +110 -0
  91. package/templates/production.server.template.js +233 -0
  92. package/templates/rateLimiter.middleware.js +36 -0
  93. package/templates/requestLogger.middleware.js +19 -0
  94. package/templates/response.helper.js +179 -0
  95. package/templates/route.template.js +130 -0
  96. package/templates/security.middleware.js +78 -0
  97. package/templates/server.template.js +91 -0
  98. package/templates/socket.server.template.js +433 -0
  99. package/templates/utils.helper.js +157 -0
  100. package/templates/validation.middleware.js +63 -0
  101. package/templates/validation.schema.js +128 -0
  102. package/utils/fileWriter.js +15 -0
  103. package/utils/logger.js +18 -0
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Advanced Model Template
3
+ * Production-ready Mongoose schema with hooks, validation, and methods
4
+ * Generated by offbyt
5
+ */
6
+
7
+ import mongoose from 'mongoose';
8
+
9
+ const __MODEL_NAME__Schema = new mongoose.Schema(
10
+ {
11
+ // Metadata fields
12
+ isActive: {
13
+ type: Boolean,
14
+ default: true,
15
+ index: true
16
+ },
17
+ isDeleted: {
18
+ type: Boolean,
19
+ default: false,
20
+ index: true
21
+ },
22
+ metadata: {
23
+ createdBy: mongoose.Schema.Types.ObjectId,
24
+ updatedBy: mongoose.Schema.Types.ObjectId,
25
+ version: { type: Number, default: 1 },
26
+ deletedAt: Date,
27
+ tags: [String]
28
+ }
29
+ },
30
+ {
31
+ timestamps: true,
32
+ collection: '__COLLECTION_NAME__'
33
+ }
34
+ );
35
+
36
+ // ============================================
37
+ // INDEXES - For query optimization
38
+ // ============================================
39
+ __MODEL_NAME__Schema.index({ createdAt: -1 });
40
+ __MODEL_NAME__Schema.index({ updatedAt: -1 });
41
+ __MODEL_NAME__Schema.index({ isActive: 1 });
42
+ __MODEL_NAME__Schema.index({ isDeleted: 1, createdAt: -1 });
43
+
44
+ // ============================================
45
+ // PRE-HOOKS
46
+ // ============================================
47
+
48
+ // Before save
49
+ __MODEL_NAME__Schema.pre('save', function(next) {
50
+ if (this.isModified()) {
51
+ this.metadata.version = (this.metadata.version || 0) + 1;
52
+ }
53
+ next();
54
+ });
55
+
56
+ // Before update
57
+ __MODEL_NAME__Schema.pre('findByIdAndUpdate', function(next) {
58
+ this.set({ updatedAt: new Date() });
59
+ next();
60
+ });
61
+
62
+ // Before delete (soft delete)
63
+ __MODEL_NAME__Schema.pre('findByIdAndRemove', function(next) {
64
+ this.set({
65
+ isDeleted: true,
66
+ 'metadata.deletedAt': new Date()
67
+ });
68
+ next();
69
+ });
70
+
71
+ // ============================================
72
+ // POST-HOOKS
73
+ // ============================================
74
+
75
+ // After save
76
+ __MODEL_NAME__Schema.post('save', function(doc) {
77
+ // Can be used for logging, notifications, etc.
78
+ console.log(`New ${this.constructor.modelName} created`);
79
+ });
80
+
81
+ // ============================================
82
+ // QUERY HELPERS
83
+ // ============================================
84
+
85
+ // Find only active documents by default
86
+ __MODEL_NAME__Schema.query.active = function() {
87
+ return this.find({ isActive: true, isDeleted: false });
88
+ };
89
+
90
+ // Find only deleted documents
91
+ __MODEL_NAME__Schema.query.deleted = function() {
92
+ return this.find({ isDeleted: true });
93
+ };
94
+
95
+ // ============================================
96
+ // STATIC METHODS
97
+ // ============================================
98
+
99
+ // Find by ID (only active)
100
+ __MODEL_NAME__Schema.statics.findActiveById = async function(id) {
101
+ return this.findOne({ _id: id, isActive: true, isDeleted: false });
102
+ };
103
+
104
+ // Find all active
105
+ __MODEL_NAME__Schema.statics.findAllActive = async function(options = {}) {
106
+ return this.find({ isActive: true, isDeleted: false })
107
+ .sort(options.sort || { createdAt: -1 })
108
+ .limit(options.limit || 100)
109
+ .skip(options.skip || 0);
110
+ };
111
+
112
+ // Count active
113
+ __MODEL_NAME__Schema.statics.countActive = async function() {
114
+ return this.countDocuments({ isActive: true, isDeleted: false });
115
+ };
116
+
117
+ // Soft delete
118
+ __MODEL_NAME__Schema.statics.softDelete = async function(id) {
119
+ return this.findByIdAndUpdate(
120
+ id,
121
+ {
122
+ isDeleted: true,
123
+ 'metadata.deletedAt': new Date()
124
+ },
125
+ { new: true }
126
+ );
127
+ };
128
+
129
+ // Restore
130
+ __MODEL_NAME__Schema.statics.restore = async function(id) {
131
+ return this.findByIdAndUpdate(
132
+ id,
133
+ {
134
+ isDeleted: false,
135
+ 'metadata.deletedAt': null
136
+ },
137
+ { new: true }
138
+ );
139
+ };
140
+
141
+ // ============================================
142
+ // INSTANCE METHODS
143
+ // ============================================
144
+
145
+ // Convert to JSON
146
+ __MODEL_NAME__Schema.methods.toJSON = function() {
147
+ const obj = this.toObject();
148
+ delete obj.__v;
149
+ return obj;
150
+ };
151
+
152
+ // Mark as active
153
+ __MODEL_NAME__Schema.methods.activate = async function() {
154
+ this.isActive = true;
155
+ return this.save();
156
+ };
157
+
158
+ // Mark as inactive
159
+ __MODEL_NAME__Schema.methods.deactivate = async function() {
160
+ this.isActive = false;
161
+ return this.save();
162
+ };
163
+
164
+ // Soft delete
165
+ __MODEL_NAME__Schema.methods.delete = async function() {
166
+ this.isDeleted = true;
167
+ this.metadata.deletedAt = new Date();
168
+ return this.save();
169
+ };
170
+
171
+ // ============================================
172
+ // VIRTUALS
173
+ // ============================================
174
+
175
+ // Format dates for response
176
+ __MODEL_NAME__Schema.virtual('formattedCreatedAt').get(function() {
177
+ return this.createdAt ? this.createdAt.toISOString() : null;
178
+ });
179
+
180
+ __MODEL_NAME__Schema.virtual('formattedUpdatedAt').get(function() {
181
+ return this.updatedAt ? this.updatedAt.toISOString() : null;
182
+ });
183
+
184
+ // ============================================
185
+ // VALIDATION
186
+ // ============================================
187
+
188
+ // Custom validation example
189
+ __MODEL_NAME__Schema.pre('validate', function(next) {
190
+ // Add custom validation logic here
191
+ next();
192
+ });
193
+
194
+ // ============================================
195
+ // MODEL EXPORT
196
+ // ============================================
197
+
198
+ const __MODEL_NAME__ = mongoose.model('__MODEL_NAME__', __MODEL_NAME__Schema);
199
+
200
+ export default __MODEL_NAME__;
201
+
@@ -0,0 +1,341 @@
1
+ /**
2
+ * Advanced Route Template - Production Ready
3
+ * Includes authentication, authorization, pagination, filtering, sorting, search
4
+ * Auto-generated by offbyt
5
+ */
6
+
7
+ import express from 'express';
8
+ import { query, body, param } from 'express-validator';
9
+ import { validateErrors } from '../middleware/validation.js';
10
+ import { PaginationHelper } from '../utils/pagination.js';
11
+ import { authenticateToken } from '../middleware/auth.js';
12
+ import __MODEL_NAME__ from '../models/__MODEL_NAME__.js';
13
+
14
+ const router = express.Router();
15
+
16
+ // ============================================
17
+ // ADVANCED GET - List with Pagination & Filters
18
+ // ============================================
19
+ router.get(
20
+ '/',
21
+ [
22
+ query('page').optional().isInt({ min: 1 }).toInt(),
23
+ query('limit').optional().isInt({ min: 1, max: 100 }).toInt(),
24
+ query('sort').optional().isString(),
25
+ query('search').optional().isString().trim(),
26
+ validateErrors
27
+ ],
28
+ async (req, res, next) => {
29
+ try {
30
+ const { page = 1, limit = 10, skip = 0 } = PaginationHelper.getPaginationParams(req);
31
+ const sort = PaginationHelper.getSortObject(req.query.sort);
32
+
33
+ // Build query with filters and search
34
+ let query = {};
35
+
36
+ // Apply search
37
+ if (req.query.search) {
38
+ query = PaginationHelper.buildSearchQuery(req.query.search, [__SEARCH_FIELDS__]);
39
+ }
40
+
41
+ // Apply additional filters from query params
42
+ const filterFields = [__FILTER_FIELDS__];
43
+ const filters = PaginationHelper.buildFilterQuery(req, filterFields);
44
+ query = { ...query, ...filters };
45
+
46
+ // Execute paginated query
47
+ const result = await PaginationHelper.paginate(
48
+ __MODEL_NAME__,
49
+ query,
50
+ { page, limit, skip, sort }
51
+ );
52
+
53
+ res.status(200).json({
54
+ success: true,
55
+ message: '__MODEL_NAME__ retrieved successfully',
56
+ ...result
57
+ });
58
+ } catch (error) {
59
+ next(error);
60
+ }
61
+ }
62
+ );
63
+
64
+ // ============================================
65
+ // GET SINGLE BY ID
66
+ // ============================================
67
+ router.get(
68
+ '/:id',
69
+ [param('id').isMongoId().withMessage('Invalid ID'), validateErrors],
70
+ async (req, res, next) => {
71
+ try {
72
+ const data = await __MODEL_NAME__.findById(req.params.id);
73
+
74
+ if (!data) {
75
+ return res.status(404).json({
76
+ success: false,
77
+ message: '__MODEL_NAME__ not found'
78
+ });
79
+ }
80
+
81
+ res.status(200).json({
82
+ success: true,
83
+ data
84
+ });
85
+ } catch (error) {
86
+ next(error);
87
+ }
88
+ }
89
+ );
90
+
91
+ // ============================================
92
+ // CREATE NEW
93
+ // ============================================
94
+ router.post(
95
+ '/',
96
+ authenticateToken,
97
+ [__CREATE_VALIDATORS__validateErrors],
98
+ async (req, res, next) => {
99
+ try {
100
+ const newData = new __MODEL_NAME__(req.body);
101
+ const saved = await newData.save();
102
+
103
+ res.status(201).json({
104
+ success: true,
105
+ message: '__MODEL_NAME__ created successfully',
106
+ data: saved
107
+ });
108
+ } catch (error) {
109
+ if (error.name === 'ValidationError') {
110
+ return res.status(400).json({
111
+ success: false,
112
+ message: 'Validation Error',
113
+ errors: Object.values(error.errors).map(e => e.message)
114
+ });
115
+ }
116
+ next(error);
117
+ }
118
+ }
119
+ );
120
+
121
+ // ============================================
122
+ // UPDATE ENTIRE DOCUMENT (PUT)
123
+ // ============================================
124
+ router.put(
125
+ '/:id',
126
+ authenticateToken,
127
+ [
128
+ param('id').isMongoId().withMessage('Invalid ID'),
129
+ __UPDATE_VALIDATORS__validateErrors
130
+ ],
131
+ async (req, res, next) => {
132
+ try {
133
+ const updated = await __MODEL_NAME__.findByIdAndUpdate(
134
+ req.params.id,
135
+ req.body,
136
+ { new: true, runValidators: true }
137
+ );
138
+
139
+ if (!updated) {
140
+ return res.status(404).json({
141
+ success: false,
142
+ message: '__MODEL_NAME__ not found'
143
+ });
144
+ }
145
+
146
+ res.status(200).json({
147
+ success: true,
148
+ message: '__MODEL_NAME__ updated successfully',
149
+ data: updated
150
+ });
151
+ } catch (error) {
152
+ if (error.name === 'ValidationError') {
153
+ return res.status(400).json({
154
+ success: false,
155
+ message: 'Validation Error',
156
+ errors: Object.values(error.errors).map(e => e.message)
157
+ });
158
+ }
159
+ next(error);
160
+ }
161
+ }
162
+ );
163
+
164
+ // ============================================
165
+ // PARTIAL UPDATE (PATCH)
166
+ // ============================================
167
+ router.patch(
168
+ '/:id',
169
+ authenticateToken,
170
+ [
171
+ param('id').isMongoId().withMessage('Invalid ID'),
172
+ validateErrors
173
+ ],
174
+ async (req, res, next) => {
175
+ try {
176
+ // Remove fields that shouldn't be updated
177
+ const restrictedFields = ['_id', 'createdAt'];
178
+ restrictedFields.forEach(field => delete req.body[field]);
179
+
180
+ const updated = await __MODEL_NAME__.findByIdAndUpdate(
181
+ req.params.id,
182
+ { $set: req.body },
183
+ { new: true, runValidators: true }
184
+ );
185
+
186
+ if (!updated) {
187
+ return res.status(404).json({
188
+ success: false,
189
+ message: '__MODEL_NAME__ not found'
190
+ });
191
+ }
192
+
193
+ res.status(200).json({
194
+ success: true,
195
+ message: '__MODEL_NAME__ updated successfully',
196
+ data: updated
197
+ });
198
+ } catch (error) {
199
+ next(error);
200
+ }
201
+ }
202
+ );
203
+
204
+ // ============================================
205
+ // DELETE
206
+ // ============================================
207
+ router.delete(
208
+ '/:id',
209
+ authenticateToken,
210
+ [param('id').isMongoId().withMessage('Invalid ID'), validateErrors],
211
+ async (req, res, next) => {
212
+ try {
213
+ const deleted = await __MODEL_NAME__.findByIdAndDelete(req.params.id);
214
+
215
+ if (!deleted) {
216
+ return res.status(404).json({
217
+ success: false,
218
+ message: '__MODEL_NAME__ not found'
219
+ });
220
+ }
221
+
222
+ res.status(200).json({
223
+ success: true,
224
+ message: '__MODEL_NAME__ deleted successfully',
225
+ data: deleted
226
+ });
227
+ } catch (error) {
228
+ next(error);
229
+ }
230
+ }
231
+ );
232
+
233
+ // ============================================
234
+ // BULK OPERATIONS
235
+ // ============================================
236
+
237
+ // Bulk Create
238
+ router.post(
239
+ '/bulk/create',
240
+ authenticateToken,
241
+ [
242
+ body('items').isArray({ min: 1 }).withMessage('Must provide array of items'),
243
+ validateErrors
244
+ ],
245
+ async (req, res, next) => {
246
+ try {
247
+ const items = await __MODEL_NAME__.insertMany(req.body.items, { ordered: false });
248
+
249
+ res.status(201).json({
250
+ success: true,
251
+ message: `Created ${items.length} records`,
252
+ count: items.length,
253
+ data: items
254
+ });
255
+ } catch (error) {
256
+ next(error);
257
+ }
258
+ }
259
+ );
260
+
261
+ // Bulk Delete
262
+ router.delete(
263
+ '/bulk/delete',
264
+ authenticateToken,
265
+ [
266
+ body('ids').isArray({ min: 1 }).withMessage('Must provide array of IDs'),
267
+ validateErrors
268
+ ],
269
+ async (req, res, next) => {
270
+ try {
271
+ const result = await __MODEL_NAME__.deleteMany({
272
+ _id: { $in: req.body.ids }
273
+ });
274
+
275
+ res.status(200).json({
276
+ success: true,
277
+ message: `Deleted ${result.deletedCount} records`,
278
+ deletedCount: result.deletedCount
279
+ });
280
+ } catch (error) {
281
+ next(error);
282
+ }
283
+ }
284
+ );
285
+
286
+ // Bulk Update
287
+ router.put(
288
+ '/bulk/update',
289
+ [
290
+ body('updates').isArray().withMessage('Must provide array of updates'),
291
+ validateErrors
292
+ ],
293
+ async (req, res, next) => {
294
+ try {
295
+ const bulkOps = req.body.updates.map(update => ({
296
+ updateOne: {
297
+ filter: { _id: update.id },
298
+ update: { $set: update.data },
299
+ upsert: false
300
+ }
301
+ }));
302
+
303
+ const result = await __MODEL_NAME__.bulkWrite(bulkOps);
304
+
305
+ res.status(200).json({
306
+ success: true,
307
+ message: 'Bulk update completed',
308
+ modifiedCount: result.modifiedCount,
309
+ matchedCount: result.matchedCount
310
+ });
311
+ } catch (error) {
312
+ next(error);
313
+ }
314
+ }
315
+ );
316
+
317
+ // ============================================
318
+ // STATISTICS & AGGREGATION
319
+ // ============================================
320
+ router.get(
321
+ '/stats/aggregate',
322
+ async (req, res, next) => {
323
+ try {
324
+ const total = await __MODEL_NAME__.countDocuments();
325
+ const recent = await __MODEL_NAME__.find().sort({ createdAt: -1 }).limit(5);
326
+
327
+ res.status(200).json({
328
+ success: true,
329
+ stats: {
330
+ total,
331
+ recent
332
+ }
333
+ });
334
+ } catch (error) {
335
+ next(error);
336
+ }
337
+ }
338
+ );
339
+
340
+ export default router;
341
+
@@ -0,0 +1,87 @@
1
+ import jwt from 'jsonwebtoken';
2
+
3
+ // Ensure JWT_SECRET is consistent across all auth checks
4
+ const JWT_SECRET = process.env.JWT_SECRET || 'your_super_secret_jwt_key_change_this_in_production';
5
+
6
+ /**
7
+ * JWT Authentication Middleware
8
+ * Verifies JWT token from Authorization header
9
+ */
10
+ export const authenticateToken = (req, res, next) => {
11
+ try {
12
+ const authHeader = req.headers['authorization'];
13
+ const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
14
+
15
+ if (!token) {
16
+ return res.status(401).json({
17
+ success: false,
18
+ message: 'Access token required',
19
+ code: 'NO_TOKEN'
20
+ });
21
+ }
22
+
23
+ jwt.verify(token, JWT_SECRET, (err, user) => {
24
+ if (err) {
25
+ if (err.name === 'TokenExpiredError') {
26
+ return res.status(401).json({
27
+ success: false,
28
+ message: 'Token has expired',
29
+ code: 'TOKEN_EXPIRED'
30
+ });
31
+ }
32
+ return res.status(403).json({
33
+ success: false,
34
+ message: 'Invalid token',
35
+ code: 'INVALID_TOKEN'
36
+ });
37
+ }
38
+
39
+ req.user = user;
40
+ next();
41
+ });
42
+ } catch (error) {
43
+ res.status(500).json({
44
+ success: false,
45
+ message: 'Token verification failed',
46
+ error: error.message
47
+ });
48
+ }
49
+ };
50
+
51
+ /**
52
+ * Optional Authentication Middleware
53
+ * Doesn't fail if no token, just sets req.user if valid token exists
54
+ */
55
+ export const optionalAuth = (req, res, next) => {
56
+ try {
57
+ const authHeader = req.headers['authorization'];
58
+ const token = authHeader && authHeader.split(' ')[1];
59
+
60
+ if (!token) {
61
+ return next();
62
+ }
63
+
64
+ jwt.verify(token, JWT_SECRET, (err, user) => {
65
+ if (!err) {
66
+ req.user = user;
67
+ }
68
+ next();
69
+ });
70
+ } catch (error) {
71
+ next();
72
+ }
73
+ };
74
+
75
+ /**
76
+ * Admin Only Middleware
77
+ */
78
+ export const adminOnly = (req, res, next) => {
79
+ if (!req.user || req.user.role !== 'admin') {
80
+ return res.status(403).json({
81
+ success: false,
82
+ message: 'Admin access required',
83
+ code: 'ADMIN_ONLY'
84
+ });
85
+ }
86
+ next();
87
+ };