Event Feedback

Event Feedback: Backend API & Data Model

The backend contract for event feedback: endpoints, the complete validation pipeline in feedbackHelpers.js, submission storage, and the DynamoDB schema.


Endpoints

MethodPathAuthHandlerDescription
GET/events/{id}/{year}/feedback/{formType}PublicgetFeedbackFormGet form config
POST/events/{id}/{year}/feedback/{formType}PublicsubmitFeedbackSubmit a response
GET/events/{id}/{year}/feedback/{formType}/submissionsCognitogetFeedbackSubmissionsAdmin: list all submissions

Related admin write path (saves feedback config on the event):

  • PATCH /events/{id}/{year} — updates attendeeFeedbackQuestions, partnerFeedbackQuestions, enabled flags

All handlers are in services/events/handler.js.


formType

Accepted values: "attendee" or "partner". Any other value returns 400.

Internally, parseFormType() from feedbackHelpers.js maps this to the correct event fields:

formTypeEnabled fieldQuestions field
attendeeattendeeFeedbackEnabledattendeeFeedbackQuestions
partnerpartnerFeedbackEnabledpartnerFeedbackQuestions

Question Types

Defined as FEEDBACK_QUESTION_TYPES in feedbackHelpers.js:

TypeDescriptionValidation rules
SHORT_TEXTSingle-line textMax 280 characters
LONG_TEXTMulti-line textMax 4000 characters
MULTIPLE_CHOICESingle selection from choicesValue must be in the question's choices array
CHECKBOXESMultiple selectionsEach value must be in choices
LINEAR_SCALENumeric ratingInteger, within scaleMinscaleMax

feedbackHelpers.js — Normalization Pipeline

File: services/events/feedbackHelpers.js (~350 lines)

When an admin creates or updates feedback questions (via POST /events/ or PATCH /events/{id}/{year}), the handler runs each question array through this pipeline:

normalizeFeedbackQuestions(questions, prefix)

For each question:

  1. Type validation — must be in FEEDBACK_QUESTION_TYPES set, returns 406 otherwise
  2. Label validation — must exist and be ≤500 characters
  3. UUID assignment — if no questionId, assigns uuid.v4(). Deduplicates IDs (returns 406 on duplicate)
  4. Selectable type validation (MULTIPLE_CHOICE, CHECKBOXES):
    • Must have a choices array with ≥2 items
    • Each choice is trimmed, empty choices are filtered out
    • Duplicate choices are rejected
  5. Scale validation (LINEAR_SCALE):
    • scaleMin and scaleMax must be integers
    • scaleMin < scaleMax
    • Both must be 0–20 range
    • scaleMinLabel and scaleMaxLabel are trimmed, max 120 characters each
  6. Max count — enforces ≤50 questions per form

ensureDefaultOverallRatingQuestion(questions)

Called on both create and update. Strips any existing question with questionId === "overall-rating" and prepends:

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

This question is locked — admins cannot remove or modify it.


feedbackHelpers.js — Validation Pipeline

When a user submits feedback via POST /events/{id}/{year}/feedback/{formType}:

validateFeedbackPayload(questions, payload)

  1. Respondent validation:

    • respondentName — optional, ≤120 characters
    • respondentEmail — optional, must match email regex
  2. validateResponseObjectShape(responses) — responses must be a plain object

  3. validateNoUnknownQuestionIds(questions, responses) — every key in responses must match a questionId in the question config. Unknown keys return 400.

  4. Per-question validation — for each question in the config:

    • If required: true and the response is missing/empty → 400
    • Type-specific validation:
TypeValidatorRules
SHORT_TEXTvalidateTextResponseMust be string, ≤280 chars
LONG_TEXTvalidateTextResponseMust be string, ≤4000 chars
MULTIPLE_CHOICEvalidateMultipleChoiceResponseMust be string, must be in choices
CHECKBOXESvalidateCheckboxResponseMust be array, each value in choices
LINEAR_SCALEvalidateScaleResponseMust be number, integer, within scaleMin–scaleMax

Submit Payload

{
  "respondentName": "Jane Doe",
  "respondentEmail": "jane@example.com",
  "responses": {
    "overall-rating": 9,
    "q-networking": "Great networking opportunities",
    "q-format": ["Panels", "Workshops"]
  }
}

Response on success:

{
  "message": "Feedback submitted successfully.",
  "id": "<submission-uuid>"
}

getFeedbackForm Response

Returns a subset of the event record tailored for the public form:

{
  "id": "blueprint",
  "year": 2026,
  "ename": "Blueprint 2026",
  "description": "Product and design conference",
  "imageUrl": "https://...",
  "endDate": "2026-01-25T18:00:00.000Z",
  "isCompleted": true,
  "formType": "attendee",
  "enabled": true,
  "questions": [
    {
      "questionId": "overall-rating",
      "type": "LINEAR_SCALE",
      "label": "How would you rate this event overall?",
      "required": true,
      "scaleMin": 1,
      "scaleMax": 10,
      "scaleMinLabel": "Poor",
      "scaleMaxLabel": "Excellent"
    }
  ]
}

Submission Storage — biztechEventFeedback

FieldTypeDescription
idString (UUID)Submission UUID — partition key
eventIDStringEvent ID
yearNumberEvent year
formTypeString"attendee" or "partner"
eventFormKeyString"{id};{year};{formType}" — GSI partition key
eventIDYearString"{id};{year}"
submittedAtString (ISO)Submission timestamp — GSI sort key
responsesObjectAnswer map keyed by questionId
respondentNameStringOptional respondent name
respondentEmailStringOptional respondent email

GSIs

GSIPartition KeySort KeyUsed by
event-form-queryeventFormKeysubmittedAtgetFeedbackSubmissions — lists submissions for one event + form type
event-year-queryeventIDYearsubmittedAtQuery all submissions for an event regardless of form type

Table billing: PAY_PER_REQUEST (on-demand).


Error Responses

ConditionStatusMessage pattern
Invalid formType400"Invalid form type"
Feedback disabled403"attendee feedback is not enabled"
No questions configured400"No questions found"
Unknown questionId in response400"Unknown question IDs"
Required question missing400"is required but was not answered"
Text exceeds limit400"exceeds {limit} characters"
Choice not in options400"is not a valid choice"
Scale out of bounds400"must be between {min} and {max}"
Scale not integer400"must be a whole number"
respondentName > 120400"respondentName exceeds 120 characters"
Invalid email format400"respondentEmail is not valid"

Key Files

FilePurpose
services/events/handler.jsgetFeedbackForm, submitFeedback, getFeedbackSubmissionsEndpoint handlers
services/events/feedbackHelpers.jsNormalization (questions) + validation (responses) — ~350 lines
services/events/serverless.ymlRoute definitions, IAM, biztechEventFeedback table

Legacy feedback field

The event model still includes a legacy feedback URL field from the old external-form flow (e.g., Google Forms). The new built-in system runs off attendeeFeedbackQuestions / partnerFeedbackQuestions and the biztechEventFeedback table.


Previous
Public Forms