@@ -4,21 +4,23 @@ import (
44 "encoding/json"
55 "errors"
66 "fmt"
7+ "io"
78 "net/http"
8- "reflect"
99 "time"
1010
1111 "github.com/go-chi/chi/v5"
1212 "github.com/go-playground/validator/v10"
1313 "github.com/google/uuid"
1414 "github.com/rs/zerolog"
15+ "github.com/rs/zerolog/log"
1516 res "github.com/swamphacks/core/apps/api/internal/api/response"
1617 "github.com/swamphacks/core/apps/api/internal/config"
1718 "github.com/swamphacks/core/apps/api/internal/ctxutils"
1819 "github.com/swamphacks/core/apps/api/internal/db/repository"
1920 "github.com/swamphacks/core/apps/api/internal/db/sqlc"
2021 "github.com/swamphacks/core/apps/api/internal/email"
2122 "github.com/swamphacks/core/apps/api/internal/parse"
23+ . "github.com/swamphacks/core/apps/api/internal/parse"
2224 "github.com/swamphacks/core/apps/api/internal/ptr"
2325 "github.com/swamphacks/core/apps/api/internal/services"
2426 "github.com/swamphacks/core/apps/api/internal/web"
@@ -170,6 +172,22 @@ func (h *EventHandler) GetEventByID(w http.ResponseWriter, r *http.Request) {
170172 res .Send (w , http .StatusOK , event )
171173}
172174
175+ type UpdateEventFields struct {
176+ Name Optional [string ] `json:"name"`
177+ Description Optional [* string ] `json:"description"`
178+ Location Optional [* string ] `json:"location"`
179+ LocationUrl Optional [* string ] `json:"location_url"`
180+ MaxAttendees Optional [* int32 ] `json:"max_attendees"`
181+ ApplicationOpen Optional [time.Time ] `json:"application_open"`
182+ ApplicationClose Optional [time.Time ] `json:"application_close"`
183+ RsvpDeadline Optional [* time.Time ] `json:"rsvp_deadline"`
184+ DecisionRelease Optional [* time.Time ] `json:"decision_release"`
185+ StartTime Optional [time.Time ] `json:"start_time"`
186+ EndTime Optional [time.Time ] `json:"end_time"`
187+ WebsiteUrl Optional [* string ] `json:"website_url"`
188+ IsPublished Optional [bool ] `json:"is_published"`
189+ }
190+
173191// Update an event
174192//
175193// @Summary Update an event
@@ -193,7 +211,7 @@ func (h *EventHandler) UpdateEventById(w http.ResponseWriter, r *http.Request) {
193211 return
194212 }
195213
196- var req sqlc. UpdateEventByIdParams
214+ var req UpdateEventFields
197215
198216 decoder := json .NewDecoder (r .Body )
199217 decoder .DisallowUnknownFields () // Prevents requests with extraneous fields
@@ -202,25 +220,53 @@ func (h *EventHandler) UpdateEventById(w http.ResponseWriter, r *http.Request) {
202220 return
203221 }
204222
205- // Refactorme: could be improved by unmarshalling values into a generic that can include nil information
206- // Todo: make sure that non nullable values can't be updated to null
207- // Todo: Time validation
208- req .NameDoUpdate = reflect .ValueOf (req .Name ).IsValid ()
209- req .DescriptionDoUpdate = reflect .ValueOf (req .Description ).IsValid ()
210- req .LocationDoUpdate = reflect .ValueOf (req .Location ).IsValid ()
211- req .LocationUrlDoUpdate = reflect .ValueOf (req .LocationUrl ).IsValid ()
212- req .MaxAttendeesDoUpdate = reflect .ValueOf (req .MaxAttendees ).IsValid ()
213- req .ApplicationOpenDoUpdate = reflect .ValueOf (req .ApplicationOpen ).IsValid ()
214- req .ApplicationCloseDoUpdate = reflect .ValueOf (req .ApplicationClose ).IsValid ()
215- req .RsvpDeadlineDoUpdate = reflect .ValueOf (req .RsvpDeadline ).IsValid ()
216- req .DecisionReleaseDoUpdate = reflect .ValueOf (req .DecisionRelease ).IsValid ()
217- req .StartTimeDoUpdate = reflect .ValueOf (req .StartTime ).IsValid ()
218- req .EndTimeDoUpdate = reflect .ValueOf (req .EndTime ).IsValid ()
219- req .WebsiteUrlDoUpdate = reflect .ValueOf (req .WebsiteUrl ).IsValid ()
220- req .IsPublishedDoUpdate = reflect .ValueOf (req .IsPublished ).IsValid ()
221- req .ID = eventId
223+ var params = sqlc.UpdateEventByIdParams {
224+ NameDoUpdate : req .Name .Present ,
225+ Name : req .Name .Value ,
226+
227+ DescriptionDoUpdate : req .Description .Present ,
228+ Description : req .Description .Value ,
229+
230+ LocationDoUpdate : req .Location .Present ,
231+ Location : req .Location .Value ,
232+
233+ LocationUrlDoUpdate : req .LocationUrl .Present ,
234+ LocationUrl : req .LocationUrl .Value ,
235+
236+ MaxAttendeesDoUpdate : req .MaxAttendees .Present ,
237+ MaxAttendees : req .MaxAttendees .Value ,
238+
239+ ApplicationOpenDoUpdate : req .ApplicationOpen .Present ,
240+ ApplicationOpen : req .ApplicationOpen .Value ,
241+
242+ ApplicationCloseDoUpdate : req .ApplicationClose .Present ,
243+ ApplicationClose : req .ApplicationClose .Value ,
244+
245+ RsvpDeadlineDoUpdate : req .RsvpDeadline .Present ,
246+ RsvpDeadline : req .RsvpDeadline .Value ,
222247
223- event , err := h .eventService .UpdateEventById (r .Context (), req )
248+ DecisionReleaseDoUpdate : req .DecisionRelease .Present ,
249+ DecisionRelease : req .DecisionRelease .Value ,
250+
251+ StartTimeDoUpdate : req .StartTime .Present ,
252+ StartTime : req .StartTime .Value ,
253+
254+ EndTimeDoUpdate : req .EndTime .Present ,
255+ EndTime : req .EndTime .Value ,
256+
257+ WebsiteUrlDoUpdate : req .WebsiteUrl .Present ,
258+ WebsiteUrl : req .WebsiteUrl .Value ,
259+
260+ IsPublishedDoUpdate : req .IsPublished .Present ,
261+ IsPublished : & req .IsPublished .Value ,
262+
263+ BannerDoUpdate : false , // Banners are uploaded using a separate endpoint
264+ Banner : nil ,
265+
266+ ID : eventId ,
267+ }
268+
269+ event , err := h .eventService .UpdateEventById (r .Context (), params )
224270
225271 if err != nil {
226272 switch err {
@@ -484,3 +530,83 @@ func (h *EventHandler) AssignEventRole(w http.ResponseWriter, r *http.Request) {
484530
485531 w .WriteHeader (http .StatusOK )
486532}
533+
534+ func deferredCloser (c io.Closer , name string ) func () {
535+ return func () {
536+ if err := c .Close (); err != nil {
537+ log .Err (err ).Msg ("Failed to close " + name )
538+ }
539+ }
540+ }
541+
542+ const maxBannerUploadSize = 5 << 20 // 5 Mb
543+
544+ type EventBannerUploadResponse struct {
545+ BannerUrl string `json:"banner_url"`
546+ }
547+
548+ func (h * EventHandler ) UploadEventBanner (w http.ResponseWriter , r * http.Request ) {
549+ eventIdStr := chi .URLParam (r , "eventId" )
550+ if eventIdStr == "" {
551+ res .SendError (w , http .StatusBadRequest , res .NewError ("missing_event_id" , "The event ID is missing from the URL!" ))
552+ return
553+ }
554+ eventId , err := uuid .Parse (eventIdStr )
555+ if err != nil {
556+ res .SendError (w , http .StatusBadRequest , res .NewError ("invalid_event_id" , "The event ID is not a valid UUID" ))
557+ return
558+ }
559+
560+ if err := r .ParseMultipartForm (maxBannerUploadSize ); err != nil {
561+ res .SendError (w , http .StatusBadRequest , res .NewError ("invalid_request" , "could not parse multipart form" ))
562+ return
563+ }
564+
565+ bannerFile , header , err := r .FormFile ("image" )
566+ if err != nil {
567+ res .SendError (w , http .StatusBadRequest , res .NewError ("invalid_request" , "invalid resume file" ))
568+ return
569+ }
570+ defer deferredCloser (bannerFile , "banner file" )
571+
572+ url , err := h .eventService .UploadBanner (r .Context (), eventId , bannerFile , header )
573+ switch err {
574+ case services .ErrFailedToUploadBanner :
575+ res .SendError (w , http .StatusInternalServerError , res .NewError ("internal_err" , "Something went wrong on our end" ))
576+ return
577+ case services .ErrUnexpectedFileType :
578+ res .SendError (w , http .StatusBadRequest , res .NewError ("file_error" , err .Error ()))
579+ return
580+ case nil :
581+ // Continue
582+ default :
583+ res .SendError (w , http .StatusInternalServerError , res .NewError ("internal_err" , "Something went wrong on our end" ))
584+ return
585+ }
586+
587+ res .Send (w , http .StatusOK , EventBannerUploadResponse {
588+ BannerUrl : * url ,
589+ })
590+
591+ }
592+
593+ func (h * EventHandler ) DeleteBanner (w http.ResponseWriter , r * http.Request ) {
594+ eventIdStr := chi .URLParam (r , "eventId" )
595+ if eventIdStr == "" {
596+ res .SendError (w , http .StatusBadRequest , res .NewError ("missing_event_id" , "The event ID is missing from the URL!" ))
597+ return
598+ }
599+ eventId , err := uuid .Parse (eventIdStr )
600+ if err != nil {
601+ res .SendError (w , http .StatusBadRequest , res .NewError ("invalid_event_id" , "The event ID is not a valid UUID" ))
602+ return
603+ }
604+
605+ err = h .eventService .DeleteBanner (r .Context (), eventId )
606+ if err != nil {
607+ res .SendError (w , http .StatusInternalServerError , res .NewError ("internal_err" , "Something went wrong on our end" ))
608+ return
609+ }
610+
611+ w .WriteHeader (http .StatusOK )
612+ }
0 commit comments