Skip to content
53 changes: 52 additions & 1 deletion services/hexathons/src/routes/interaction.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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);
})
);
Expand Down
1 change: 1 addition & 0 deletions services/registration/src/models/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
17 changes: 17 additions & 0 deletions services/registration/src/routes/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
})
);
4 changes: 3 additions & 1 deletion services/registration/src/routes/statistics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down