diff --git a/src/auth-service/models/Preference.js b/src/auth-service/models/Preference.js index 15c8b141d9..17e881f496 100644 --- a/src/auth-service/models/Preference.js +++ b/src/auth-service/models/Preference.js @@ -243,24 +243,7 @@ PreferenceSchema.pre( } }; - // Define field categories - const singleIdFields = [ - "user_id", - "group_id", - "airqloud_id", - "grid_id", - "cohort_id", - "network_id", - ]; - const arrayIdFields = [ - "airqloud_ids", - "grid_ids", - "cohort_ids", - "network_ids", - "site_ids", - "device_ids", - "group_ids", - ]; + // Define selected array fields with subschemas const selectedArrayFields = [ "selected_sites", "selected_grids", @@ -269,180 +252,63 @@ PreferenceSchema.pre( "selected_airqlouds", ]; - // Process single ID fields - singleIdFields.forEach((field) => { - if (updateData[field]) { - updateData[field] = processObjectId(updateData[field]); - } - }); - - // Validate user_id - if (!updateData.user_id) { - return next(new Error("user_id is required")); - } - - // Set default values if not provided - const defaultFields = [ - { field: "pollutant", default: "pm2_5" }, - { field: "frequency", default: "hourly" }, - { field: "chartType", default: "line" }, - { field: "chartTitle", default: "Chart Title" }, - { field: "chartSubTitle", default: "Chart SubTitle" }, - ]; - - defaultFields.forEach(({ field, default: defaultValue }) => { - if (isNew && !updateData[field]) { - updateData[field] = defaultValue; - } - }); - - // Handle date fields - if (isNew) { - const currentDate = new Date(); - updateData.startDate = - updateData.startDate || addWeeksToProvideDateTime(currentDate, -2); - updateData.endDate = updateData.endDate || currentDate; - } - - // Validate and process period schema - if (updateData.period) { - const validPeriodFields = ["value", "label", "unitValue", "unit"]; - const periodUpdate = {}; - - validPeriodFields.forEach((field) => { - if (updateData.period[field] !== undefined) { - periodUpdate[field] = updateData.period[field]; - } - }); - - // Additional period validation - if ( - periodUpdate.unitValue !== undefined && - typeof periodUpdate.unitValue !== "number" - ) { - periodUpdate.unitValue = Number(periodUpdate.unitValue); - } - - updateData.period = periodUpdate; - } - - // Process and validate selected arrays with their specific schemas - const selectedArrayProcessors = { - selected_sites: (site) => { - const processedSite = { ...site }; - - // Validate and process ObjectIds - if (site._id) processedSite._id = processObjectId(site._id); - if (site.grid_id) - processedSite.grid_id = processObjectId(site.grid_id); - - // Validate numeric fields - const numericFields = [ - "latitude", - "longitude", - "approximate_latitude", - "approximate_longitude", - ]; - numericFields.forEach((field) => { - if (processedSite[field] !== undefined) { - processedSite[field] = Number(processedSite[field]); - } - }); - - // Ensure createdAt is a valid date - processedSite.createdAt = site.createdAt || new Date(); - - // Validate string fields - const stringFields = [ - "country", - "district", - "sub_county", - "parish", - "county", - "generated_name", - "name", - "city", - "formatted_name", - "region", - "search_name", - ]; - 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 + // Process selected arrays to ensure uniqueness based on _id selectedArrayFields.forEach((field) => { if (updateData[field]) { - updateData[field] = updateData[field].map( - selectedArrayProcessors[field] + // Remove duplicates based on _id + const uniqueArray = updateData[field].filter( + (item, index, self) => + index === + self.findIndex( + (t) => + t._id && item._id && t._id.toString() === item._id.toString() + ) ); - // Use $addToSet for selected arrays - updateData.$addToSet = { - ...updateData.$addToSet, - [field]: { - $each: updateData[field], - }, - }; + // Use $set to replace the existing array with unique entries + updateData.$set = updateData.$set || {}; + updateData.$set[field] = uniqueArray; + + // Optional: Remove the original field to prevent double processing delete updateData[field]; } }); - // Process array ID fields + // Repeat similar logic for array ID fields + const arrayIdFields = [ + "airqloud_ids", + "grid_ids", + "cohort_ids", + "network_ids", + "site_ids", + "device_ids", + "group_ids", + ]; + arrayIdFields.forEach((field) => { if (updateData[field]) { - updateData.$addToSet = { - ...updateData.$addToSet, - [field]: { - $each: Array.isArray(updateData[field]) - ? updateData[field].map(processObjectId) - : [processObjectId(updateData[field])], - }, - }; + // Ensure unique ObjectIds + const uniqueIds = [ + ...new Set( + (Array.isArray(updateData[field]) + ? updateData[field] + : [updateData[field]] + ) + .map(processObjectId) + .filter(Boolean) + .map((id) => id.toString()) + ), + ].map(processObjectId); + + // Use $set to replace the existing array with unique entries + updateData.$set = updateData.$set || {}; + updateData.$set[field] = uniqueIds; + + // Remove the original 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, - } - ); - next(); } catch (error) { console.error("Error in Preference pre-hook:", error); diff --git a/src/auth-service/utils/create-preference.js b/src/auth-service/utils/create-preference.js index 794447370d..35d4fb1578 100644 --- a/src/auth-service/utils/create-preference.js +++ b/src/auth-service/utils/create-preference.js @@ -54,13 +54,35 @@ const validateUserAndGroup = async (tenant, userId, groupId, next) => { const prepareUpdate = (body, fieldsToUpdate, fieldsToAddToSet) => { const update = { ...body }; + // Utility function to remove duplicates based on _id + const removeDuplicates = (arr, idField = "_id") => { + return arr.filter( + (item, index, self) => + index === + self.findIndex( + (t) => + t[idField] && + item[idField] && + t[idField].toString() === item[idField].toString() + ) + ); + }; + // Handle fields that should be added to set (array fields) fieldsToAddToSet.forEach((field) => { if (update[field]) { - update["$addToSet"] = update["$addToSet"] || {}; - update["$addToSet"][field] = { - $each: Array.isArray(update[field]) ? update[field] : [update[field]], - }; + const processedArray = Array.isArray(update[field]) + ? update[field] + : [update[field]]; + + // Remove duplicates for specific fields + const uniqueArray = + field === "selected_sites" + ? removeDuplicates(processedArray) + : processedArray; + + update["$set"] = update["$set"] || {}; + update["$set"][field] = uniqueArray; delete update[field]; } }); @@ -68,21 +90,30 @@ const prepareUpdate = (body, fieldsToUpdate, fieldsToAddToSet) => { // Handle fields that need special processing (with createdAt) fieldsToUpdate.forEach((field) => { if (update[field]) { - update[field] = update[field].map((item) => ({ + // Process each item + const processedArray = update[field].map((item) => ({ ...item, createdAt: item.createdAt || new Date(), })); - update["$addToSet"] = update["$addToSet"] || {}; - update["$addToSet"][field] = { $each: update[field] }; + // Remove duplicates for specific fields + const uniqueArray = + field === "selected_sites" + ? removeDuplicates(processedArray) + : processedArray; + + update["$set"] = update["$set"] || {}; + update["$set"][field] = uniqueArray; delete update[field]; } }); - // Remove simple ObjectId fields from being processed by $addToSet + // Process single ObjectId fields const singleObjectIdFields = ["user_id", "group_id"]; singleObjectIdFields.forEach((field) => { if (update[field]) { + // Ensure single ObjectId fields are processed as-is + update[field] = update[field]; } });