diff --git a/src/services/checkin/checkin-router.test.ts b/src/services/checkin/checkin-router.test.ts index 30777007..8dc8b230 100644 --- a/src/services/checkin/checkin-router.test.ts +++ b/src/services/checkin/checkin-router.test.ts @@ -57,6 +57,21 @@ const REGULAR_EVENT_FOR_CHECKIN = { attendanceCount: 0, }; +const SPECIAL_EVENT_FOR_CHECKIN = { + eventId: uuidv4(), + name: "Second Regular Event", + startTime: new Date((NOW_SECONDS - 600) * 1000).toISOString(), + endTime: new Date((NOW_SECONDS + ONE_HOUR_SECONDS) * 1000).toISOString(), + points: 50, + description: "A second regular event.", + isVirtual: false, + imageUrl: null, + location: "Siebel 2405", + eventType: EventType.enum.SPECIAL, + isVisible: true, + attendanceCount: 0, +}; + const MEALS_EVENT = { eventId: uuidv4(), name: "Lunch Time", @@ -338,19 +353,8 @@ describe("POST /checkin/scan/staff", () => { expect(attendeeError).toBeNull(); expect(updatedAttendee).toMatchObject({ points: TEST_ATTENDEE_1.points + REGULAR_EVENT_FOR_CHECKIN.points, - [`hasPriority${currentDay}`]: true, + [`hasPriority${currentDay}`]: false, // No priority on first check-in }); - - const { data: subscription } = await SupabaseDB.SUBSCRIPTIONS.select( - "*" - ) - .eq("userId", TEST_ATTENDEE_1.userId) - .eq("mailingList", payload.eventId) - .single() - .throwOnError(); - expect(subscription).not.toBeNull(); - expect(subscription!.userId).toBe(TEST_ATTENDEE_1.userId); - expect(subscription!.mailingList).toBe(payload.eventId); }, 100000); it("should successfully check-in user to a CHECKIN type event and update records", async () => { @@ -584,19 +588,8 @@ describe("POST /checkin/event", () => { .single(); expect(updatedAttendee).toMatchObject({ points: TEST_ATTENDEE_1.points + REGULAR_EVENT_FOR_CHECKIN.points, - [`hasPriority${currentDay}`]: true, + [`hasPriority${currentDay}`]: false, // No priority on first check-in }); - - const { data: subscription } = await SupabaseDB.SUBSCRIPTIONS.select( - "*" - ) - .eq("userId", TEST_ATTENDEE_1.userId) - .eq("mailingList", payload.eventId) - .single() - .throwOnError(); - expect(subscription).not.toBeNull(); - expect(subscription.userId!).toBe(TEST_ATTENDEE_1.userId); - expect(subscription.mailingList!).toBe(payload.eventId); }); it("should successfully check-in to a check in event and update records", async () => { @@ -766,6 +759,71 @@ describe("POST /checkin/event", () => { expect(attendeeAfter).toEqual(attendeeBefore); expect(attendanceCountAfter).toBe(attendanceCountBefore); }); + + it("should give priority after second check-in to regular event", async () => { + // First check-in - should not get priority + payload.eventId = REGULAR_EVENT_FOR_CHECKIN.eventId; + payload.userId = TEST_ATTENDEE_1.userId; + + await postAsAdmin("/checkin/event") + .send(payload) + .expect(StatusCodes.OK); + + // Verify no priority after first check-in + const { data: attendeeAfterFirst } = await SupabaseDB.ATTENDEES.select() + .eq("userId", payload.userId) + .single(); + expect(attendeeAfterFirst).toMatchObject({ + [`hasPriority${currentDay}`]: false, + }); + + // Verify first event is in attendance record + const { data: attendeeAttendanceAfterFirst } = + await SupabaseDB.ATTENDEE_ATTENDANCES.select("eventsAttended") + .eq("userId", payload.userId) + .single(); + expect(attendeeAttendanceAfterFirst?.eventsAttended).toContain( + REGULAR_EVENT_FOR_CHECKIN.eventId + ); + expect(attendeeAttendanceAfterFirst?.eventsAttended).toHaveLength(1); + + await SupabaseDB.EVENTS.insert([SPECIAL_EVENT_FOR_CHECKIN]); + + // Second check-in - should get priority + payload.eventId = SPECIAL_EVENT_FOR_CHECKIN.eventId; + + await postAsAdmin("/checkin/event") + .send(payload) + .expect(StatusCodes.OK); + + // Verify priority is given after second check-in + const { data: attendeeAfterSecond } = + await SupabaseDB.ATTENDEES.select() + .eq("userId", payload.userId) + .single(); + expect(attendeeAfterSecond).toMatchObject({ + [`hasPriority${currentDay}`]: true, + }); + + // Verify both events are in the eventsAttended array + const { data: attendeeAttendance } = + await SupabaseDB.ATTENDEE_ATTENDANCES.select("eventsAttended") + .eq("userId", payload.userId) + .single(); + expect(attendeeAttendance?.eventsAttended).toContain( + SPECIAL_EVENT_FOR_CHECKIN.eventId + ); + expect(attendeeAttendance?.eventsAttended).toContain( + REGULAR_EVENT_FOR_CHECKIN.eventId + ); + expect(attendeeAttendance?.eventsAttended).toHaveLength(2); + + // Clean up + await SupabaseDB.EVENTS.delete().eq( + "eventId", + SPECIAL_EVENT_FOR_CHECKIN.eventId + ); + }); }); describe("POST /checkin/scan/merch", () => { diff --git a/src/services/checkin/checkin-utils.ts b/src/services/checkin/checkin-utils.ts index 6ca8e898..cfd3f4b9 100644 --- a/src/services/checkin/checkin-utils.ts +++ b/src/services/checkin/checkin-utils.ts @@ -121,19 +121,55 @@ export async function checkInUserToEvent(eventId: string, userId: string) { .single() .throwOnError(); + // Update attendance records first + await updateAttendanceRecords(eventId, userId); + + // Check if user should get priority (only for non-meal/checkin events and if they have attended >1 event) if ( event.eventType !== EventType.Enum.MEALS && event.eventType !== EventType.Enum.CHECKIN ) { - await updateAttendeePriority(userId); + // Check how many events the user has attended (including the current one) + const { data: attendeeAttendance } = + await SupabaseDB.ATTENDEE_ATTENDANCES.select("eventsAttended") + .eq("userId", userId) + .maybeSingle() + .throwOnError(); + + const eventsAttended = attendeeAttendance?.eventsAttended || []; + + if (eventsAttended.length > 0) { + // Get details of all attended events to filter by type and day + const { data: attendedEvents } = await SupabaseDB.EVENTS.select( + "eventId, eventType, startTime" + ) + .in("eventId", eventsAttended) + .throwOnError(); + + const currentDay = getCurrentDay(); + + // Filter events: exclude MEALS and CHECKIN, and only count events from current day + const filteredEvents = + attendedEvents?.filter((eventData) => { + const eventDate = new Date(eventData.startTime); + const eventDay = new Intl.DateTimeFormat("en-US", { + timeZone: "America/Chicago", + weekday: "short", + }).format(eventDate) as DayKey; + + return ( + eventData.eventType !== EventType.Enum.MEALS && + eventData.eventType !== EventType.Enum.CHECKIN && + eventDay === currentDay + ); + }) || []; + + // Only give priority if they have attended 2 or more qualifying events today + if (filteredEvents.length >= 2) { + await updateAttendeePriority(userId); + } + } } - - await SupabaseDB.SUBSCRIPTIONS.insert({ - userId: userId, - mailingList: eventId, - }).throwOnError(); - - await updateAttendanceRecords(eventId, userId); await assignPixelsToUser(userId, event.points); }