From 40d86e28dbc10d3d48bba1efca758fbd432735e9 Mon Sep 17 00:00:00 2001
From: Hugo Liu <98724522+hugoliu-code@users.noreply.github.com>
Date: Mon, 12 Jan 2026 17:11:24 -0500
Subject: [PATCH 1/5] Add Withdraw Attendance + Update Withdraw Acceptance +
Fix some documentation
---
apps/api/internal/api/api.go | 3 +
apps/api/internal/api/handlers/application.go | 58 ++++++++++++++-----
apps/api/internal/services/application.go | 29 +++++++++-
3 files changed, 74 insertions(+), 16 deletions(-)
diff --git a/apps/api/internal/api/api.go b/apps/api/internal/api/api.go
index 9001c779..3fa89676 100644
--- a/apps/api/internal/api/api.go
+++ b/apps/api/internal/api/api.go
@@ -192,6 +192,9 @@ func (api *API) setupRoutes(mw *mw.Middleware) {
//Accept acceptance
r.Patch("/accept-acceptance", api.Handlers.Application.AcceptApplicationAcceptance)
+ //Withdraw attendance
+ r.Patch("/withdraw-attendance", api.Handlers.Application.WithdrawAttendance)
+
//Waitlist application
r.Patch("/join-waitlist", api.Handlers.Application.JoinWaitlist)
})
diff --git a/apps/api/internal/api/handlers/application.go b/apps/api/internal/api/handlers/application.go
index 7fcfc706..c7175874 100644
--- a/apps/api/internal/api/handlers/application.go
+++ b/apps/api/internal/api/handlers/application.go
@@ -375,14 +375,14 @@ func (h *ApplicationHandler) GetApplicationStatistics(w http.ResponseWriter, r *
func (h *ApplicationHandler) GetApplication(w http.ResponseWriter, r *http.Request) {
eventId, err := web.PathParamToUUID(r, "eventId")
if err != nil {
- res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not a valid."))
+ res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not valid."))
return
}
// So funny story, there is no ID in the application table, this is just an abstracted user_id.
applicationId, err := web.PathParamToUUID(r, "applicationId")
if err != nil {
- res.SendError(w, http.StatusBadRequest, res.NewError("invalid_application_id", "The application ID is not a valid."))
+ res.SendError(w, http.StatusBadRequest, res.NewError("invalid_application_id", "The application ID is not valid."))
return
}
@@ -417,13 +417,13 @@ type ReviewRatings struct {
func (h *ApplicationHandler) SubmitApplicationReview(w http.ResponseWriter, r *http.Request) {
eventId, err := web.PathParamToUUID(r, "eventId")
if err != nil {
- res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not a valid."))
+ res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not valid."))
return
}
applicationId, err := web.PathParamToUUID(r, "applicationId")
if err != nil {
- res.SendError(w, http.StatusBadRequest, res.NewError("invalid_application_id", "The application ID is not a valid."))
+ res.SendError(w, http.StatusBadRequest, res.NewError("invalid_application_id", "The application ID is not valid."))
return
}
@@ -468,7 +468,7 @@ func (h *ApplicationHandler) SubmitApplicationReview(w http.ResponseWriter, r *h
func (h *ApplicationHandler) GetAssignedApplications(w http.ResponseWriter, r *http.Request) {
eventId, err := web.PathParamToUUID(r, "eventId")
if err != nil {
- res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not a valid."))
+ res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not valid."))
return
}
@@ -502,7 +502,7 @@ func (h *ApplicationHandler) GetAssignedApplications(w http.ResponseWriter, r *h
func (h *ApplicationHandler) AssignApplicationReviewers(w http.ResponseWriter, r *http.Request) {
eventId, err := web.PathParamToUUID(r, "eventId")
if err != nil {
- res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not a valid."))
+ res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not valid."))
return
}
@@ -536,7 +536,7 @@ func (h *ApplicationHandler) AssignApplicationReviewers(w http.ResponseWriter, r
func (h *ApplicationHandler) ResetApplicationReviews(w http.ResponseWriter, r *http.Request) {
eventId, err := web.PathParamToUUID(r, "eventId")
if err != nil {
- res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not a valid."))
+ res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not valid."))
return
}
@@ -603,7 +603,7 @@ func (h *ApplicationHandler) GetResumePresignedUrl(w http.ResponseWriter, r *htt
func (h *ApplicationHandler) JoinWaitlist(w http.ResponseWriter, r *http.Request) {
eventId, err := web.PathParamToUUID(r, "eventId")
if err != nil {
- res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not a valid."))
+ res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not valid."))
return
}
userId := ctxutils.GetUserIdFromCtx(r.Context())
@@ -623,15 +623,15 @@ func (h *ApplicationHandler) JoinWaitlist(w http.ResponseWriter, r *http.Request
// @Description Sets application status from accepted to rejected
// @Tags Application
//
-// @Param eventId path string true "ID of the event to join the waitlist for"
-// @Success 200 "Acceptance withdrawn joined successfully"
+// @Param eventId path string true "ID of the event to withdraw acceptance from"
+// @Success 200 "Acceptance withdrawn successfully"
// @Failure 400 {object} res.ErrorResponse "Bad request: invalid event ID"
-// @Failure 500 {object} res.ErrorResponse "Server error: failed to withdraw"
+// @Failure 500 {object} res.ErrorResponse "Server error: failed to withdraw acceptance"
// @Router /events/{eventId}/application/withdraw-acceptance [patch]
func (h *ApplicationHandler) WithdrawAcceptance(w http.ResponseWriter, r *http.Request) {
eventId, err := web.PathParamToUUID(r, "eventId")
if err != nil {
- res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not a valid."))
+ res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not valid."))
return
}
userId := ctxutils.GetUserIdFromCtx(r.Context())
@@ -645,10 +645,38 @@ func (h *ApplicationHandler) WithdrawAcceptance(w http.ResponseWriter, r *http.R
w.WriteHeader(http.StatusOK)
}
+// Withdraw attendance to an event
+//
+// @Summary Withdraw attendance after accepting to go to an event.
+// @Description Sets application status from accepted to withdrawn. Sets event role from attendee, back to applicant.
+// @Tags Application
+//
+// @Param eventId path string true "ID of the event to withdraw attendance from"
+// @Success 200 "Attendance withdrawn successfully"
+// @Failure 400 {object} res.ErrorResponse "Bad request: invalid event ID"
+// @Failure 500 {object} res.ErrorResponse "Server error: failed to withdraw attendance"
+// @Router /events/{eventId}/application/withdraw-attendance [patch]
+func (h *ApplicationHandler) WithdrawAttendance(w http.ResponseWriter, r *http.Request) {
+ eventId, err := web.PathParamToUUID(r, "eventId")
+ if err != nil {
+ res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not valid."))
+ return
+ }
+ userId := ctxutils.GetUserIdFromCtx(r.Context())
+
+ err = h.appService.WithdrawAttendance(r.Context(), *userId, eventId)
+ if err != nil {
+ res.SendError(w, http.StatusInternalServerError, res.NewError("withdraw_attendance_error", "Something went wrong while withdrawing attendance"))
+ return
+ }
+ w.WriteHeader(http.StatusOK)
+
+}
+
// Accept an Acceptance for an Event/Application
//
// @Summary Accept an acceptance after being accepted to an event.
-// @Description Sets application status from accepted to rejected
+// @Description Sets event role to attendee, from applicant
// @Tags Application Event
//
// @Param eventId path string true "ID of the event to join the waitlist for"
@@ -659,7 +687,7 @@ func (h *ApplicationHandler) WithdrawAcceptance(w http.ResponseWriter, r *http.R
func (h *ApplicationHandler) AcceptApplicationAcceptance(w http.ResponseWriter, r *http.Request) {
eventId, err := web.PathParamToUUID(r, "eventId")
if err != nil {
- res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not a valid."))
+ res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not valid."))
return
}
userId := ctxutils.GetUserIdFromCtx(r.Context())
@@ -687,7 +715,7 @@ func (h *ApplicationHandler) AcceptApplicationAcceptance(w http.ResponseWriter,
func (h *ApplicationHandler) TransitionWaitlistedApplications(w http.ResponseWriter, r *http.Request) {
eventId, err := web.PathParamToUUID(r, "eventId")
if err != nil {
- res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not a valid."))
+ res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not valid."))
return
}
diff --git a/apps/api/internal/services/application.go b/apps/api/internal/services/application.go
index 6ceba220..496f8f58 100644
--- a/apps/api/internal/services/application.go
+++ b/apps/api/internal/services/application.go
@@ -542,7 +542,34 @@ func (s *ApplicationService) WithdrawAcceptance(ctx context.Context, userId uuid
EventID: eventId,
//TODO: Make it so I don't have to set this!
StatusDoUpdate: true,
- Status: sqlc.ApplicationStatusRejected,
+ Status: sqlc.ApplicationStatusWithdrawn,
+ })
+ if err != nil {
+ s.logger.Err(err).Msg(err.Error())
+ return err
+ }
+ return nil
+}
+
+func (s *ApplicationService) WithdrawAttendance(ctx context.Context, userId uuid.UUID, eventId uuid.UUID) error {
+ // Make atomic
+ err := s.txm.WithTx(ctx, func(tx pgx.Tx) error {
+ txAppRepo := s.appRepo.NewTx(tx)
+ txEventRepo := s.eventsService.eventRepo.NewTx(tx)
+ if err := txAppRepo.UpdateApplication(ctx, sqlc.UpdateApplicationParams{
+ UserID: userId,
+ EventID: eventId,
+ StatusDoUpdate: true,
+ Status: sqlc.ApplicationStatusWithdrawn,
+ }); err != nil {
+ return err
+ }
+
+ return txEventRepo.UpdateRole(ctx,
+ userId,
+ eventId,
+ sqlc.EventRoleTypeApplicant,
+ )
})
if err != nil {
s.logger.Err(err).Msg(err.Error())
From b5edbc1e06da602587b9d699a205e2a8aa0c0887 Mon Sep 17 00:00:00 2001
From: Hugo Liu <98724522+hugoliu-code@users.noreply.github.com>
Date: Mon, 12 Jan 2026 22:26:12 -0500
Subject: [PATCH 2/5] Frontend
---
.../src/features/Event/applicationStatus.ts | 12 ++-
...tsx => EventAcceptanceWithdrawalModal.tsx} | 14 +--
.../EventAttendanceWithdrawalModal.tsx | 86 +++++++++++++++++++
.../features/Event/components/EventButton.tsx | 12 ++-
apps/web/src/features/Event/utils/mapper.ts | 2 +-
.../components/AttendeeOverview.tsx | 27 +++++-
.../events/$eventId/dashboard/index.tsx | 2 +-
7 files changed, 141 insertions(+), 14 deletions(-)
rename apps/web/src/features/Event/components/{EventWithdrawalModal.tsx => EventAcceptanceWithdrawalModal.tsx} (85%)
create mode 100644 apps/web/src/features/Event/components/EventAttendanceWithdrawalModal.tsx
diff --git a/apps/web/src/features/Event/applicationStatus.ts b/apps/web/src/features/Event/applicationStatus.ts
index 5d44fc60..d6d875b3 100644
--- a/apps/web/src/features/Event/applicationStatus.ts
+++ b/apps/web/src/features/Event/applicationStatus.ts
@@ -114,7 +114,7 @@ const applicationStatus = defineStatus({
button: {
className:
"bg-event-button-bg-not-going text-event-button-text-not-going hover:bg-event-button-bg-not-going-hover",
- text: "Help Us Improve",
+ text: "Withdraw",
},
},
completed: {
@@ -137,6 +137,16 @@ const applicationStatus = defineStatus({
text: "Coming Soon",
},
},
+ withdrawn: {
+ className: "bg-badge-bg-not-applied text-badge-text-withdrawn",
+ text: "Withdrawn",
+ icon: TablerBan,
+ button: {
+ className:
+ "bg-event-button-bg-completed text-event-button-text-completed hover:bg-event-button-bg-completed-hover",
+ text: "Help Us Improve",
+ },
+ },
});
export default applicationStatus;
diff --git a/apps/web/src/features/Event/components/EventWithdrawalModal.tsx b/apps/web/src/features/Event/components/EventAcceptanceWithdrawalModal.tsx
similarity index 85%
rename from apps/web/src/features/Event/components/EventWithdrawalModal.tsx
rename to apps/web/src/features/Event/components/EventAcceptanceWithdrawalModal.tsx
index 228745cd..6b8b540e 100644
--- a/apps/web/src/features/Event/components/EventWithdrawalModal.tsx
+++ b/apps/web/src/features/Event/components/EventAcceptanceWithdrawalModal.tsx
@@ -7,11 +7,13 @@ import { useQueryClient } from "@tanstack/react-query";
import { eventsQueryKey } from "../hooks/useEventsWithUserInfo";
import { myApplicationBaseKey } from "@/features/Application/hooks/useMyApplication";
-interface EventWithdrawalModalProps {
+interface EventAcceptanceWithdrawalModalProps {
eventId: string;
}
-function EventWithdrawalModal({ eventId }: EventWithdrawalModalProps) {
+function EventAcceptanceWithdrawalModal({
+ eventId,
+}: EventAcceptanceWithdrawalModalProps) {
const queryClient = useQueryClient();
const { data: userData } = auth.useUser();
@@ -38,7 +40,7 @@ function EventWithdrawalModal({ eventId }: EventWithdrawalModalProps) {
queryKey: [...myApplicationBaseKey, eventId],
});
} catch (error) {
- console.error("Failed to join waitlist", error);
+ console.error("Failed to withdraw application", error);
showToast({
title: "Failed to Withdraw",
message: "Failed to Withdraw Acceptance. Please try again.",
@@ -59,9 +61,9 @@ function EventWithdrawalModal({ eventId }: EventWithdrawalModalProps) {
Are you sure? This action cannot be undone.
-
+ {/*
You can still join the waitlist after withdrawing.
-
+ */}
;
+ }
+
+ const { user } = userData;
+
+ const handleWithdrawAttendance = async (userId: string, eventId: string) => {
+ try {
+ await api.patch(
+ `events/${eventId}/application/withdraw-attendance?userId=${userId}`,
+ );
+ showToast({
+ title: "Attendance Withdrawn",
+ message: "Successfully Withdrawn Attendance.",
+ type: "success",
+ });
+ await queryClient.invalidateQueries({
+ queryKey: eventsQueryKey,
+ });
+ await queryClient.invalidateQueries({
+ queryKey: [...myApplicationBaseKey, eventId],
+ });
+ navigate({
+ to: "/portal",
+ });
+ } catch (error) {
+ console.error("Failed to withdraw attendance", error);
+ showToast({
+ title: "Failed to Withdraw",
+ message: "Failed to Withdraw Attendance. Please try again.",
+ type: "error",
+ });
+ }
+ };
+
+ return (
+
+
+
+ Withdraw Attendance?
+
+
+ Are you sure? This action cannot be undone.
+
+ {/*
+ You can still join the waitlist after withdrawing.
+
*/}
+
+
+
+
+
+ );
+}
+
+export { EventAttendanceWithdrawalModal as EventAttendanceWithdrawalModal };
diff --git a/apps/web/src/features/Event/components/EventButton.tsx b/apps/web/src/features/Event/components/EventButton.tsx
index 659c3264..151a886e 100644
--- a/apps/web/src/features/Event/components/EventButton.tsx
+++ b/apps/web/src/features/Event/components/EventButton.tsx
@@ -6,7 +6,7 @@ import { cn } from "@/utils/cn";
import { useRouter } from "@tanstack/react-router";
import { toast } from "react-toastify";
import { DialogTrigger } from "react-aria-components";
-import { EventWithdrawalModal } from "./EventWithdrawalModal";
+import { EventAcceptanceWithdrawalModal } from "./EventAcceptanceWithdrawalModal";
import { EventWaitlistModal } from "./EventWaitlistModal";
import { api } from "@/lib/ky";
import { auth } from "@/lib/authClient";
@@ -76,7 +76,7 @@ const EventButton = ({
to: `/events/${eventId}/dashboard`,
});
} catch (error) {
- console.error("Failed to join waitlist", error);
+ console.error("Failed to Accept", error);
showToast({
title: "Acceptance Failed",
message: "Failed to Accept. Please try again.",
@@ -127,6 +127,10 @@ const EventButton = ({
position: "bottom-right",
});
break;
+ case "withdrawn":
+ window.location.href =
+ "https://swamphack.notion.site/2e73b41de22f806da958fa548a176725?pvs=105";
+ break;
}
};
@@ -139,7 +143,9 @@ const EventButton = ({
>
{text || applicationStatus[statusProp].button.text}
-
+
);
} else if (statusProp === "rejected") {
diff --git a/apps/web/src/features/Event/utils/mapper.ts b/apps/web/src/features/Event/utils/mapper.ts
index 71b15f94..6b6ca8c1 100644
--- a/apps/web/src/features/Event/utils/mapper.ts
+++ b/apps/web/src/features/Event/utils/mapper.ts
@@ -26,7 +26,7 @@ const statusMap: Record = {
submitted: "underReview",
under_review: "underReview",
started: "notApplied",
- withdrawn: "notGoing",
+ withdrawn: "withdrawn",
staff: "staff",
admin: "admin",
attendee: "attending",
diff --git a/apps/web/src/features/EventOverview/components/AttendeeOverview.tsx b/apps/web/src/features/EventOverview/components/AttendeeOverview.tsx
index c18f584b..c7092b50 100644
--- a/apps/web/src/features/EventOverview/components/AttendeeOverview.tsx
+++ b/apps/web/src/features/EventOverview/components/AttendeeOverview.tsx
@@ -1,3 +1,26 @@
-export default function AttendeeOverview() {
- return More here coming soon!
;
+import { DialogTrigger } from "react-aria-components";
+import { EventAttendanceWithdrawalModal } from "@/features/Event/components/EventAttendanceWithdrawalModal";
+import { Button } from "@/components/ui/Button";
+
+interface ApplicationOverviewProps {
+ eventId: string;
+}
+
+export default function AttendeeOverview({
+ eventId,
+}: ApplicationOverviewProps) {
+ return (
+
+
More here coming soon!
+
+
Can't make it to the event?
+
+
+
+
+
+
+ );
}
diff --git a/apps/web/src/routes/_protected/events/$eventId/dashboard/index.tsx b/apps/web/src/routes/_protected/events/$eventId/dashboard/index.tsx
index 15602176..88fec509 100644
--- a/apps/web/src/routes/_protected/events/$eventId/dashboard/index.tsx
+++ b/apps/web/src/routes/_protected/events/$eventId/dashboard/index.tsx
@@ -28,7 +28,7 @@ function RouteComponent() {
}
if (eventRole === "attendee") {
- return ;
+ return ;
}
// Should never reach here due to the redirect in beforeLoad
From de4c1ffd9d620cbd159e385f76d844414a8841a7 Mon Sep 17 00:00:00 2001
From: h1divp <71522316+h1divp@users.noreply.github.com>
Date: Tue, 13 Jan 2026 18:45:06 -0500
Subject: [PATCH 3/5] feat:add guard clause for when the event has begun
---
apps/api/internal/services/application.go | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/apps/api/internal/services/application.go b/apps/api/internal/services/application.go
index 496f8f58..af078725 100644
--- a/apps/api/internal/services/application.go
+++ b/apps/api/internal/services/application.go
@@ -3,6 +3,7 @@ package services
import (
"context"
"errors"
+ "time"
"github.com/google/uuid"
"github.com/hibiken/asynq"
@@ -21,6 +22,7 @@ var (
ErrGetApplicationStatistics = errors.New("failed to aggregate application stats")
ErrMismatchedReviewerCounts = errors.New("the total number of applications does not match the total number of assigned reviews")
ErrWrongReviewerAssignment = errors.New("an application has been assigned to a reviewer who is not authorized to review it")
+ ErrEventAlreadyStarted = errors.New("the event has already started")
)
// TODO: figure out a way to create the submission fields dynamically using the json form files with proper validation.
@@ -94,7 +96,7 @@ func NewApplicationService(appRepo *repository.ApplicationRepository, userRepo *
buckets: buckets,
txm: txm,
scheduler: scheduler,
- logger: logger,
+ logger: logger.With().Str("component", "applicationService").Logger(),
}
}
@@ -598,7 +600,19 @@ func (s *ApplicationService) TransitionWaitlistedApplications(ctx context.Contex
err := s.txm.WithTx(ctx, func(tx pgx.Tx) error {
txAppRepo := s.appRepo.NewTx(tx)
- err := txAppRepo.TransitionAcceptedApplicationsToWaitlistByEventID(ctx, eventId)
+ // Get event start date, error if past the start of the event.
+ event, err := s.eventsService.GetEventByID(ctx, eventId)
+ if err != nil {
+ s.logger.Err(err).Msg(err.Error())
+ return err
+ }
+ currentTime := time.Now()
+ if event.StartTime.After(currentTime) {
+ s.logger.Err(ErrEventAlreadyStarted).Msg("Could not transition waitlisted applications: the event has already started.")
+ return ErrEventAlreadyStarted
+ }
+
+ err = txAppRepo.TransitionAcceptedApplicationsToWaitlistByEventID(ctx, eventId)
if err != nil {
s.logger.Err(err).Msg(err.Error())
return err
From 5a45f7e1145c41b32be37c20bdc2faf2aea6b21d Mon Sep 17 00:00:00 2001
From: h1divp <71522316+h1divp@users.noreply.github.com>
Date: Thu, 15 Jan 2026 11:55:10 -0500
Subject: [PATCH 4/5] fix: close() taskQueue client, move guard clause to
HandleScheduleTransitionWaitlistTask
---
apps/api/cmd/BAT_worker/main.go | 10 ++--------
apps/api/internal/services/application.go | 16 +---------------
apps/api/internal/workers/bat.go | 22 +++++++++++++++++++++-
3 files changed, 24 insertions(+), 24 deletions(-)
diff --git a/apps/api/cmd/BAT_worker/main.go b/apps/api/cmd/BAT_worker/main.go
index 79cff8a0..3ebc57ef 100644
--- a/apps/api/cmd/BAT_worker/main.go
+++ b/apps/api/cmd/BAT_worker/main.go
@@ -1,7 +1,6 @@
package main
import (
- "fmt"
"time"
"github.com/hibiken/asynq"
@@ -57,9 +56,6 @@ func main() {
},
)
- logger.Info().Msg("Debug test")
- fmt.Print("Debug test")
-
schedulerLocation, err := time.LoadLocation("America/New_York")
if err != nil {
panic(err)
@@ -71,10 +67,8 @@ func main() {
},
)
- logger.Info().Msg("Debug test")
- fmt.Print("Debug test")
-
taskQueueClient := asynq.NewClient(redisOpt)
+ defer taskQueueClient.Close()
database := db.NewDB(cfg.DatabaseURL)
defer database.Close()
@@ -92,7 +86,7 @@ func main() {
batService := services.NewBatService(applicationRepo, eventRepo, userRepo, batRunsRepo, emailService, txm, nil, scheduler, logger)
applicationService := services.NewApplicationService(applicationRepo, userRepo, eventService, emailService, txm, nil, nil, scheduler, logger)
- BATWorker := workers.NewBATWorker(batService, applicationService, scheduler, taskQueueClient, logger)
+ BATWorker := workers.NewBATWorker(batService, applicationService, eventService, scheduler, taskQueueClient, logger)
mux := asynq.NewServeMux()
mux.HandleFunc(tasks.TypeCalculateAdmissions, BATWorker.HandleCalculateAdmissionsTask)
diff --git a/apps/api/internal/services/application.go b/apps/api/internal/services/application.go
index af078725..08e29b3c 100644
--- a/apps/api/internal/services/application.go
+++ b/apps/api/internal/services/application.go
@@ -3,7 +3,6 @@ package services
import (
"context"
"errors"
- "time"
"github.com/google/uuid"
"github.com/hibiken/asynq"
@@ -22,7 +21,6 @@ var (
ErrGetApplicationStatistics = errors.New("failed to aggregate application stats")
ErrMismatchedReviewerCounts = errors.New("the total number of applications does not match the total number of assigned reviews")
ErrWrongReviewerAssignment = errors.New("an application has been assigned to a reviewer who is not authorized to review it")
- ErrEventAlreadyStarted = errors.New("the event has already started")
)
// TODO: figure out a way to create the submission fields dynamically using the json form files with proper validation.
@@ -600,19 +598,7 @@ func (s *ApplicationService) TransitionWaitlistedApplications(ctx context.Contex
err := s.txm.WithTx(ctx, func(tx pgx.Tx) error {
txAppRepo := s.appRepo.NewTx(tx)
- // Get event start date, error if past the start of the event.
- event, err := s.eventsService.GetEventByID(ctx, eventId)
- if err != nil {
- s.logger.Err(err).Msg(err.Error())
- return err
- }
- currentTime := time.Now()
- if event.StartTime.After(currentTime) {
- s.logger.Err(ErrEventAlreadyStarted).Msg("Could not transition waitlisted applications: the event has already started.")
- return ErrEventAlreadyStarted
- }
-
- err = txAppRepo.TransitionAcceptedApplicationsToWaitlistByEventID(ctx, eventId)
+ err := txAppRepo.TransitionAcceptedApplicationsToWaitlistByEventID(ctx, eventId)
if err != nil {
s.logger.Err(err).Msg(err.Error())
return err
diff --git a/apps/api/internal/workers/bat.go b/apps/api/internal/workers/bat.go
index 90cdcf76..1c4108f5 100644
--- a/apps/api/internal/workers/bat.go
+++ b/apps/api/internal/workers/bat.go
@@ -3,6 +3,8 @@ package workers
import (
"context"
"encoding/json"
+ "errors"
+ "time"
"github.com/hibiken/asynq"
"github.com/rs/zerolog"
@@ -12,6 +14,10 @@ import (
"github.com/swamphacks/core/apps/api/internal/tasks"
)
+var (
+ ErrEventAlreadyStarted = errors.New("the event has already started")
+)
+
// BAT Worker
// The BAT worker runs the background execution pipeline for our
// Balanced Admissions Thresher (BAT). This worker processes
@@ -22,15 +28,17 @@ import (
type BATWorker struct {
batService *services.BatService
applicationService *services.ApplicationService
+ eventService *services.EventService
scheduler *asynq.Scheduler
taskQueue *asynq.Client
logger zerolog.Logger
}
-func NewBATWorker(batService *services.BatService, applicationService *services.ApplicationService, scheduler *asynq.Scheduler, taskQueue *asynq.Client, logger zerolog.Logger) *BATWorker {
+func NewBATWorker(batService *services.BatService, applicationService *services.ApplicationService, eventService *services.EventService, scheduler *asynq.Scheduler, taskQueue *asynq.Client, logger zerolog.Logger) *BATWorker {
return &BATWorker{
batService: batService,
applicationService: applicationService,
+ eventService: eventService,
logger: logger.With().Str("worker", "BATWorker").Str("component", "BAT").Logger(),
scheduler: scheduler,
taskQueue: taskQueue,
@@ -72,6 +80,18 @@ func (w *BATWorker) HandleScheduleTransitionWaitlistTask(ctx context.Context, t
return err
}
+ // Get event start date, error if past the start of the event.
+ event, err := w.eventService.GetEventByID(ctx, payload.EventID)
+ if err != nil {
+ w.logger.Err(err).Msg(err.Error())
+ return err
+ }
+ currentTime := time.Now()
+ if currentTime.After(event.StartTime) {
+ w.logger.Err(ErrEventAlreadyStarted).Msg("Could not transition waitlisted applications: the event has already started.")
+ return ErrEventAlreadyStarted
+ }
+
cfg := config.Load()
task, err := tasks.NewTaskTransitionWaitlist(tasks.TransitionWaitlistPayload{
EventID: payload.EventID,
From 8e56625196acd938f08c7f43306879901cef7a37 Mon Sep 17 00:00:00 2001
From: h1divp <71522316+h1divp@users.noreply.github.com>
Date: Thu, 15 Jan 2026 12:04:59 -0500
Subject: [PATCH 5/5] fix: readded guard clause to
TransitionWaitlistedApplications
---
apps/api/internal/services/application.go | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/apps/api/internal/services/application.go b/apps/api/internal/services/application.go
index 08e29b3c..159d0b59 100644
--- a/apps/api/internal/services/application.go
+++ b/apps/api/internal/services/application.go
@@ -3,6 +3,7 @@ package services
import (
"context"
"errors"
+ "time"
"github.com/google/uuid"
"github.com/hibiken/asynq"
@@ -21,6 +22,7 @@ var (
ErrGetApplicationStatistics = errors.New("failed to aggregate application stats")
ErrMismatchedReviewerCounts = errors.New("the total number of applications does not match the total number of assigned reviews")
ErrWrongReviewerAssignment = errors.New("an application has been assigned to a reviewer who is not authorized to review it")
+ ErrEventAlreadyStarted = errors.New("the event has already started")
)
// TODO: figure out a way to create the submission fields dynamically using the json form files with proper validation.
@@ -595,7 +597,19 @@ func (s *ApplicationService) AcceptApplicationAcceptance(ctx context.Context, us
func (s *ApplicationService) TransitionWaitlistedApplications(ctx context.Context, eventId uuid.UUID, acceptanceCount uint32, acceptanceQuota uint32) error {
var acceptedUserIds []uuid.UUID
- err := s.txm.WithTx(ctx, func(tx pgx.Tx) error {
+
+ event, err := s.eventsService.GetEventByID(ctx, eventId)
+ if err != nil {
+ s.logger.Err(err).Msg(err.Error())
+ return err
+ }
+ currentTime := time.Now()
+ if currentTime.After(event.StartTime) {
+ s.logger.Err(ErrEventAlreadyStarted).Msg("Could not transition waitlisted applications: the event has already started.")
+ return ErrEventAlreadyStarted
+ }
+
+ err = s.txm.WithTx(ctx, func(tx pgx.Tx) error {
txAppRepo := s.appRepo.NewTx(tx)
err := txAppRepo.TransitionAcceptedApplicationsToWaitlistByEventID(ctx, eventId)