diff --git a/services/registration/src/routes/application.ts b/services/registration/src/routes/application.ts index 5318b8f..e474bcb 100644 --- a/services/registration/src/routes/application.ts +++ b/services/registration/src/routes/application.ts @@ -825,3 +825,90 @@ applicationRouter.route("/:id/check-in-status").get( }); }) ); + +/** + * decide multiple applications, given a list of Ids. + * Returns how many applications were updated. + * + * This only allows updating to ACCEPTED, WAITLISTED, or DENIED. + */ +applicationRouter.route("/bulk/decide-applications").post( + checkAbility("manage", "Application"), + asyncHandler(async (req, res) => { + const { + ids, + newStatus, + confirmationBranchId, // optional, for acceptance only. ignored for not accepting. + } = req.body ?? {}; + + // VALIDATIONS ===== + if (!Array.isArray(ids) || typeof newStatus !== "string") { + throw new BadRequestError("ids: string[] and newStatus: StatusType (string) are required."); + } + if (ids.some(id => !isValidObjectId(id))) { + throw new BadRequestError("ids must contain only valid application IDs."); + } + // require and only allow confirmation branch if ACCEPTING + if (newStatus === StatusType.ACCEPTED && !confirmationBranchId) { + throw new BadRequestError( + "confirmationBranchId is required when updating status to ACCEPTED." + ); + } + // only allow this route to accept,wl,or deny + const ALLOWED_NEWSTATUSES = [StatusType.ACCEPTED, StatusType.WAITLISTED, StatusType.DENIED]; + if (!ALLOWED_NEWSTATUSES.includes(newStatus as StatusType)) { + throw new BadRequestError( + `This endpoint can only be used to set application statuses to ${ALLOWED_NEWSTATUSES.join(", ")}.` + ); + } + // ===== + + const filter = { + _id: { $in: ids.map(id => new Types.ObjectId(id)) }, + status: StatusType.APPLIED, + }; + const updater = { + $set: { + status: newStatus, + confirmationBranch: newStatus === StatusType.ACCEPTED ? confirmationBranchId : undefined, + }, + }; + const result = await ApplicationModel.accessibleBy(req.ability).updateMany(filter, updater, { + runValidators: true, + }); + + return res.status(200).json({ updatedCount: result.modifiedCount }); + }) +); + +/** + * bulk-fetch a list of application IDs given a list of emails. + * just ignores emails which have no matching application for + * the current hexathon and branch. + * + * currently used for the manual include/exclude list on the + * registration decisions page for applications (bulk status updates) + */ +applicationRouter.route("/bulk/emails-to-applications").post( + checkAbility("manage", "Application"), + asyncHandler(async (req, res) => { + const emails = req.body?.emails; + + if (!Array.isArray(emails)) { + throw new BadRequestError("List of emails required"); + } + + // require hexathon and branch for this + const hexathonId = req.body?.hexathonId; + const branchId = req.body?.branchId; + if (!hexathonId || !branchId) { + throw new BadRequestError("hexathonId and branchId are required."); + } + + const result = await ApplicationModel.accessibleBy(req.ability) + .find({ email: { $in: emails }, hexathon: hexathonId, applicationBranch: branchId }) + .select({ _id: 1, name: 1, finalScore: 1, applicationBranch: 1 }); + + return res.status(200).json(result); + }) +);