vanta-api 1.0.4 → 1.1.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/package.json +1 -1
- package/src/api-features.js +21 -19
package/package.json
CHANGED
package/src/api-features.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
// api-features.js
|
|
2
1
|
import mongoose from "mongoose";
|
|
3
2
|
import winston from "winston";
|
|
4
3
|
import { securityConfig } from "./config.js";
|
|
5
4
|
import HandleERROR from "./handleError.js";
|
|
6
5
|
|
|
7
|
-
// تنظیم logger با winston
|
|
8
6
|
const logger = winston.createLogger({
|
|
9
7
|
level: "info",
|
|
10
8
|
format: winston.format.combine(
|
|
@@ -22,7 +20,6 @@ export class ApiFeatures {
|
|
|
22
20
|
this.pipeline = [];
|
|
23
21
|
this.countPipeline = [];
|
|
24
22
|
this.manualFilters = {};
|
|
25
|
-
// انتخاب استفاده از cursor برای پردازش دادههای حجیم
|
|
26
23
|
this.useCursor = false;
|
|
27
24
|
this.#initialSanitization();
|
|
28
25
|
}
|
|
@@ -34,7 +31,6 @@ export class ApiFeatures {
|
|
|
34
31
|
const safeFilters = this.#applySecurityFilters(mergedFilters);
|
|
35
32
|
|
|
36
33
|
if (Object.keys(safeFilters).length > 0) {
|
|
37
|
-
// اضافه کردن فیلتر به ابتدای pipeline جهت بهبود عملکرد
|
|
38
34
|
this.pipeline.push({ $match: safeFilters });
|
|
39
35
|
this.countPipeline.push({ $match: safeFilters });
|
|
40
36
|
}
|
|
@@ -138,11 +134,11 @@ export class ApiFeatures {
|
|
|
138
134
|
lookupStage = {
|
|
139
135
|
$lookup: {
|
|
140
136
|
from: collection,
|
|
141
|
-
let: {
|
|
137
|
+
let: { localId: `$${field}` },
|
|
142
138
|
pipeline: [
|
|
143
139
|
{
|
|
144
140
|
$match: {
|
|
145
|
-
$expr: { $eq: ["$_id", "$$
|
|
141
|
+
$expr: { $eq: ["$_id", "$$localId"] }
|
|
146
142
|
}
|
|
147
143
|
},
|
|
148
144
|
{ $project: projection }
|
|
@@ -170,8 +166,6 @@ export class ApiFeatures {
|
|
|
170
166
|
});
|
|
171
167
|
});
|
|
172
168
|
|
|
173
|
-
// پشتیبانی از nested populate: در صورت نیاز، منطق تو در تو را میتوانید اینجا اضافه کنید.
|
|
174
|
-
|
|
175
169
|
return this;
|
|
176
170
|
}
|
|
177
171
|
|
|
@@ -184,11 +178,9 @@ export class ApiFeatures {
|
|
|
184
178
|
|
|
185
179
|
async execute(options = {}) {
|
|
186
180
|
try {
|
|
187
|
-
// انتخاب حالت cursor در مواقع پردازش دادههای حجیم
|
|
188
181
|
if (options.useCursor === true) {
|
|
189
182
|
this.useCursor = true;
|
|
190
183
|
}
|
|
191
|
-
// اجرای موازی pipelineهای شمارش و داده
|
|
192
184
|
const [countResult, dataResult] = await Promise.all([
|
|
193
185
|
this.Model.aggregate([...this.countPipeline, { $count: "total" }]),
|
|
194
186
|
(this.useCursor
|
|
@@ -258,7 +250,22 @@ export class ApiFeatures {
|
|
|
258
250
|
|
|
259
251
|
#sanitizeNestedObjects(obj) {
|
|
260
252
|
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
261
|
-
|
|
253
|
+
// Handle ObjectId fields with nested operators
|
|
254
|
+
if (key.endsWith("Id") && typeof value === "object" && !Array.isArray(value)) {
|
|
255
|
+
const sanitizedObj = {};
|
|
256
|
+
for (const [op, val] of Object.entries(value)) {
|
|
257
|
+
if (["$eq", "$ne", "$gt", "$gte", "$lt", "$lte"].includes(op) && mongoose.isValidObjectId(val)) {
|
|
258
|
+
sanitizedObj[op] = new mongoose.Types.ObjectId(val);
|
|
259
|
+
} else if (["$in", "$nin"].includes(op) && Array.isArray(val)) {
|
|
260
|
+
sanitizedObj[op] = val
|
|
261
|
+
.filter(v => mongoose.isValidObjectId(v))
|
|
262
|
+
.map(v => new mongoose.Types.ObjectId(v));
|
|
263
|
+
} else {
|
|
264
|
+
sanitizedObj[op] = val;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
acc[key] = sanitizedObj;
|
|
268
|
+
} else if (typeof value === "object" && !Array.isArray(value)) {
|
|
262
269
|
acc[key] = this.#sanitizeNestedObjects(value);
|
|
263
270
|
} else {
|
|
264
271
|
acc[key] = this.#sanitizeValue(key, value);
|
|
@@ -274,7 +281,7 @@ export class ApiFeatures {
|
|
|
274
281
|
if (typeof value === "string") {
|
|
275
282
|
if (value === "true") return true;
|
|
276
283
|
if (value === "false") return false;
|
|
277
|
-
if (/^\d+$/.test(value)) return parseInt(value);
|
|
284
|
+
if (/^\d+$/.test(value)) return parseInt(value, 10);
|
|
278
285
|
}
|
|
279
286
|
return value;
|
|
280
287
|
}
|
|
@@ -285,13 +292,8 @@ export class ApiFeatures {
|
|
|
285
292
|
throw new HandleERROR(`Invalid populate field: ${field}`, 400);
|
|
286
293
|
}
|
|
287
294
|
|
|
288
|
-
const refModel = mongoose.model(schemaPath.options.ref);
|
|
289
|
-
if (refModel.schema.options.restricted && this.userRole !== "admin") {
|
|
290
|
-
throw new HandleERROR(`Unauthorized to populate ${field}`, 403);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
295
|
return {
|
|
294
|
-
collection:
|
|
296
|
+
collection: schemaPath.options.ref.toLowerCase()+'s',
|
|
295
297
|
isArray: schemaPath.instance === "Array"
|
|
296
298
|
};
|
|
297
299
|
}
|
|
@@ -303,4 +305,4 @@ export class ApiFeatures {
|
|
|
303
305
|
}
|
|
304
306
|
}
|
|
305
307
|
|
|
306
|
-
export default ApiFeatures;
|
|
308
|
+
export default ApiFeatures;
|