Skip to content

Commit 36e067d

Browse files
committed
feat: Departed users api changes.
1 parent b6351b7 commit 36e067d

File tree

5 files changed

+122
-42
lines changed

5 files changed

+122
-42
lines changed

controllers/users.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const { addLog } = require("../models/logs");
3030
const { getUserStatus } = require("../models/userStatus");
3131
const config = require("config");
3232
const { generateUniqueUsername } = require("../services/users");
33+
const userService = require("../services/users");
3334
const discordDeveloperRoleId = config.get("discordDeveloperRoleId");
3435

3536
const verifyUser = async (req, res) => {
@@ -191,6 +192,30 @@ const getUsers = async (req, res) => {
191192
}
192193
}
193194

195+
const isDeparted = req.query.departed === "true";
196+
197+
if (isDeparted) {
198+
if (!dev) {
199+
return res.boom.notFound("Route not found");
200+
}
201+
try {
202+
const result = await dataAccess.retrieveUsers({ query: req.query });
203+
const departedUsers = await userService.getUsersWithIncompleteTasks(result.users);
204+
if (departedUsers.length === 0) return res.status(204).send();
205+
return res.json({
206+
message: "Users with abandoned tasks fetched successfully",
207+
users: departedUsers,
208+
links: {
209+
next: result.nextId ? getPaginationLink(req.query, "next", result.nextId) : "",
210+
prev: result.prevId ? getPaginationLink(req.query, "prev", result.prevId) : "",
211+
},
212+
});
213+
} catch (error) {
214+
logger.error("Error when fetching users who abandoned tasks:", error);
215+
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
216+
}
217+
}
218+
194219
if (transformedQuery?.filterBy === OVERDUE_TASKS) {
195220
try {
196221
const tasksData = await getOverdueTasks(days);

middlewares/validators/user.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ async function getUsers(req, res, next) {
200200
filterBy: joi.string().optional(),
201201
days: joi.string().optional(),
202202
dev: joi.string().optional(),
203+
departed: joi.string().optional(),
203204
roles: joi.optional().custom((value, helpers) => {
204205
if (value !== "member") {
205206
return helpers.message("only member role is supported");

models/tasks.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,26 @@ const markUnDoneTasksOfArchivedUsersBacklog = async (users) => {
701701
}
702702
};
703703

704+
/**
705+
* Fetch incomplete tasks assigned to a specific user
706+
* @param {string} userId - The unique identifier for the user.
707+
* @returns {Promise<Array>} - A promise that resolves to an array of incomplete tasks for the given user.
708+
* @throws {Error} - Throws an error if the database query fails.
709+
*/
710+
const fetchIncompleteTaskForUser = async (userId) => {
711+
const COMPLETED_STATUSES = [DONE, COMPLETED];
712+
try {
713+
const incompleteTaskForUser = await tasksModel
714+
.where("assigneeId", "==", userId)
715+
.where("status", "not-in", COMPLETED_STATUSES)
716+
.get();
717+
return incompleteTaskForUser;
718+
} catch (error) {
719+
logger.error("Error when fetching incomplete tasks:", error);
720+
throw error;
721+
}
722+
};
723+
704724
module.exports = {
705725
updateTask,
706726
fetchTasks,
@@ -720,4 +740,5 @@ module.exports = {
720740
updateTaskStatus,
721741
updateOrphanTasksStatus,
722742
markUnDoneTasksOfArchivedUsersBacklog,
743+
fetchIncompleteTaskForUser,
723744
};

models/users.js

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ const firestore = require("../utils/firestore");
88
const { fetchWallet, createWallet } = require("../models/wallets");
99
const { updateUserStatus } = require("../models/userStatus");
1010
const { arraysHaveCommonItem, chunks } = require("../utils/array");
11-
const { archiveUsers } = require("../services/users");
12-
const { ALLOWED_FILTER_PARAMS, FIRESTORE_IN_CLAUSE_SIZE } = require("../constants/users");
11+
const {
12+
ALLOWED_FILTER_PARAMS,
13+
FIRESTORE_IN_CLAUSE_SIZE,
14+
USERS_PATCH_HANDLER_SUCCESS_MESSAGES,
15+
USERS_PATCH_HANDLER_ERROR_MESSAGES,
16+
} = require("../constants/users");
1317
const { DOCUMENT_WRITE_SIZE } = require("../constants/constants");
1418
const { userState } = require("../constants/userStatus");
1519
const { BATCH_SIZE_IN_CLAUSE } = require("../constants/firebase");
@@ -27,6 +31,52 @@ const { formatUsername } = require("../utils/username");
2731
const { logType } = require("../constants/logs");
2832
const { addLog } = require("../services/logService");
2933

34+
/**
35+
* Archive users by setting the roles.archived field to true.
36+
* This function commits the write in batches to avoid reaching the maximum number of writes per batch.
37+
* @param {Array} usersData - An array of user objects with the following properties: id, first_name, last_name
38+
* @returns {Promise} - A promise that resolves with a summary object containing the number of users updated and failed, and an array of updated and failed user details.
39+
*/
40+
const archiveUsers = async (usersData) => {
41+
const batch = firestore.batch();
42+
const usersBatch = [];
43+
const summary = {
44+
totalUsersArchived: 0,
45+
totalOperationsFailed: 0,
46+
updatedUserDetails: [],
47+
failedUserDetails: [],
48+
};
49+
50+
usersData.forEach((user) => {
51+
const { id, first_name: firstName, last_name: lastName } = user;
52+
const updatedUserData = {
53+
...user,
54+
roles: {
55+
...user.roles,
56+
archived: true,
57+
},
58+
updated_at: Date.now(),
59+
};
60+
batch.update(userModel.doc(id), updatedUserData);
61+
usersBatch.push({ id, firstName, lastName });
62+
});
63+
64+
try {
65+
await batch.commit();
66+
summary.totalUsersArchived += usersData.length;
67+
summary.updatedUserDetails = [...usersBatch];
68+
return {
69+
message: USERS_PATCH_HANDLER_SUCCESS_MESSAGES.ARCHIVE_USERS.SUCCESSFULLY_COMPLETED_BATCH_UPDATES,
70+
...summary,
71+
};
72+
} catch (err) {
73+
logger.error("Firebase batch Operation Failed!");
74+
summary.totalOperationsFailed += usersData.length;
75+
summary.failedUserDetails = [...usersBatch];
76+
return { message: USERS_PATCH_HANDLER_ERROR_MESSAGES.ARCHIVE_USERS.BATCH_DATA_UPDATED_FAILED, ...summary };
77+
}
78+
};
79+
3080
/**
3181
* Adds or updates the user data
3282
*
@@ -182,11 +232,11 @@ const getSuggestedUsers = async (skill) => {
182232
*/
183233
const fetchPaginatedUsers = async (query) => {
184234
const isDevMode = query.dev === "true";
185-
186235
try {
187236
const size = parseInt(query.size) || 100;
188237
const doc = (query.next || query.prev) && (await userModel.doc(query.next || query.prev).get());
189238

239+
const isArchived = query.departed === "true";
190240
let dbQuery;
191241
/**
192242
* !!NOTE : At the time of writing we only support member in the role query
@@ -195,9 +245,9 @@ const fetchPaginatedUsers = async (query) => {
195245
* if you're making changes to this code remove the archived check in the role query, example: role=archived,member
196246
*/
197247
if (query.roles === "member") {
198-
dbQuery = userModel.where("roles.archived", "==", false).where("roles.member", "==", true);
248+
dbQuery = userModel.where("roles.archived", "==", isArchived).where("roles.member", "==", true);
199249
} else {
200-
dbQuery = userModel.where("roles.archived", "==", false).orderBy("username");
250+
dbQuery = userModel.where("roles.archived", "==", isArchived).orderBy("username");
201251
}
202252

203253
let compositeQuery = [dbQuery];
@@ -217,6 +267,10 @@ const fetchPaginatedUsers = async (query) => {
217267
}
218268

219269
if (Object.keys(query).length) {
270+
if (query.departed) {
271+
compositeQuery = compositeQuery.map((query) => query.where("roles.in_discord", "==", false));
272+
dbQuery = dbQuery.where("roles.in_discord", "==", false);
273+
}
220274
if (query.search) {
221275
const searchValue = query.search.toLowerCase().trim();
222276
dbQuery = dbQuery.startAt(searchValue).endAt(searchValue + "\uf8ff");
@@ -1031,6 +1085,7 @@ const updateUsersWithNewUsernames = async () => {
10311085
};
10321086

10331087
module.exports = {
1088+
archiveUsers,
10341089
addOrUpdate,
10351090
fetchPaginatedUsers,
10361091
fetchUser,

services/users.js

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,22 @@
1-
const { USERS_PATCH_HANDLER_SUCCESS_MESSAGES, USERS_PATCH_HANDLER_ERROR_MESSAGES } = require("../constants/users");
21
const firestore = require("../utils/firestore");
32
const { formatUsername } = require("../utils/username");
43
const userModel = firestore.collection("users");
5-
const archiveUsers = async (usersData) => {
6-
const batch = firestore.batch();
7-
const usersBatch = [];
8-
const summary = {
9-
totalUsersArchived: 0,
10-
totalOperationsFailed: 0,
11-
updatedUserDetails: [],
12-
failedUserDetails: [],
13-
};
14-
15-
usersData.forEach((user) => {
16-
const { id, first_name: firstName, last_name: lastName } = user;
17-
const updatedUserData = {
18-
...user,
19-
roles: {
20-
...user.roles,
21-
archived: true,
22-
},
23-
updated_at: Date.now(),
24-
};
25-
batch.update(userModel.doc(id), updatedUserData);
26-
usersBatch.push({ id, firstName, lastName });
27-
});
4+
const tasksQuery = require("../models/tasks");
285

6+
const getUsersWithIncompleteTasks = async (users) => {
7+
if (users.length === 0) return [];
298
try {
30-
await batch.commit();
31-
summary.totalUsersArchived += usersData.length;
32-
summary.updatedUserDetails = [...usersBatch];
33-
return {
34-
message: USERS_PATCH_HANDLER_SUCCESS_MESSAGES.ARCHIVE_USERS.SUCCESSFULLY_COMPLETED_BATCH_UPDATES,
35-
...summary,
36-
};
37-
} catch (err) {
38-
logger.error("Firebase batch Operation Failed!");
39-
summary.totalOperationsFailed += usersData.length;
40-
summary.failedUserDetails = [...usersBatch];
41-
return { message: USERS_PATCH_HANDLER_ERROR_MESSAGES.ARCHIVE_USERS.BATCH_DATA_UPDATED_FAILED, ...summary };
9+
const eligibleUsersWithTasks = [];
10+
for (const user of users) {
11+
const abandonedTasksQuerySnapshot = await tasksQuery.fetchIncompleteTaskForUser(user.id);
12+
if (!abandonedTasksQuerySnapshot.empty) {
13+
eligibleUsersWithTasks.push(user);
14+
}
15+
}
16+
return eligibleUsersWithTasks;
17+
} catch (error) {
18+
logger.error(`Error in getting users who abandoned tasks: ${error}`);
19+
throw error;
4220
}
4321
};
4422

@@ -63,6 +41,6 @@ const generateUniqueUsername = async (firstName, lastName) => {
6341
};
6442

6543
module.exports = {
66-
archiveUsers,
6744
generateUniqueUsername,
45+
getUsersWithIncompleteTasks,
6846
};

0 commit comments

Comments
 (0)