Skip to content
10 changes: 2 additions & 8 deletions apps/api/cmd/BAT_worker/main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"fmt"
"time"

"github.com/hibiken/asynq"
Expand Down Expand Up @@ -57,9 +56,6 @@
},
)

logger.Info().Msg("Debug test")
fmt.Print("Debug test")

schedulerLocation, err := time.LoadLocation("America/New_York")
if err != nil {
panic(err)
Expand All @@ -71,10 +67,8 @@
},
)

logger.Info().Msg("Debug test")
fmt.Print("Debug test")

taskQueueClient := asynq.NewClient(redisOpt)
defer taskQueueClient.Close()

Check failure on line 71 in apps/api/cmd/BAT_worker/main.go

View workflow job for this annotation

GitHub Actions / API Lint

Error return value of `taskQueueClient.Close` is not checked (errcheck)

database := db.NewDB(cfg.DatabaseURL)
defer database.Close()
Expand All @@ -92,7 +86,7 @@
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)
Expand Down
3 changes: 3 additions & 0 deletions apps/api/internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
fmt.Printf("%v", err)
}

fmt.Fprintln(w, htmlContent)

Check failure on line 77 in apps/api/internal/api/api.go

View workflow job for this annotation

GitHub Actions / API Lint

Error return value of `fmt.Fprintln` is not checked (errcheck)
})

// Health check
Expand Down Expand Up @@ -192,6 +192,9 @@
//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)
})
Expand Down
58 changes: 43 additions & 15 deletions apps/api/internal/api/handlers/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@
return
}

defer resumeFile.Close()

Check failure on line 187 in apps/api/internal/api/handlers/application.go

View workflow job for this annotation

GitHub Actions / API Lint

Error return value of `resumeFile.Close` is not checked (errcheck)

resumeFileBuffer := bytes.NewBuffer(nil)

Expand Down Expand Up @@ -375,14 +375,14 @@
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
}

Expand Down Expand Up @@ -417,13 +417,13 @@
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
}

Expand Down Expand Up @@ -468,7 +468,7 @@
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
}

Expand Down Expand Up @@ -502,7 +502,7 @@
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
}

Expand Down Expand Up @@ -536,7 +536,7 @@
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
}

Expand Down Expand Up @@ -603,7 +603,7 @@
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())
Expand All @@ -623,15 +623,15 @@
// @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())
Expand All @@ -645,10 +645,38 @@
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"
Expand All @@ -659,7 +687,7 @@
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())
Expand Down Expand Up @@ -687,7 +715,7 @@
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
}

Expand Down
47 changes: 44 additions & 3 deletions apps/api/internal/services/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package services
import (
"context"
"errors"
"time"

"github.com/google/uuid"
"github.com/hibiken/asynq"
Expand All @@ -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.
Expand Down Expand Up @@ -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(),
}
}

Expand Down Expand Up @@ -542,7 +544,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())
Expand All @@ -568,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)
Expand Down
22 changes: 21 additions & 1 deletion apps/api/internal/workers/bat.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import (
"context"
"encoding/json"
"errors"
"time"

"github.com/hibiken/asynq"
"github.com/rs/zerolog"
Expand All @@ -12,6 +14,10 @@
"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
Expand All @@ -22,15 +28,17 @@
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,
Expand Down Expand Up @@ -72,6 +80,18 @@
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,
Expand All @@ -82,7 +102,7 @@
// The scheduler will make its first run after the period cycles once. So we queue our task immediately as well.
_, err = w.taskQueue.Enqueue(task, asynq.Queue("bat"))

w.scheduler.Start()

Check failure on line 105 in apps/api/internal/workers/bat.go

View workflow job for this annotation

GitHub Actions / API Lint

Error return value of `w.scheduler.Start` is not checked (errcheck)
_, err = w.scheduler.Register(payload.Period, task, asynq.Queue("bat"))
if err != nil {
w.logger.Err(err)
Expand Down
12 changes: 11 additions & 1 deletion apps/web/src/features/Event/applicationStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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;
Loading
Loading