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

fix: update preference job to handle compound unique key with group_id #4078

Merged
merged 3 commits into from
Dec 14, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 87 additions & 19 deletions src/auth-service/bin/jobs/preferences-update-job.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const cron = require("node-cron");
const UserModel = require("@models/User");
const mongoose = require("mongoose");
const PreferenceModel = require("@models/Preference");
const SelectedSiteModel = require("@models/SelectedSite");
const constants = require("@config/constants");
Expand All @@ -12,6 +13,30 @@ const stringify = require("@utils/stringify");
const isEmpty = require("is-empty");
const BATCH_SIZE = 100;

// Function to validate critical default values
const validateDefaultValues = () => {
const criticalDefaults = [
{ key: "DEFAULT_GROUP", value: constants.DEFAULT_GROUP },
{ key: "DEFAULT_AIRQLOUD", value: constants.DEFAULT_AIRQLOUD },
{ key: "DEFAULT_GRID", value: constants.DEFAULT_GRID },
{ key: "DEFAULT_NETWORK", value: constants.DEFAULT_NETWORK },
];

const missingDefaults = criticalDefaults.filter(
(item) => isEmpty(item.value) || item.value === undefined
);

if (missingDefaults.length > 0) {
const missingKeys = missingDefaults.map((item) => item.key).join(", ");
logger.error(
`🚨 Aborting preference update: Missing critical default values: ${missingKeys}`
);
return false;
}

return true;
};

// Default preference object
const defaultPreference = {
pollutant: "pm2_5",
Expand All @@ -27,10 +52,22 @@ const defaultPreference = {
unitValue: 14,
unit: "day",
},
airqloud_id: constants.DEFAULT_AIRQLOUD || "NA",
grid_id: constants.DEFAULT_GRID || "NA",
network_id: constants.DEFAULT_NETWORK || "NA",
group_id: constants.DEFAULT_GROUP || "NA",
airqloud_id: constants.DEFAULT_AIRQLOUD,
grid_id: constants.DEFAULT_GRID,
network_id: constants.DEFAULT_NETWORK,
group_id: constants.DEFAULT_GROUP,
};

// Function to validate user's group membership
const validateUserGroupMembership = (user, defaultGroupId) => {
// Check if user has group_roles and is a member of the default group
if (!user.group_roles || user.group_roles.length === 0) {
return false;
}

return user.group_roles.some(
(role) => role.group.toString() === defaultGroupId.toString()
);
};

// Function to get selected sites based on the specified method
Expand Down Expand Up @@ -62,6 +99,11 @@ const getSelectedSites = async (method = "featured") => {
};

const updatePreferences = async (siteSelectionMethod = "featured") => {
// Validate default values before proceeding
if (!validateDefaultValues()) {
return;
}

try {
const batchSize = BATCH_SIZE;
let skip = 0;
Expand All @@ -74,43 +116,60 @@ const updatePreferences = async (siteSelectionMethod = "featured") => {
return;
}

// Use constants.DEFAULT_GROUP directly
const defaultGroupId = mongoose.Types.ObjectId(constants.DEFAULT_GROUP);

while (true) {
// Fetch users with their group_roles
const users = await UserModel("airqo")
.find()
.limit(batchSize)
.skip(skip)
.select("_id")
.select("_id group_roles")
.lean();

if (users.length === 0) {
break;
}

// Fetch existing preferences for users in batch
const userIds = users.map((user) => user._id);
// Filter users who are members of the default group
const validUsers = users.filter((user) =>
validateUserGroupMembership(user, defaultGroupId)
);

// Get user IDs of valid users
const validUserIds = validUsers.map((user) => user._id);

// Fetch existing preferences for valid users
const preferences = await PreferenceModel("airqo")
.find({ user_id: { $in: userIds } })
.find({
user_id: { $in: validUserIds },
group_id: defaultGroupId,
})
.select("_id user_id selected_sites")
.lean();

const preferencesMap = new Map();

preferences.forEach((pref) => {
preferencesMap.set(pref.user_id.toString(), pref);
});

for (const user of users) {
for (const user of validUsers) {
const userIdStr = user._id.toString();
const preference = preferencesMap.get(userIdStr);

// Prepare the default preference object with the specific group_id
const defaultPreferenceWithGroupId = {
...defaultPreference,
user_id: user._id,
group_id: defaultGroupId,
selected_sites: selectedSites,
};
Comment on lines +162 to +167
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

Ensure all required fields are included in defaultPreferenceWithGroupId

While constructing defaultPreferenceWithGroupId, make sure that all required fields defined in the PreferenceModel schema are included. This prevents validation errors during creation or update operations.

Review the PreferenceModel schema and update defaultPreferenceWithGroupId if any required fields are missing.


if (!preference) {
// No preference exists, create a new one
// No preference exists for the user in the default group, create a new one
await PreferenceModel("airqo")
.create({
...defaultPreference,
user_id: user._id,
selected_sites: selectedSites,
})
.create(defaultPreferenceWithGroupId)
.catch((error) => {
logger.error(
`🐛🐛 Failed to create preference for user ${userIdStr}: ${stringify(
Expand All @@ -122,14 +181,21 @@ const updatePreferences = async (siteSelectionMethod = "featured") => {
// Preference exists but selected_sites is empty, update it
await PreferenceModel("airqo")
.findOneAndUpdate(
{ _id: preference._id },
{
user_id: user._id,
group_id: defaultGroupId,
},
{
$set: {
...defaultPreference,
selected_sites: selectedSites,
group_id: defaultGroupId,
},
},
{ new: true }
{
new: true,
upsert: true,
setDefaultsOnInsert: true,
}
)
.catch((error) => {
logger.error(
Expand All @@ -154,3 +220,5 @@ cron.schedule(schedule, () => updatePreferences("featured"), {
scheduled: true,
timezone: "Africa/Nairobi",
});

module.exports = { updatePreferences };
Loading