Skip to content

Commit

Permalink
add optional trailing slash for all routes, add event-reg-purch rou…
Browse files Browse the repository at this point in the history
…te + handler for `new` events, add API step for creating a UUID if one doesn't exist, route to newly created event with uuid via window.history.pushstate
  • Loading branch information
brianfeister committed Jan 8, 2025
1 parent ff857db commit f54ae7e
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 55 deletions.
40 changes: 32 additions & 8 deletions functions/gateway/handlers/data_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handlers

import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
Expand All @@ -14,6 +15,7 @@ import (

"github.com/aws/aws-lambda-go/events"
"github.com/go-playground/validator"
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/meetnearme/api/functions/gateway/handlers/dynamodb_handlers"
"github.com/meetnearme/api/functions/gateway/helpers"
Expand Down Expand Up @@ -480,6 +482,7 @@ func GetUsersHandler(w http.ResponseWriter, r *http.Request) http.HandlerFunc {

// Search for matching users
matches, err := helpers.SearchUsersByIDs(ids)
log.Println("matches: ", matches)
if err != nil {
transport.SendServerRes(w, []byte("Failed to search users: "+err.Error()), http.StatusInternalServerError, err)
return
Expand Down Expand Up @@ -1154,21 +1157,33 @@ func UpdateEventRegPurch(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
eventId := vars[helpers.EVENT_ID_KEY]
if eventId == "" {
transport.SendServerRes(w, []byte("Missing purchasable event_id"), http.StatusBadRequest, nil)
return
}

userInfo := helpers.UserInfo{}
if _, ok := ctx.Value("userInfo").(helpers.UserInfo); ok {
userInfo = ctx.Value("userInfo").(helpers.UserInfo)
}
roleClaims := []helpers.RoleClaim{}
if claims, ok := ctx.Value("roleClaims").([]helpers.RoleClaim); ok {
roleClaims = claims
}

validRoles := []string{"superAdmin", "eventEditor"}
userId := userInfo.Sub
if userId == "" {
transport.SendServerRes(w, []byte("Missing user ID"), http.StatusUnauthorized, nil)
return
}

if !helpers.HasRequiredRole(roleClaims, validRoles) {
err := errors.New("only event editors can add or edit events")
transport.SendServerRes(w, []byte(err.Error()), http.StatusForbidden, err)
return
}

if eventId == "" {
eventId = uuid.NewString()
}

var updateEventRegPurchPayload UpdateEventRegPurchPayload
updateEventRegPurchPayload.RegistrationFieldsUpdate.UpdatedBy = userId
body, err := io.ReadAll(r.Body)
Expand All @@ -1184,7 +1199,6 @@ func UpdateEventRegPurch(w http.ResponseWriter, r *http.Request) {
}

// we should use goroutines to parallelize the three distinct database update operations here

db := transport.GetDB()

// Update purchasable
Expand All @@ -1194,7 +1208,7 @@ func UpdateEventRegPurch(w http.ResponseWriter, r *http.Request) {

purchService := dynamodb_service.NewPurchasableService()
purchHandler := dynamodb_handlers.NewPurchasableHandler(purchService)
_, err = purchHandler.PurchasableService.UpdatePurchasable(r.Context(), db, updatePurchasable)
purchRes, err := purchHandler.PurchasableService.UpdatePurchasable(r.Context(), db, updatePurchasable)
if err != nil {
transport.SendServerRes(w, []byte("Failed to update purchasable: "+err.Error()), http.StatusInternalServerError, err)
return
Expand All @@ -1204,7 +1218,7 @@ func UpdateEventRegPurch(w http.ResponseWriter, r *http.Request) {
updateEventRegPurchPayload.RegistrationFieldsUpdate.EventId = eventId
regFieldsService := dynamodb_service.NewRegistrationFieldsService()
regFieldsHandler := dynamodb_handlers.NewRegistrationFieldsHandler(regFieldsService)
_, err = regFieldsHandler.RegistrationFieldsService.UpdateRegistrationFields(r.Context(), db, eventId, updateEventRegPurchPayload.RegistrationFieldsUpdate)
regFieldsRes, err := regFieldsHandler.RegistrationFieldsService.UpdateRegistrationFields(r.Context(), db, eventId, updateEventRegPurchPayload.RegistrationFieldsUpdate)
if err != nil {
transport.SendServerRes(w, []byte("Failed to update registration fields: "+err.Error()), http.StatusInternalServerError, err)
return
Expand All @@ -1229,12 +1243,22 @@ func UpdateEventRegPurch(w http.ResponseWriter, r *http.Request) {
events[i] = event
}

services.BulkUpsertEventToMarqo(marqoClient, events)
eventsRes, err := services.BulkUpsertEventToMarqo(marqoClient, events)
if err != nil {
transport.SendServerRes(w, []byte("Failed to upsert events to marqo: "+err.Error()), http.StatusInternalServerError, err)
return
}

// Create response object
response := map[string]interface{}{
"status": "success",
"message": "Event(s), registration fields, and purchasable(s) updated successfully",
"data": map[string]interface{}{
"parentEvent": eventsRes.Items[0],
"events": eventsRes,
"regFields": regFieldsRes,
"purchasable": purchRes,
},
}

// Marshal the response
Expand Down
31 changes: 16 additions & 15 deletions functions/gateway/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,28 +69,29 @@ func init() {
// API routes

// == START == need to expose these via permanent key for headless clients
{"/api/event", "POST", handlers.PostEventHandler, Require},
{"/api/events", "POST", handlers.PostBatchEventsHandler, Require},
{"/api/events", "GET", handlers.SearchEventsHandler, None},
{"/api/events", "PUT", handlers.BulkUpdateEventsHandler, Require},
{"/api/event{trailingslash:\\/?}", "POST", handlers.PostEventHandler, Require},
{"/api/events{trailingslash:\\/?}", "POST", handlers.PostBatchEventsHandler, Require},
{"/api/events{trailingslash:\\/?}", "GET", handlers.SearchEventsHandler, None},
{"/api/events{trailingslash:\\/?}", "PUT", handlers.BulkUpdateEventsHandler, Require},
{"/api/events/{" + helpers.EVENT_ID_KEY + "}", "GET", handlers.GetOneEventHandler, None},
{"/api/events/{" + helpers.EVENT_ID_KEY + "}", "PUT", handlers.UpdateOneEventHandler, Require},
{"/api/event-reg-purch{trailingslash:\\/?}", "PUT", handlers.UpdateEventRegPurchHandler, Require},
{"/api/event-reg-purch/{" + helpers.EVENT_ID_KEY + "}", "PUT", handlers.UpdateEventRegPurchHandler, Require},
{"/api/locations", "GET", handlers.SearchLocationsHandler, None},
{"/api/locations{trailingslash:\\/?}", "GET", handlers.SearchLocationsHandler, None},
// == END == need to expose these via permanent key for headless clients
{"/api/auth/users/set-subdomain", "POST", handlers.SetUserSubdomain, Require},
{"/api/auth/users/update-interests", "POST", handlers.UpdateUserInterests, Require},
{"/api/auth/users/update-about", "POST", handlers.UpdateUserAbout, Require},
{"/api/auth/users/set-subdomain{trailingslash:\\/?}", "POST", handlers.SetUserSubdomain, Require},
{"/api/auth/users/update-interests{trailingslash:\\/?}", "POST", handlers.UpdateUserInterests, Require},
{"/api/auth/users/update-about{trailingslash:\\/?}", "POST", handlers.UpdateUserAbout, Require},
// TODO: delete this comment once user location is implemented in profile,
// "/api/location/geo" is for use there
{"/api/location/geo", "POST", handlers.GeoLookup, None},
{"/api/user-search", "GET", handlers.SearchUsersHandler, Require},
{"/api/users", "GET", handlers.GetUsersHandler, Require},
{"/api/html/events", "GET", handlers.GetEventsPartial, None},
{"/api/location/geo{trailingslash:\\/?}", "POST", handlers.GeoLookup, None},
{"/api/user-search{trailingslash:\\/?}", "GET", handlers.SearchUsersHandler, Require},
{"/api/users{trailingslash:\\/?}", "GET", handlers.GetUsersHandler, Require},
{"/api/html/events{trailingslash:\\/?}", "GET", handlers.GetEventsPartial, None},
{"/api/html/event-series-form/{" + helpers.EVENT_ID_KEY + "}", "GET", handlers.GetEventAdminChildrenPartial, None},
{"/api/html/seshu/session/submit", "POST", handlers.SubmitSeshuSession, Require},
{"/api/html/seshu/session/location", "PUT", handlers.GeoThenPatchSeshuSession, Require},
{"/api/html/seshu/session/events", "PUT", handlers.SubmitSeshuEvents, Require},
{"/api/html/seshu/session/submit{trailingslash:\\/?}", "POST", handlers.SubmitSeshuSession, Require},
{"/api/html/seshu/session/location{trailingslash:\\/?}", "PUT", handlers.GeoThenPatchSeshuSession, Require},
{"/api/html/seshu/session/events{trailingslash:\\/?}", "PUT", handlers.SubmitSeshuEvents, Require},

// // Purchasables routes
{"/api/purchasables/{" + helpers.EVENT_ID_KEY + ":[0-9a-fA-F-]+}", "POST", dynamodb_handlers.CreatePurchasableHandler, Require}, // Create a new purchasable
Expand Down
76 changes: 44 additions & 32 deletions functions/gateway/templates/pages/event_add_edit.templ
Original file line number Diff line number Diff line change
Expand Up @@ -252,31 +252,29 @@ templ AddOrEditEventPage(pageObj helpers.SitePage, event types.Event, isEditor b
</div>
<h2 class="text-2xl sticky sticky-under-top-nav subheader bg-base-100 z-40 py-2">Date &amp; Media</h2>
<div id="basic" class="card border-2 border-base-300 bg-base-200 p-10 rounded-box mb-4">
<div class="card">
<div class="form-control">
<label class="label">{ helpers.GetFieldDisplayName("StartTime") }</label>
<input
class="input input-bordered"
type="datetime-local"
x-model.fill={ "formData.event.startTime" }
if (event.StartTime > 0) {
value={ helpers.GetDatetimePickerFormatted(event.StartTime, event.Timezone) }
}
:disabled="saveReqInFlight"
/>
</div>
<div class="form-control">
<label class="label">{ helpers.GetFieldDisplayName("EndTime") }</label>
<input
class="input input-bordered"
type="datetime-local"
x-model.fill={ "formData.event.endTime" }
if (event.EndTime > 0) {
value={ helpers.GetDatetimePickerFormatted(event.EndTime, event.Timezone) }
}
:disabled="saveReqInFlight"
/>
</div>
<div class="form-control">
<label class="label">{ helpers.GetFieldDisplayName("StartTime") }</label>
<input
class="input input-bordered"
type="datetime-local"
x-model.fill={ "formData.event.startTime" }
if (event.StartTime > 0) {
value={ helpers.GetDatetimePickerFormatted(event.StartTime, event.Timezone) }
}
:disabled="saveReqInFlight"
/>
</div>
<div class="form-control">
<label class="label">{ helpers.GetFieldDisplayName("EndTime") }</label>
<input
class="input input-bordered"
type="datetime-local"
x-model.fill={ "formData.event.endTime" }
if (event.EndTime > 0) {
value={ helpers.GetDatetimePickerFormatted(event.EndTime, event.Timezone) }
}
:disabled="saveReqInFlight"
/>
</div>
<div class="form-control">
<label class="label">Timezone</label>
Expand Down Expand Up @@ -748,19 +746,31 @@ templ AddOrEditEventPage(pageObj helpers.SitePage, event types.Event, isEditor b
})
},
transformEventToPayload(event) {
const eventFiltered = Object.fromEntries(
Object.entries(this.formData.event)
.filter(([_, value]) => value !== '')
)
if (!this.formData.event.hasPurchasable) {
delete eventFiltered.startingPrice
delete eventFiltered.currency
delete eventFiltered.payeeId
}

return {
...Object.fromEntries(
Object.entries(this.formData.event)
.filter(([_, value]) => value !== '')
),
...eventFiltered,
startTime: this.formData.event.startTime ? this.formData.event.startTime + this.timeZuluSuffix : null,
endTime: this.formData.event.endTime ? this.formData.event.endTime + this.timeZuluSuffix : null,
lat: parseFloat(this.formData.event.lat),
long: parseFloat(this.formData.event.long),
eventSourceType: this.isEventSeries ? this.eventTypeSeriesParentConstant : this.eventTypeSingleConstant,
eventOwners: this.owners.map(owner => owner.value),
eventOwnerName: this.owners.map(owner => owner.label).join(this.eventOwnerDelimiter),
startingPrice: this.formData.event.startingPrice * 100,
...(this.formData.event.hasPurchasable ? {
startingPrice: this.formData.event.startingPrice * 100,
currency: this.formData.event.currency,
payeeId: this.formData.event.payeeId,
} : {}
),
}
},
setEventChildrenLoaded() {
Expand Down Expand Up @@ -812,16 +822,18 @@ templ AddOrEditEventPage(pageObj helpers.SitePage, event types.Event, isEditor b
type: 'success',
message: 'Event updated successfully',
}
const newUrl = `/admin/event/${json.data.parentEvent._id}/edit`;
window.history.pushState({ path: newUrl }, '', newUrl);
} else {
throw new Error('Failed to update event')
throw new Error(`Failed to update event ${ json?.error?.message ? ": " + json.error.message : ''}`)
}
} catch (error) {
console.error('Failed to update event:', error);
this.saveReqInFlight = false
this.showToast = true;
this.toastContent = {
type: 'error',
message: 'Error: ' + error,
message: error,
}
}
}
Expand Down

0 comments on commit f54ae7e

Please sign in to comment.