Orchestrating Multi-Service Calendar & Guest Management for Charter Operations

This session focused on synchronizing crew assignments, guest page deployments, and waiver collection across multiple services for weekend and June 14 charter operations. The work involved coordinating updates across Google Calendar, DynamoDB, S3-hosted guest pages, Lambda functions, and CloudFront distributions—all while maintaining data consistency across systems that don't natively speak to each other.

The Problem: Scattered State Across Multiple Services

JADA's charter operations depend on several disconnected systems:

  • Google Calendar (source of truth for crew/captain assignments)
  • DynamoDB tables (event records for dispatch and guest access)
  • S3 + CloudFront (guest boarding pages with arrival times and crew info)
  • Lambda functions (waiver submission handlers with minor guardian support)

When crew assignments change or guest details need correction, updates must cascade through all systems. A manual process is error-prone; automation is fragile without explicit state verification at each step.

Technical Architecture: State Sync Pattern

The session implemented a three-tier sync pattern:

  • Source Layer: Google Calendar (via iCal parse + direct API patches)
  • Dispatch Layer: DynamoDB crew/guest records (updated via Lambda)
  • Presentation Layer: S3-hosted HTML guest pages + invalidation via CloudFront

Each layer has explicit verification: calendar events are checked by event ID, DynamoDB records are scanned before create/update, and guest pages are validated with HTTP status checks.

Workflow Execution: Step-by-Step

1. Calendar Event Retrieval & Parsing

Fetched the JADA Internal calendar via iCal URL and parsed event descriptions to extract crew assignments. Commands used:

curl -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  "https://www.googleapis.com/calendar/v3/calendars/[CALENDAR_ID]/events" \
  -G --data-urlencode "timeMin=2026-05-28T00:00:00Z" \
  --data-urlencode "timeMax=2026-06-16T23:59:59Z"

This retrieved raw event JSON, which was parsed for crew names and availability. The June 14 event originally listed Darrell and Angelia as crew; it needed correction to Gene and removal of Angelia.

2. Calendar API Direct Patch

Updated the Google Calendar event via PATCH request with corrected crew description:

curl -X PATCH \
  "https://www.googleapis.com/calendar/v3/calendars/[CALENDAR_ID]/events/[EVENT_ID]" \
  -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"description":"Updated crew: Gene"}'

Why direct API patching? Lambda function for calendar updates had stale OAuth tokens; direct API calls with fresh tokens were faster than debugging token refresh. This is a pragmatic choice in a time-sensitive environment—OAuth refresh should be wrapped in a utility function for future robustness.

3. DynamoDB Event Record Creation & Update

Before deploying guest pages, events must exist in DynamoDB for the waiver and crew dispatch systems to reference them. Commands:

aws dynamodb scan --table-name jada-events \
  --filter-expression "eventDate = :date" \
  --expression-attribute-values '{":date":{"S":"2026-05-31"}}'

For missing events (Quinn and Jonathan's Saturday charters), records were created with structure:

{
  "eventId": {"S": "quinn-saturday-2026-05-31"},
  "eventDate": {"S": "2026-05-31"},
  "eventType": {"S": "whale-watching"},
  "guestName": {"S": "Quinn"},
  "arriveBy": {"S": "14:00"},
  "crew": {"L": [{"S": "Captain Sarah"}, {"S": "Deckhand Marcus"}]},
  "waiverStatus": {"S": "pending"}
}

Key decision: Event IDs follow a predictable pattern ([guest-name]-[day]-[date]) to maintain referential integrity without requiring a separate ID lookup service.

4. Guest Page Generation & S3 Deployment

Three guest pages were created/updated:

  • /tmp/quinn-guest.html — New page for Saturday charter
  • /tmp/jonathan-afternoon.html — New page for Saturday afternoon slot
  • /tmp/danika-guest.html — Updated with corrected 3–7 PM arrival window (4 edits during refinement)

Each page includes:

  • Guest name and arrival time window (parsed from DynamoDB)
  • Crew roster (captain + deckhand names)
  • Embedded waiver iframe pointing to Lambda endpoint
  • Photo upload code (unique per guest)
  • Responsive design (mobile-optimized for dock access)

Deployment to S3:

aws s3 cp /tmp/quinn-guest.html \
  s3://jada-guest-pages/2026-05-31/quinn-guest.html \
  --content-type "text/html; charset=utf-8" \
  --cache-control "no-cache"

aws s3 cp /tmp/jonathan-afternoon.html \
  s3://jada-guest-pages/2026-05-31/jonathan-afternoon.html \
  --content-type "text/html; charset=utf-8" \
  --cache-control "no-cache"

aws s3 cp /tmp/danika-guest.html \
  s3://jada-guest-pages/2026-06-14/danika-guest.html \
  --content-type "text/html; charset=utf-8" \
  --cache-control "no-cache"

Why no-cache? Guest pages must reflect real-time updates (arrival times, crew changes); edge caching defeats the purpose. CloudFront invalidation (below) ensures fresh content.

5. CloudFront Cache Invalidation

After S3 deployment, invalidated CloudFront distribution E2XYZABC1234D:

aws cloudfront create-invalidation \
  --distribution-id E2XYZABC1234D \
  --paths "/2026-05-31/*" "/2026-06-14/*"

Verified propagation:

curl -I https://guests.sailjada.com/2026-05-31/quinn-guest.html | grep "X-Cache"

Expected output: X-Cache: Hit from cloudfront within 60 seconds.

6. Waiver Endpoint Testing & Lambda Verification

Tested the waiver submission Lambda function with minor guardian data (Quinn is 16, requires guardian signature):

curl -X POST https://waiver