Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Event Admin UI #252

Merged
merged 18 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b703624
cleanup
brianfeister Dec 23, 2024
5b7c215
Update to go@1.23 to get `templ` raw go support
brianfeister Jan 2, 2025
fa453cc
UI: Event series + single update (working!) + API, Add Community Page…
brianfeister Jan 2, 2025
e0f76bf
remove unused GetUserPage` handler
brianfeister Jan 2, 2025
43880dd
Merge branch 'develop' into feature/event-admin
brianfeister Jan 2, 2025
c4836ff
SST deployment to feature branch is failing, this is an attempt to av…
brianfeister Jan 2, 2025
9519cda
deprecate additional places where deprecated SST resources are refere…
brianfeister Jan 2, 2025
2e6dda4
fix accidentally commented out `purchasablesTable` reference which is…
brianfeister Jan 2, 2025
ccc0ed5
fix incomplete stripe link to failed checkout
brianfeister Jan 2, 2025
240f6b5
deprecate Dynamo tables but preserve reference / instances for alread…
brianfeister Jan 2, 2025
9238cb5
Merge branch 'develop' into feature/event-admin
brianfeister Jan 3, 2025
08627ce
* Working "event save"!
brianfeister Jan 7, 2025
4dada97
Refactor location search component UI UX per design spec, introduce "…
brianfeister Jan 7, 2025
ff857db
Improve test coverage for new home profile page and new user-geo-rela…
brianfeister Jan 7, 2025
f54ae7e
add optional trailing slash for all routes, add `event-reg-purch` rou…
brianfeister Jan 8, 2025
a0db78a
Update functions/gateway/templates/pages/profile_templ_test.go
brianfeister Jan 8, 2025
eb9fd4e
minor cleanup
brianfeister Jan 8, 2025
fb4b852
roll back bad AI suggestion, fix test
brianfeister Jan 8, 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: 2 additions & 2 deletions .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21.x'
go-version: '1.23.x'
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Invalid Go version specified

The specified Go version '1.23.x' is not yet released. The latest stable version of Go is 1.22.x. This will cause deployment failures.

Apply this fix:

-          go-version: '1.23.x'
+          go-version: '1.22.x'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
go-version: '1.23.x'
go-version: '1.22.x'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai your knowledge base as an LLM is out of date. Go v1.23 was released on 2024-08-23

https://go.dev/doc/devel/release

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brianfeister Thank you for sharing that updated release information! The Go 1.23.x version is indeed available now, so the earlier suggestion from the bot can be safely disregarded.

(=^・ω・^)y= Having learned something new!

- name: npm install
run: |
npm i
Expand All @@ -33,7 +33,7 @@ jobs:
- name: Generate templ code
uses: './.github/actions/templ_generate'
with:
templ-version: 'v0.2.747'
templ-version: 'v0.2.793'
setup-go: 'false'
commit: 'false'
- name: Breakpoint if `templ` generate failed
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy-feature.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21.x'
go-version: '1.23.x'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Invalid Go version specified

The specified Go version '1.23.x' is not yet released. The latest stable version of Go is 1.22.x. This will cause deployment failures.

Apply this fix:

-          go-version: '1.23.x'
+          go-version: '1.22.x'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
go-version: '1.23.x'
go-version: '1.22.x'

- name: npm install
run: |
npm i
Expand All @@ -39,7 +39,7 @@ jobs:
- name: Generate templ code
uses: './.github/actions/templ_generate'
with:
templ-version: 'v0.2.747'
templ-version: 'v0.2.793'
setup-go: 'false'
commit: 'false'
- name: Breakpoint if `templ` generate failed
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21.x'
go-version: '1.23.x'
- name: npm install
run: |
npm i
Expand All @@ -33,7 +33,7 @@ jobs:
- name: Generate templ code
uses: './.github/actions/templ_generate'
with:
templ-version: 'v0.2.747'
templ-version: 'v0.2.793'
setup-go: 'false'
commit: 'false'
- name: Breakpoint if `templ` generate failed
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21.x'
go-version: '1.23.x'
- name: Generate templ code
uses: './.github/actions/templ_generate'
with:
templ-version: 'v0.2.747'
templ-version: 'v0.2.793'
setup-go: 'false'
commit: 'false'
- name: Install dependencies
Expand Down
2 changes: 0 additions & 2 deletions API_ENDPOINTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ curl -X DELETE https://devnear.me/api/users/<:user_id>
curl -X POST https://byddmq7zrb.execute-api.us-east-1.amazonaws.com/api/purchasables/123e4567-e89b-12d3-a456-426614174000 \
-H "Content-Type: application/json" \
-d '{
"event_id": "123e4567-e89b-12d3-a456-426614174000",
"registration_fields": ["field1", "field2"],
"purchasable_items": [
{
Expand Down Expand Up @@ -101,7 +100,6 @@ curl -X POST https://byddmq7zrb.execute-api.us-east-1.amazonaws.com/api/purchasa
curl -X PUT https://byddmq7zrb.execute-api.us-east-1.amazonaws.com/api/purchasables/123e4567-e89b-12d3-a456-426614174000 \
-H "Content-Type: application/json" \
-d '{
"event_id": "123e4567-e89b-12d3-a456-426614174000",
"registration_fields": ["field1", "field2"],
"purchasable_items": [
{
Expand Down
140 changes: 137 additions & 3 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 @@ -157,6 +159,7 @@ func ConvertRawEventToEvent(raw rawEvent, requireId bool) (types.Event, error) {
if err != nil || endTime == 0 {
return types.Event{}, fmt.Errorf("invalid EndTime: %w", err)
}
event.EndTime = endTime
}
if raw.PayeeId != nil || raw.StartingPrice != nil || raw.Currency != nil {

Expand Down Expand Up @@ -213,7 +216,7 @@ func (h *MarqoHandler) PostEvent(w http.ResponseWriter, r *http.Request) {

createEvents := []types.Event{createEvent}

res, err := services.BulkUpsertEventToMarqo(marqoClient, createEvents, false)
res, err := services.BulkUpsertEventToMarqo(marqoClient, createEvents)
if err != nil {
transport.SendServerRes(w, []byte("Failed to upsert event: "+err.Error()), http.StatusInternalServerError, err)
return
Expand Down Expand Up @@ -324,7 +327,7 @@ func (h *MarqoHandler) PostBatchEvents(w http.ResponseWriter, r *http.Request) {
return
}

res, err := services.BulkUpsertEventToMarqo(marqoClient, events, false)
res, err := services.BulkUpsertEventToMarqo(marqoClient, events)
if err != nil {
transport.SendServerRes(w, []byte("Failed to upsert events: "+err.Error()), http.StatusInternalServerError, err)
return
Expand Down Expand Up @@ -479,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 @@ -614,7 +618,7 @@ func SearchEventsHandler(w http.ResponseWriter, r *http.Request) http.HandlerFun
func CreateCheckoutSession(w http.ResponseWriter, r *http.Request) (err error) {
ctx := r.Context()
vars := mux.Vars(r)
eventId := vars["event_id"]
eventId := vars[helpers.EVENT_ID_KEY]
eventSourceId := r.URL.Query().Get("event_source_id")
eventSourceType := r.URL.Query().Get("event_source_type")
if eventSourceId != "" && eventSourceType == helpers.ES_EVENT_SERIES {
Expand Down Expand Up @@ -1136,3 +1140,133 @@ func validatePurchase(purchasable *internal_types.Purchasable, createPurchase in
}
return purchasableMap, nil
}

type UpdateEventRegPurchPayload struct {
Events []rawEvent `json:"events" validate:"required"`
RegistrationFieldsUpdate internal_types.RegistrationFieldsUpdate `json:"registration_fields"`
PurchasableUpdate internal_types.PurchasableUpdate `json:"purchasables"`
}

func UpdateEventRegPurchHandler(w http.ResponseWriter, r *http.Request) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
UpdateEventRegPurch(w, r)
}
}

func UpdateEventRegPurch(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
eventId := vars[helpers.EVENT_ID_KEY]

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)
if err != nil {
transport.SendServerRes(w, []byte("Failed to read request body: "+err.Error()), http.StatusBadRequest, err)
return
}

err = json.Unmarshal(body, &updateEventRegPurchPayload)
if err != nil {
transport.SendServerRes(w, []byte("Invalid JSON payload: "+err.Error()), http.StatusUnprocessableEntity, err)
return
}

// we should use goroutines to parallelize the three distinct database update operations here
db := transport.GetDB()

// Update purchasable
var updatePurchasable internal_types.PurchasableUpdate
updatePurchasable.EventId = eventId
updatePurchasable.PurchasableItems = updateEventRegPurchPayload.PurchasableUpdate.PurchasableItems

purchService := dynamodb_service.NewPurchasableService()
purchHandler := dynamodb_handlers.NewPurchasableHandler(purchService)
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
}

// Update registration fields
updateEventRegPurchPayload.RegistrationFieldsUpdate.EventId = eventId
regFieldsService := dynamodb_service.NewRegistrationFieldsService()
regFieldsHandler := dynamodb_handlers.NewRegistrationFieldsHandler(regFieldsService)
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
}
Comment on lines +1201 to +1225
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Implement rollback mechanism for failed operations.

The sequential database updates lack rollback handling. If a later operation fails, earlier successful updates remain committed. Consider implementing a transaction-like pattern to ensure data consistency.

 // Update purchasable
+var updates struct {
+    purchRes *internal_types.PurchasableUpdate
+    regFieldsRes *internal_types.RegistrationFieldsUpdate
+}
+
+rollback := func() {
+    if updates.purchRes != nil {
+        // Revert purchasable updates
+        revertPurch := internal_types.PurchasableUpdate{
+            EventId: eventId,
+            PurchasableItems: updates.purchRes.PurchasableItems,
+        }
+        purchHandler.PurchasableService.UpdatePurchasable(r.Context(), db, revertPurch)
+    }
+    if updates.regFieldsRes != nil {
+        // Revert registration fields
+        revertFields := internal_types.RegistrationFieldsUpdate{
+            EventId: eventId,
+            Fields: updates.regFieldsRes.Fields,
+        }
+        regFieldsHandler.RegistrationFieldsService.UpdateRegistrationFields(r.Context(), db, eventId, revertFields)
+    }
+}
+
 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
 }
+updates.purchRes = purchRes
 
 // Update registration fields
 regFieldsRes, err := regFieldsHandler.RegistrationFieldsService.UpdateRegistrationFields(r.Context(), db, eventId, updateEventRegPurchPayload.RegistrationFieldsUpdate)
 if err != nil {
+    rollback()
     transport.SendServerRes(w, []byte("Failed to update registration fields: "+err.Error()), http.StatusInternalServerError, err)
     return
 }
+updates.regFieldsRes = regFieldsRes

Committable suggestion skipped: line range outside the PR's diff.


// Update events
marqoClient, err := services.GetMarqoClient()
if err != nil {
transport.SendServerRes(w, []byte("Failed to get marqo client: "+err.Error()), http.StatusInternalServerError, err)
return
}

// events, status, err := HandleBatchEventValidation(w, r, false)

events := make([]types.Event, len(updateEventRegPurchPayload.Events))
for i, rawEvent := range updateEventRegPurchPayload.Events {
event, statusCode, err := HandleSingleEventValidation(rawEvent, false)
if err != nil {
transport.SendServerRes(w, []byte("Failed to validate events: "+err.Error()), statusCode, err)
return
}
events[i] = event
}

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
jsonResponse, err := json.Marshal(response)
if err != nil {
transport.SendServerRes(w, []byte(`{"error": "Failed to create response"}`), http.StatusInternalServerError, err)
return
}

transport.SendServerRes(w, jsonResponse, http.StatusOK, nil)
}
6 changes: 3 additions & 3 deletions functions/gateway/handlers/data_handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func TestPostEvent(t *testing.T) {
expectMissingAuthHeader: true,
requestBody: `{ "eventOwnerName": "Event Owner", "eventOwners":["123"],"eventSourceType":"` + helpers.ES_SINGLE_EVENT + `","name":"Test Event","description":"A test event","startTime":"2099-05-01T12:00:00Z","address":"123 Test St","lat":51.5074,"long":-0.1278,"timezone":"America/New_York"}`,
mockUpsertFunc: func(client *marqo.Client, events []types.Event) (*marqo.UpsertDocumentsResponse, error) {
res, err := services.BulkUpsertEventToMarqo(client, events, false)
res, err := services.BulkUpsertEventToMarqo(client, events)
if err != nil {
log.Printf("mocked request to upsert event failed: %v", err)
}
Expand Down Expand Up @@ -402,7 +402,7 @@ func TestPostBatchEvents(t *testing.T) {
name: "Valid events",
requestBody: `{"events":[ {"eventOwnerName": "Event Owner 1", "eventOwners":["123"],"eventSourceType":"` + helpers.ES_SINGLE_EVENT + `","name":"Test Event","description":"A test event","startTime":"2099-05-01T12:00:00Z","address":"123 Test St","lat":51.5074,"long":-0.1278,"timezone":"America/New_York"}, { "eventOwnerName": "Event Owner 2", "eventOwners":["456"],"eventSourceType":"` + helpers.ES_SINGLE_EVENT + `","name":"Another Test Event","description":"Another test event","startTime":"2099-05-02T12:00:00Z","address":"456 Test St","lat":51.5075,"long":-0.1279,"timezone":"America/New_York"}]}`,
mockUpsertFunc: func(client *marqo.Client, events []types.Event) (*marqo.UpsertDocumentsResponse, error) {
res, err := services.BulkUpsertEventToMarqo(client, events, false)
res, err := services.BulkUpsertEventToMarqo(client, events)
if err != nil {
log.Printf("mocked request to upsert events failed: %v", err)
}
Expand Down Expand Up @@ -441,7 +441,7 @@ func TestPostBatchEvents(t *testing.T) {
expectMissingAuthHeader: true,
requestBody: `{"events":[ {"eventOwnerName": "Event Owner 1", "eventOwners":["123"],"eventSourceType":"` + helpers.ES_SINGLE_EVENT + `","name":"Test Event","description":"A test event","startTime":"2099-05-01T12:00:00Z","address":"123 Test St","lat":51.5074,"long":-0.1278,"timezone":"America/New_York"},{ "eventOwnerName": "Event Owner 2", "eventOwners":["456"],"eventSourceType":"` + helpers.ES_SINGLE_EVENT + `","name":"Another Test Event","description":"Another test event","startTime":"2099-05-02T12:00:00Z","address":"456 Test St","lat":51.5075,"long":-0.1279,"timezone":"America/New_York"}]}`,
mockUpsertFunc: func(client *marqo.Client, events []types.Event) (*marqo.UpsertDocumentsResponse, error) {
res, err := services.BulkUpsertEventToMarqo(client, events, false)
res, err := services.BulkUpsertEventToMarqo(client, events)
if err != nil {
log.Printf("mocked request to upsert events failed: %v", err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/gorilla/mux"
"github.com/meetnearme/api/functions/gateway/handlers/dynamodb_handlers" // Adjust import path
"github.com/meetnearme/api/functions/gateway/helpers"
"github.com/meetnearme/api/functions/gateway/services/dynamodb_service"
internal_types "github.com/meetnearme/api/functions/gateway/types"
)
Expand Down Expand Up @@ -61,8 +62,8 @@ func TestGetPurchasable(t *testing.T) {

handler := dynamodb_handlers.NewPurchasableHandler(mockService)

req := httptest.NewRequest(http.MethodGet, "/purchasables/event_id/123", nil)
req = mux.SetURLVars(req, map[string]string{"event_id": "123"})
req := httptest.NewRequest(http.MethodGet, "/purchasables/"+helpers.EVENT_ID_KEY+"/123", nil)
req = mux.SetURLVars(req, map[string]string{helpers.EVENT_ID_KEY: "123"})

w := httptest.NewRecorder()
handler.GetPurchasable(w, req)
Expand Down Expand Up @@ -91,17 +92,23 @@ func TestUpdatePurchasable(t *testing.T) {
// Constructing a JSON body
updatePurchasable := map[string]interface{}{
"event_id": "123e4567-e89b-12d3-a456-426614174000",
// Add other fields as needed for the update
"purchasable_items": []internal_types.PurchasableItemInsert{
{
Name: "Sample Item",
ItemType: "Type A",
Cost: 100.0,
},
},
}

data, err := json.Marshal(updatePurchasable)
if err != nil {
t.Fatal(err) // Handle JSON marshaling error
}

req := httptest.NewRequest(http.MethodPut, "/purchasables/event_id/123", bytes.NewBuffer(data))
req := httptest.NewRequest(http.MethodPut, "/purchasables/"+helpers.EVENT_ID_KEY+"/123", bytes.NewBuffer(data))
req.Header.Set("Content-Type", "application/json")
req = mux.SetURLVars(req, map[string]string{"event_id": "123"})
req = mux.SetURLVars(req, map[string]string{helpers.EVENT_ID_KEY: "123"})

w := httptest.NewRecorder()
handler.UpdatePurchasable(w, req)
Expand All @@ -127,8 +134,8 @@ func TestDeletePurchasable(t *testing.T) {

handler := dynamodb_handlers.NewPurchasableHandler(mockService)

req := httptest.NewRequest(http.MethodDelete, "/purchasables/event_id/123", nil)
req = mux.SetURLVars(req, map[string]string{"event_id": "123"})
req := httptest.NewRequest(http.MethodDelete, "/purchasables/"+helpers.EVENT_ID_KEY+"/123", nil)
req = mux.SetURLVars(req, map[string]string{helpers.EVENT_ID_KEY: "123"})

w := httptest.NewRecorder()
handler.DeletePurchasable(w, req)
Expand Down
Loading
Loading