Skip to content

Commit f37d094

Browse files
h1divpSobaSkeeAlexanderWangYStanley KeStanleyKeUF
authored
release nfc checkin (#302)
* feat: complete webhook and add auto assign commands * feat: create endpoint for discord bot and event listener to add auto add role * actual update * refactor: old implementation reworked to use endpoints from api * chore: remove old db file * chore: remove gemini api key requirement * fix: revert accounts go sqlc version * fix: rename hacker_role to role * fix: refactor code to get event role by discord and event id * Check in scanner (#284) * feat: qr code generation for frontend * temp: adding api for checkin * temp: some frontend changes * fix: remove useless intents * temp: get user with event info * temp: modal for checking in and rfid hidden text input * temp: check in modal; * feat: check in scanner * fix: remove event id tests from intents (#285) * fix/parse intents tests (#286) * fix: remove event id tests from intents * fix: remove dangling component * chore: change to to be announced (#287) * feat: add event id when calling endpoint * fix: sqlc generate, deleted large bin file * Stanley/basic bot structure (#216) * chore(env): setup development and production python venvs * feat: add message trigger command for bot * Create readme.md (#3) * chore: complete merge conflict and rebase * chore: rewrite readme * feat: add regex to identify potential scam/spam messages in antiSpam cog * refactor: organize and break up functions, add detailed comments, add remove role command * refactor: include direction in readme to select interpreter and add any pycache or venvs file types to gitignore * feat: create panel command and reusable buttons added * feat: update anti_spam to have new spam detection, add support cog with request features * add mentor role functions and rework ticket panel * feat: implement round-robin for pinging available mentors * feat: make threads archived, add limits to user inputs, clear selection options * feat: add mod command to grant user access to private vcs * feat: add command to create a support vc * fix: create the logs folder if it does not exist * feat: add command to prompt gemini and integrate uv as new package manager * chore: add llm command since it did not add in merge * feat: update readme with new uv commands * feat: add structured output for llm * feat: make mod role based on perms and not rely on config * chore: add docstrings for each function describing their purposes * feat: create first iteration of workflow for discord bot * feat: push workflow file * fix: update dev yml file for discord * feat: add join button to reports embed * feat: create fastapi backend to add check user in server api * refactor: abstract role names to roles_config file * feat: update round robin logic and renew api token * feat: create announcement command * fix: update announcemnet command to allow mentions * refactor: rename support vcs category * fix: add is mod slash function back * Update .gitignore * Delete apps/discord-bot/uv.lock --------- Co-authored-by: Phoenix <71522316+h1divp@users.noreply.github.com> Co-authored-by: Stanley Ke <stanleyke@Mac.lan> Co-authored-by: Stanley <ke.st@ufl.edu> * Redemptions (#288) * Table Migration * SQLC Go functions * Backend Scaffolding * Basic CRUD operations + Refactor API * Fixed repo level issue + finished logic * Small Push * Remigrate Deleted and remigrated a migration to avoid bugs from merging dev * Redeemables UI * Update Modal + QR Code QR is untested currently * Slight UI Adjustment + Deletion * UI Adjustment * Clarified Redemption Info * Documentation * UpdateRedemption missing body fix * 1 line bug fix * Cleaner handlers uuid parsing + qr throttling * feat: resume downloader script; fix: add to gitignore (#289) * fix: injected services (#290) * feat: welcome email release (#291) * feat: QueueWelcomeEmail api route, handler and service * refactor: pass template data in struct * fix: property names * feat: qr image generation, upload, link generation, and welcome email queue * feat: get slice of attendee userIds, added route to send all welcome emails; fixes: various * qr code fixes (#293) * feat: QueueWelcomeEmail api route, handler and service * refactor: pass template data in struct * fix: property names * feat: qr image generation, upload, link generation, and welcome email queue * feat: get slice of attendee userIds, added route to send all welcome emails; fixes: various * fix: error msg, email template * feat: err log on empty contact email and continue loop * fix: dont break loop * feat: change 72 hours to 24 in email (#296) * Redeemablebugfixes (#297) * Everything except prettifying the overview page * Made overview page a little more paletable * Include full id * Checkin check before scanning qr code * Update sqlc * feat: production github workflow and docker config for discord bot (#295) * chore(env): setup development and production python venvs * feat: add message trigger command for bot * Create readme.md (#3) * chore: complete merge conflict and rebase * chore: rewrite readme * feat: add regex to identify potential scam/spam messages in antiSpam cog * refactor: organize and break up functions, add detailed comments, add remove role command * refactor: include direction in readme to select interpreter and add any pycache or venvs file types to gitignore * feat: create panel command and reusable buttons added * feat: update anti_spam to have new spam detection, add support cog with request features * add mentor role functions and rework ticket panel * feat: implement round-robin for pinging available mentors * feat: make threads archived, add limits to user inputs, clear selection options * feat: add mod command to grant user access to private vcs * feat: add command to create a support vc * fix: create the logs folder if it does not exist * feat: add command to prompt gemini and integrate uv as new package manager * chore: add llm command since it did not add in merge * feat: update readme with new uv commands * feat: add structured output for llm * feat: make mod role based on perms and not rely on config * chore: add docstrings for each function describing their purposes * feat: create first iteration of workflow for discord bot * feat: push workflow file * fix: update dev yml file for discord * feat: add join button to reports embed * feat: create fastapi backend to add check user in server api * refactor: abstract role names to roles_config file * feat: update round robin logic and renew api token * feat: create announcement command * fix: update announcemnet command to allow mentions * refactor: rename support vcs category * fix: add is mod slash function back * Update .gitignore * Delete apps/discord-bot/uv.lock * feat: add close button to thread embed and close associated vc * fix: remove all users from thread when closed internally * feat: github workflow and docker config * fix: remove old workflow * fix: docker config * feat: fix role assignment command * chore: update env example for discord bot * fix: command role requirements updated * fix: remove workflow --------- Co-authored-by: Stanley <kestanley101@gmail.com> Co-authored-by: Stanley Ke <stanleyke@Mac.lan> Co-authored-by: Stanley <ke.st@ufl.edu> * feat: mobile endpoints (#298) Co-authored-by: Phoenix <71522316+h1divp@users.noreply.github.com> * fix: remove bad discord docker stuff, fix syntax (#299) * update rfid (#300) * feat/update rfid (#301) * update rfid * hotfix: hard code attendee --------- Co-authored-by: Phoenix <71522316+h1divp@users.noreply.github.com> --------- Co-authored-by: Stanley Ke <kestanley101@gmail.com> Co-authored-by: Alexander Wang <98280966+AlexanderWangY@users.noreply.github.com> Co-authored-by: Stanley Ke <117794857+SobaSkee@users.noreply.github.com> Co-authored-by: Stanley Ke <stanleyke@Mac.lan> Co-authored-by: Stanley <ke.st@ufl.edu> Co-authored-by: Hugo Liu <98724522+hugoliu-code@users.noreply.github.com>
1 parent acd1197 commit f37d094

File tree

29 files changed

+1142
-163
lines changed

29 files changed

+1142
-163
lines changed

apps/api/internal/api/api.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,19 @@ func (api *API) setupRoutes(mw *mw.Middleware) {
7777
fmt.Fprintln(w, htmlContent)
7878
})
7979

80+
api.Router.Route("/mobile", func(r chi.Router) {
81+
// This means you have to set a Authorization header with "Key xxx".
82+
r.Use(mw.Auth.RequireMobileAuth)
83+
84+
r.Get("/events/{eventId}/users/{userId}", api.Handlers.Event.GetUserForEvent)
85+
r.Get("/events/{eventId}/users/by-rfid/{rfid}", api.Handlers.Event.GetUserByRFID)
86+
r.Post("/events/{eventId}/checkin", api.Handlers.Admission.HandleEventCheckIn)
87+
88+
r.Get("/events/{eventId}/redeemables", api.Handlers.Redeemables.GetRedeemables)
89+
r.Post("/redeemables/{redeemableId}/users/{userId}", api.Handlers.Redeemables.RedeemRedeemable)
90+
r.Post("/events/{eventId}/users/{userId}/update-rfid", api.Handlers.Event.UpdateUserRFID)
91+
})
92+
8093
// Health check
8194
api.Router.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
8295
api.Logger.Trace().Str("method", r.Method).Str("path", r.URL.Path).Msg("Received ping.")
@@ -152,6 +165,8 @@ func (api *API) setupRoutes(mw *mw.Middleware) {
152165
r.With(ensureEventStaff).Get("/users/{userId}", api.Handlers.Event.GetUserForEvent)
153166
// Get user ID by RFID
154167
r.With(ensureEventStaff).Get("/users/by-rfid/{rfid}", api.Handlers.Event.GetUserByRFID)
168+
// Is the user checked in
169+
r.With(ensureEventStaff).Get("/users/{userId}/checked-in-status", api.Handlers.Event.GetCheckedInStatusByIds)
155170

156171
// Admin-only
157172
r.With(ensureEventAdmin).Post("/queue-confirmation-email", api.Handlers.Email.QueueConfirmationEmail)
@@ -225,7 +240,7 @@ func (api *API) setupRoutes(mw *mw.Middleware) {
225240
// Update and delete specific redeemable
226241
r.Route("/{redeemableId}", func(r chi.Router) {
227242
r.Patch("/", api.Handlers.Redeemables.UpdateRedeemable)
228-
r.Delete("/", api.Handlers.Redeemables.DeleteRedeemable)
243+
r.With(ensureEventAdmin).Delete("/", api.Handlers.Redeemables.DeleteRedeemable)
229244

230245
r.Route("/users/{userId}", func(r chi.Router) {
231246
r.Post("/", api.Handlers.Redeemables.RedeemRedeemable)

apps/api/internal/api/handlers/events.go

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io"
88
"net/http"
9+
"strconv"
910
"time"
1011

1112
"github.com/go-chi/chi/v5"
@@ -865,4 +866,96 @@ func (h *EventHandler) GetUserByRFID(w http.ResponseWriter, r *http.Request) {
865866

866867
// Return just the user ID as a simple JSON object
867868
res.Send(w, http.StatusOK, map[string]string{"user_id": user.ID.String()})
868-
}
869+
}
870+
871+
// Get User by RFID
872+
//
873+
// @Summary Retrieves a user's ID by their RFID
874+
// @Description Looks up a user's ID by their RFID code for a specific event. Returns the user ID which can be used for other operations.
875+
// @Tags Event
876+
// @Produce json
877+
// @Param eventId path string true "Event ID" Format(uuid)
878+
// @Param rfid path string true "RFID code (10 digits)"
879+
// @Success 200 {object} map[string]string "OK - Returns user ID"
880+
// @Failure 400 {object} response.ErrorResponse "Bad request/Malformed request."
881+
// @Failure 404 {object} response.ErrorResponse "User not found with the provided RFID"
882+
// @Failure 500 {object} response.ErrorResponse "Server Error: error getting user by RFID"
883+
// @Router /events/{eventId}/users/by-rfid/{rfid} [get]
884+
func (h *EventHandler) GetCheckedInStatusByIds(w http.ResponseWriter, r *http.Request) {
885+
eventId, err := web.PathParamToUUID(r, "eventId")
886+
if err != nil {
887+
res.SendError(w, http.StatusBadRequest, res.NewError("missing_event_id", "The event ID is missing from the URL!"))
888+
return
889+
}
890+
891+
userId, err := web.PathParamToUUID(r, "userId")
892+
if err != nil {
893+
res.SendError(w, http.StatusBadRequest, res.NewError("missing_user_id", "The user ID is missing from the URL!"))
894+
return
895+
}
896+
result, err := h.eventService.GetCheckedInStatusByIds(r.Context(), userId, eventId)
897+
if err != nil {
898+
res.SendError(w, http.StatusNotFound, res.NewError("error", "Something went wrong internally."))
899+
return
900+
}
901+
// Return just the checked in status as a simple JSON object
902+
res.Send(w, http.StatusOK, map[string]string{
903+
"checked_in_status": strconv.FormatBool(result),
904+
})
905+
}
906+
907+
type UpdateRFID struct {
908+
RFID string `json:"rfid"`
909+
}
910+
911+
// UpdateUserRFID
912+
//
913+
// @Summary Updates a user's RFID tag
914+
// @Description Associates a new RFID string with a specific user for the given event. This overwrites any existing RFID association.
915+
// @Tags Event
916+
// @Accept json
917+
// @Produce json
918+
// @Param eventId path string true "Event ID" Format(uuid)
919+
// @Param userId path string true "User ID" Format(uuid)
920+
// @Param body body UpdateRFID true "New RFID data"
921+
// @Success 204 "No Content - RFID updated successfully"
922+
// @Failure 400 {object} response.ErrorResponse "Invalid request body or UUID format"
923+
// @Failure 404 {object} response.ErrorResponse "User or Event not found"
924+
// @Failure 500 {object} response.ErrorResponse "Internal server error"
925+
// @Router /events/{eventId}/users/{userId}/update-rfid [post]
926+
func (h *EventHandler) UpdateUserRFID(w http.ResponseWriter, r *http.Request) {
927+
eventId, err := web.PathParamToUUID(r, "eventId")
928+
if err != nil {
929+
res.SendError(w, http.StatusBadRequest, res.NewError("missing_event_id", "The event ID is missing from the URL!"))
930+
return
931+
}
932+
933+
userId, err := web.PathParamToUUID(r, "userId")
934+
if err != nil {
935+
res.SendError(w, http.StatusBadRequest, res.NewError("missing_user_id", "The user ID is missing from the URL!"))
936+
return
937+
}
938+
939+
var payload UpdateRFID
940+
err = json.NewDecoder(r.Body).Decode(&payload)
941+
if err != nil {
942+
res.SendError(w, http.StatusBadRequest, res.NewError("body_malformed", "Invalid body"))
943+
return
944+
}
945+
defer r.Body.Close()
946+
947+
if payload.RFID == "" {
948+
res.SendError(w, http.StatusBadRequest, res.NewError("body_malformed", "Invalid body"))
949+
return
950+
}
951+
952+
tempRole := sqlc.EventRoleTypeAttendee
953+
954+
err = h.eventService.UpdateEventRoleByIds(r.Context(), userId, eventId, &tempRole, nil, &payload.RFID)
955+
if err != nil {
956+
res.SendError(w, http.StatusNotFound, res.NewError("error", "Something went wrong internally."))
957+
return
958+
}
959+
960+
w.WriteHeader(http.StatusNoContent)
961+
}

apps/api/internal/api/middleware/auth.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"net/http"
88
"slices"
9+
"strings"
910
"time"
1011

1112
"github.com/google/uuid"
@@ -69,6 +70,34 @@ func NewAuthMiddleware(db *db.DB, logger zerolog.Logger, cfg *config.Config) *Au
6970
}
7071
}
7172

73+
func (m *AuthMiddleware) RequireMobileAuth(next http.Handler) http.Handler {
74+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
75+
m.logger.Trace().Msg("Incoming mobile request")
76+
77+
auth := r.Header.Get("Authorization")
78+
if auth == "" {
79+
m.logger.Warn().Msg("Authorization header is empty.")
80+
response.SendError(w, http.StatusUnauthorized, response.NewError("no_auth", "You are not authorized"))
81+
return
82+
}
83+
84+
parts := strings.SplitN(auth, " ", 2)
85+
if len(parts) != 2 {
86+
m.logger.Warn().Msg("Authorization header is malformed > 2 parts")
87+
response.SendError(w, http.StatusUnauthorized, response.NewError("no_auth", "You are not authorized"))
88+
return
89+
}
90+
91+
if parts[0] != "Key" || parts[1] != m.cfg.MobileAuthKey {
92+
m.logger.Warn().Msg("Authorization header is not a valid value")
93+
response.SendError(w, http.StatusUnauthorized, response.NewError("no_auth", "You are not authorized"))
94+
return
95+
}
96+
97+
next.ServeHTTP(w, r)
98+
})
99+
}
100+
72101
func (m *AuthMiddleware) RequireAuth(next http.Handler) http.Handler {
73102
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
74103
m.logger.Trace().Msg("Checking auth status")

apps/api/internal/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ type Config struct {
7575
CoreBuckets CoreBuckets `envPrefix:"CORE_BUCKETS_"`
7676
Smtp SmtpConfig `envPrefix:"SMTP_"`
7777
AWS AWSConfig `envPrefix:"AWS_"`
78+
79+
MobileAuthKey string `env:"MOBILE_AUTH_KEY"`
7880
}
7981

8082
func Load() *Config {

apps/api/internal/db/queries/event_roles.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,12 @@ FROM auth.users u
7171
JOIN event_roles er ON u.id = er.user_id
7272
WHERE er.event_id = $1
7373
AND er.rfid = $2;
74+
75+
-- name: GetCheckedInStatusByIds :one
76+
SELECT EXISTS (
77+
SELECT 1
78+
FROM event_roles
79+
WHERE user_id = $1
80+
AND event_id = $2
81+
AND checked_in_at IS NOT NULL
82+
)::bool;

apps/api/internal/db/repository/events.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ var (
1616
ErrDuplicateEvent = errors.New("event already exists in database")
1717
ErrNoEventsDeleted = errors.New("no events deleted")
1818
ErrMultipleEventsDeleted = errors.New("multiple events affected by delete query while only expecting one to delete one")
19+
ErrUserEventNotFound = errors.New("the user and event id combination was not found")
1920
ErrUnknown = errors.New("an unkown error was caught")
2021
)
2122

@@ -206,6 +207,21 @@ func (r *EventRepository) GetEventRoleByDiscordIDAndEventId(ctx context.Context,
206207
return &eventRole, nil
207208
}
208209

210+
func (r *EventRepository) GetCheckedInStatusByUserIdAndEventId(ctx context.Context, userId uuid.UUID, eventId uuid.UUID) (bool, error) {
211+
params := sqlc.GetCheckedInStatusByIdsParams{
212+
UserID: userId,
213+
EventID: eventId,
214+
}
215+
216+
result, err := r.db.Query.GetCheckedInStatusByIds(ctx, params)
217+
218+
if err != nil {
219+
return false, err
220+
}
221+
222+
return result, nil
223+
}
224+
209225
func (r *EventRepository) GetAttendeeUserIdsByEventId(ctx context.Context, eventID uuid.UUID) ([]uuid.UUID, error) {
210226
return r.db.Query.GetAttendeeUserIdsByEventId(ctx, eventID)
211227
}

apps/api/internal/db/sqlc/event_roles.sql.go

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/api/internal/email/templates/WaitlistAcceptanceEmail.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ <h2 style="margin:0; font-size:22px; color:#1a1a1a;">Hi {{ .Name }},</h2>
3939
</p>
4040

4141
<p style="margin:0 0 15px 0; font-size:16px;">
42-
You have <strong><span style="color:#CC0000;">72 hours</span></strong> to confirm your spot. If you
42+
You have <strong><span style="color:#CC0000;">24 hours</span></strong> to confirm your spot. If you
4343
don’t,
4444
you’ll be moved back to the
4545
waitlist to make room for others.

apps/api/internal/services/events.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,3 +422,13 @@ func (s *EventService) GetUserInfoForEvent(ctx context.Context, userId, eventId
422422

423423
return result, nil
424424
}
425+
426+
func (s *EventService) GetCheckedInStatusByIds(ctx context.Context, userId uuid.UUID, eventId uuid.UUID) (bool, error) {
427+
result, err := s.eventRepo.GetCheckedInStatusByUserIdAndEventId(ctx, userId, eventId)
428+
if err != nil {
429+
s.logger.Err(err).Msg("Failed to get user by ids")
430+
return false, err
431+
}
432+
return result, nil
433+
434+
}

apps/api/internal/services/redeemables.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ func (s *RedeemablesService) UpdateRedeemable(ctx context.Context, redeemableID
6060
}
6161

6262
func (s *RedeemablesService) RedeemRedeemable(ctx context.Context, redeemableID uuid.UUID, userID uuid.UUID) error {
63+
// Need to do a check to see if the user is checked in
64+
// Probably need event service
65+
66+
// CREATE NEW SQL function for getting checked in status
6367
_, err := s.redeemablesRepo.RedeemRedeemable(ctx, redeemableID, userID)
6468

6569
if err != nil {

0 commit comments

Comments
 (0)