Skip to content

Commit

Permalink
Merge pull request #760 from OneCommunityGlobal/revert-703-shengwei_u…
Browse files Browse the repository at this point in the history
…pdate_code_logic_for_badge_assignment_and_update

Revert "Shengwei Enhancement: badge assignment and modification will not remove earned date"
  • Loading branch information
one-community authored Feb 22, 2024
2 parents 5db3d27 + f565d3f commit 3cd6b92
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 59 deletions.
32 changes: 28 additions & 4 deletions src/controllers/badgeController.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ const badgeController = function (Badge) {
};

/**
* Updated Date: 01/12/2024
* Updated Date: 12/06/2023
* 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. (Deleted due to new requirement)
* - Added fillEarnedDateToMatchCount function to resolve earned date and badge count mismatch.
* - 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.
Expand All @@ -58,6 +58,14 @@ 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.');
Expand All @@ -80,16 +88,32 @@ const badgeController = function (Badge) {
newBadgeCollection = req.body.badgeCollection.map((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}`,
);
}
return element;
});
} 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}`);
res
.status(500)
.send(`Internal Error: Badge Collection. ${err.message}`);
return;
}
record.badgeCollection = newBadgeCollection;
Expand Down
22 changes: 9 additions & 13 deletions src/helpers/userHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -985,21 +985,18 @@ const userHelper = function () {
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;
// 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 (dateToAdd > 0) {
if (copyOfEarnedDate.length < 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) {
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(
Expand All @@ -1009,7 +1006,6 @@ const userHelper = function () {
'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) => {
Expand Down
81 changes: 39 additions & 42 deletions src/models/userProfile.js
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -15,11 +15,12 @@ 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 },
Expand Down Expand Up @@ -49,7 +50,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" }),
],
},
copiedAiPrompt: { type: Date, default: Date.now() },
Expand All @@ -68,18 +69,14 @@ 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 },
lastModified: { type: Date, required: true, default: new Date()},
featured: {
type: Boolean,
required: true,
Expand Down Expand Up @@ -118,13 +115,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: [
{
Expand All @@ -146,7 +143,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 },
Expand Down Expand Up @@ -176,17 +173,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 },
},
Expand All @@ -197,27 +194,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: [
Expand All @@ -232,22 +229,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"
);

0 comments on commit 3cd6b92

Please sign in to comment.