Skip to content
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
42dae43
feat: created event api repo, service, and handler
h1divp Jul 18, 2025
b05cd3d
docs: added /event route to openapi.yml
h1divp Jul 18, 2025
a63de79
feat: added more checks for CreateEvent
h1divp Jul 19, 2025
aba98c6
fix: corrected error message
h1divp Jul 19, 2025
6f58093
removed extraneous code
h1divp Jul 19, 2025
af9aef3
feat: added GetEventById
h1divp Jul 20, 2025
f9e180e
feat: added update route. various fixes and refactored UpdateEventByI…
h1divp Jul 21, 2025
f8dc36b
feat: added generic badge component, event badge component, and icon …
hieunguyent12 Jul 1, 2025
bcb162d
fix: removed mounting empty node_modules in docker compose file (#41)
AlexanderWangY Jul 1, 2025
a1f7199
feat: added swagger ui renderer plugin
h1divp Jul 2, 2025
21468ec
refactor: changed referance to link to other site that renders api docs
h1divp Jul 2, 2025
91c1de4
TECH-107: Add mailing list (#40)
hugoliu-code Jul 8, 2025
b3ed0ff
Fix/discord pfp (#56)
AlexanderWangY Jul 11, 2025
1e9139a
fix: allow go sum for build (#57)
AlexanderWangY Jul 11, 2025
5432c3b
Stanley/basic bot structure (#59)
AlexanderWangY Jul 12, 2025
d975656
feat: added event card (#58)
hieunguyent12 Jul 14, 2025
c2dbd0f
feat: swamphacks infra files (#61)
AlexanderWangY Jul 17, 2025
ae76c93
fix: add more build images (#62)
AlexanderWangY Jul 17, 2025
3782a4b
Fix/multi platform build (#63)
AlexanderWangY Jul 17, 2025
cc8337f
feat: new deployment for prod server (#64)
AlexanderWangY Jul 18, 2025
8560457
hotfix: fix script to indented
AlexanderWangY Jul 18, 2025
be3c5cd
fix goose migraitons hotfix
AlexanderWangY Jul 18, 2025
4f2f504
feat: dev deployment (#65)
AlexanderWangY Jul 18, 2025
3f3e101
Feat/dev deployment api (#66)
AlexanderWangY Jul 18, 2025
70d2dd9
hotfix: dev api
AlexanderWangY Jul 18, 2025
2bb8706
Merge and fastforward timeline (#68)
AlexanderWangY Jul 18, 2025
a84c3b0
Admin dashboard links (#69)
AlexanderWangY Jul 19, 2025
b9777a7
Speedup git history and merge master to dev (#71)
AlexanderWangY Jul 21, 2025
6886701
Create sql query to fetch event, user, and application information (…
h1divp Jul 21, 2025
1836b10
feat: dev deployment (#65)
AlexanderWangY Jul 18, 2025
0c7a96d
Feat/dev deployment api (#66)
AlexanderWangY Jul 18, 2025
3b4f77e
Merge and fastforward timeline (#68)
AlexanderWangY Jul 18, 2025
b9dfe13
Refactor: made UpdateEvent sql easier to use, renamed query. Fix: rem…
h1divp Jul 21, 2025
975dc0e
Revert "TECH-123: Create sql query to fetch event, user, and applicat…
h1divp Jul 21, 2025
c131f96
fastforward git history and changes (#74)
AlexanderWangY Jul 21, 2025
d8b1fbc
Merge branch 'dev' into phoenix/TECH-124
h1divp Jul 21, 2025
424ddd3
fix: fixing rebase
h1divp Jul 21, 2025
56925e9
feat: added delete route
h1divp Jul 21, 2025
33e4b88
feat: added better validation for missing/empty parameters
h1divp Jul 27, 2025
ea10a6b
feat: progress in optional fields for CreateEvent
h1divp Aug 1, 2025
26f4645
Fix: prevent SQL query default values being controlled by struct in h…
h1divp Aug 1, 2025
a76d72b
Fix: fixed default values being overridden by NULL in insert query
h1divp Aug 1, 2025
79a8518
chore: updated comment
h1divp Aug 1, 2025
5f11f00
Feat: error throws when no events are deleted after a delete request
h1divp Aug 1, 2025
b24ca84
Merge branch 'dev' into phoenix/TECH-124
h1divp Aug 1, 2025
64aeb72
docs: updated openapi documentation
h1divp Aug 2, 2025
c991827
Fix: fixed error handling flow, events not being returned to client, …
h1divp Aug 4, 2025
1d7cd07
Fix: protected routes
h1divp Aug 4, 2025
510599b
Refactor: moved time validation functions
h1divp Aug 5, 2025
635d2c8
Refactor: replaced required field function by using validator library
h1divp Aug 5, 2025
ed2f842
Fix: update query optional parameters now support null values
h1divp Aug 5, 2025
36b4419
Feat: added middleware for checking user event role
h1divp Aug 5, 2025
8b8a42d
Fix: RequireEventRole middleware now works
h1divp Aug 6, 2025
b0cd25c
Chore: go mod tidy
h1divp Aug 6, 2025
71fc24c
Improvement: allow RequireRole middleware to take slices
h1divp Aug 6, 2025
1e7ce1f
Improvement: allow superusers whenever when any role is required
h1divp Aug 7, 2025
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
4 changes: 3 additions & 1 deletion apps/api/cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,16 @@ func main() {
accountRepo := repository.NewAccountRespository(database)
sessionRepo := repository.NewSessionRepository(database)
eventInterestRepo := repository.NewEventInterestRepository(database)
eventRepo := repository.NewEventRespository(database)

// Injections into services
authService := services.NewAuthService(userRepo, accountRepo, sessionRepo, txm, client, logger, &cfg.Auth)
eventInterestService := services.NewEventInterestService(eventInterestRepo, logger)
eventService := services.NewEventService(eventRepo, logger)
emailService := services.NewEmailService(taskQueueClient, logger)

// Injections into handlers
apiHandlers := handlers.NewHandlers(authService, eventInterestService, emailService, cfg, logger)
apiHandlers := handlers.NewHandlers(authService, eventInterestService, eventService, emailService, cfg, logger)

api := api.NewAPI(&logger, apiHandlers, mw)

Expand Down
6 changes: 6 additions & 0 deletions apps/api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,22 @@ require (
require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.27.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/redis/go-redis/v9 v9.7.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/stretchr/testify v1.10.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.25.0 // indirect
Expand Down
12 changes: 12 additions & 0 deletions apps/api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,18 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
Expand All @@ -39,6 +47,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
Expand Down Expand Up @@ -67,6 +77,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
14 changes: 13 additions & 1 deletion apps/api/internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ func NewAPI(logger *zerolog.Logger, handlers *handlers.Handlers, middleware *mw.
}

func (api *API) setupRoutes(mw *mw.Middleware) {
var (
ensureSuperuser = mw.Auth.RequirePlatformRole(sqlc.AuthUserRoleSuperuser)
ensureEventAdmin = mw.Event.RequireEventRole(sqlc.EventRoleTypeAdmin)
)

api.Router.Use(middleware.Logger)
api.Router.Use(middleware.RealIP)
api.Router.Use(cors.Handler(cors.Options{
Expand Down Expand Up @@ -68,7 +73,14 @@ func (api *API) setupRoutes(mw *mw.Middleware) {

// Event routes
api.Router.Route("/event", func(r chi.Router) {
r.Post("/{eventId}/interest", api.Handlers.EventInterest.AddEmailToEvent)
r.With(ensureSuperuser).Post("/", api.Handlers.Event.CreateEvent)
r.Route("/{eventId}", func(r chi.Router) {
r.With(ensureEventAdmin, ensureSuperuser).Patch("/", api.Handlers.Event.UpdateEventById)
r.Patch("/", api.Handlers.Event.UpdateEventById)
r.With(ensureSuperuser).Delete("/", api.Handlers.Event.DeleteEventById)
r.Get("/", api.Handlers.Event.GetEventByID)
r.Post("/interest", api.Handlers.EventInterest.AddEmailToEvent)
})
})

// Email routes
Expand Down
217 changes: 217 additions & 0 deletions apps/api/internal/api/handlers/events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package handlers

import (
"encoding/json"
"net/http"
"reflect"
"time"

"github.com/go-chi/chi/v5"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
"github.com/rs/zerolog"
res "github.com/swamphacks/core/apps/api/internal/api/response"
"github.com/swamphacks/core/apps/api/internal/config"
"github.com/swamphacks/core/apps/api/internal/db/sqlc"
"github.com/swamphacks/core/apps/api/internal/services"
)

type EventHandler struct {
eventService *services.EventService
cfg *config.Config
logger zerolog.Logger
}

func NewEventHandler(eventService *services.EventService, cfg *config.Config, logger zerolog.Logger) *EventHandler {
return &EventHandler{
eventService: eventService,
cfg: cfg,
logger: logger.With().Str("handler", "EventHandler").Str("component", "events").Logger(),
}
}

// Be very careful with the types in this struct. If a type is not a pointer (pointer types allow a null value), and the field is not present in the json body, its default value will be passed to the SQL query, and be a non-null value will be put into coalese(), which will then make a NULL value impossible and instead make the default value the type's zero value in Go.
type CreateEventFields struct {
Name string `json:"name" validate:"required,min=5,max=30"`
ApplicationOpen time.Time `json:"application_open" validate:"required"`
ApplicationClose time.Time `json:"application_close" validate:"required"`
StartTime time.Time `json:"start_time" validate:"required"`
EndTime time.Time `json:"end_time" validate:"required"`
Description *string `json:"description"`
Location *string `json:"location"`
LocationUrl *string `json:"location_url"`
MaxAttendees *int32 `json:"max_attendees"`
RsvpDeadline *time.Time `json:"rsvp_deadline"`
DecisionRelease *time.Time `json:"decision_release"`
WebsiteUrl *string `json:"website_url"`
IsPublished *bool `json:"is_published"`
}

func (st CreateEventFields) ValidateTimeFields() bool {
if st.ApplicationClose.Before(st.ApplicationOpen) || st.ApplicationClose.Equal(st.ApplicationOpen) {
return false
}
if st.EndTime.Before(st.StartTime) || st.EndTime.Equal(st.StartTime) {
return false
}
if st.ApplicationOpen.Before(time.Now()) ||
st.ApplicationClose.Before(time.Now()) ||
st.StartTime.Before(time.Now()) ||
st.EndTime.Before(time.Now()) {
return false
}
return true
}

func (h *EventHandler) CreateEvent(w http.ResponseWriter, r *http.Request) {

// Parse JSON body
var req CreateEventFields
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields() // Prevents requests with extraneous fields
// This will also throw an error for empty values for fields which correspond to types that cannot convert an empty string to a zero value (e.g. time.Time)
if err := decoder.Decode(&req); err != nil {
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_request", "Could not parse request body"))
return
}

validate := validator.New()
if err := validate.Struct(req); err != nil {
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_request", err.Error()))
}

if !req.ValidateTimeFields() {
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_time", "Time fields must be sequential and not in the past."))
return
}

params := sqlc.CreateEventParams{
Name: req.Name,
ApplicationOpen: req.ApplicationOpen,
ApplicationClose: req.ApplicationClose,
StartTime: req.StartTime,
EndTime: req.EndTime,
Description: req.Description,
Location: req.Location,
LocationUrl: req.LocationUrl,
MaxAttendees: req.MaxAttendees,
RsvpDeadline: req.RsvpDeadline,
DecisionRelease: req.DecisionRelease,
WebsiteUrl: req.WebsiteUrl,
IsPublished: req.IsPublished,
}

event, err := h.eventService.CreateEvent(r.Context(), params)
if err != nil {
if err == services.ErrFailedToCreateEvent {
res.SendError(w, http.StatusInternalServerError, res.NewError("creation_error", "Failed to create event"))
} else {
res.SendError(w, http.StatusInternalServerError, res.NewError("internal_err", "Something went wrong"))
}
}

res.Send(w, http.StatusCreated, event)
}

func (h *EventHandler) GetEventByID(w http.ResponseWriter, r *http.Request) {
eventIdStr := chi.URLParam(r, "eventId")
if eventIdStr == "" {
res.SendError(w, http.StatusBadRequest, res.NewError("missing_event_id", "The event ID is missing from the URL!"))
return
}
eventId, err := uuid.Parse(eventIdStr)
if err != nil {
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not a valid UUID"))
return
}

event, err := h.eventService.GetEventByID(r.Context(), eventId)
if err != nil {
switch err {
case services.ErrFailedToGetEvent:
res.SendError(w, http.StatusNotFound, res.NewError("no_event", "Event not found"))
default:
res.SendError(w, http.StatusInternalServerError, res.NewError("internal_err", "Something went wrong"))
}
}

res.Send(w, http.StatusOK, event)
}

func (h *EventHandler) UpdateEventById(w http.ResponseWriter, r *http.Request) {
eventIdStr := chi.URLParam(r, "eventId")
if eventIdStr == "" {
res.SendError(w, http.StatusBadRequest, res.NewError("missing_event_id", "The event ID is missing from the URL!"))
return
}
eventId, err := uuid.Parse(eventIdStr)
if err != nil {
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not a valid UUID"))
return
}

var req sqlc.UpdateEventByIdParams

decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields() // Prevents requests with extraneous fields
if err := decoder.Decode(&req); err != nil {
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_request", "Invalid request body"))
return
}

// Refactorme: could be improved by unmarshalling values into a generic that can include nil information
// Todo: make sure that non nullable values can't be updated to null
// Todo: Time validation
req.NameDoUpdate = reflect.ValueOf(req.Name).IsValid()
req.DescriptionDoUpdate = reflect.ValueOf(req.Description).IsValid()
req.LocationDoUpdate = reflect.ValueOf(req.Location).IsValid()
req.LocationUrlDoUpdate = reflect.ValueOf(req.LocationUrl).IsValid()
req.MaxAttendeesDoUpdate = reflect.ValueOf(req.MaxAttendees).IsValid()
req.ApplicationOpenDoUpdate = reflect.ValueOf(req.ApplicationOpen).IsValid()
req.ApplicationCloseDoUpdate = reflect.ValueOf(req.ApplicationClose).IsValid()
req.RsvpDeadlineDoUpdate = reflect.ValueOf(req.RsvpDeadline).IsValid()
req.DecisionReleaseDoUpdate = reflect.ValueOf(req.DecisionRelease).IsValid()
req.StartTimeDoUpdate = reflect.ValueOf(req.StartTime).IsValid()
req.EndTimeDoUpdate = reflect.ValueOf(req.EndTime).IsValid()
req.WebsiteUrlDoUpdate = reflect.ValueOf(req.WebsiteUrl).IsValid()
req.IsPublishedDoUpdate = reflect.ValueOf(req.IsPublished).IsValid()
req.ID = eventId

event, err := h.eventService.UpdateEventById(r.Context(), req)

if err != nil {
switch err {
case services.ErrFailedToUpdateEvent:
res.SendError(w, http.StatusInternalServerError, res.NewError("patch_error", "Failed to update event"))
default:
res.SendError(w, http.StatusInternalServerError, res.NewError("internal_err", "Something went wrong"))
}
}

res.Send(w, http.StatusOK, event)
}

func (h *EventHandler) DeleteEventById(w http.ResponseWriter, r *http.Request) {
eventIdStr := chi.URLParam(r, "eventId")
if eventIdStr == "" {
res.SendError(w, http.StatusBadRequest, res.NewError("missing_event_id", "The event ID is missing from the URL!"))
return
}
eventId, err := uuid.Parse(eventIdStr)
if err != nil {
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not a valid UUID"))
return
}
err = h.eventService.DeleteEventById(r.Context(), eventId)

if err != nil {
switch err {
case services.ErrFailedToDeleteEvent:
res.SendError(w, http.StatusInternalServerError, res.NewError("delete_error", "Failed to delete event"))
default:
res.SendError(w, http.StatusInternalServerError, res.NewError("internal_err", "Something went wrong"))
}
}

w.WriteHeader(http.StatusNoContent)
}
4 changes: 3 additions & 1 deletion apps/api/internal/api/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import (
type Handlers struct {
Auth *AuthHandler
EventInterest *EventInterestHandler
Event *EventHandler
Email *EmailHandler
}

func NewHandlers(authService *services.AuthService, eventInterestService *services.EventInterestService, emailService *services.EmailService, cfg *config.Config, logger zerolog.Logger) *Handlers {
func NewHandlers(authService *services.AuthService, eventInterestService *services.EventInterestService, eventService *services.EventService, emailService *services.EmailService, cfg *config.Config, logger zerolog.Logger) *Handlers {
return &Handlers{
Auth: NewAuthHandler(authService, cfg, logger),
EventInterest: NewEventInterestHandler(eventInterestService, cfg, logger),
Event: NewEventHandler(eventService, cfg, logger),
Email: NewEmailHandler(emailService, logger),
}
}
57 changes: 57 additions & 0 deletions apps/api/internal/api/middleware/events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package middleware

import (
"net/http"

"github.com/google/uuid"
"github.com/rs/zerolog"
"github.com/swamphacks/core/apps/api/internal/api/response"
"github.com/swamphacks/core/apps/api/internal/config"
"github.com/swamphacks/core/apps/api/internal/db"
"github.com/swamphacks/core/apps/api/internal/db/sqlc"
)

const EventRoleContextKey ctxKey = "event_role"

type EventMiddleware struct {
db *db.DB
logger zerolog.Logger
cfg *config.Config
}

type UserRoleContext struct {
UserID uuid.UUID `json:"userId"`
EventID uuid.UUID `json:"eventId"`
Role sqlc.EventRoleType `json:"role"`
}

func NewEventMiddleware(db *db.DB, logger zerolog.Logger, cfg *config.Config) *EventMiddleware {
return &EventMiddleware{
db: db,
logger: logger.With().Str("middleware", "EventMiddleware").Str("component", "api").Logger(),
cfg: cfg,
}
}

func (m *EventMiddleware) RequireEventRole(role sqlc.EventRoleType) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// get user from context
userCtx, ok := r.Context().Value(EventRoleContextKey).(*UserRoleContext)
if !ok {
m.logger.Warn().Msg("No user context found.")
Comment thread
h1divp marked this conversation as resolved.
Outdated
response.SendError(w, http.StatusUnauthorized, response.NewError("no_auth", "You are not authorized."))
return
}

// check if user role matches required role
if userCtx.Role != role {
m.logger.Warn().Msgf("User tried to access %s with insufficient permissions as role %s", r.URL.Path, string(userCtx.Role))
response.SendError(w, http.StatusForbidden, response.NewError("forbidden", "You are forbidden from this resource."))
return
}

next.ServeHTTP(w, r)
})
}
}
Loading
Loading