Building the Sailor Board: Photo Upload Pipeline, Instagram Integration, and Guest Page Deployment for Queen of San Diego
What Was Done
This session consolidated a fragmented guest experience into a unified photo-sharing workflow for Queen of San Diego's post-sail guest pages. The core deliverable: a fully functional Sailor Board feature—a per-event photo gallery that aggregates user-uploaded images, videos, and same-day Instagram posts using charter-specific hashtags. We deployed infrastructure changes across three layers: the Lambda photo handler, the static guest page template, and CloudFront caching invalidation, culminating in a live feature on Keely's May 24, 2026 afternoon charter guest page.
Technical Details: The Photo Upload Pipeline
The guest page architecture centers on a two-phase upload mechanism with event-code-gated access:
- Phase 1: Client-side validation and queueing
File input handler in
/Users/cb/Documents/repos/sites/queenofsandiego.com/sailor-board/index.html(lines 362–515) accepts JPEG, PNG, WebP, HEIC, HEIF, MP4, MOV, AVI, and WebM. ThehandleFiles()function enforces a hard cap of 24 files per submission:Array.from(files).slice(0, 24).forEach(uploadFile)This prevents accidental bulk uploads and reduces Lambda invocation overhead.
- Phase 2: Event code gating
Before upload, users must enter an event code (input ID
#g-code, line 354). ThereadCode()function (line 424) validates against the charter's unlock code. Guests with the code bypass Lambda moderation entirely—photos are instantly published to the grid. Guests without the code submit to a review queue managed by the ship operator, with copy explaining: "The code keeps strangers from posting to your charter page." This two-tier system balances guest convenience with spam prevention. - Phase 3: Lambda handler dispatch
The upload endpoint routes through
tools/shipcaptaincrew/lambda_function.py, which splits on authentication state. The handler logs uploads to an S3 event folder, generates moderation emails for code-less submissions (with links back to the guest page), and immediately publishes code-authorized photos to the visible gallery.
Instagram Integration: Same-Day Hashtag Pull
The Sailor Board aggregates user uploads and Instagram posts tagged with #jada or #queenofsandiego from the event date. This happens server-side in the Lambda handler.
When a guest loads the page, the frontend makes a request:
GET https://shipcaptaincrew.queenofsandiego.com/api/g/{event_id}/photos
The Lambda handler at lambda_function.py lines ~1800–2000 (the IG fetcher logic) performs the following:
- Extracts the charter date from
event_id(e.g.,2026-05-24from2026-05-24-keely-afternoon) - Queries Instagram's API for posts matching hashtags
#jadaand#queenofsandiegoposted on that calendar date - Filters by timestamp to include only same-day posts (configurable UTC window)
- Returns a JSON array of Instagram media objects
- The frontend renders these into
#ig-grid(lines 452–459) alongside user-uploaded photos
This design choice—fetching Instagram posts server-side rather than on the client—provides two benefits: (1) Instagram API credentials are never exposed to the browser, and (2) the response is cacheable at the CloudFront level, reducing repeated API calls during high traffic periods immediately post-event.
Infrastructure: Deployment and Distribution
The Sailor Board exists as a parameterized static page deployed per charter:
- Source template:
/Users/cb/Documents/repos/sites/queenofsandiego.com/sailor-board/index.html(newly created this session) - Production URL:
https://queenofsandiego.com/g/{event_id}(e.g.,https://queenofsandiego.com/g/2026-05-24-keely-afternoon) - S3 bucket: Production site bucket (exact bucket name withheld for security; inferred from deployment logs)
- CloudFront distribution: Staging and production distributions, invalidated post-deployment
- Staging test URL: Verified at
https://staging-queenofsandiego.com/g/2026-05-24-keely-afternoonbefore prod push
Deployment workflow:
- Render guest page template with charter metadata (event ID, date, captain name)
- Upload to S3 at path
/g/{event_id}/index.html - Deploy updated Lambda (if IG fetcher logic changed) via zip archive to the
shipcaptaincrewfunction - Invalidate CloudFront cache paths:
/g/*and/api/g/* - Smoke test both staging and production URLs
This session's changes required two CloudFront invalidations—one after the initial sailor-board page deployment, and a second after the homepage update (which links to sailor board pages via the "Upload your photos" call-to-action).
Homepage Integration and UX Polish
The homepage (/Users/cb/Documents/repos/sites/queenofsandiego.com/index.html) was updated to promote the Sailor Board feature with the copy: "Thanks for sailing with us on JADA. Upload your photos and we'll add the best ones to our sailor board!" This text links to the appropriate guest page.
A critical UX fix was also made: the "Book a Sail" button now triggers the scheduling/payment modal directly, rather than returning users to the homepage. This required modifying the button handler to invoke the modal's auto-open mechanism instead of navigating. The specific change: identified the existing deep-link modal trigger code and wired the button to call the same initialization hook used by URL-based modal triggers (e.g., ?modal=booking).
Key Decisions and Rationale
- 24-file upload cap: Balances guest experience (batch uploads are convenient) with system load (reduces Lambda cold starts and S3 put operations). Instagram's native limit is 10; we doubled it to account for high-energy charters.
- Event code vs. moderation queue: Creates a lightweight incentive for captains to distribute codes (and keep the charter exclusive) while still allowing open guest participation. Guests who lose their code can still upload and await review.
- Server-side Instagram fetch: Protects API credentials and enables CloudFront caching; avoids browser-side CORS issues and repeated API calls on page reload.
- Per-event page URLs: Keeps bookmarking and social sharing granular to individual charters, which is better for brand recall and guest community-building than a single cross-charter board.