diff --git a/services/hexathons/src/routes/interaction.ts b/services/hexathons/src/routes/interaction.ts index b91aab9..82c9218 100644 --- a/services/hexathons/src/routes/interaction.ts +++ b/services/hexathons/src/routes/interaction.ts @@ -1,4 +1,5 @@ -import { asyncHandler, BadRequestError, checkAbility, ConfigError } from "@api/common"; +import { asyncHandler, BadRequestError, checkAbility, ConfigError, apiCall } from "@api/common"; +import { Service } from "@api/config"; import express from "express"; import { FilterQuery } from "mongoose"; @@ -44,6 +45,10 @@ interactionRoutes.route("/").post( throw new BadRequestError("Only members can create event interactions"); } + if (!req.user?.roles.member && req.body.type === InteractionType.CHECK_IN) { + throw new BadRequestError("Only members can create check-in interactions"); + } + // For event or scavenger hunt interactions, the identifier is required and must be unique if ([InteractionType.EVENT, InteractionType.SCAVENGER_HUNT].includes(req.body.type)) { const existingInteraction = await InteractionModel.findOne({ @@ -105,6 +110,52 @@ interactionRoutes.route("/").post( }); } + // If this is a check-in interaction, update the application status to CHECKED_IN + if (req.body.type === InteractionType.CHECK_IN) { + try { + // Find the user's application for this hexathon + const applicationsResponse = await apiCall( + Service.REGISTRATION, + { + method: "GET", + url: "/applications", + params: { + hexathon: req.body.hexathon, + userId: req.body.userId, + }, + }, + req + ); + + if (applicationsResponse.applications && applicationsResponse.applications.length > 0) { + const application = applicationsResponse.applications[0]; + + // Update the application status to CHECKED_IN + await apiCall( + Service.REGISTRATION, + { + method: "POST", + url: `/applications/${application._id}/actions/update-status`, + data: { + status: "CHECKED_IN", + }, + }, + req + ); + } else { + console.warn( + `User ${req.body.userId} checked in but no application found for hexathon ${req.body.hexathon}` + ); + } + } catch (error) { + console.error( + "Failed to update application status for user %s check-in:", + req.body.userId, + error + ); + } + } + return res.send(interaction); }) ); diff --git a/services/registration/src/models/application.ts b/services/registration/src/models/application.ts index f41cd73..01c9bdb 100644 --- a/services/registration/src/models/application.ts +++ b/services/registration/src/models/application.ts @@ -13,6 +13,7 @@ export enum StatusType { CONFIRMED = "CONFIRMED", DENIED = "DENIED", NOT_ATTENDING = "NOT_ATTENDING", + CHECKED_IN = "CHECKED_IN", } export interface Essay extends Types.Subdocument { diff --git a/services/registration/src/routes/application.ts b/services/registration/src/routes/application.ts index eafe2d6..aeebb26 100644 --- a/services/registration/src/routes/application.ts +++ b/services/registration/src/routes/application.ts @@ -794,3 +794,20 @@ applicationRouter.route("/slack/confirmed-users").get( }); }) ); + +applicationRouter.route("/:id/check-in-status").get( + checkAbility("read", "Application"), + asyncHandler(async (req, res) => { + const application = await ApplicationModel.findById(req.params.id).accessibleBy(req.ability); + + if (!application) { + throw new BadRequestError("Application not found or you do not have permission to access."); + } + + const isCheckedInByStatus = application.status === StatusType.CHECKED_IN; + + return res.status(200).json({ + isCheckedIn: isCheckedInByStatus, + }); + }) +); diff --git a/services/registration/src/routes/statistics.ts b/services/registration/src/routes/statistics.ts index 045dc1b..148ca4d 100644 --- a/services/registration/src/routes/statistics.ts +++ b/services/registration/src/routes/statistics.ts @@ -281,11 +281,13 @@ statisticsRouter.route("/").get( acceptedUsers: (allUsersStatusCount.ACCEPTED || 0) + (allUsersStatusCount.CONFIRMED || 0) + + (allUsersStatusCount.CHECKED_IN || 0) + (allUsersStatusCount.NOT_ATTENDING || 0), confirmedUsers: allUsersStatusCount.CONFIRMED || 0, waitlistedUsers: allUsersStatusCount.WAITLISTED || 0, withdrawnUsers: allUsersStatusCount.NOT_ATTENDING || 0, - checkedinUsers: checkinInteractions, + checkedinUsers: checkinInteractions, // Physical check-in interactions + checkedInStatusUsers: allUsersStatusCount.CHECKED_IN || 0, // Application status count deniedUsers: allUsersStatusCount.DENIED || 0, };