Events

Event Creation Flow

How an event gets created, from the admin filling out the form to the record landing in DynamoDB.


Overview

Admin fills EventForm          Frontend transforms           Backend handler
(/admin/event/new)      →      schema to API format   →     POST /events/

                                                             ├─ validate fields
                                                             ├─ normalize feedback questions
                                                             ├─ assign question UUIDs
                                                             ├─ check for duplicates
                                                             └─ db.create() → biztechEvents

Step 1: Admin Form (Frontend)

Page: src/pages/admin/event/new.tsx Component: src/components/Events/EventForm.tsx Schema: src/components/Events/EventFormSchema.ts

The admin fills out a form with these sections:

Settings

FieldTypeDefault
isApplicationBasedBooleanfalse
nonBizTechAllowedBooleanfalse

Cover Photo

The admin uploads an image via EventThumbnailUploader.tsx. See Event Image Upload for the full pipeline. The resulting S3 URL is stored as imageUrl.

Event Information

FieldFrontend nameMaps to
Event NameeventNameename
Event SlugeventSlugid
Capacitycapacitycapac
Descriptiondescriptiondescription

The slug becomes the id in the API — the permanent URL-friendly identifier.

Date & Time

FieldFrontend nameMaps to
Start DatestartDatestartDate
End DateendDateendDate
Registration Deadlinedeadlinedeadline

Location

Maps to elocation, longitude, latitude.

Pricing

FieldFrontend nameMaps to
Member Pricepricepricing.members
Non-Member PricenonMemberPricepricing.nonMembers

Both are in dollars on the form and converted to cents for the API.

Registration Questions

Uses CustomQuestions.tsx — a React Hook Form field array. Admins can add, remove, and reorder questions. Each question has:

  • Type: TEXT, SELECT, CHECKBOX, UPLOAD, WORKSHOP_SELECTION, SKILLS
  • Label (question text)
  • Required flag
  • Options (for select/checkbox types)

Partner-Specific Fields

Partner description and partnerRegistrationQuestions are configured in a separate section for partner-facing events.


Step 2: Frontend Transformation

When the admin clicks "Create Event", new.tsx transforms the frontend schema to the backend API format:

  • eventNameename
  • eventSlugid
  • capacitycapac
  • pricepricing.members (dollars)
  • nonMemberPricepricing.nonMembers (dollars)
  • customQuestionsregistrationQuestions
  • partnerCustomQuestionspartnerRegistrationQuestions

Validation runs before submission:

  • Dates are validated (start before end)
  • Required fields are checked via Zod schema validation

The transformed payload is POSTed to /events/.


Step 3: Backend Handler create

File: services/events/handler.jsexport const create

3a. Validate feedback questions

If attendeeFeedbackQuestions or partnerFeedbackQuestions are provided, the handler validates they are arrays. Then runs each array through normalizeFeedbackQuestions() from feedbackHelpers.js:

  • Checks each question has a valid type (one of SHORT_TEXT, LONG_TEXT, MULTIPLE_CHOICE, CHECKBOXES, LINEAR_SCALE)
  • Validates labels exist and are ≤500 characters
  • Assigns UUIDs to questions missing a questionId
  • Deduplicates and validates option lists for selectable types
  • Validates scale bounds for LINEAR_SCALE (integers, min < max, 0-20 range)
  • Enforces max 50 questions per form
  • Returns 406 on validation failure

3b. Inject default overall-rating question

ensureDefaultOverallRatingQuestion() prepends the locked default question to both feedback arrays:

{
  "questionId": "overall-rating",
  "type": "LINEAR_SCALE",
  "label": "How would you rate this event overall?",
  "required": true,
  "scaleMin": 1,
  "scaleMax": 10,
  "scaleMinLabel": "Poor",
  "scaleMaxLabel": "Excellent"
}

3c. Validate enabled/questions consistency

If attendeeFeedbackEnabled is true but there are no attendee questions (beyond the default), returns 406. Same for partner.

3d. Check required fields

Uses helpers.checkPayloadProps() to verify:

  • id — required
  • year — required, must be a number
  • capac — required, must be a number

3e. Check for duplicates

Attempts db.getOne(data.id, EVENTS_TABLE, { year: data.year }). If a non-empty result is returned, throws a duplicate error. This prevents overwriting an existing event with the same id + year.

3f. Assign registration question UUIDs

Calls eventHelpers.addIdsToRegistrationQuestions() on both registrationQuestions and partnerRegistrationQuestions. Each question without a questionId gets a uuid.v4().

3g. Write to DynamoDB

Constructs the full event item with all fields and calls db.create(item, EVENTS_TABLE). This uses a PutItem with ConditionExpression: "attribute_not_exists(id)" as a secondary safeguard against duplicates.

Returns 201 with the created item.


Step 4: Post-Creation

After successful creation, new.tsx redirects the admin to the edit page at /admin/event/{id}/{year}/edit where they can further refine the event.

The event is not published by defaultisPublished defaults to false. The admin must explicitly publish it from the edit page or dashboard to make it visible on the public events page.


Event Edit Flow

Page: src/pages/admin/event/[eventId]/[year]/edit.tsx

Editing follows a similar pattern:

  1. Fetches the existing event data via GET /events/{id}/{year}
  2. Transforms backend format back to frontend schema
  3. Admin makes changes in the same EventForm.tsx
  4. On save, transforms and sends PATCH /events/{id}/{year}

The update handler in the backend:

  • Fetches the existing event to verify it exists (404 if not)
  • Validates any updated feedback questions through normalizeFeedbackQuestions()
  • Assigns UUIDs to new registration questions
  • Resolves feedback enabled/questions state (merges new data with existing)
  • Builds a dynamic UpdateExpression using db.createUpdateExpression()
  • Writes with ConditionExpression: "attribute_exists(id) and attribute_exists(#vyear)"

Error Cases

ConditionHTTP StatusMessage
Duplicate event id + year500 (duplicate check)"event id and year already exists"
Missing id, year, or capac500"Missing required props"
Invalid feedback question type406"{formType}FeedbackQuestions[{i}] has unsupported type"
Feedback enabled with no questions406"Enable attendee feedback only after adding at least one attendee feedback question"
Feedback question label > 500 chars406"{prefix} exceeds 500 characters"
Duplicate question IDs406"contains duplicate questionId"

Key Files

FileRole
src/pages/admin/event/new.tsxAdmin creation page — schema transform + POST
src/pages/admin/event/[eventId]/[year]/edit.tsxAdmin edit page — fetch + PATCH
src/components/Events/EventForm.tsxShared form component with live preview
src/components/Events/EventFormSchema.tsZod validation schema
src/components/Events/CustomQuestions.tsxRegistration question field array manager
src/components/Events/EventThumbnailUploader.tsxImage upload component
services/events/handler.jscreate, updateBackend handlers
services/events/helpers.jsaddIdsToRegistrationQuestions()
services/events/feedbackHelpers.jsQuestion normalization and validation

Previous
Data Model