From 8b27229675d2455de91b7e690481cb63aeb87eca Mon Sep 17 00:00:00 2001 From: Shengwei Peng Date: Sun, 14 Jan 2024 01:55:39 -0500 Subject: [PATCH 1/2] Update Badge Assignment Code Logic - Remove code for auto fill mising earned dates in both manual and weekly automatic badge assignment - Update code for badege assignment - Update code for UserProfile model - Update code in weekly badge assignment to include new code logic --- src/controllers/badgeController.js | 27 ++-------- src/helpers/userHelper.js | 20 +++++--- src/models/userProfile.js | 79 ++++++++++++++++-------------- 3 files changed, 56 insertions(+), 70 deletions(-) diff --git a/src/controllers/badgeController.js b/src/controllers/badgeController.js index 5dd2113a6..d5fcf06a4 100644 --- a/src/controllers/badgeController.js +++ b/src/controllers/badgeController.js @@ -30,11 +30,11 @@ const badgeController = function (Badge) { }; /** - * Updated Date: 12/06/2023 + * Updated Date: 01/12/2024 * Updated By: Shengwei * Function added: * - Added data validation for earned date and badge count mismatch. - * - Added fillEarnedDateToMatchCount function to resolve earned date and badge count mismatch. + * - Added fillEarnedDateToMatchCount function to resolve earned date and badge count mismatch. (Deleted due to new requirement) * - Refactored data validation for duplicate badge id. * - Added data validation for badge count should greater than 0. * - Added formatDate function to format date to MMM-DD-YY. @@ -45,14 +45,6 @@ const badgeController = function (Badge) { return moment(currentDate).tz('America/Los_Angeles').format('MMM-DD-YY'); }; - const fillEarnedDateToMatchCount = (earnedDate, count) => { - const result = [...earnedDate]; - while (result.length < count) { - result.push(formatDate()); - } - return result; - }; - const assignBadges = async function (req, res) { if (!(await hasPermission(req.body.requestor, 'assignBadges'))) { res.status(403).send('You are not authorized to assign badges.'); @@ -74,28 +66,15 @@ const badgeController = function (Badge) { req.body.badgeCollection.forEach((element) => { if (badgeCounts[element.badge]) { throw new Error('Duplicate badges sent in.'); - // res.status(500).send('Duplicate badges sent in.'); - // return; } badgeCounts[element.badge] = element.count; // Validation: count should be greater than 0 if (element.count < 1) { throw new Error('Badge count should be greater than 0.'); } - if (element.count !== element.earnedDate.length) { - element.earnedDate = fillEarnedDateToMatchCount( - element.earnedDate, - element.count, - ); - element.lastModified = Date.now(); - logger.logInfo( - `Badge count and earned dates mismatched found. ${Date.now()} was generated for user ${userToBeAssigned}. Badge record ID ${ - element._id - }; Badge Type ID ${element.badge}`, - ); - } }); } catch (err) { + logger.logException(`Internal Error: Badge Collection. ${err.message} User ID: ${userToBeAssigned} Badge Collection: ${JSON.stringify(req.body.badgeCollection)}`); res.status(500).send(`Internal Error: Badge Collection. ${ err.message}`); return; } diff --git a/src/helpers/userHelper.js b/src/helpers/userHelper.js index b4b2acb46..782b448a8 100644 --- a/src/helpers/userHelper.js +++ b/src/helpers/userHelper.js @@ -866,18 +866,21 @@ const changeBadgeCount = async function (personId, badgeId, count) { if (!recordToUpdate) { throw new Error('Badge not found'); } + // If the count is the same, do nothing + if (recordToUpdate.count === count) { + return; + } const copyOfEarnedDate = recordToUpdate.earnedDate; - if (copyOfEarnedDate.length < count) { + // Update: We refrain from automatically correcting the mismatch problem as we intend to preserve the original + // earned date even when a badge is deleted. This approach ensures that a record of badges earned is maintained, + // preventing oversight of any mismatches caused by bugs. + if (recordToUpdate.count < count) { + let dateToAdd = count - recordToUpdate.count; // if the EarnedDate count is less than the new count, add a earned date to the end of the collection - while (copyOfEarnedDate.length < count) { + while (dateToAdd > 0) { copyOfEarnedDate.push(earnedDateBadge()); + dateToAdd -= 1; } - } else { - // if the EarnedDate count is greater than the new count, remove the oldest earned date of the collection until it matches the new count - 1 - while (copyOfEarnedDate.length >= count) { - copyOfEarnedDate.shift(); - } - copyOfEarnedDate.push(earnedDateBadge()); } newEarnedDate = [...copyOfEarnedDate]; userProfile.updateOne( @@ -887,6 +890,7 @@ const changeBadgeCount = async function (personId, badgeId, count) { 'badgeCollection.$.count': count, 'badgeCollection.$.lastModified': Date.now().toString(), 'badgeCollection.$.earnedDate': newEarnedDate, + 'badgeCollection.$.hasBadgeDeletionImpact': recordToUpdate.count > count, // badge deletion impact set to true if the new count is less than the old count }, }, (err) => { diff --git a/src/models/userProfile.js b/src/models/userProfile.js index 633a81026..89c9565af 100644 --- a/src/models/userProfile.js +++ b/src/models/userProfile.js @@ -1,9 +1,9 @@ -const mongoose = require("mongoose"); -const moment = require("moment-timezone"); +const mongoose = require('mongoose'); +const moment = require('moment-timezone'); const { Schema } = mongoose; -const validate = require("mongoose-validator"); -const bcrypt = require("bcryptjs"); +const validate = require('mongoose-validator'); +const bcrypt = require('bcryptjs'); const SALT_ROUNDS = 10; const nextDay = new Date(); @@ -15,12 +15,11 @@ const userProfileSchema = new Schema({ required: true, validate: { validator(v) { - const passwordregex = - /(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/; + const passwordregex = /(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/; return passwordregex.test(v); }, message: - "{VALUE} is not a valid password!password should be at least 8 charcaters long with uppercase, lowercase and number/special char.", + '{VALUE} is not a valid password!password should be at least 8 charcaters long with uppercase, lowercase and number/special char.', }, }, isActive: { type: Boolean, required: true, default: true }, @@ -49,7 +48,7 @@ const userProfileSchema = new Schema({ required: true, unique: true, validate: [ - validate({ validator: "isEmail", message: "Email address is invalid" }), + validate({ validator: 'isEmail', message: 'Email address is invalid' }), ], }, weeklycommittedHours: { type: Number, default: 10 }, @@ -67,14 +66,18 @@ const userProfileSchema = new Schema({ { _id: Schema.Types.ObjectId, Name: String, Link: { type: String } }, ], adminLinks: [{ _id: Schema.Types.ObjectId, Name: String, Link: String }], - teams: [{ type: mongoose.SchemaTypes.ObjectId, ref: "team" }], - projects: [{ type: mongoose.SchemaTypes.ObjectId, ref: "project" }], + teams: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'team' }], + projects: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'project' }], badgeCollection: [ { - badge: { type: mongoose.SchemaTypes.ObjectId, ref: "badge" }, + badge: { type: mongoose.SchemaTypes.ObjectId, ref: 'badge' }, count: { type: Number, default: 0 }, earnedDate: { type: Array, default: [] }, lastModified: { type: Date, required: true, default: Date.now() }, + // This field is used to determine if the badge deletion will impact the user's badge collection. + // If the user has a badge with hasBadgeDeletionImpact set to true, then the a mismatch in badge + // count and earned date will be intentionally created. + hasBadgeDeletionImpact: { type: Boolean, default: false }, featured: { type: Boolean, required: true, @@ -90,13 +93,13 @@ const userProfileSchema = new Schema({ }, ], location: { - userProvided: { type: String, default: "" }, + userProvided: { type: String, default: '' }, coords: { - lat: { type: Number, default: "" }, - lng: { type: Number, default: "" }, + lat: { type: Number, default: '' }, + lng: { type: Number, default: '' }, }, - country: { type: String, default: "" }, - city: { type: String, default: "" }, + country: { type: String, default: '' }, + city: { type: String, default: '' }, }, oldInfringements: [ { @@ -118,7 +121,7 @@ const userProfileSchema = new Schema({ dueDate: { type: Date, required: true, - default: moment().tz("America/Los_Angeles").endOf("week"), + default: moment().tz('America/Los_Angeles').endOf('week'), }, summary: { type: String }, uploadDate: { type: Date }, @@ -148,17 +151,17 @@ const userProfileSchema = new Schema({ category: { type: String, enum: [ - "Food", - "Energy", - "Housing", - "Education", - "Society", - "Economics", - "Stewardship", - "Other", - "Unspecified", + 'Food', + 'Energy', + 'Housing', + 'Education', + 'Society', + 'Economics', + 'Stewardship', + 'Other', + 'Unspecified', ], - default: "Other", + default: 'Other', }, hrs: { type: Number, default: 0 }, }, @@ -169,27 +172,27 @@ const userProfileSchema = new Schema({ date: { type: Date, required: true, - default: moment().tz("America/Los_Angeles").toDate(), + default: moment().tz('America/Los_Angeles').toDate(), }, initialSeconds: { type: Number, required: true }, newSeconds: { type: Number, required: true }, }, ], weeklySummaryNotReq: { type: Boolean, default: false }, - timeZone: { type: String, required: true, default: "America/Los_Angeles" }, + timeZone: { type: String, required: true, default: 'America/Los_Angeles' }, isVisible: { type: Boolean, default: false }, weeklySummaryOption: { type: String }, - bioPosted: { type: String, default: "default" }, + bioPosted: { type: String, default: 'default' }, isFirstTimelog: { type: Boolean, default: true }, teamCode: { type: String, - default: "", + default: '', validate: { validator(v) { const teamCoderegex = /^([a-zA-Z]-[a-zA-Z]{3}|[a-zA-Z]{5})$|^$/; return teamCoderegex.test(v); }, - message: "Please enter a code in the format of A-AAA or AAAAA", + message: 'Please enter a code in the format of A-AAA or AAAAA', }, }, infoCollections: [ @@ -204,22 +207,22 @@ const userProfileSchema = new Schema({ timeOffTill: { type: Date, default: undefined }, }); -userProfileSchema.pre("save", function (next) { +userProfileSchema.pre('save', function (next) { const user = this; - if (!user.isModified("password")) return next(); + if (!user.isModified('password')) return next(); return bcrypt .genSalt(SALT_ROUNDS) - .then((result) => bcrypt.hash(user.password, result)) + .then(result => bcrypt.hash(user.password, result)) .then((hash) => { user.password = hash; return next(); }) - .catch((error) => next(error)); + .catch(error => next(error)); }); module.exports = mongoose.model( - "userProfile", + 'userProfile', userProfileSchema, - "userProfiles" + 'userProfiles', ); From 6c727ef397801c0c2e415f3347b56b66efee718a Mon Sep 17 00:00:00 2001 From: Shengwei Peng Date: Mon, 19 Feb 2024 23:13:02 -0500 Subject: [PATCH 2/2] resolve merge conflict --- src/helpers/userHelper.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/helpers/userHelper.js b/src/helpers/userHelper.js index 827b507b2..761ce178b 100644 --- a/src/helpers/userHelper.js +++ b/src/helpers/userHelper.js @@ -1009,10 +1009,7 @@ const userHelper = function () { 'badgeCollection.$.count': count, 'badgeCollection.$.lastModified': Date.now().toString(), 'badgeCollection.$.earnedDate': newEarnedDate, -<<<<<<< HEAD 'badgeCollection.$.hasBadgeDeletionImpact': recordToUpdate.count > count, // badge deletion impact set to true if the new count is less than the old count -======= ->>>>>>> ee2aaa5b92c95a3a1f5d6ee9e67d1802e71ef6c1 }, }, (err) => {