@feardread/fear 1.0.1

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 (99) hide show
  1. package/FEAR.js +459 -0
  2. package/FEARServer.js +280 -0
  3. package/controllers/agent.js +438 -0
  4. package/controllers/auth/index.js +345 -0
  5. package/controllers/auth/token.js +50 -0
  6. package/controllers/blog.js +105 -0
  7. package/controllers/brand.js +10 -0
  8. package/controllers/cart.js +425 -0
  9. package/controllers/category.js +9 -0
  10. package/controllers/coupon.js +63 -0
  11. package/controllers/crud/crud.js +508 -0
  12. package/controllers/crud/index.js +36 -0
  13. package/controllers/email.js +34 -0
  14. package/controllers/enquiry.js +65 -0
  15. package/controllers/events.js +9 -0
  16. package/controllers/order.js +125 -0
  17. package/controllers/payment.js +31 -0
  18. package/controllers/product.js +147 -0
  19. package/controllers/review.js +247 -0
  20. package/controllers/tag.js +10 -0
  21. package/controllers/task.js +10 -0
  22. package/controllers/upload.js +41 -0
  23. package/controllers/user.js +401 -0
  24. package/index.js +7 -0
  25. package/libs/agent/index.js +561 -0
  26. package/libs/agent/modules/ai/ai.js +285 -0
  27. package/libs/agent/modules/ai/chat.js +518 -0
  28. package/libs/agent/modules/ai/config.js +688 -0
  29. package/libs/agent/modules/ai/operations.js +787 -0
  30. package/libs/agent/modules/analyze/api.js +546 -0
  31. package/libs/agent/modules/analyze/dorks.js +395 -0
  32. package/libs/agent/modules/ccard/README.md +454 -0
  33. package/libs/agent/modules/ccard/audit.js +479 -0
  34. package/libs/agent/modules/ccard/checker.js +674 -0
  35. package/libs/agent/modules/ccard/payment-processors.json +16 -0
  36. package/libs/agent/modules/ccard/validator.js +629 -0
  37. package/libs/agent/modules/code/analyzer.js +303 -0
  38. package/libs/agent/modules/code/jquery.js +1093 -0
  39. package/libs/agent/modules/code/react.js +1536 -0
  40. package/libs/agent/modules/code/refactor.js +499 -0
  41. package/libs/agent/modules/crypto/exchange.js +564 -0
  42. package/libs/agent/modules/net/proxy.js +409 -0
  43. package/libs/agent/modules/security/cve.js +442 -0
  44. package/libs/agent/modules/security/monitor.js +360 -0
  45. package/libs/agent/modules/security/scanner.js +300 -0
  46. package/libs/agent/modules/security/vulnerability.js +506 -0
  47. package/libs/agent/modules/security/web.js +465 -0
  48. package/libs/agent/modules/utils/browser.js +492 -0
  49. package/libs/agent/modules/utils/colorizer.js +285 -0
  50. package/libs/agent/modules/utils/manager.js +478 -0
  51. package/libs/cloud/index.js +228 -0
  52. package/libs/config/db.js +21 -0
  53. package/libs/config/validator.js +82 -0
  54. package/libs/db/index.js +318 -0
  55. package/libs/emailer/imap.js +126 -0
  56. package/libs/emailer/info.js +41 -0
  57. package/libs/emailer/smtp.js +77 -0
  58. package/libs/handler/async.js +3 -0
  59. package/libs/handler/error.js +66 -0
  60. package/libs/handler/index.js +161 -0
  61. package/libs/logger/index.js +49 -0
  62. package/libs/logger/morgan.js +24 -0
  63. package/libs/passport/passport.js +109 -0
  64. package/libs/search/api.js +384 -0
  65. package/libs/search/features.js +219 -0
  66. package/libs/search/service.js +64 -0
  67. package/libs/swagger/config.js +18 -0
  68. package/libs/swagger/index.js +35 -0
  69. package/libs/validator/index.js +254 -0
  70. package/models/blog.js +31 -0
  71. package/models/brand.js +12 -0
  72. package/models/cart.js +14 -0
  73. package/models/category.js +11 -0
  74. package/models/coupon.js +9 -0
  75. package/models/customer.js +0 -0
  76. package/models/enquiry.js +29 -0
  77. package/models/events.js +13 -0
  78. package/models/order.js +94 -0
  79. package/models/product.js +32 -0
  80. package/models/review.js +14 -0
  81. package/models/tag.js +10 -0
  82. package/models/task.js +11 -0
  83. package/models/user.js +68 -0
  84. package/package.json +12 -0
  85. package/routes/agent.js +615 -0
  86. package/routes/auth.js +13 -0
  87. package/routes/blog.js +19 -0
  88. package/routes/brand.js +15 -0
  89. package/routes/cart.js +105 -0
  90. package/routes/category.js +16 -0
  91. package/routes/coupon.js +15 -0
  92. package/routes/enquiry.js +14 -0
  93. package/routes/events.js +16 -0
  94. package/routes/mail.js +170 -0
  95. package/routes/order.js +19 -0
  96. package/routes/product.js +22 -0
  97. package/routes/review.js +11 -0
  98. package/routes/task.js +12 -0
  99. package/routes/user.js +17 -0
@@ -0,0 +1,384 @@
1
+ const logger = require("../logger");
2
+
3
+ /**
4
+ * Advanced search, filter, and pagination features for Mongoose models
5
+ * Provides chainable methods for building complex database queries
6
+ */
7
+ module.exports = class SearchApi {
8
+ /**
9
+ * Initialize SearchFeatures with model and query parameters
10
+ * @param {mongoose.Model} Model - Mongoose model to query
11
+ * @param {Object} queryObj - Query parameters from request
12
+ * @param {Object} [options={}] - Additional configuration options
13
+ */
14
+ constructor(Model, queryObj, options = {}) {
15
+ if (!Model) {
16
+ throw new Error('Model parameter is required');
17
+ }
18
+
19
+ this.Model = Model;
20
+ this.query = Model.find();
21
+ this.queryObj = queryObj || {};
22
+ this.options = {
23
+ defaultSort: { createdAt: -1 }, // Default sorting
24
+ searchFields: [], // Specific fields to search in
25
+ excludeFields: ["keyword", "page", "limit", "sort", "fields"], // Fields to exclude from filtering
26
+ caseSensitive: false, // Case sensitivity for search
27
+ fuzzySearch: true, // Enable fuzzy matching
28
+ ...options
29
+ };
30
+
31
+ this.originalQuery = { ...this.queryObj };
32
+ this.isExecuted = false;
33
+ }
34
+
35
+ /**
36
+ * Perform text search across specified fields or all string fields
37
+ * @param {string[]} [searchFields] - Specific fields to search in
38
+ * @returns {SearchApi} Chainable instance
39
+ */
40
+ search(searchFields = null) {
41
+ try {
42
+ const fieldsToSearch = searchFields || this.options.searchFields;
43
+ const searchTerms = {};
44
+ const keyword = this.queryObj.keyword;
45
+
46
+ if (!keyword) {
47
+ return this;
48
+ }
49
+
50
+ // If specific search fields are provided
51
+ if (fieldsToSearch.length > 0) {
52
+ const searchConditions = fieldsToSearch.map(field => ({
53
+ [field]: {
54
+ $regex: keyword,
55
+ $options: this.options.caseSensitive ? "" : "i"
56
+ }
57
+ }));
58
+
59
+ this.query = this.query.find({
60
+ $or: searchConditions
61
+ });
62
+ } else {
63
+ // Search in all provided query fields (excluding system fields)
64
+ Object.keys(this.queryObj).forEach(prop => {
65
+ if (this.queryObj.hasOwnProperty(prop) &&
66
+ !this.options.excludeFields.includes(prop)) {
67
+ searchTerms[prop] = {
68
+ $regex: this.queryObj[prop],
69
+ $options: this.options.caseSensitive ? "" : "$i"
70
+ };
71
+ }
72
+ });
73
+
74
+ if (Object.keys(searchTerms).length > 0) {
75
+ logger.info('Search terms applied:', searchTerms);
76
+ this.query = this.query.find(searchTerms);
77
+ }
78
+ }
79
+
80
+ return this;
81
+ } catch (error) {
82
+ logger.error('Search operation failed:', error);
83
+ throw new Error(`Search failed: ${error.message}`);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Apply filters for exact matches and range queries
89
+ * @param {string[]} [additionalExcludeFields] - Additional fields to exclude
90
+ * @returns {SearchFeatures} Chainable instance
91
+ */
92
+ filter(additionalExcludeFields = []) {
93
+ try {
94
+ const queryCopy = { ...this.queryObj };
95
+ const excludeFields = [...this.options.excludeFields, ...additionalExcludeFields];
96
+
97
+ // Remove excluded fields
98
+ excludeFields.forEach(field => delete queryCopy[field]);
99
+
100
+ if (Object.keys(queryCopy).length === 0) {
101
+ return this;
102
+ }
103
+
104
+ // Handle range operators (gt, gte, lt, lte, ne, in, nin)
105
+ let queryString = JSON.stringify(queryCopy);
106
+ queryString = queryString.replace(
107
+ /\b(gt|gte|lt|lte|ne|in|nin|regex)\b/g,
108
+ match => `$${match}`
109
+ );
110
+
111
+ const filterQuery = JSON.parse(queryString);
112
+
113
+ // Handle array fields (in/nin operations)
114
+ Object.keys(filterQuery).forEach(key => {
115
+ const value = filterQuery[key];
116
+ if (typeof value === 'object' && value !== null) {
117
+ // Handle comma-separated values for $in operator
118
+ Object.keys(value).forEach(operator => {
119
+ if ((operator === '$in' || operator === '$nin') &&
120
+ typeof value[operator] === 'string') {
121
+ value[operator] = value[operator].split(',').map(item => item.trim());
122
+ }
123
+ });
124
+ }
125
+ });
126
+
127
+ logger.info('Filter query applied:', filterQuery);
128
+ this.query = this.query.find(filterQuery);
129
+
130
+ return this;
131
+ } catch (error) {
132
+ logger.error('Filter operation failed:', error);
133
+ throw new Error(`Filter failed: ${error.message}`);
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Apply sorting to the query
139
+ * @param {string|Object} [customSort] - Custom sort criteria
140
+ * @returns {SearchFeatures} Chainable instance
141
+ */
142
+ sort(customSort = null) {
143
+ try {
144
+ let sortCriteria = customSort || this.queryObj.sort || this.options.defaultSort;
145
+
146
+ // Convert string sort to object
147
+ if (typeof sortCriteria === 'string') {
148
+ const sortObj = {};
149
+ sortCriteria.split(',').forEach(field => {
150
+ const trimmedField = field.trim();
151
+ if (trimmedField.startsWith('-')) {
152
+ sortObj[trimmedField.substring(1)] = -1;
153
+ } else {
154
+ sortObj[trimmedField] = 1;
155
+ }
156
+ });
157
+ sortCriteria = sortObj;
158
+ }
159
+
160
+ this.query = this.query.sort(sortCriteria);
161
+ logger.info('Sort applied:', sortCriteria);
162
+
163
+ return this;
164
+ } catch (error) {
165
+ logger.error('Sort operation failed:', error);
166
+ throw new Error(`Sort failed: ${error.message}`);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Select specific fields to return
172
+ * @param {string|string[]} [fields] - Fields to select
173
+ * @returns {SearchFeatures} Chainable instance
174
+ */
175
+ selectFields(fields = null) {
176
+ try {
177
+ const fieldsToSelect = fields || this.queryObj.fields;
178
+
179
+ if (fieldsToSelect) {
180
+ const selectFields = Array.isArray(fieldsToSelect)
181
+ ? fieldsToSelect.join(' ')
182
+ : fieldsToSelect.split(',').join(' ');
183
+
184
+ this.query = this.query.select(selectFields);
185
+ logger.info('Fields selected:', selectFields);
186
+ }
187
+
188
+ return this;
189
+ } catch (error) {
190
+ logger.error('Field selection failed:', error);
191
+ throw new Error(`Field selection failed: ${error.message}`);
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Apply pagination with configurable limits
197
+ * @param {number} [resultPerPage=10] - Number of results per page
198
+ * @param {number} [maxLimit=100] - Maximum allowed limit
199
+ * @returns {SearchFeatures} Chainable instance
200
+ */
201
+ paginate(resultPerPage = 10, maxLimit = 100) {
202
+ try {
203
+ const page = Math.max(1, Number(this.queryObj.page) || 1);
204
+ const limit = Math.min(
205
+ maxLimit,
206
+ Math.max(1, Number(this.queryObj.limit) || resultPerPage)
207
+ );
208
+
209
+ const skip = limit * (page - 1);
210
+
211
+ this.query = this.query.limit(limit).skip(skip);
212
+
213
+ // Store pagination info for later use
214
+ this.paginationInfo = {
215
+ page,
216
+ limit,
217
+ skip
218
+ };
219
+
220
+ logger.info('Pagination applied:', { page, limit, skip });
221
+
222
+ return this;
223
+ } catch (error) {
224
+ logger.error('Pagination failed:', error);
225
+ throw new Error(`Pagination failed: ${error.message}`);
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Populate referenced fields
231
+ * @param {string|Object|Array} [populateOptions] - Population configuration
232
+ * @returns {SearchFeatures} Chainable instance
233
+ */
234
+ populate(populateOptions = null) {
235
+ try {
236
+ if (populateOptions) {
237
+ this.query = this.query.populate(populateOptions);
238
+ logger.info('Population applied:', populateOptions);
239
+ } else if (this.queryObj.populate) {
240
+ // Handle populate from query string
241
+ const populateFields = this.queryObj.populate.split(',').map(field => field.trim());
242
+ populateFields.forEach(field => {
243
+ this.query = this.query.populate(field);
244
+ });
245
+ logger.info('Population applied from query:', populateFields);
246
+ }
247
+
248
+ return this;
249
+ } catch (error) {
250
+ logger.error('Population failed:', error);
251
+ throw new Error(`Population failed: ${error.message}`);
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Execute the query and return results with metadata
257
+ * @returns {Promise<Object>} Results with pagination metadata
258
+ */
259
+ async execute() {
260
+ try {
261
+ if (this.isExecuted) {
262
+ logger.warn('Query has already been executed');
263
+ }
264
+
265
+ // Get total count for pagination metadata
266
+ const totalDocuments = await this.Model.countDocuments(this.query.getQuery());
267
+
268
+ // Execute the main query
269
+ const results = await this.query.exec();
270
+
271
+ // Prepare pagination metadata
272
+ const paginationMeta = this.paginationInfo ? {
273
+ currentPage: this.paginationInfo.page,
274
+ totalPages: Math.ceil(totalDocuments / this.paginationInfo.limit),
275
+ totalDocuments,
276
+ documentsPerPage: this.paginationInfo.limit,
277
+ hasNextPage: this.paginationInfo.page < Math.ceil(totalDocuments / this.paginationInfo.limit),
278
+ hasPrevPage: this.paginationInfo.page > 1
279
+ } : null;
280
+
281
+ this.isExecuted = true;
282
+
283
+ return {
284
+ result: results,
285
+ meta: {
286
+ total: totalDocuments,
287
+ count: results.length,
288
+ pagination: paginationMeta,
289
+ query: this.originalQuery
290
+ }
291
+ };
292
+ } catch (error) {
293
+ logger.error('Query execution failed:', error);
294
+ throw new Error(`Query execution failed: ${error.message}`);
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Get the current query object (for debugging)
300
+ * @returns {mongoose.Query} Current Mongoose query
301
+ */
302
+ getQuery() {
303
+ return this.query;
304
+ }
305
+
306
+ /**
307
+ * Get the current query filter (for debugging)
308
+ * @returns {Object} Current query filter
309
+ */
310
+ getFilter() {
311
+ return this.query.getQuery();
312
+ }
313
+
314
+ /**
315
+ * Reset the query to its initial state
316
+ * @returns {SearchFeatures} Chainable instance
317
+ */
318
+ reset() {
319
+ this.query = this.Model.find();
320
+ this.isExecuted = false;
321
+ this.paginationInfo = null;
322
+ logger.info('Query reset to initial state');
323
+ return this;
324
+ }
325
+
326
+ /**
327
+ * Clone the current SearchFeatures instance
328
+ * @returns {SearchFeatures} New SearchFeatures instance
329
+ */
330
+ clone() {
331
+ const cloned = new SearchFeatures(this.Model, this.queryObj, this.options);
332
+ cloned.query = this.query.clone();
333
+ return cloned;
334
+ }
335
+
336
+ /**
337
+ * Apply full-text search using MongoDB text indexes
338
+ * @param {string} searchText - Text to search for
339
+ * @param {Object} [options] - Text search options
340
+ * @returns {SearchFeatures} Chainable instance
341
+ */
342
+ textSearch(searchText, options = {}) {
343
+ try {
344
+ if (!searchText) {
345
+ return this;
346
+ }
347
+
348
+ const textSearchOptions = {
349
+ $search: searchText,
350
+ ...options
351
+ };
352
+
353
+ this.query = this.query.find({ $text: textSearchOptions });
354
+ logger.info('Text search applied:', textSearchOptions);
355
+
356
+ return this;
357
+ } catch (error) {
358
+ logger.error('Text search failed:', error);
359
+ throw new Error(`Text search failed: ${error.message}`);
360
+ }
361
+ }
362
+
363
+ safeSearch(obj) {
364
+ // Fallback to basic search if SearchFeatures fails
365
+ const { keyword } = this.queryObj || obj;
366
+ const limit = parseInt(this.queryObj.limit) || 10;
367
+
368
+ if (!keyword) {
369
+ return res.status(400).json({
370
+ success: false, result: null, message: "Search keyword is required"
371
+ });
372
+ }
373
+
374
+ this.query = this.query.find({
375
+ $or: [
376
+ { name: { $regex: keyword, $options: 'i' } },
377
+ { description: { $regex: keyword, $options: 'i' } }
378
+ ]})
379
+ .limit(limit)
380
+ .sort({ createdAt: -1 });
381
+
382
+ return this;
383
+ }
384
+ }
@@ -0,0 +1,219 @@
1
+ const logger = require("../logger");
2
+ const SearchApi = require('./api');
3
+
4
+ // Helper function to get/set model
5
+ let currentModel = null;
6
+
7
+ exports.getModel = () => {
8
+ return currentModel;
9
+ };
10
+
11
+ exports.setModel = (Model) => {
12
+ currentModel = Model;
13
+ return currentModel;
14
+ };
15
+
16
+ exports.basicSearch = async (params, Model = currentModel) => {
17
+ const queryParams = params || {
18
+ keyword: "laptop",
19
+ page: 1,
20
+ limit: 10
21
+ };
22
+
23
+ const Search = new SearchApi(Model, queryParams, {
24
+ searchFields: ['name', 'description', 'brand']
25
+ });
26
+
27
+ const results = await Search
28
+ .search()
29
+ .sort()
30
+ .paginate()
31
+ .execute();
32
+
33
+ console.log('Basic Search Results:', results);
34
+ return results;
35
+ };
36
+
37
+ exports.textSearch = async (searchText, Model = currentModel) => {
38
+ const queryParams = { searchText } || { searchText: "marvel comic book" };
39
+
40
+ const Search = new SearchApi(Model, queryParams);
41
+
42
+ const results = await Search
43
+ .textSearch(queryParams.searchText, {
44
+ $language: 'english',
45
+ $caseSensitive: false
46
+ })
47
+ .sort({ score: { $meta: 'textScore' } })
48
+ .paginate()
49
+ .execute();
50
+
51
+ console.log('Full-text Search Results:', results);
52
+ return results;
53
+ };
54
+
55
+ exports.standardSearch = async (Model, query) => {
56
+ const Search = new SearchApi(Model, query, {
57
+ searchFields: ['title', 'description', 'brand'],
58
+ defaultSort: { createdAt: -1 }
59
+ });
60
+
61
+ const results = await Search.search()
62
+ .filter()
63
+ .sort()
64
+ .selectFields()
65
+ .populate('category brand')
66
+ .paginate()
67
+ .execute();
68
+
69
+ return results;
70
+ };
71
+
72
+ exports.productSearch = async (query, Product) => {
73
+ const {
74
+ keyword,
75
+ category,
76
+ brand,
77
+ minPrice,
78
+ maxPrice,
79
+ rating,
80
+ sortBy = 'popularity',
81
+ page = 1,
82
+ limit = 24
83
+ } = query;
84
+
85
+ // Build query object
86
+ const queryObj = { page, limit };
87
+ if (keyword) queryObj.keyword = keyword;
88
+ if (category) queryObj.category = category;
89
+ if (brand) queryObj.brand = { in: brand }; // Support multiple brands
90
+ if (minPrice || maxPrice) {
91
+ queryObj.price = {};
92
+ if (minPrice) queryObj.price.gte = minPrice;
93
+ if (maxPrice) queryObj.price.lte = maxPrice;
94
+ }
95
+ if (rating) queryObj.rating = { gte: rating };
96
+
97
+ const sortOptions = {
98
+ popularity: { popularity: -1 },
99
+ newest: { createdAt: -1 },
100
+ 'price-low': { price: 1 },
101
+ 'price-high': { price: -1 },
102
+ rating: { rating: -1 }
103
+ };
104
+
105
+ const Search = new SearchApi(Product, queryObj, {
106
+ searchFields: ['name', 'description', 'tags'],
107
+ defaultSort: sortOptions[sortBy] || sortOptions.popularity
108
+ });
109
+
110
+ const results = await Search
111
+ .search(['name', 'description', 'brand', 'tags'])
112
+ .filter()
113
+ .sort()
114
+ .selectFields('-__v,-updatedAt')
115
+ .populate('reviews')
116
+ .paginate(limit, 50) // Max 50 items per page
117
+ .execute();
118
+
119
+ return results;
120
+ };
121
+
122
+ exports.orderSearch = async (searchParams, Order) => {
123
+ const {
124
+ customerEmail,
125
+ status,
126
+ minAmount,
127
+ maxAmount,
128
+ dateFrom,
129
+ dateTo,
130
+ paymentMethod
131
+ } = searchParams;
132
+
133
+ const queryObj = {};
134
+ if (status) queryObj.status = status;
135
+ if (paymentMethod) queryObj.paymentMethod = paymentMethod;
136
+
137
+ if (minAmount || maxAmount) {
138
+ queryObj.totalAmount = {};
139
+ if (minAmount) queryObj.totalAmount.gte = parseFloat(minAmount);
140
+ if (maxAmount) queryObj.totalAmount.lte = parseFloat(maxAmount);
141
+ }
142
+
143
+ if (dateFrom || dateTo) {
144
+ queryObj.orderDate = {};
145
+ if (dateFrom) queryObj.orderDate.gte = new Date(dateFrom);
146
+ if (dateTo) queryObj.orderDate.lte = new Date(dateTo);
147
+ }
148
+
149
+ const Search = new SearchApi(Order, queryObj);
150
+
151
+ let query = Search
152
+ .filter()
153
+ .sort({ orderDate: -1 })
154
+ .populate({
155
+ path: 'customer',
156
+ select: 'firstName lastName email'
157
+ })
158
+ .populate({
159
+ path: 'items.product',
160
+ select: 'name price'
161
+ });
162
+
163
+ // Add customer email search if provided
164
+ if (customerEmail) {
165
+ // This requires a more complex approach since we're searching in populated fields
166
+ query = query.populate({
167
+ path: 'customer',
168
+ match: { email: { $regex: customerEmail, $options: 'i' } },
169
+ select: 'firstName lastName email'
170
+ });
171
+ }
172
+
173
+ return await query.paginate().execute();
174
+ };
175
+
176
+ exports.userSearch = async (adminQuery, User) => {
177
+ const {
178
+ keyword,
179
+ role,
180
+ status,
181
+ dateFrom,
182
+ dateTo,
183
+ sortBy = 'createdAt',
184
+ order = 'desc'
185
+ } = adminQuery;
186
+
187
+ const queryObj = {};
188
+ if (keyword) queryObj.keyword = keyword;
189
+ if (role) queryObj.role = role;
190
+ if (status) queryObj.status = status;
191
+
192
+ // Date range filtering
193
+ if (dateFrom || dateTo) {
194
+ queryObj.createdAt = {};
195
+ if (dateFrom) queryObj.createdAt.gte = new Date(dateFrom);
196
+ if (dateTo) queryObj.createdAt.lte = new Date(dateTo);
197
+ }
198
+
199
+ const sortOrder = order === 'desc' ? -1 : 1;
200
+ const sortOptions = { [sortBy]: sortOrder };
201
+
202
+ const Search = new SearchApi(User, queryObj, {
203
+ searchFields: ['firstName', 'lastName', 'email', 'username'],
204
+ excludeFields: ['password', 'resetToken']
205
+ });
206
+
207
+ const results = await Search
208
+ .search()
209
+ .filter()
210
+ .sort(sortOptions)
211
+ .selectFields('-password,-resetToken,-__v')
212
+ .paginate(25)
213
+ .execute();
214
+
215
+ return results;
216
+ };
217
+
218
+ // Also provide module.exports for compatibility
219
+ module.exports = exports;
@@ -0,0 +1,64 @@
1
+ const SearchApi = require('./api');
2
+
3
+ module.exports = class SearchService {
4
+ constructor(model, config = {}) {
5
+ this.model = model;
6
+ this.config = {
7
+ searchFields: [],
8
+ sortOptions: {},
9
+ filterRules: {},
10
+ ...config
11
+ };
12
+ }
13
+
14
+ async search(params) {
15
+ const searchFeatures = new SearchApi(this.model, params, {
16
+ searchFields: this.config.searchFields,
17
+ defaultSort: this.config.sortOptions.default || { createdAt: -1 }
18
+ });
19
+
20
+ // Apply custom filter rules
21
+ if (this.config.filterRules.beforeSearch) {
22
+ await this.config.filterRules.beforeSearch(searchFeatures, params);
23
+ }
24
+
25
+ let query = searchFeatures.search().filter().sort();
26
+
27
+ // Apply population rules
28
+ if (this.config.populate) {
29
+ query = query.populate(this.config.populate);
30
+ }
31
+
32
+ const results = await query.paginate().execute();
33
+
34
+ // Apply post-processing
35
+ if (this.config.filterRules.afterSearch) {
36
+ await this.config.filterRules.afterSearch(results, params);
37
+ }
38
+
39
+ return results;
40
+ }
41
+ }
42
+
43
+ /*
44
+ // Usage of Dynamic Search Service
45
+ const productSearchService = new DynamicSearchService(Product, {
46
+ searchFields: ['name', 'description', 'tags'],
47
+ populate: 'category reviews',
48
+ filterRules: {
49
+ beforeSearch: async (searchFeatures, params) => {
50
+ // Apply business logic before search
51
+ if (params.featured) {
52
+ searchFeatures.query = searchFeatures.query.find({ featured: true });
53
+ }
54
+ },
55
+ afterSearch: async (results, params) => {
56
+ // Apply business logic after search
57
+ results.data = results.data.map(item => ({
58
+ ...item.toObject(),
59
+ discountedPrice: item.price * 0.9 // Apply 10% discount
60
+ }));
61
+ }
62
+ }
63
+ });
64
+ */
@@ -0,0 +1,18 @@
1
+ const swaggerJSDoc = require("swagger-jsdoc");
2
+
3
+ const swaggerDefinition = {
4
+ openapi: "3.0.0",
5
+ info: {
6
+ title: "FEAR API",
7
+ version: "1.0.0",
8
+ description: "The Main API for all FEAR apps (Ekomix, Ekoins, Admin CRM",
9
+ },
10
+ };
11
+
12
+ const options = {
13
+ swaggerDefinition,
14
+ apis: [`../routes/*.js`],
15
+ };
16
+
17
+ const swaggerSpec = swaggerJSDoc(options);
18
+ module.exports = swaggerSpec;