Consolidating Event Management Infrastructure: Building a Unified Ops Dashboard for Multi-Site Event Operations
Over the past development session, we've consolidated scattered tooling and payment infrastructure into a single operations dashboard at https://ops.queenofsandiego.com/. This post details the technical architecture, infrastructure decisions, and deployment strategy for what is essentially a control center for managing event bookings, payments, and site deployments across multiple CloudFront-distributed properties.
The Problem We Solved
Event operations were spread across multiple Google Sheets, Stripe dashboards, AWS consoles, and loosely-organized HTML pages. When needing to quickly access a payment link, check deployment status, or reference event configuration, operators had to context-switch between tools. The solution: a centralized ops hub that aggregates links, payment flows, and infrastructure references into a single, purpose-built dashboard.
Architecture: Static Site + API Aggregation Pattern
The ops dashboard follows a straightforward architecture:
- Static HTML frontend —
/Users/cb/Documents/repos/sites/ops/index.htmlserves as the single source of truth - S3 + CloudFront distribution — Deployed to an S3 bucket for global CDN delivery with cache invalidation on updates
- Link aggregation layer — Rather than building a backend service, we aggregated existing Stripe payment links, Google Sheet URLs, AWS console links, and DNS records into semantic sections
- Credential isolation — Sensitive credentials (Stripe API keys, AWS credentials) remain in
.envfiles, never committed to the ops page itself
This approach trades dynamic functionality for operational simplicity. All links are maintained in version control and updated via git + S3 deployment, which is appropriate for infrastructure that changes infrequently but must be bulletproof when accessed.
Technical Implementation Details
Stripe Integration: Payment Links Instead of Custom Forms
Rather than building custom payment collection logic, we leveraged Stripe's Payment Links feature. For the $1,560 JADA charter deposit:
stripe.products.create({
name: "JADA Charter — $1,560.00",
type: "service"
})
stripe.prices.create({
product: "prod_...",
unit_amount: 156000, // in cents
currency: "usd"
})
stripe.paymentLinks.create({
line_items: [{price: "price_...", quantity: 1}]
})
The resulting link — https://buy.stripe.com/dRmdR29XnfJlfdC6A2fjG0f — appears as a prominent CTA button on the ops page. This outsources PCI compliance, currency handling, and subscription management to Stripe's hosted checkout, reducing our attack surface and operational burden.
Event Site Deployments: S3 + CloudFront Invalidation Pattern
Multiple event subdomains (birthday.queenofsandiego.com, gipsykings.queenofsandiego.com, birthday-afternoon.queenofsandiego.com) follow an identical deployment pattern:
# Deploy updated HTML to S3
aws s3 cp /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/sites/birthday/index.html \
s3://birthday-queenofsandiego-com/index.html
# Invalidate CloudFront cache
aws cloudfront create-invalidation \
--distribution-id E2XXXXX \
--paths "/*"
Each event site has:
- A dedicated S3 bucket (e.g.,
birthday-queenofsandiego-com) - A CloudFront distribution ID tracked in
ACTIVE.mdhandoff documentation - A Route53 CNAME record pointing the subdomain to the CloudFront distribution
- A sticky modal booking form injected via JavaScript (shared across all event sites for consistency)
This pattern allows us to ship updates in ~3 commands: git commit, S3 sync, CloudFront invalidate. No Lambda functions, no build pipelines, no CI/CD overhead.
Multi-Event Configuration: Centralized events.json
Rather than hardcoding event details in each HTML file, we maintain a shared configuration file that the booking modal reads at runtime. For example, the Gipsy Kings concert event includes:
{
"slug": "gipsykings",
"title": "Gipsy Kings Concert Night",
"date": "2025-06-14",
"capacity": 75,
"pricePerPerson": 125,
"description": "...",
"heroImages": ["hero-1.jpg", "hero-2.jpg", "hero-3.jpg"]
}
This decouples event metadata from presentation code, allowing marketing to update dates/pricing without touching HTML.
Infrastructure Resource Inventory
The ops page now serves as a master reference for:
- AWS CloudFront distributions — 7+ distribution IDs for ops, birthday, birthday-afternoon, gipsykings, JADA site, etc.
- S3 buckets — Organized by domain (e.g.,
queenofsandiego-com,sailjada-com) - Route53 hosted zones — DNS records for all subdomains (ops.queenofsandiego.com, birthday.queenofsandiego.com, etc.)
- DynamoDB tables —
jada-crew-dispatchfor crew scheduling - Google Apps Script projects — Rady Shell booking engine (separate from general GAS utilities)
- Stripe products/prices — $1,560 deposit, $125 birthday sails, future charter options
- Email infrastructure — SES SMTP for bookings@queenofsandiego.com, ImprovMX for email aliases
Each resource includes direct links to the AWS/Stripe/Google console where applicable, eliminating the need to search through account dashboards.
Key Architectural Decisions and Trade-offs
Static Over Dynamic
We chose static HTML + version control over a database-backed dashboard because:
- Operators need instant access even if a database or API is down
- Every change is auditable in git history
- No additional secrets management needed (no database credentials, API auth tokens)
- Deployment is a single CloudFront invalidation, cacheable indefinitely
Centralized vs. Decentralized Event Configuration
We store event details in a shared events.json rather than per-page config files because:
- Consistency — One source of truth for capacity, pricing, and availability
- Bulk operations — Can update all events' pricing rules with a single file change
- Analytics — A single JSON schema makes it easier to aggregate booking data across events
Payment Links Over Custom Forms
Stripe Payment Links outsource:
- PCI compliance (Stripe's responsibility, not ours)