Skip to content

Commit 6dbda50

Browse files
Merge branch 'develop' into iss2223
2 parents d47204d + 10bc222 commit 6dbda50

File tree

15 files changed

+785
-76
lines changed

15 files changed

+785
-76
lines changed

controllers/tasks.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const { updateUserStatusOnTaskUpdate, updateStatusOnTaskCompletion } = require("
1313
const dataAccess = require("../services/dataAccessLayer");
1414
const { parseSearchQuery } = require("../utils/tasks");
1515
const { addTaskCreatedAtAndUpdatedAtFields } = require("../services/tasks");
16+
const tasksService = require("../services/tasks");
1617
const { RQLQueryParser } = require("../utils/RQLParser");
1718
const { getMissedProgressUpdatesUsers } = require("../models/discordactions");
1819
const { logType } = require("../constants/logs");
@@ -134,7 +135,19 @@ const fetchPaginatedTasks = async (query) => {
134135

135136
const fetchTasks = async (req, res) => {
136137
try {
137-
const { status, page, size, prev, next, q: queryString, assignee, title, userFeatureFlag } = req.query;
138+
const {
139+
status,
140+
page,
141+
size,
142+
prev,
143+
next,
144+
q: queryString,
145+
assignee,
146+
title,
147+
userFeatureFlag,
148+
orphaned,
149+
dev,
150+
} = req.query;
138151
const transformedQuery = transformQuery(status, size, page, assignee, title);
139152

140153
if (queryString !== undefined) {
@@ -159,6 +172,28 @@ const fetchTasks = async (req, res) => {
159172
});
160173
}
161174

175+
const isOrphaned = orphaned === "true";
176+
const isDev = dev === "true";
177+
if (isOrphaned) {
178+
if (!isDev) {
179+
return res.boom.notFound("Route not found");
180+
}
181+
try {
182+
const orphanedTasks = await tasksService.fetchOrphanedTasks();
183+
if (!orphanedTasks || orphanedTasks.length === 0) {
184+
return res.sendStatus(204);
185+
}
186+
const tasksWithRdsAssigneeInfo = await fetchTasksWithRdsAssigneeInfo(orphanedTasks);
187+
return res.status(200).json({
188+
message: "Orphan tasks fetched successfully",
189+
data: tasksWithRdsAssigneeInfo,
190+
});
191+
} catch (error) {
192+
logger.error("Error in getting tasks which were orphaned", error);
193+
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
194+
}
195+
}
196+
162197
const paginatedTasks = await fetchPaginatedTasks({ ...transformedQuery, prev, next, userFeatureFlag });
163198
return res.json({
164199
message: "Tasks returned successfully!",

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/tasks.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ const getTasksValidator = async (req, res, next) => {
193193
return value;
194194
}, "Invalid query format"),
195195
userFeatureFlag: joi.string().optional(),
196+
orphaned: joi.boolean().optional(),
196197
});
197198

198199
try {

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: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ const userModel = firestore.collection("users");
44
const ItemModel = firestore.collection("itemTags");
55
const dependencyModel = firestore.collection("taskDependencies");
66
const userUtils = require("../utils/users");
7-
const { updateTaskStatusToDone } = require("../services/tasks");
87
const { chunks } = require("../utils/array");
98
const { DOCUMENT_WRITE_SIZE } = require("../constants/constants");
109
const { fromFirestoreData, toFirestoreData, buildTasks } = require("../utils/tasks");
@@ -24,6 +23,42 @@ const {
2423
const { OLD_ACTIVE, OLD_BLOCKED, OLD_PENDING, OLD_COMPLETED } = TASK_STATUS_OLD;
2524
const { INTERNAL_SERVER_ERROR } = require("../constants/errorMessages");
2625

26+
/**
27+
* Update multiple tasks' status to DONE in one batch operation.
28+
* @param {Object[]} tasksData - Tasks data to update, must contain 'id' and 'status' fields.
29+
* @returns {Object} - Summary of the batch operation.
30+
* @property {number} totalUpdatedStatus - Number of tasks that has their status updated to DONE.
31+
* @property {number} totalOperationsFailed - Number of tasks that failed to update.
32+
* @property {string[]} updatedTaskDetails - IDs of tasks that has their status updated to DONE.
33+
* @property {string[]} failedTaskDetails - IDs of tasks that failed to update.
34+
*/
35+
const updateTaskStatusToDone = async (tasksData) => {
36+
const batch = firestore.batch();
37+
const tasksBatch = [];
38+
const summary = {
39+
totalUpdatedStatus: 0,
40+
totalOperationsFailed: 0,
41+
updatedTaskDetails: [],
42+
failedTaskDetails: [],
43+
};
44+
tasksData.forEach((task) => {
45+
const updateTaskData = { ...task, status: "DONE" };
46+
batch.update(tasksModel.doc(task.id), updateTaskData);
47+
tasksBatch.push(task.id);
48+
});
49+
try {
50+
await batch.commit();
51+
summary.totalUpdatedStatus += tasksData.length;
52+
summary.updatedTaskDetails = [...tasksBatch];
53+
return { ...summary };
54+
} catch (err) {
55+
logger.error("Firebase batch Operation Failed!");
56+
summary.totalOperationsFailed += tasksData.length;
57+
summary.failedTaskDetails = [...tasksBatch];
58+
return { ...summary };
59+
}
60+
};
61+
2762
/**
2863
* Adds and Updates tasks
2964
*
@@ -701,6 +736,33 @@ const markUnDoneTasksOfArchivedUsersBacklog = async (users) => {
701736
}
702737
};
703738

739+
/**
740+
* Fetches all incomplete tasks for given user IDs.
741+
*
742+
* @param {string[]} userIds - The IDs of the users to fetch incomplete tasks for.
743+
* @returns {Promise<firebase.firestore.QuerySnapshot>} - The query snapshot object.
744+
* @throws {Error} - Throws an error if the database query fails.
745+
*/
746+
const fetchIncompleteTasksByUserIds = async (userIds) => {
747+
const COMPLETED_STATUSES = [DONE, COMPLETED];
748+
749+
if (!userIds || userIds.length === 0) {
750+
return [];
751+
}
752+
try {
753+
const incompleteTasksQuery = await tasksModel.where("assigneeId", "in", userIds).get();
754+
755+
const incompleteTaskForUsers = incompleteTasksQuery.docs.filter(
756+
(task) => !COMPLETED_STATUSES.includes(task.data().status)
757+
);
758+
759+
return incompleteTaskForUsers;
760+
} catch (error) {
761+
logger.error("Error when fetching incomplete tasks for users:", error);
762+
throw error;
763+
}
764+
};
765+
704766
module.exports = {
705767
updateTask,
706768
fetchTasks,
@@ -720,4 +782,6 @@ module.exports = {
720782
updateTaskStatus,
721783
updateOrphanTasksStatus,
722784
markUnDoneTasksOfArchivedUsersBacklog,
785+
updateTaskStatusToDone,
786+
fetchIncompleteTasksByUserIds,
723787
};

models/users.js

Lines changed: 79 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
*
@@ -186,11 +236,11 @@ const getSuggestedUsers = async (skill) => {
186236
*/
187237
const fetchPaginatedUsers = async (query) => {
188238
const isDevMode = query.dev === "true";
189-
190239
try {
191240
const size = parseInt(query.size) || 100;
192241
const doc = (query.next || query.prev) && (await userModel.doc(query.next || query.prev).get());
193242

243+
const isArchived = query.departed === "true";
194244
let dbQuery;
195245
/**
196246
* !!NOTE : At the time of writing we only support member in the role query
@@ -199,9 +249,9 @@ const fetchPaginatedUsers = async (query) => {
199249
* if you're making changes to this code remove the archived check in the role query, example: role=archived,member
200250
*/
201251
if (query.roles === "member") {
202-
dbQuery = userModel.where("roles.archived", "==", false).where("roles.member", "==", true);
252+
dbQuery = userModel.where("roles.archived", "==", isArchived).where("roles.member", "==", true);
203253
} else {
204-
dbQuery = userModel.where("roles.archived", "==", false).orderBy("username");
254+
dbQuery = userModel.where("roles.archived", "==", isArchived).orderBy("username");
205255
}
206256

207257
let compositeQuery = [dbQuery];
@@ -221,6 +271,10 @@ const fetchPaginatedUsers = async (query) => {
221271
}
222272

223273
if (Object.keys(query).length) {
274+
if (query.departed) {
275+
compositeQuery = compositeQuery.map((query) => query.where("roles.in_discord", "==", false));
276+
dbQuery = dbQuery.where("roles.in_discord", "==", false);
277+
}
224278
if (query.search) {
225279
const searchValue = query.search.toLowerCase().trim();
226280
dbQuery = dbQuery.startAt(searchValue).endAt(searchValue + "\uf8ff");
@@ -1034,7 +1088,26 @@ const updateUsersWithNewUsernames = async () => {
10341088
}
10351089
};
10361090

1091+
/**
1092+
* Fetches users who are not in the Discord server.
1093+
* @returns {Promise<FirebaseFirestore.QuerySnapshot>} - A promise that resolves to a Firestore QuerySnapshot containing the users matching the criteria.
1094+
* @throws {Error} - Throws an error if the database query fails.
1095+
*/
1096+
const fetchUsersNotInDiscordServer = async () => {
1097+
try {
1098+
const usersNotInDiscordServer = await userModel
1099+
.where("roles.archived", "==", true)
1100+
.where("roles.in_discord", "==", false)
1101+
.get();
1102+
return usersNotInDiscordServer;
1103+
} catch (error) {
1104+
logger.error(`Error in getting users who are not in discord server: ${error}`);
1105+
throw error;
1106+
}
1107+
};
1108+
10371109
module.exports = {
1110+
archiveUsers,
10381111
addOrUpdate,
10391112
fetchPaginatedUsers,
10401113
fetchUser,
@@ -1063,4 +1136,5 @@ module.exports = {
10631136
fetchUserForKeyValue,
10641137
getNonNickNameSyncedUsers,
10651138
updateUsersWithNewUsernames,
1139+
fetchUsersNotInDiscordServer,
10661140
};

0 commit comments

Comments
 (0)