@feardread/fear 2.0.0 → 2.0.2
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/FEAR.js +1 -37
- package/controllers/crud/crud.js +265 -175
- package/controllers/review.js +7 -6
- package/libs/emailer/smtp.js +1 -1
- package/models/category.js +2 -55
- package/models/review.js +5 -2
- package/package.json +1 -1
- package/routes/review.js +1 -1
package/FEAR.js
CHANGED
|
@@ -173,14 +173,11 @@ module.exports = FEAR = (() => {
|
|
|
173
173
|
|
|
174
174
|
mailinfo.service = mailService;
|
|
175
175
|
this.mailinfo = mailinfo;
|
|
176
|
-
|
|
177
176
|
if ( !this.mailer ) {
|
|
178
177
|
this.mailer = new smtp(this);
|
|
179
178
|
}
|
|
180
179
|
},
|
|
181
|
-
|
|
182
|
-
* Parse allowed origins from environment
|
|
183
|
-
*/
|
|
180
|
+
|
|
184
181
|
getAllowedOrigins() {
|
|
185
182
|
if (!this.env.ALLOWED_ORIGINS) return [];
|
|
186
183
|
|
|
@@ -190,9 +187,6 @@ module.exports = FEAR = (() => {
|
|
|
190
187
|
.filter(origin => origin.length > 0);
|
|
191
188
|
},
|
|
192
189
|
|
|
193
|
-
/**
|
|
194
|
-
* Get CORS configuration object
|
|
195
|
-
*/
|
|
196
190
|
getCorsConfig() {
|
|
197
191
|
return {
|
|
198
192
|
credentials: true,
|
|
@@ -209,9 +203,6 @@ module.exports = FEAR = (() => {
|
|
|
209
203
|
};
|
|
210
204
|
},
|
|
211
205
|
|
|
212
|
-
/**
|
|
213
|
-
* Auto-load routes from routes directory
|
|
214
|
-
*/
|
|
215
206
|
setupRoutes() {
|
|
216
207
|
const routesDir = path.join(__dirname, "routes");
|
|
217
208
|
|
|
@@ -241,9 +232,6 @@ module.exports = FEAR = (() => {
|
|
|
241
232
|
}
|
|
242
233
|
},
|
|
243
234
|
|
|
244
|
-
/**
|
|
245
|
-
* Register a single router
|
|
246
|
-
*/
|
|
247
235
|
useRouter(router, routePath = DEFAULT_ROUTE_PATH, corsOptions = null) {
|
|
248
236
|
if (!router || typeof router !== 'function') {
|
|
249
237
|
throw new Error('Router must be a valid Express router instance');
|
|
@@ -264,9 +252,6 @@ module.exports = FEAR = (() => {
|
|
|
264
252
|
return this;
|
|
265
253
|
},
|
|
266
254
|
|
|
267
|
-
/**
|
|
268
|
-
* Register multiple routers
|
|
269
|
-
*/
|
|
270
255
|
useRouters(routers) {
|
|
271
256
|
if (!Array.isArray(routers)) {
|
|
272
257
|
throw new Error('Routers must be an array');
|
|
@@ -285,9 +270,6 @@ module.exports = FEAR = (() => {
|
|
|
285
270
|
return this;
|
|
286
271
|
},
|
|
287
272
|
|
|
288
|
-
/**
|
|
289
|
-
* Create a new router with FEAR utilities attached
|
|
290
|
-
*/
|
|
291
273
|
createRouter() {
|
|
292
274
|
const router = express.Router();
|
|
293
275
|
|
|
@@ -305,9 +287,6 @@ module.exports = FEAR = (() => {
|
|
|
305
287
|
return router;
|
|
306
288
|
},
|
|
307
289
|
|
|
308
|
-
/**
|
|
309
|
-
* Get list of registered routers
|
|
310
|
-
*/
|
|
311
290
|
getRegisteredRouters() {
|
|
312
291
|
return this.registeredRouters.map(info => ({
|
|
313
292
|
path: info.path,
|
|
@@ -315,9 +294,6 @@ module.exports = FEAR = (() => {
|
|
|
315
294
|
}));
|
|
316
295
|
},
|
|
317
296
|
|
|
318
|
-
/**
|
|
319
|
-
* Start the HTTP server
|
|
320
|
-
*/
|
|
321
297
|
start(port = null) {
|
|
322
298
|
const serverPort = port || this.app.get("PORT") || DEFAULT_PORT;
|
|
323
299
|
|
|
@@ -340,9 +316,6 @@ module.exports = FEAR = (() => {
|
|
|
340
316
|
});
|
|
341
317
|
},
|
|
342
318
|
|
|
343
|
-
/**
|
|
344
|
-
* Gracefully shutdown the server
|
|
345
|
-
*/
|
|
346
319
|
shutdown() {
|
|
347
320
|
this.logger.info('Initiating graceful shutdown...');
|
|
348
321
|
|
|
@@ -382,9 +355,6 @@ module.exports = FEAR = (() => {
|
|
|
382
355
|
});
|
|
383
356
|
},
|
|
384
357
|
|
|
385
|
-
/**
|
|
386
|
-
* Close database connections
|
|
387
|
-
*/
|
|
388
358
|
closeDatabase() {
|
|
389
359
|
return new Promise((resolve, reject) => {
|
|
390
360
|
if (this.db && typeof this.db.disconnect === 'function') {
|
|
@@ -400,9 +370,6 @@ module.exports = FEAR = (() => {
|
|
|
400
370
|
});
|
|
401
371
|
},
|
|
402
372
|
|
|
403
|
-
/**
|
|
404
|
-
* Set FEAR utilities as global
|
|
405
|
-
*/
|
|
406
373
|
setAsGlobal() {
|
|
407
374
|
global.FearRouter = {
|
|
408
375
|
createRouter: () => this.createRouter(),
|
|
@@ -420,9 +387,6 @@ module.exports = FEAR = (() => {
|
|
|
420
387
|
return FEAR;
|
|
421
388
|
})();
|
|
422
389
|
|
|
423
|
-
/**
|
|
424
|
-
* Factory function to create new FEAR instance
|
|
425
|
-
*/
|
|
426
390
|
exports.FearFactory = () => {
|
|
427
391
|
return new FEAR();
|
|
428
392
|
};
|
package/controllers/crud/crud.js
CHANGED
|
@@ -12,6 +12,41 @@ const isValidObjectId = (id) => {
|
|
|
12
12
|
return mongoose.Types.ObjectId.isValid(id) && /^[0-9a-fA-F]{24}$/.test(id);
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Sanitizes update data by removing undefined values
|
|
17
|
+
* @param {Object} data - Data object to sanitize
|
|
18
|
+
* @returns {Object} Sanitized data object
|
|
19
|
+
*/
|
|
20
|
+
const sanitizeUpdateData = (data) => {
|
|
21
|
+
const sanitized = { ...data };
|
|
22
|
+
Object.keys(sanitized).forEach(key => {
|
|
23
|
+
if (sanitized[key] === undefined) {
|
|
24
|
+
delete sanitized[key];
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return sanitized;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Processes image data for upload
|
|
32
|
+
* @param {string|Array} images - Images to process
|
|
33
|
+
* @returns {Promise<Array>} Promise resolving to uploaded image URLs
|
|
34
|
+
*/
|
|
35
|
+
const processImages = (images) => {
|
|
36
|
+
if (!images) return Promise.resolve(null);
|
|
37
|
+
|
|
38
|
+
const imageArray = Array.isArray(images)
|
|
39
|
+
? images
|
|
40
|
+
: images.split(',').map(item => item.trim());
|
|
41
|
+
|
|
42
|
+
return cloud.uploadImages(imageArray)
|
|
43
|
+
.then(imageLinks => imageLinks || null)
|
|
44
|
+
.catch(error => {
|
|
45
|
+
console.error('Error uploading images:', error);
|
|
46
|
+
return null;
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
15
50
|
/**
|
|
16
51
|
* Get all documents for requested Model
|
|
17
52
|
* @param {mongoose.Model} Model - Mongoose model
|
|
@@ -19,34 +54,27 @@ const isValidObjectId = (id) => {
|
|
|
19
54
|
* @param {Object} res - Express response object
|
|
20
55
|
* @returns {Promise<void>}
|
|
21
56
|
*/
|
|
22
|
-
exports.all = tryCatch(
|
|
57
|
+
exports.all = tryCatch((Model, req, res) => {
|
|
23
58
|
const { sort = 'category', order = 'asc', populate = 'true' } = req.query;
|
|
24
|
-
|
|
25
59
|
const sortOrder = order === 'desc' ? -1 : 1;
|
|
60
|
+
|
|
26
61
|
let query = Model.find().sort({ [sort]: sortOrder });
|
|
27
62
|
|
|
28
|
-
|
|
29
|
-
if (populate !== 'false') {
|
|
30
|
-
query = query.populate();
|
|
31
|
-
}
|
|
63
|
+
if (populate !== 'false') query = query.populate();
|
|
32
64
|
|
|
33
|
-
|
|
65
|
+
return query
|
|
66
|
+
.exec()
|
|
67
|
+
.then(result => {
|
|
68
|
+
if (!result || result.length === 0) {
|
|
69
|
+
return res.status(200).json({result: [],success: true,message: "No documents found",count: 0});
|
|
70
|
+
}
|
|
34
71
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
count: 0
|
|
72
|
+
return res.status(200).json({result,success: true,message: `Found ${result.length} documents`,count: result.length});
|
|
73
|
+
})
|
|
74
|
+
.catch(error => {
|
|
75
|
+
console.error('Error fetching documents:', error);
|
|
76
|
+
return res.status(500).json({result: null,success: false});
|
|
41
77
|
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return res.status(200).json({
|
|
45
|
-
result,
|
|
46
|
-
success: true,
|
|
47
|
-
message: `Found ${result.length} documents`,
|
|
48
|
-
count: result.length
|
|
49
|
-
});
|
|
50
78
|
});
|
|
51
79
|
|
|
52
80
|
/**
|
|
@@ -56,31 +84,43 @@ exports.all = tryCatch(async (Model, req, res) => {
|
|
|
56
84
|
* @param {Object} res - Express response object
|
|
57
85
|
* @returns {Promise<void>}
|
|
58
86
|
*/
|
|
59
|
-
exports.read = tryCatch(
|
|
87
|
+
exports.read = tryCatch((Model, req, res) => {
|
|
60
88
|
const { id } = req.params;
|
|
61
89
|
|
|
62
90
|
if (!id || !isValidObjectId(id)) {
|
|
63
|
-
return
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
91
|
+
return Promise.resolve(
|
|
92
|
+
res.status(400).json({
|
|
93
|
+
result: null,
|
|
94
|
+
success: false,
|
|
95
|
+
message: "Invalid or missing document ID"
|
|
96
|
+
})
|
|
97
|
+
);
|
|
68
98
|
}
|
|
69
|
-
const result = await Model.findById(id).exec();
|
|
70
99
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
result
|
|
74
|
-
|
|
75
|
-
|
|
100
|
+
return Model.findById(id).exec()
|
|
101
|
+
.then(result => {
|
|
102
|
+
if (!result) {
|
|
103
|
+
return res.status(404).json({
|
|
104
|
+
result: null,
|
|
105
|
+
success: false,
|
|
106
|
+
message: `Document with ID ${id} not found`
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return res.status(200).json({
|
|
111
|
+
result,
|
|
112
|
+
success: true,
|
|
113
|
+
message: "Document found successfully"
|
|
114
|
+
});
|
|
115
|
+
})
|
|
116
|
+
.catch(error => {
|
|
117
|
+
console.error('Error reading document:', error);
|
|
118
|
+
return res.status(500).json({
|
|
119
|
+
result: null,
|
|
120
|
+
success: false,
|
|
121
|
+
message: "Error reading document"
|
|
122
|
+
});
|
|
76
123
|
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return res.status(200).json({
|
|
80
|
-
result,
|
|
81
|
-
success: true,
|
|
82
|
-
message: "Document found successfully"
|
|
83
|
-
});
|
|
84
124
|
});
|
|
85
125
|
|
|
86
126
|
/**
|
|
@@ -90,27 +130,36 @@ exports.read = tryCatch(async (Model, req, res) => {
|
|
|
90
130
|
* @param {Object} res - Express response object
|
|
91
131
|
* @returns {Promise<void>}
|
|
92
132
|
*/
|
|
93
|
-
exports.create = tryCatch(
|
|
133
|
+
exports.create = tryCatch((Model, req, res) => {
|
|
94
134
|
const documentData = { ...req.body };
|
|
95
|
-
// Handle image uploads if present
|
|
96
|
-
if (documentData.images) {
|
|
97
|
-
|
|
98
|
-
documentData.images.split(',').map(item => item.trim());
|
|
99
|
-
|
|
100
|
-
const imageLinks = await cloud.uploadImages(documentData.images);
|
|
101
|
-
|
|
102
|
-
if ( imageLinks ) documentData.images = imageLinks;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
console.log('Creating document:', documentData);
|
|
106
135
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
136
|
+
return processImages(documentData.images)
|
|
137
|
+
.then(imageLinks => {
|
|
138
|
+
if (imageLinks) {
|
|
139
|
+
documentData.images = imageLinks;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log('Creating document:', documentData);
|
|
143
|
+
const document = new Model(documentData);
|
|
144
|
+
|
|
145
|
+
return document.save();
|
|
146
|
+
})
|
|
147
|
+
.then(result => {
|
|
148
|
+
return res.status(201).json({
|
|
149
|
+
result,
|
|
150
|
+
success: true,
|
|
151
|
+
message: `Document created successfully in ${Model.modelName} collection`
|
|
152
|
+
});
|
|
153
|
+
})
|
|
154
|
+
.catch(error => {
|
|
155
|
+
console.error('Error creating document:', error);
|
|
156
|
+
return res.status(500).json({
|
|
157
|
+
result: null,
|
|
158
|
+
success: false,
|
|
159
|
+
message: "Error creating document",
|
|
160
|
+
error: error.message
|
|
161
|
+
});
|
|
162
|
+
});
|
|
114
163
|
});
|
|
115
164
|
|
|
116
165
|
/**
|
|
@@ -120,60 +169,71 @@ exports.create = tryCatch(async (Model, req, res) => {
|
|
|
120
169
|
* @param {Object} res - Express response object
|
|
121
170
|
* @returns {Promise<void>}
|
|
122
171
|
*/
|
|
123
|
-
exports.update = tryCatch(
|
|
172
|
+
exports.update = tryCatch((Model, req, res) => {
|
|
124
173
|
const { id } = req.params;
|
|
125
174
|
|
|
126
175
|
if (!id) {
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
176
|
+
return Promise.resolve(
|
|
177
|
+
res.status(400).json({
|
|
178
|
+
result: null,
|
|
179
|
+
success: false,
|
|
180
|
+
message: "Document ID is required"
|
|
181
|
+
})
|
|
182
|
+
);
|
|
132
183
|
}
|
|
133
184
|
|
|
134
185
|
if (!isValidObjectId(id)) {
|
|
135
|
-
return
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const updateData = { ...req.body };
|
|
143
|
-
|
|
144
|
-
// Remove undefined values
|
|
145
|
-
Object.keys(updateData).forEach(key => {
|
|
146
|
-
if (updateData[key] === undefined) {
|
|
147
|
-
delete updateData[key];
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// Handle image uploads if present
|
|
152
|
-
if (updateData.images && Array.isArray(updateData.images) && updateData.images.length > 0) {
|
|
153
|
-
const uploadedImages = await cloud.uploadImages(updateData.images);
|
|
154
|
-
updateData.images = uploadedImages;
|
|
186
|
+
return Promise.resolve(
|
|
187
|
+
res.status(400).json({
|
|
188
|
+
result: null,
|
|
189
|
+
success: false,
|
|
190
|
+
message: "Invalid document ID format"
|
|
191
|
+
})
|
|
192
|
+
);
|
|
155
193
|
}
|
|
156
194
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
updateData
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
195
|
+
const updateData = sanitizeUpdateData(req.body);
|
|
196
|
+
|
|
197
|
+
const imagePromise = (updateData.images && Array.isArray(updateData.images) && updateData.images.length > 0)
|
|
198
|
+
? processImages(updateData.images)
|
|
199
|
+
: Promise.resolve(null);
|
|
200
|
+
|
|
201
|
+
return imagePromise
|
|
202
|
+
.then(uploadedImages => {
|
|
203
|
+
if (uploadedImages) {
|
|
204
|
+
updateData.images = uploadedImages;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return Model.findByIdAndUpdate(
|
|
208
|
+
id,
|
|
209
|
+
updateData,
|
|
210
|
+
{ new: true, runValidators: true }
|
|
211
|
+
).exec();
|
|
212
|
+
})
|
|
213
|
+
.then(result => {
|
|
214
|
+
if (!result) {
|
|
215
|
+
return res.status(404).json({
|
|
216
|
+
result: null,
|
|
217
|
+
success: false,
|
|
218
|
+
message: `Document with ID ${id} not found`
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return res.status(200).json({
|
|
223
|
+
result,
|
|
224
|
+
success: true,
|
|
225
|
+
message: `Document updated successfully with ID: ${id}`
|
|
226
|
+
});
|
|
227
|
+
})
|
|
228
|
+
.catch(error => {
|
|
229
|
+
console.error('Error updating document:', error);
|
|
230
|
+
return res.status(500).json({
|
|
231
|
+
result: null,
|
|
232
|
+
success: false,
|
|
233
|
+
message: "Error updating document",
|
|
234
|
+
error: error.message
|
|
235
|
+
});
|
|
169
236
|
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return res.status(200).json({
|
|
173
|
-
result,
|
|
174
|
-
success: true,
|
|
175
|
-
message: `Document updated successfully with ID: ${id}`
|
|
176
|
-
});
|
|
177
237
|
});
|
|
178
238
|
|
|
179
239
|
/**
|
|
@@ -183,47 +243,60 @@ exports.update = tryCatch(async (Model, req, res) => {
|
|
|
183
243
|
* @param {Object} res - Express response object
|
|
184
244
|
* @returns {Promise<void>}
|
|
185
245
|
*/
|
|
186
|
-
exports.delete = tryCatch(
|
|
246
|
+
exports.delete = tryCatch((Model, req, res) => {
|
|
187
247
|
const { id } = req.params;
|
|
188
248
|
|
|
189
249
|
if (!id) {
|
|
190
|
-
return
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
250
|
+
return Promise.resolve(
|
|
251
|
+
res.status(400).json({
|
|
252
|
+
result: null,
|
|
253
|
+
success: false,
|
|
254
|
+
message: "Document ID is required"
|
|
255
|
+
})
|
|
256
|
+
);
|
|
195
257
|
}
|
|
196
258
|
|
|
197
259
|
if (!isValidObjectId(id)) {
|
|
198
|
-
return
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
260
|
+
return Promise.resolve(
|
|
261
|
+
res.status(400).json({
|
|
262
|
+
result: null,
|
|
263
|
+
success: false,
|
|
264
|
+
message: "Invalid document ID format"
|
|
265
|
+
})
|
|
266
|
+
);
|
|
203
267
|
}
|
|
204
268
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
269
|
+
return Model.findByIdAndDelete(id)
|
|
270
|
+
.exec()
|
|
271
|
+
.then(result => {
|
|
272
|
+
if (!result) {
|
|
273
|
+
return res.status(404).json({ result: null, success: false });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Clean up associated images if they exist
|
|
277
|
+
if (result.images && Array.isArray(result.images) && result.images.length > 0) {
|
|
278
|
+
console.log('Document had images that should be cleaned up:', result.images);
|
|
279
|
+
// Uncomment when cloud.deleteImages is available:
|
|
280
|
+
// return cloud.deleteImages(result.images)
|
|
281
|
+
// .then(() => result)
|
|
282
|
+
// .catch(err => {
|
|
283
|
+
// console.error('Error deleting images:', err);
|
|
284
|
+
// return result;
|
|
285
|
+
// });
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return result;
|
|
289
|
+
})
|
|
290
|
+
.then(result => {
|
|
291
|
+
return res.status(200)
|
|
292
|
+
.json({ result, success: true,
|
|
293
|
+
message: `Document deleted successfully with ID: ${id}`
|
|
294
|
+
});
|
|
295
|
+
})
|
|
296
|
+
.catch(error => {
|
|
297
|
+
console.error('Error deleting document:', error);
|
|
298
|
+
return res.status(500).json({ result: error, success: false });
|
|
212
299
|
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// TODO: Clean up associated images if they exist
|
|
216
|
-
if (result.images && Array.isArray(result.images) && result.images.length > 0) {
|
|
217
|
-
console.log('Document had images that should be cleaned up:', result.images);
|
|
218
|
-
// Uncomment when cloud.deleteImages is available:
|
|
219
|
-
// await cloud.deleteImages(result.images);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return res.status(200).json({
|
|
223
|
-
result,
|
|
224
|
-
success: true,
|
|
225
|
-
message: `Document deleted successfully with ID: ${id}`
|
|
226
|
-
});
|
|
227
300
|
});
|
|
228
301
|
|
|
229
302
|
/**
|
|
@@ -233,7 +306,7 @@ exports.delete = tryCatch(async (Model, req, res) => {
|
|
|
233
306
|
* @param {Object} res - Express response object
|
|
234
307
|
* @returns {Promise<void>}
|
|
235
308
|
*/
|
|
236
|
-
exports.list = tryCatch(
|
|
309
|
+
exports.list = tryCatch((Model, req, res) => {
|
|
237
310
|
const page = Math.max(1, parseInt(req.query.page) || 1);
|
|
238
311
|
const limit = Math.min(100, Math.max(1, parseInt(req.query.items) || 10));
|
|
239
312
|
const skip = (page - 1) * limit;
|
|
@@ -241,35 +314,44 @@ exports.list = tryCatch(async (Model, req, res) => {
|
|
|
241
314
|
|
|
242
315
|
const sortOrder = order === 'asc' ? 1 : -1;
|
|
243
316
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
])
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
317
|
+
const findQuery = Model.find()
|
|
318
|
+
.skip(skip)
|
|
319
|
+
.limit(limit)
|
|
320
|
+
.sort({ [sort]: sortOrder })
|
|
321
|
+
.populate(populate !== 'false' ? undefined : null)
|
|
322
|
+
.exec();
|
|
323
|
+
|
|
324
|
+
const countQuery = Model.countDocuments().exec();
|
|
325
|
+
|
|
326
|
+
return Promise.all([findQuery, countQuery])
|
|
327
|
+
.then(([result, count]) => {
|
|
328
|
+
const totalPages = Math.ceil(count / limit);
|
|
329
|
+
const pagination = {
|
|
330
|
+
currentPage: page,
|
|
331
|
+
totalPages,
|
|
332
|
+
totalItems: count,
|
|
333
|
+
itemsPerPage: limit,
|
|
334
|
+
hasNextPage: page < totalPages,
|
|
335
|
+
hasPrevPage: page > 1
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
return res.status(200).json({
|
|
339
|
+
result,
|
|
340
|
+
success: true,
|
|
341
|
+
pagination,
|
|
342
|
+
message: count > 0
|
|
343
|
+
? `Found ${result.length} of ${count} documents`
|
|
344
|
+
: "Collection is empty"
|
|
345
|
+
});
|
|
346
|
+
})
|
|
347
|
+
.catch(error => {
|
|
348
|
+
console.error('Error listing documents:', error);
|
|
349
|
+
return res.status(500).json({
|
|
350
|
+
result: null,
|
|
351
|
+
success: false,
|
|
352
|
+
message: "Error listing documents"
|
|
353
|
+
});
|
|
354
|
+
});
|
|
273
355
|
});
|
|
274
356
|
|
|
275
357
|
/**
|
|
@@ -279,7 +361,7 @@ exports.list = tryCatch(async (Model, req, res) => {
|
|
|
279
361
|
* @param {Object} res - Express response object
|
|
280
362
|
* @returns {Promise<void>}
|
|
281
363
|
*/
|
|
282
|
-
exports.search = tryCatch(
|
|
364
|
+
exports.search = tryCatch((Model, req, res) => {
|
|
283
365
|
console.log('CRUD search query:', req.query);
|
|
284
366
|
|
|
285
367
|
const {
|
|
@@ -294,7 +376,6 @@ exports.search = tryCatch(async (Model, req, res) => {
|
|
|
294
376
|
limit = 24
|
|
295
377
|
} = req.query;
|
|
296
378
|
|
|
297
|
-
// Build query object
|
|
298
379
|
const queryObj = {
|
|
299
380
|
page: parseInt(page),
|
|
300
381
|
limit: parseInt(limit)
|
|
@@ -325,19 +406,28 @@ exports.search = tryCatch(async (Model, req, res) => {
|
|
|
325
406
|
defaultSort: sortOptions[sortBy] || sortOptions.popularity
|
|
326
407
|
});
|
|
327
408
|
|
|
328
|
-
|
|
409
|
+
return searchApi
|
|
329
410
|
.search(['_id', 'id', 'price', 'name', 'description', 'brand', 'tags'])
|
|
330
411
|
.filter()
|
|
331
412
|
.sort()
|
|
332
413
|
.selectFields('-__v,-updatedAt')
|
|
333
414
|
.populate('reviews')
|
|
334
415
|
.paginate(parseInt(limit), 50)
|
|
335
|
-
.execute()
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
416
|
+
.execute()
|
|
417
|
+
.then(result => {
|
|
418
|
+
return res.status(200).json({
|
|
419
|
+
success: true,
|
|
420
|
+
...result
|
|
421
|
+
});
|
|
422
|
+
})
|
|
423
|
+
.catch(error => {
|
|
424
|
+
console.error('Error searching documents:', error);
|
|
425
|
+
return res.status(500).json({
|
|
426
|
+
success: false,
|
|
427
|
+
message: "Error searching documents",
|
|
428
|
+
error: error.message
|
|
429
|
+
});
|
|
430
|
+
});
|
|
341
431
|
});
|
|
342
432
|
|
|
343
433
|
/**
|
package/controllers/review.js
CHANGED
|
@@ -8,9 +8,9 @@ const Product = require("../models/product");
|
|
|
8
8
|
* @param {Object} res - Express response object
|
|
9
9
|
*/
|
|
10
10
|
exports.review = tryCatch(async (req, res) => {
|
|
11
|
-
const { rating, comment } = req.body;
|
|
12
|
-
const productId = req.params.id;
|
|
13
|
-
const userId = req.user._id;
|
|
11
|
+
const { productId, username, email, rating, comment } = req.body;
|
|
12
|
+
//const productId = req.params.id;
|
|
13
|
+
//const userId = req.user._id;
|
|
14
14
|
|
|
15
15
|
// Validate required fields
|
|
16
16
|
if (!rating || rating < 1 || rating > 5) {
|
|
@@ -42,8 +42,9 @@ exports.review = tryCatch(async (req, res) => {
|
|
|
42
42
|
);
|
|
43
43
|
|
|
44
44
|
const reviewData = {
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
productId: productId,
|
|
46
|
+
username,
|
|
47
|
+
email,
|
|
47
48
|
rating: Number(rating),
|
|
48
49
|
comment: comment.trim(),
|
|
49
50
|
createdAt: new Date()
|
|
@@ -74,7 +75,7 @@ exports.review = tryCatch(async (req, res) => {
|
|
|
74
75
|
if (existingReviewIndex !== -1) {
|
|
75
76
|
// Update existing review in Review collection
|
|
76
77
|
await Review.findOneAndUpdate(
|
|
77
|
-
{
|
|
78
|
+
{ username, productId },
|
|
78
79
|
reviewData,
|
|
79
80
|
{ upsert: true, new: true }
|
|
80
81
|
);
|
package/libs/emailer/smtp.js
CHANGED
|
@@ -24,7 +24,7 @@ module.exports = function (fear) {
|
|
|
24
24
|
throw new Error('Mailgun requires apiKey and domain in configuration.');
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const mailgun = new Mailgun(
|
|
27
|
+
const mailgun = new Mailgun(_this.mailConfig.mailgun);
|
|
28
28
|
const clientOptions = {
|
|
29
29
|
username: 'api',
|
|
30
30
|
key: apiKey
|
package/models/category.js
CHANGED
|
@@ -125,15 +125,13 @@ categorySchema.virtual("postCount", {
|
|
|
125
125
|
// Pre-save middleware
|
|
126
126
|
categorySchema.pre("save", async function(next) {
|
|
127
127
|
// Generate slug
|
|
128
|
-
if (this.isModified("
|
|
129
|
-
const nameToSlugify = this.title
|
|
128
|
+
if (this.isModified("title")) {
|
|
129
|
+
const nameToSlugify = this.title;
|
|
130
130
|
this.slug = slugify(nameToSlugify, {
|
|
131
131
|
lower: true,
|
|
132
132
|
strict: true,
|
|
133
133
|
remove: /[*+~.()'"!:@]/g
|
|
134
134
|
});
|
|
135
|
-
|
|
136
|
-
// Ensure unique slug
|
|
137
135
|
const slugRegEx = new RegExp(`^${this.slug}(-[0-9]*)?$`, "i");
|
|
138
136
|
const categoriesWithSlug = await this.constructor.find({
|
|
139
137
|
slug: slugRegEx,
|
|
@@ -145,22 +143,6 @@ categorySchema.pre("save", async function(next) {
|
|
|
145
143
|
}
|
|
146
144
|
}
|
|
147
145
|
|
|
148
|
-
// Sync name and title if one is missing
|
|
149
|
-
if (!this.name && this.title) {
|
|
150
|
-
this.name = this.title;
|
|
151
|
-
}
|
|
152
|
-
if (!this.title && this.name) {
|
|
153
|
-
this.title = this.name;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Sync isActive with active
|
|
157
|
-
if (this.isModified("isActive")) {
|
|
158
|
-
this.active = this.isActive;
|
|
159
|
-
}
|
|
160
|
-
if (this.isModified("active")) {
|
|
161
|
-
this.isActive = this.active;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
146
|
// Calculate level and build ancestors
|
|
165
147
|
if (this.isModified("parent")) {
|
|
166
148
|
if (this.parent) {
|
|
@@ -185,43 +167,8 @@ categorySchema.pre("save", async function(next) {
|
|
|
185
167
|
this.path = this.slug;
|
|
186
168
|
}
|
|
187
169
|
}
|
|
188
|
-
|
|
189
|
-
// Set SEO defaults
|
|
190
|
-
if (!this.seo.metaTitle) {
|
|
191
|
-
this.seo.metaTitle = (this.title || this.name).substring(0, 70);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (!this.seo.metaDescription && this.description) {
|
|
195
|
-
this.seo.metaDescription = this.description.substring(0, 160);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
next();
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
// Pre-update middleware
|
|
202
|
-
categorySchema.pre("findOneAndUpdate", async function(next) {
|
|
203
|
-
const update = this.getUpdate();
|
|
204
|
-
|
|
205
|
-
if (update.name || update.title) {
|
|
206
|
-
const nameToSlugify = update.title || update.name;
|
|
207
|
-
update.slug = slugify(nameToSlugify, {
|
|
208
|
-
lower: true,
|
|
209
|
-
strict: true,
|
|
210
|
-
remove: /[*+~.()'"!:@]/g
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (update.isActive !== undefined) {
|
|
215
|
-
update.active = update.isActive;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (update.active !== undefined) {
|
|
219
|
-
update.isActive = update.active;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
170
|
next();
|
|
223
171
|
});
|
|
224
|
-
|
|
225
172
|
// Post-remove middleware to clean up references
|
|
226
173
|
categorySchema.post("remove", async function(doc) {
|
|
227
174
|
// Remove from parent's children
|
package/models/review.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
const mongoose = require("mongoose");
|
|
2
2
|
|
|
3
3
|
const reviewSchema = new mongoose.Schema({
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
productId: { type: mongoose.Schema.Types.ObjectId, ref:"Product", required:true },
|
|
5
|
+
username: { type: String, required: true },
|
|
6
|
+
email: { type: String, required: false, unique: true, lowercase: true, trim: true,
|
|
7
|
+
match: [/^\S+@\S+\.\S+$/, 'Please enter a valid email']
|
|
8
|
+
},
|
|
6
9
|
rating: { type:Number, required:true, min:1, max:5 },
|
|
7
10
|
comment: { type:String, required:true },
|
|
8
11
|
createdAt: { type:Date, default:Date.now }
|
package/package.json
CHANGED