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.
- package/README.md +2 -0
- package/cli/index.js +2 -0
- package/cli.js +206 -0
- package/core/detector/detectAxios.js +107 -0
- package/core/detector/detectFetch.js +148 -0
- package/core/detector/detectForms.js +55 -0
- package/core/detector/detectSocket.js +341 -0
- package/core/generator/generateControllers.js +17 -0
- package/core/generator/generateModels.js +25 -0
- package/core/generator/generateRoutes.js +17 -0
- package/core/generator/generateServer.js +18 -0
- package/core/generator/generateSocket.js +160 -0
- package/core/index.js +14 -0
- package/core/ir/IRTypes.js +25 -0
- package/core/ir/buildIR.js +83 -0
- package/core/parser/parseJS.js +26 -0
- package/core/parser/parseTS.js +27 -0
- package/core/rules/relationRules.js +38 -0
- package/core/rules/resourceRules.js +32 -0
- package/core/rules/schemaInference.js +26 -0
- package/core/scanner/scanProject.js +58 -0
- package/deploy/cloudflare.js +41 -0
- package/deploy/cloudflareWorker.js +122 -0
- package/deploy/connect.js +198 -0
- package/deploy/flyio.js +51 -0
- package/deploy/index.js +322 -0
- package/deploy/netlify.js +29 -0
- package/deploy/railway.js +215 -0
- package/deploy/render.js +195 -0
- package/deploy/utils.js +383 -0
- package/deploy/vercel.js +29 -0
- package/index.js +18 -0
- package/lib/generator/advancedCrudGenerator.js +475 -0
- package/lib/generator/crudCodeGenerator.js +486 -0
- package/lib/generator/irBasedGenerator.js +360 -0
- package/lib/ir-builder/index.js +16 -0
- package/lib/ir-builder/irBuilder.js +330 -0
- package/lib/ir-builder/rulesEngine.js +353 -0
- package/lib/ir-builder/templateEngine.js +193 -0
- package/lib/ir-builder/templates/index.js +14 -0
- package/lib/ir-builder/templates/model.template.js +47 -0
- package/lib/ir-builder/templates/routes-generic.template.js +66 -0
- package/lib/ir-builder/templates/routes-user.template.js +105 -0
- package/lib/ir-builder/templates/routes.template.js +102 -0
- package/lib/ir-builder/templates/validation.template.js +15 -0
- package/lib/ir-integration.js +349 -0
- package/lib/modes/benchmark.js +162 -0
- package/lib/modes/configBasedGenerator.js +2258 -0
- package/lib/modes/connect.js +1125 -0
- package/lib/modes/doctorAi.js +172 -0
- package/lib/modes/generateApi.js +435 -0
- package/lib/modes/interactiveSetup.js +548 -0
- package/lib/modes/offline.clean.js +14 -0
- package/lib/modes/offline.enhanced.js +787 -0
- package/lib/modes/offline.js +295 -0
- package/lib/modes/offline.v2.js +13 -0
- package/lib/modes/sync.js +629 -0
- package/lib/scanner/apiEndpointExtractor.js +387 -0
- package/lib/scanner/authPatternDetector.js +54 -0
- package/lib/scanner/frontendScanner.js +642 -0
- package/lib/utils/apiClientGenerator.js +242 -0
- package/lib/utils/apiScanner.js +95 -0
- package/lib/utils/codeInjector.js +350 -0
- package/lib/utils/doctor.js +381 -0
- package/lib/utils/envGenerator.js +36 -0
- package/lib/utils/loadTester.js +61 -0
- package/lib/utils/performanceAnalyzer.js +298 -0
- package/lib/utils/resourceDetector.js +281 -0
- package/package.json +20 -0
- package/templates/.env.template +31 -0
- package/templates/advanced.model.template.js +201 -0
- package/templates/advanced.route.template.js +341 -0
- package/templates/auth.middleware.template.js +87 -0
- package/templates/auth.routes.template.js +238 -0
- package/templates/auth.user.model.template.js +78 -0
- package/templates/cache.middleware.js +34 -0
- package/templates/chat.models.template.js +260 -0
- package/templates/chat.routes.template.js +478 -0
- package/templates/compression.middleware.js +19 -0
- package/templates/database.config.js +74 -0
- package/templates/errorHandler.middleware.js +54 -0
- package/templates/express/controller.ejs +26 -0
- package/templates/express/model.ejs +9 -0
- package/templates/express/route.ejs +18 -0
- package/templates/express/server.ejs +16 -0
- package/templates/frontend.env.template +14 -0
- package/templates/model.template.js +86 -0
- package/templates/package.production.json +51 -0
- package/templates/package.template.json +41 -0
- package/templates/pagination.utility.js +110 -0
- package/templates/production.server.template.js +233 -0
- package/templates/rateLimiter.middleware.js +36 -0
- package/templates/requestLogger.middleware.js +19 -0
- package/templates/response.helper.js +179 -0
- package/templates/route.template.js +130 -0
- package/templates/security.middleware.js +78 -0
- package/templates/server.template.js +91 -0
- package/templates/socket.server.template.js +433 -0
- package/templates/utils.helper.js +157 -0
- package/templates/validation.middleware.js +63 -0
- package/templates/validation.schema.js +128 -0
- package/utils/fileWriter.js +15 -0
- 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
|
+
};
|