Skip to content

Commit

Permalink
Merge pull request #2121 from Real-Dev-Squad/develop
Browse files Browse the repository at this point in the history
Dev to Main Sync
  • Loading branch information
iamitprakash committed Sep 9, 2024
2 parents 0d51a6d + 0edca3d commit 5a7f583
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 2 deletions.
11 changes: 11 additions & 0 deletions controllers/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,16 @@ const getIdentityStats = async (req, res) => {
});
};

const updateUsernames = async (req, res) => {
try {
const response = await userQuery.updateUsersWithNewUsernames();
return res.status(200).json(response);
} catch (error) {
logger.error("Error in username update script", error);
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
}
};

module.exports = {
verifyUser,
generateChaincode,
Expand Down Expand Up @@ -986,4 +996,5 @@ module.exports = {
usersPatchHandler,
isDeveloper,
getIdentityStats,
updateUsernames,
};
6 changes: 5 additions & 1 deletion models/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,18 @@ const getApplicationsBasedOnStatus = async (status: string, limit: number, lastD
const getUserApplications = async (userId: string) => {
try {
const applicationsResult = [];
const applications = await ApplicationsModel.where("userId", "==", userId).get();
const applications = await ApplicationsModel.where("userId", "==", userId)
.orderBy("createdAt", "desc")
.limit(1)
.get();

applications.forEach((application) => {
applicationsResult.push({
id: application.id,
...application.data(),
});
});

return applicationsResult;
} catch (err) {
logger.log("error in getting user intro", err);
Expand Down
131 changes: 131 additions & 0 deletions models/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,136 @@ const getNonNickNameSyncedUsers = async () => {
}
};

const updateUsernamesInBatch = async (usersData) => {
const batch = firestore.batch();
const usersBatch = [];
const summary = {
totalUpdatedUsernames: 0,
totalOperationsFailed: 0,
failedUserDetails: [],
};

usersData.forEach((user) => {
const updateUserData = { ...user, username: user.username };
batch.update(userModel.doc(user.id), updateUserData);
usersBatch.push(user.id);
});

try {
await batch.commit();
summary.totalUpdatedUsernames += usersData.length;
return { ...summary };
} catch (err) {
logger.error("Firebase batch Operation Failed!");
summary.totalOperationsFailed += usersData.length;
summary.failedUserDetails = [...usersBatch];
return { ...summary };
}
};

const sanitizeString = (str) => {
if (!str) return "";
return str.replace(/[^a-zA-Z0-9-]/g, "-");
};

const formatUsername = (firstName, lastName, suffix) => {
const actualFirstName = firstName.split(" ")[0].toLowerCase();
const sanitizedFirstName = sanitizeString(actualFirstName);
const sanitizedLastName = sanitizeString(lastName).toLowerCase();

const baseUsername = `${sanitizedFirstName}-${sanitizedLastName}`;
const fullUsername = `${baseUsername}-${suffix}`;

return fullUsername.length <= 32
? fullUsername
: `${baseUsername.slice(0, 32 - suffix.toString().length - 1)}-${suffix}`;
};

const updateUsersWithNewUsernames = async () => {
try {
const snapshot = await userModel.get();

const nonMemberUsers = snapshot.docs.filter((doc) => {
const userData = doc.data();
const roles = userData.roles;

return !(roles?.member === true || roles?.super_user === true || userData.incompleteUserDetails === true);
});

const summary = {
totalUsers: nonMemberUsers.length,
totalUpdatedUsernames: 0,
totalOperationsFailed: 0,
failedUserDetails: [],
};

if (nonMemberUsers.length === 0) {
return summary;
}

const usersToUpdate = [];
const nameToUsersMap = new Map();

nonMemberUsers.forEach((userDoc) => {
const userData = userDoc.data();
const id = userDoc.id;

const firstName = userData.first_name?.split(" ")[0]?.toLowerCase();
const lastName = userData.last_name?.toLowerCase();

if (!firstName || !lastName) {
return;
}

const fullName = `${firstName}-${lastName}`;
if (!nameToUsersMap.has(fullName)) {
nameToUsersMap.set(fullName, []);
}

nameToUsersMap.get(fullName).push({ id, userData, createdAt: userData.created_at });
});

for (const [, usersWithSameName] of nameToUsersMap.entries()) {
usersWithSameName.sort((a, b) => a.createdAt - b.createdAt);

usersWithSameName.forEach((user, index) => {
const suffix = index + 1;
const formattedUsername = formatUsername(user.userData.first_name, user.userData.last_name, suffix);

if (user.userData.username !== formattedUsername) {
usersToUpdate.push({ ...user.userData, id: user.id, username: formattedUsername });
}
});
}

const userChunks = chunks(usersToUpdate, DOCUMENT_WRITE_SIZE);

const updatedUsersPromises = await Promise.all(
userChunks.map(async (users) => {
const res = await updateUsernamesInBatch(users);
return res;
})
);

updatedUsersPromises.forEach((res) => {
summary.totalUpdatedUsernames += res.totalUpdatedUsernames;
summary.totalOperationsFailed += res.totalOperationsFailed;
if (res.failedUserDetails.length > 0) {
summary.failedUserDetails.push(...res.failedUserDetails);
}
});

if (summary.totalOperationsFailed === summary.totalUsers) {
throw new Error("INTERNAL_SERVER_ERROR");
}

return summary;
} catch (error) {
logger.error(`Error in updating usernames: ${error}`);
throw error;
}
};

module.exports = {
addOrUpdate,
fetchPaginatedUsers,
Expand Down Expand Up @@ -942,4 +1072,5 @@ module.exports = {
fetchUsersListForMultipleValues,
fetchUserForKeyValue,
getNonNickNameSyncedUsers,
updateUsersWithNewUsernames,
};
2 changes: 2 additions & 0 deletions routes/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,5 @@ router.patch("/rejectDiff", authenticate, authorizeRoles([SUPERUSER]), users.rej
router.patch("/:userId", authenticate, authorizeRoles([SUPERUSER]), users.updateUser);
router.get("/suggestedUsers/:skillId", authenticate, authorizeRoles([SUPERUSER]), users.getSuggestedUsers);
module.exports = router;
router.post("/batch-username-update", authenticate, authorizeRoles([SUPERUSER]), users.updateUsernames);
module.exports = router;
34 changes: 34 additions & 0 deletions test/integration/users.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2423,4 +2423,38 @@ describe("Users", function () {
});
});
});

describe("POST USERS MIGRATION", function () {
it("should run the migration and update usernames successfully", async function () {
const res = await chai
.request(app)
.post("/users/batch-username-update")
.set("cookie", `${cookieName}=${superUserAuthToken}`)
.send();

expect(res).to.have.status(200);
});

it("should not update usernames for super_user or member", async function () {
const res = await chai
.request(app)
.post("/users/batch-username-update")
.set("cookie", `${cookieName}=${superUserAuthToken}`)
.send();

expect(res).to.have.status(200);
const affectedUsers = res.body.totalUpdatedUsernames;
expect(affectedUsers).to.equal(0);
});

it("should return 401 for unauthorized user attempting migration", async function () {
const res = await chai
.request(app)
.post("/users/batch-username-update")
.set("cookie", `${cookieName}=${jwt}`)
.send();

expect(res).to.have.status(401);
});
});
});
2 changes: 1 addition & 1 deletion test/unit/models/application.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe("applications", function () {
});

describe("getUserApplications", function () {
it("should return all the user applications", async function () {
it("should return users most recent application", async function () {
const applications = await ApplicationModel.getUserApplications("kfasdjfkdlfjkasdjflsdjfk");
expect(applications).to.be.a("array");
expect(applications.length).to.be.equal(1);
Expand Down

0 comments on commit 5a7f583

Please sign in to comment.