Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/services/admission/admission-router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,14 @@ describe("PUT /admission/rsvp/accept/", () => {
});

it("lets applicant accept accepted decision", async () => {
await putAsApplicant("/admission/rsvp/accept/").expect(StatusCode.SuccessOK);
await putAsApplicant("/admission/rsvp/accept/")
.send({
avatarId: TESTER.avatarId,
displayName: TESTER.name,
discordTag: TESTER.discordTag,
dietaryRestrictions: [],
})
.expect(StatusCode.SuccessOK);
const stored = await Models.AdmissionDecision.findOne({ userId: TESTER.id });

expect(sendMail).toBeCalledWith({
Expand Down Expand Up @@ -248,6 +255,11 @@ describe("PUT /admission/rsvp/accept/", () => {
const response = await putAsApplicant("/admission/rsvp/accept/").expect(StatusCode.ClientErrorConflict);
expect(JSON.parse(response.text)).toHaveProperty("error", "AlreadyRSVPed");
});

it("does not let applicant accept without profile data", async () => {
const response = await putAsApplicant("/admission/rsvp/accept/").expect(StatusCode.ClientErrorBadRequest);
expect(JSON.parse(response.text)).toHaveProperty("error", "ProfileDataRequired");
});
});

describe("PUT /admission/rsvp/decline/", () => {
Expand Down
55 changes: 54 additions & 1 deletion src/services/admission/admission-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,25 @@ import {
DecisionNotFoundErrorSchema,
AdmissionDecisionSchema,
AdmissionDecisionUpdatesSchema,
ProfileDataRequiredError,
ProfileDataRequiredErrorSchema,
} from "./admission-schemas";
import Models from "../../common/models";
import { getAuthenticatedUser } from "../../common/auth";
import { StatusCode } from "status-code-enum";
import { MailInfo } from "../mail/mail-schemas";
import { Templates } from "../../common/config";
import Config, { Templates } from "../../common/config";
import { sendMail } from "../mail/mail-lib";
import specification, { Tag } from "../../middleware/specification";
import { z } from "zod";
import { SuccessResponseSchema, UserIdSchema } from "../../common/schemas";
import { RegistrationNotFoundError, RegistrationNotFoundErrorSchema } from "../registration/registration-schemas";
import {
AttendeeProfileCreateRequestSchema,
AttendeeProfileAlreadyExistsError,
AttendeeProfileAlreadyExistsErrorSchema,
} from "../profile/profile-schemas";
import { getAvatarUrlForId } from "../profile/profile-lib";

const admissionRouter = Router();

Expand Down Expand Up @@ -60,6 +68,7 @@ admissionRouter.put(
parameters: z.object({
decision: DecisionRequestSchema,
}),
body: z.unknown(),
responses: {
[StatusCode.SuccessOK]: {
description: "The updated decision",
Expand All @@ -81,6 +90,18 @@ admissionRouter.put(
description: "Not accepted so can't make a decision",
schema: DecisionNotAcceptedErrorSchema,
},
[StatusCode.ClientErrorBadRequest]: [
{
id: AttendeeProfileAlreadyExistsError.error,
description: "Profile already exists",
schema: AttendeeProfileAlreadyExistsErrorSchema,
},
{
id: ProfileDataRequiredError.error,
description: "Profile data required when accepting",
schema: ProfileDataRequiredErrorSchema,
},
],
[StatusCode.ClientErrorConflict]: {
description: "Already RSVPd",
schema: DecisionAlreadyRSVPdErrorSchema,
Expand Down Expand Up @@ -114,6 +135,38 @@ admissionRouter.put(

// They can make a decision! Handle what they chose:
const response = req.params.decision === "accept" ? DecisionResponse.ACCEPTED : DecisionResponse.DECLINED;

// If accepting, create profile if it doesn't exist
if (response === DecisionResponse.ACCEPTED) {
// Cast request body to the profile schema
const parsedBody = AttendeeProfileCreateRequestSchema.safeParse(req.body);

if (!parsedBody.success) {
return res.status(StatusCode.ClientErrorBadRequest).send(ProfileDataRequiredError);
}

const existingProfile = await Models.AttendeeProfile.findOne({ userId });
if (existingProfile) {
return res.status(StatusCode.ClientErrorBadRequest).send(AttendeeProfileAlreadyExistsError);
}

const profileData = parsedBody.data;
const { avatarId, discordTag, displayName, dietaryRestrictions } = profileData;

const profile = {
userId,
discordTag,
displayName,
avatarUrl: getAvatarUrlForId(avatarId),
points: Config.DEFAULT_POINT_VALUE,
pointsAccumulated: Config.DEFAULT_POINT_VALUE,
foodWave: dietaryRestrictions.filter((res) => res.toLowerCase() != "none").length > 0 ? 1 : 2,
dietaryRestrictions,
};

await Models.AttendeeProfile.create(profile);
}

const updatedDecision = await Models.AdmissionDecision.findOneAndUpdate({ userId }, { response }, { new: true });

if (!updatedDecision) {
Expand Down
5 changes: 5 additions & 0 deletions src/services/admission/admission-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,8 @@ export const [DecisionNotFoundError, DecisionNotFoundErrorSchema] = CreateErrorA
error: "DecisionNotFound",
message: "Couldn't find your decision!",
});

export const [ProfileDataRequiredError, ProfileDataRequiredErrorSchema] = CreateErrorAndSchema({
error: "ProfileDataRequired",
message: "Profile data is required when accepting admission",
});