Patching Calendar Events and Validating Guest/Crew Portal Pages for Weekend Charters

This post covers a Friday afternoon session focused on synchronizing the JADA Internal calendar with crew assignments, validating guest and crew portal pages for the weekend, and deploying corrections to our S3-backed static site infrastructure.

The Problem: Calendar-to-Portal Sync Failures

Our charter operations depend on three interconnected systems:

  • JADA Internal Calendar (Google Calendar, iCal feed) — source of truth for event metadata, crew assignments, and timing
  • Guest Portal Pages (S3 + CloudFront) — static HTML deployed per-charter with arrival times, captain names, and logistics
  • Crew Assignment System (DynamoDB via Lambda) — crew dispatch, role matching, and availability tracking

Manual crew edits and last-minute captain changes weren't propagating consistently across all three systems. Specifically, calendar event descriptions lacked crew details, and guest pages showed stale captain assignments. This session aimed to close those gaps.

Technical Investigation: Tracing the Data Flow

We started by pulling the JADA Internal calendar via its iCal endpoint and parsing upcoming events through June 16:

curl -s 'https://calendar.google.com/calendar/ical/[calendar-id]/public/basic.ics' \
  | grep -A 5 'VEVENT' | head -50

This revealed that event descriptions were missing crew roster details. Next, we checked DynamoDB's crew dispatch table:

aws dynamodb scan \
  --table-name jada-crew-dispatch \
  --projection-expression 'eventId,eventDate,assignedCrew,role' \
  --region us-west-2

DynamoDB had the crew data, but it wasn't reflected in the calendar. We then inspected the three guest portal pages deployed to S3:

aws s3 ls s3://sailjada-guest-portals/ --recursive | grep -E '(quinn|jonathan|danika)'

Three guest pages existed:

  • s3://sailjada-guest-portals/quinn-guest.html
  • s3://sailjada-guest-portals/jonathan-afternoon.html
  • s3://sailjada-guest-portals/danika-guest.html

We pulled each via CloudFront and verified HTTP status:

for page in quinn-guest jonathan-afternoon danika-guest; do
  curl -I https://charters.sailjada.com/$page.html
done

All returned 200 OK, but the HTML content showed inconsistencies: danika-guest.html had an outdated "Arrive by 2:00 PM" when the calendar showed 3:00 PM–7:00 PM (paid). Quinn's page was missing crew captain details.

The Root Cause: Manual Edits Without Sync

The issue stemmed from a workflow gap. When crew assignments changed (e.g., swapping captains or adding/removing crew members), we updated:

  1. The calendar event description (manually)
  2. The static guest pages (manual HTML edits)
  3. DynamoDB crew dispatch (Lambda PATCH)

But these updates weren't synchronized. A captain name in the calendar description didn't match the guest page. Arrival times drifted.

The Fix: Patching Calendar Events and Regenerating Pages

Step 1: Update Calendar Event Descriptions via Google Calendar API

We wrote a Lambda function to patch calendar events directly. The function takes an event ID, crew roster, and timing details, then calls the Google Calendar API:

PATCH https://www.googleapis.com/calendar/v3/calendars/[calendar-id]/events/[event-id]

Request body (simplified):

{
  "description": "Charter: Quinn\nCaptain: Gene\nCrew: [Angelia pending]\nArrive: 9:00 AM\nDepart: 1:00 PM",
  "start": { "dateTime": "2026-05-30T09:00:00Z" },
  "end": { "dateTime": "2026-05-30T13:00:00Z" }
}

We discovered that the event ID format in our DynamoDB crew dispatch table was inconsistent—some used long IDs, others used short codes. We standardized on the full Google Calendar event ID (e.g., q1a2b3c4d5e6f7g8h9i0j1k2l@google.com).

Step 2: Regenerate Guest Portal Pages from Templates

Rather than hand-editing HTML, we stored a template in /templates/guest-page-template.html and used a Node.js script to populate it from our crew dispatch data:

node scripts/generate-guest-pages.js \
  --crew-table jada-crew-dispatch \
  --output-dir /tmp/guest-pages \
  --events-start 2026-05-30 \
  --events-end 2026-05-31

The script:

  • Fetched all events from DynamoDB for the date range
  • Extracted crew assignments, captain names, and timing
  • Injected values into the HTML template
  • Generated three separate pages (one per charter)

Step 3: Deploy to S3 and Invalidate CloudFront

Once pages were regenerated locally with correct data, we deployed them to S3:

aws s3 cp /tmp/guest-pages/quinn-guest.html \
  s3://sailjada-guest-portals/quinn-guest.html \
  --content-type text/html \
  --cache-control "max-age=3600"

aws s3 cp /tmp/guest-pages/jonathan-afternoon.html \
  s3://sailjada-guest-portals/jonathan-afternoon.html \
  --content-type text/html \
  --cache-control "max-age=3600"

aws s3 cp /tmp/guest-pages/danika-guest.html \
  s3://sailjada-guest-portals/danika-guest.html \
  --content-type text/html \
  --cache-control "max-age=3600"

Then we invalidated the CloudFront distribution (ID: E2AB3CDEF4GHI) to force edge caches to refresh:

aws cloudfront create-invalidation \
  --distribution-id E2AB3CDEF4GHI \
  --paths "/*quinn-guest.html" "/*jonathan-afternoon.html" "/*danika-guest.html"

CloudFront propagation to all edge locations typically completes within 30–60 seconds.

Validation: Confirming Changes Across All Systems

We verified the fix by checking all three systems:

  1. Calendar: Parsed the iCal feed and confirmed crew names appeared in event descriptions.
  2. DynamoDB: Queried the crew dispatch table and verified