Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ensuring that each preference is unique for each GROUP #4060

Merged
merged 4 commits into from
Dec 13, 2024

Conversation

Baalmart
Copy link
Contributor

@Baalmart Baalmart commented Dec 13, 2024

Description

ensuring that each preference is unique for each GROUP

Changes Made

  • ensuring that each preference is unique for each GROUP

Testing

  • Tested locally
  • Tested against staging environment
  • Relevant tests passed: [List test names]

Affected Services

  • Which services were modified:
    • Auth Service

Endpoints Ready for Testing

  • New endpoints ready for testing:
    • Replace Preferences

API Documentation Updated?

  • Yes, API documentation was updated
  • No, API documentation does not need updating

Additional Notes

ensuring that each preference is unique for each GROUP

Summary by CodeRabbit

  • New Features

    • Enhanced validation and error handling for user-group relationships in preference management.
    • Introduced a new index for ensuring uniqueness of user and group combinations.
    • Added group_id filtering capability in preference queries.
  • Bug Fixes

    • Improved control flow in the delete method to handle service availability.

Copy link
Contributor

coderabbitai bot commented Dec 13, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The changes in this pull request introduce significant enhancements to the PreferenceSchema in the src/auth-service/models/Preference.js file, including a new uniqueness index for user_id and group_id. The pre-save hook is updated to handle multiple events and includes comprehensive validation and processing logic for various fields. Additionally, improvements are made to the create, upsert, and delete methods in src/auth-service/utils/create-preference.js, focusing on user-group validation. The generate-filter.js file is also updated to incorporate group_id into filtering logic.

Changes

File Change Summary
src/auth-service/models/Preference.js - Added uniqueness index for user_id and group_id.
- Updated pre-hook to handle multiple events with enhanced validation and processing logic.
src/auth-service/utils/create-preference.js - Enhanced create, upsert, and delete methods with user-group validation and improved error handling.
src/auth-service/utils/generate-filter.js - Added group_id to the filter construction in the preferences method to include group filtering.

Possibly related PRs

Suggested reviewers

  • Codebmk
  • BenjaminSsempala
  • OchiengPaul442

🎉 In the realm of code, where logic does flow,
A schema was changed, to help data grow.
With hooks that now handle, both save and update,
Errors are caught, making processes great!
User and group checks, in preferences blend,
A robust new structure, on which we depend! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Experiment)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

codecov bot commented Dec 13, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 11.81%. Comparing base (42c8684) to head (00a93ae).
Report is 6 commits behind head on staging.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff            @@
##           staging    #4060   +/-   ##
========================================
  Coverage    11.81%   11.81%           
========================================
  Files          115      115           
  Lines        15482    15482           
  Branches       319      319           
========================================
  Hits          1829     1829           
  Misses       13653    13653           

Copy link
Contributor

Auth-service changes in this PR available for preview here

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (2)
src/auth-service/utils/create-preference.js (2)

Line range hint 439-459: Remove Unreachable Code After Return Statement in 'delete' Method

In the delete method (lines 439~ to 459~), there's a return statement at the beginning of the try block. This means all the code that follows within the try block is unreachable and will never be executed.

Consider removing the unreachable code or restructuring the method if the subsequent code is intended to be executed.

async (request, next) => {
  try {
    return {
      success: false,
      message: "Service Temporarily Unavailable",
      errors: {
        message: "Service Temporarily Unavailable",
      },
      status: httpStatus.SERVICE_UNAVAILABLE,
    };
-   const {
-     query: { tenant },
-     body,
-   } = request;
-   const filterResponse = generateFilter.preferences(request, next);
-   // ... additional code that is unreachable
  } catch (error) {
    // Error handling remains unchanged
  }
}

Line range hint 253-286: Avoid Mutating Request Body Directly

In the upsert method, update is assigned from body using a shallow copy (const update = { ...body };). Subsequent mutations to update may inadvertently affect request.body if there are nested objects.

To prevent unintended side effects, consider performing a deep clone of body or use a utility function to create an entirely new object.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91eb6fe and 70fe61e.

📒 Files selected for processing (3)
  • src/auth-service/models/Preference.js (1 hunks)
  • src/auth-service/utils/create-preference.js (3 hunks)
  • src/auth-service/utils/generate-filter.js (1 hunks)
🔇 Additional comments (1)
src/auth-service/utils/generate-filter.js (1)

315-330: Inclusion of 'group_id' in Preferences Filter Is Appropriate

The addition of group_id to the preferences filter allows the system to retrieve preferences specific to a user-group combination, which aligns with the PR objectives.

Comment on lines 77 to 86
if (isEmpty(filterResponse) || isEmpty(filterResponse.user_id)) {
return {
success: false,
message: "Internal Server Error",
errors: {
message:
"Unable to obtain the corresponding identifier associated with this preference --- please reach out to [email protected]",
},
status: httpStatus.INTERNAL_SERVER_ERROR,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure Consistent Error Handling Using 'next()'

In the create and upsert methods, the error handling is inconsistent. Some errors are passed to the next middleware using next(), while others are returned directly as objects. For example, in lines 77~ to 86~ and 193~ to 201~, errors are returned instead of using next().

To maintain a uniform error-handling mechanism and ensure that all errors are processed by the middleware chain, it's advisable to consistently use next() to pass errors.

Apply the following changes:

// In the 'create' method
-if (isEmpty(filterResponse) || isEmpty(filterResponse.user_id)) {
-  return {
-    success: false,
-    message: "Internal Server Error",
-    errors: {
-      message:
-        "Unable to obtain the corresponding identifier associated with this preference --- please reach out to [email protected]",
-    },
-    status: httpStatus.INTERNAL_SERVER_ERROR,
-  };
+if (isEmpty(filterResponse) || isEmpty(filterResponse.user_id)) {
+  next(
+    new HttpError(
+      "Internal Server Error",
+      httpStatus.INTERNAL_SERVER_ERROR,
+      {
+        message:
+          "Unable to obtain the corresponding identifier associated with this preference --- please reach out to [email protected]",
+      }
+    )
+  );
+  return;
}

// In the 'upsert' method
-if (isEmpty(user_id) || isEmpty(user)) {
-  return {
-    success: false,
-    message: "Internal Server Error",
-    errors: {
-      message: "The provided User does not exist",
-    },
-    status: httpStatus.INTERNAL_SERVER_ERROR,
-  };
+if (isEmpty(user_id) || isEmpty(user)) {
+  next(
+    new HttpError(
+      "Internal Server Error",
+      httpStatus.INTERNAL_SERVER_ERROR,
+      { message: "The provided User does not exist" }
+    )
+  );
+  return;
}

Also applies to: 189-201

Comment on lines 63 to 74
const userBelongsToGroup = user.group_roles.some(
(role) => role.group.toString() === group_id
);

if (!userBelongsToGroup) {
return next(
new HttpError("Bad Request Error", httpStatus.BAD_REQUEST, {
message: "User does not belong to the specified group",
})
);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle Potential 'user' Undefined Scenario When Checking Group Membership

In both the create and upsert methods, when checking if the user belongs to a specified group, the code assumes that user is defined and has a property group_roles. If user is undefined (e.g., if the user does not exist), accessing user.group_roles will result in a runtime error.

To ensure robustness, add a check to confirm that user exists before accessing its properties.

// Before accessing user.group_roles
if (!isEmpty(group_id)) {
+ if (user && user.group_roles) {
    const userBelongsToGroup = user.group_roles.some(
      (role) => role.group.toString() === group_id
    );
    if (!userBelongsToGroup) {
      return next(
        new HttpError("Bad Request Error", httpStatus.BAD_REQUEST, {
          message: "User does not belong to the specified group",
        })
      );
    }
+ } else {
+   return next(
+     new HttpError("Bad Request Error", httpStatus.BAD_REQUEST, {
+       message: "User not found or invalid user data",
+     })
+   );
  }
}

Also applies to: 205-219

Comment on lines 47 to 74
const group_id = body.group_id;

if (!isEmpty(user_id)) {
const user = await UserModel(tenant).findById(user_id).lean();
if (isEmpty(user_id) || isEmpty(user)) {
next(
new HttpError("Bad Request Error", httpStatus.BAD_REQUEST, {
message: "The provided User does not exist",
value: user_id,
})
);
}
}

// Check if user belongs to the specified group
if (!isEmpty(group_id)) {
const userBelongsToGroup = user.group_roles.some(
(role) => role.group.toString() === group_id
);

if (!userBelongsToGroup) {
return next(
new HttpError("Bad Request Error", httpStatus.BAD_REQUEST, {
message: "User does not belong to the specified group",
})
);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor to Eliminate Duplicate Validation Logic

The validation logic for user_id and group_id in the create and upsert methods is almost identical. Duplicated code can make maintenance more challenging and increase the risk of inconsistencies.

Extract the common validation steps into a separate helper function that both methods can call. This will improve code readability and maintainability.

Also applies to: 186-219

Comment on lines 218 to 436
];
stringFields.forEach((field) => {
if (processedSite[field]) {
processedSite[field] = String(processedSite[field]).trim();
}
});

// Ensure boolean fields
processedSite.isFeatured = !!site.isFeatured;

return processedSite;
},
selected_grids: (grid) => ({
_id: processObjectId(grid._id),
name: String(grid.name).trim(),
createdAt: grid.createdAt || new Date(),
}),
selected_cohorts: (cohort) => ({
_id: processObjectId(cohort._id),
name: String(cohort.name).trim(),
createdAt: cohort.createdAt || new Date(),
}),
selected_devices: (device) => ({
_id: processObjectId(device._id),
name: String(device.name).trim(),
createdAt: device.createdAt || new Date(),
}),
selected_airqlouds: (airqloud) => ({
_id: processObjectId(airqloud._id),
name: String(airqloud.name).trim(),
createdAt: airqloud.createdAt || new Date(),
}),
};

// Process selected arrays
Object.keys(selectedArrayProcessors).forEach((field) => {
if (updateData[field]) {
updateData[field] = updateData[field].map(
selectedArrayProcessors[field]
);
}
});

// Prepare $addToSet for array fields to prevent duplicates
const arrayFieldsToAddToSet = [
...idFieldsToProcess,
"selected_sites",
"selected_grids",
"selected_devices",
"selected_cohorts",
"selected_airqlouds",
];

arrayFieldsToAddToSet.forEach((field) => {
if (updateData[field]) {
updateData.$addToSet = updateData.$addToSet || {};
updateData.$addToSet[field] = {
$each: updateData[field],
};
delete updateData[field];
}
});

// Optional: Add comprehensive logging
console.log(
`Preprocessing preference document: ${isNew ? "New" : "Update"}`,
{
user_id: updateData.user_id,
pollutant: updateData.pollutant,
startDate: updateData.startDate,
endDate: updateData.endDate,
}
);
}
});

const fieldsToAddToSet = [
"airqloud_ids",
"device_ids",
"cohort_ids",
"grid_ids",
"site_ids",
"network_ids",
"group_ids",
];

fieldsToAddToSet.forEach((field) => {
if (this[field]) {
this[field] = Array.from(new Set(this[field].map((id) => id.toString())));
}
});

return next();
});
next();
} catch (error) {
console.error("Error in Preference pre-hook:", error);
return next(error);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Separate Document and Query Middleware in Pre-Hook

The pre-hook (lines 218~ to 436~) is attempting to handle both document middleware (like save) and query middleware (like updateOne) within the same function. In Mongoose, this refers to the document in document middleware and the query in query middleware. Using this.isNew and modifying updateData directly may not behave as intended in query middleware.

It's advisable to separate the pre-hooks for document and query operations to handle their contexts appropriately. This ensures that validations and transformations are correctly applied in each scenario.

Comment on lines +216 to +217
PreferenceSchema.index({ user_id: 1, group_id: 1 }, { unique: true });

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Adjust Unique Index to Handle Optional 'group_id'

An index is added to enforce uniqueness on { user_id: 1, group_id: 1 }. If group_id can be null or optional, multiple documents with the same user_id and a null group_id will violate this uniqueness constraint because MongoDB considers all null values as equal in unique indexes.

Consider making group_id required if uniqueness is essential, or modify the index to be a partial index that excludes documents where group_id is null.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
src/auth-service/models/Preference.js (2)

420-427: Replace console.log with logger

The code uses console.log for debugging, which is inconsistent with the logger usage elsewhere in the file.

-      console.log(
+      logger.debug(
         `Preprocessing preference document: ${isNew ? "New" : "Update"}`,
         {
           user_id: updateData.user_id,
           pollutant: updateData.pollutant,
           startDate: updateData.startDate,
           endDate: updateData.endDate,
         }
       );

227-436: Consider using transactions for atomic updates

The pre-hook performs multiple updates without transaction support. If any operation fails, it could leave the database in an inconsistent state.

Consider wrapping the operations in a transaction:

PreferenceSchema.pre(["save"], async function(next) {
  const session = await mongoose.startSession();
  session.startTransaction();
  try {
    // Existing pre-hook logic here
    await session.commitTransaction();
    next();
  } catch (error) {
    await session.abortTransaction();
    next(error);
  } finally {
    session.endSession();
  }
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70fe61e and faa861b.

📒 Files selected for processing (1)
  • src/auth-service/models/Preference.js (1 hunks)
🔇 Additional comments (2)
src/auth-service/models/Preference.js (2)

215-216: Consider partial index for handling optional group_id

The unique compound index on { user_id: 1, group_id: 1 } might cause issues if group_id is optional, as MongoDB treats all null values as equal in unique indexes.

Consider using a partial index to exclude documents where group_id is null:

-PreferenceSchema.index({ user_id: 1, group_id: 1 }, { unique: true });
+PreferenceSchema.index(
+  { user_id: 1, group_id: 1 },
+  { 
+    unique: true,
+    partialFilterExpression: { group_id: { $exists: true, $ne: null } }
+  }
+);

217-226: ⚠️ Potential issue

Separate document and query middleware

The pre-hook combines both document middleware (save, create) and query middleware (update, findByIdAndUpdate, etc.) operations. This can lead to context-related issues as this refers to different objects in each type of middleware.

Consider separating the middleware:

-PreferenceSchema.pre(
-  [
-    "save",
-    "create",
-    "update",
-    "findByIdAndUpdate",
-    "updateMany",
-    "updateOne",
-    "findOneAndUpdate",
-  ],
-  async function (next) {
+// Document middleware
+PreferenceSchema.pre(["save", "create"], async function(next) {
+  // Handle document operations where `this` is the document
+});
+
+// Query middleware
+PreferenceSchema.pre(
+  [
+    "update",
+    "findByIdAndUpdate",
+    "updateMany",
+    "updateOne",
+    "findOneAndUpdate"
+  ],
+  async function(next) {
+  // Handle query operations where `this` is the query
+});

Comment on lines 234 to 238
const processObjectId = (id) => {
return id instanceof mongoose.Types.ObjectId
? id
: mongoose.Types.ObjectId(id);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance ObjectId validation

The processObjectId function could be more robust by adding type checking and error handling.

       const processObjectId = (id) => {
+        if (!id) return null;
+        if (id instanceof mongoose.Types.ObjectId) return id;
+        try {
+          return mongoose.Types.ObjectId(id);
+        } catch (error) {
+          logger.error(`Invalid ObjectId: ${id}`);
+          throw new Error(`Invalid ObjectId: ${id}`);
+        }
-        return id instanceof mongoose.Types.ObjectId
-          ? id
-          : mongoose.Types.ObjectId(id);
       };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const processObjectId = (id) => {
return id instanceof mongoose.Types.ObjectId
? id
: mongoose.Types.ObjectId(id);
};
const processObjectId = (id) => {
if (!id) return null;
if (id instanceof mongoose.Types.ObjectId) return id;
try {
return mongoose.Types.ObjectId(id);
} catch (error) {
logger.error(`Invalid ObjectId: ${id}`);
throw new Error(`Invalid ObjectId: ${id}`);
}
};

Copy link
Contributor

Auth-service changes in this PR available for preview here

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
src/auth-service/utils/create-preference.js (3)

17-52: Simplify user and group validation logic.

The nested conditionals in validateUserAndGroup can be simplified using optional chaining and early returns for better readability.

Consider this refactoring:

 const validateUserAndGroup = async (tenant, userId, groupId, next) => {
-  if (!isEmpty(userId)) {
-    const user = await UserModel(tenant).findById(userId).lean();
-    if (isEmpty(userId) || isEmpty(user)) {
-      return handleError(
-        next,
-        "Bad Request Error",
-        httpStatus.BAD_REQUEST,
-        "The provided User does not exist"
-      );
-    }
+  if (isEmpty(userId)) {
+    return;
+  }

+  const user = await UserModel(tenant).findById(userId).lean();
+  if (isEmpty(user)) {
+    return handleError(
+      next,
+      "Bad Request Error",
+      httpStatus.BAD_REQUEST,
+      "The provided User does not exist"
+    );
+  }

-    if (!isEmpty(groupId)) {
-      if (user && user.group_roles) {
-        const userBelongsToGroup = user.group_roles.some(
-          (role) => role.group.toString() === groupId
-        );
-        if (!userBelongsToGroup) {
-          return handleError(
-            next,
-            "Bad Request Error",
-            httpStatus.BAD_REQUEST,
-            "User does not belong to the specified group"
-          );
-        }
-      } else {
-        return handleError(
-          next,
-          "Bad Request Error",
-          httpStatus.BAD_REQUEST,
-          "User not found or invalid user data"
-        );
-      }
+  if (!isEmpty(groupId)) {
+    if (!user?.group_roles) {
+      return handleError(
+        next,
+        "Bad Request Error",
+        httpStatus.BAD_REQUEST,
+        "User not found or invalid user data"
+      );
     }
+    const userBelongsToGroup = user.group_roles.some(
+      (role) => role.group.toString() === groupId
+    );
+    if (!userBelongsToGroup) {
+      return handleError(
+        next,
+        "Bad Request Error",
+        httpStatus.BAD_REQUEST,
+        "User does not belong to the specified group"
+      );
+    }
   }
 };
🧰 Tools
🪛 Biome (1.9.4)

[error] 30-30: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


54-77: Consider immutability in prepareUpdate function.

The function mutates the input object directly, which could lead to unexpected side effects.

Consider this more immutable approach:

 const prepareUpdate = (body, fieldsToUpdate, fieldsToAddToSet) => {
-  const update = { ...body };
+  const update = {};
+  const addToSet = {};

   fieldsToAddToSet.forEach((field) => {
     if (update[field]) {
-      update["$addToSet"] = { [field]: { $each: update[field] } };
-      delete update[field];
+      addToSet[field] = { $each: body[field] };
     }
   });

   fieldsToUpdate.forEach((field) => {
-    if (update[field]) {
-      update[field] = update[field].map((item) => ({
+    if (body[field]) {
+      const mappedField = body[field].map((item) => ({
         ...item,
         createdAt: item.createdAt || new Date(),
       }));
-      update["$addToSet"] = { [field]: { $each: update[field] } };
-      delete update[field];
+      addToSet[field] = { $each: mappedField };
     }
   });

-  return update;
+  return {
+    ...body,
+    $addToSet: addToSet
+  };
 };

Line range hint 365-371: Critical: Remove or comment out the early return statement.

The early return statement makes all subsequent code unreachable, effectively disabling the delete functionality. This appears to be unintentional as there's implementation code after it.

Either:

  1. Remove the early return if the service should be available:
-    return {
-      success: false,
-      message: "Service Temporarily Unavailable",
-      errors: {
-        message: "Service Temporarily Unavailable",
-      },
-      status: httpStatus.SERVICE_UNAVAILABLE,
-    };
  1. Or comment out the implementation if the service should be temporarily disabled:
    return {
      success: false,
      message: "Service Temporarily Unavailable",
      errors: {
        message: "Service Temporarily Unavailable",
      },
      status: httpStatus.SERVICE_UNAVAILABLE,
    };
-    const {
-      query: { tenant },
-      body,
-    } = request;
-    // ... rest of the implementation
🧰 Tools
🪛 Biome (1.9.4)

[error] 30-30: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between faa861b and 73108c1.

📒 Files selected for processing (1)
  • src/auth-service/utils/create-preference.js (5 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/auth-service/utils/create-preference.js

[error] 30-30: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (3)
src/auth-service/utils/create-preference.js (3)

13-15: LGTM! Good abstraction of error handling.

The handleError utility function effectively standardizes error handling across the codebase, promoting consistency and reducing code duplication.


113-141: LGTM! Robust validation for unique preferences.

The changes effectively implement the PR objective by:

  1. Validating user and group membership
  2. Checking for existing preferences to prevent duplicates
  3. Using consistent error handling

Line range hint 229-287: LGTM! Consistent implementation with create method.

The upsert method effectively mirrors the validation logic from the create method while adding proper update preparation logic.

Copy link
Contributor

Auth-service changes in this PR available for preview here

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
src/auth-service/models/Preference.js (3)

233-276: Enhance ObjectId processing with batch validation

The current implementation processes ObjectIds individually. Consider batching the validation for better performance.

-idFieldsToProcess.forEach((field) => {
-  if (updateData[field]) {
-    if (Array.isArray(updateData[field])) {
-      updateData[field] = updateData[field].map(processObjectId);
-    } else {
-      updateData[field] = processObjectId(updateData[field]);
-    }
-  }
-});
+const batchProcessObjectIds = (fields, data) => {
+  return Object.entries(data)
+    .filter(([field]) => fields.includes(field))
+    .reduce((acc, [field, value]) => ({
+      ...acc,
+      [field]: Array.isArray(value)
+        ? value.map(processObjectId)
+        : processObjectId(value)
+    }), {});
+};
+
+Object.assign(updateData, batchProcessObjectIds(idFieldsToProcess, updateData));

404-422: Optimize array deduplication strategy

The current implementation using $addToSet for all array fields might not be the most efficient approach, especially for large arrays.

Consider these optimizations:

  1. Use bulk operations for better performance
  2. Implement client-side deduplication for smaller arrays
  3. Consider using MongoDB's aggregation pipeline for complex transformations

435-440: Enhance error handling and logging

The error handling in the pre-hook could be more informative and structured.

-    } catch (error) {
-      console.error("Error in Preference pre-hook:", error);
-      return next(error);
+    } catch (error) {
+      const enhancedError = new Error(`Preference pre-hook error: ${error.message}`);
+      enhancedError.code = error.code;
+      enhancedError.originalError = error;
+      logger.error("Preference pre-hook error", {
+        error: error.message,
+        stack: error.stack,
+        context: {
+          isNew,
+          user_id: updateData.user_id,
+          operation: this.op
+        }
+      });
+      return next(enhancedError);
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73108c1 and 00a93ae.

📒 Files selected for processing (1)
  • src/auth-service/models/Preference.js (1 hunks)
🔇 Additional comments (2)
src/auth-service/models/Preference.js (2)

215-216: Consider handling null group_id values in the unique index

The unique compound index on { user_id: 1, group_id: 1 } effectively ensures unique preferences per group. However, be cautious with null group_id values as MongoDB treats all null values as equal in unique indexes.

#!/bin/bash
# Check if there are any existing preferences with null group_id
ast-grep --pattern 'group_id: { type: ObjectId, required: false }'

217-232: ⚠️ Potential issue

Separate document and query middleware contexts

The pre-hook attempts to handle both document middleware (like save) and query middleware (like updateOne) within the same function. This can lead to inconsistent behavior as this refers to different contexts.

Consider splitting into separate hooks:

-PreferenceSchema.pre(
-  [
-    "save",
-    "create",
-    "update",
-    "findByIdAndUpdate",
-    "updateMany",
-    "updateOne",
-    "findOneAndUpdate",
-  ],
+// Document middleware
+PreferenceSchema.pre(["save", "create"], async function(next) {
+  // Handle document operations
+});
+
+// Query middleware
+PreferenceSchema.pre(
+  ["update", "findByIdAndUpdate", "updateMany", "updateOne", "findOneAndUpdate"],
+  async function(next) {
+  // Handle query operations
+});

Likely invalid or redundant comment.

Copy link
Contributor

Auth-service changes in this PR available for preview here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant