Skip to content

Commit 6fcd892

Browse files
merge confict resolution
2 parents a83c491 + 56e1116 commit 6fcd892

37 files changed

Lines changed: 1482 additions & 150 deletions

File tree

apps/api/cmd/api/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func main() {
7979
authService := services.NewAuthService(userRepo, accountRepo, sessionRepo, txm, client, logger, &cfg.Auth)
8080
userService := services.NewUserService(userRepo, logger)
8181
eventInterestService := services.NewEventInterestService(eventInterestRepo, logger)
82-
eventService := services.NewEventService(eventRepo, userRepo, logger)
82+
eventService := services.NewEventService(eventRepo, userRepo, r2Client, &cfg.CoreBuckets, logger)
8383
emailService := services.NewEmailService(taskQueueClient, logger)
8484
applicationService := services.NewApplicationService(applicationRepo, eventService, txm, r2Client, &cfg.CoreBuckets, logger)
8585

apps/api/internal/api/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ func (api *API) setupRoutes(mw *mw.Middleware) {
126126

127127
// Admin-only
128128
r.With(ensureEventAdmin).Patch("/", api.Handlers.Event.UpdateEventById)
129+
r.With(ensureEventAdmin).Post("/banner", api.Handlers.Event.UploadEventBanner)
130+
r.With(ensureEventAdmin).Delete("/banner", api.Handlers.Event.DeleteBanner)
129131
r.With(ensureEventAdmin).Get("/staff", api.Handlers.Event.GetEventStaffUsers)
130132
r.With(ensureEventAdmin).Post("/roles", api.Handlers.Event.AssignEventRole)
131133

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

Lines changed: 146 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

apps/api/internal/api/response/response.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func SendError(w http.ResponseWriter, status int, errorResponse ErrorResponse) {
3131

3232
// Send marshals any successful payload struct to JSON, sets the status code,
3333
// and writes the response.
34-
func Send(w http.ResponseWriter, status int, payload interface{}) {
34+
func Send(w http.ResponseWriter, status int, payload any) {
3535
w.Header().Set("Content-Type", "application/json; charset=utf-8")
3636
w.WriteHeader(status)
3737

apps/api/internal/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ type CoreBuckets struct {
3535
Avatars string `env:"USER_AVATARS" envDefault:"core-user-avatars-dev"`
3636
ApplicationResumes string `env:"APPLICATION_RESUMES" envDefault:"core-application-resumes-dev"`
3737
EventAssets string `env:"EVENT_ASSETS" envDefault:"core-event-assets-dev"`
38+
AvatarsBaseUrl string `env:"USER_AVATARS_BASE_URL"`
39+
EventAssetsBaseUrl string `env:"EVENT_ASSETS_BASE_URL"`
3840
}
3941

4042
type Config struct {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- +goose Up
2+
-- +goose StatementBegin
3+
ALTER TABLE events
4+
ADD COLUMN banner TEXT;
5+
-- +goose StatementEnd
6+
7+
-- +goose Down
8+
-- +goose StatementBegin
9+
ALTER TABLE events
10+
DROP COLUMN banner;
11+
-- +goose StatementEnd

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ SET
4141
start_time = CASE WHEN @start_time_do_update::boolean THEN @start_time ELSE start_time END,
4242
end_time = CASE WHEN @end_time_do_update::boolean THEN @end_time ELSE end_time END,
4343
website_url = CASE WHEN @website_url_do_update::boolean THEN @website_url ELSE website_url END,
44-
is_published = CASE WHEN @is_published_do_update::boolean THEN @is_published ELSE is_published END
44+
is_published = CASE WHEN @is_published_do_update::boolean THEN @is_published ELSE is_published END,
45+
banner = CASE WHEN @banner_do_update::boolean THEN @banner ELSE banner END
4546
WHERE
4647
id = @id::uuid
4748
RETURNING *;

0 commit comments

Comments
 (0)