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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/api-features.js +21 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanta-api",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "Advanced API features and security configuration for Node.js/MongoDB.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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: { localField: `$${field}` },
137
+ let: { localId: `$${field}` },
142
138
  pipeline: [
143
139
  {
144
140
  $match: {
145
- $expr: { $eq: ["$_id", "$$localField"] }
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
- if (typeof value === "object" && !Array.isArray(value)) {
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: refModel.collection.name,
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;