Add CloudFront OG shell infrastructure#11587
Draft
nbudin wants to merge 8 commits into
Draft
Conversation
Adds two new Rails endpoints and supporting infrastructure to serve per-resource Open Graph metadata to crawlers through CloudFront, while keeping the existing single_page_app#root intact for direct access. Rails: - GET /og-shell?path=<path> — minimal HTML with per-resource OG tags (event title/blurb, page cached_og_description, convention OG image). Intended for crawlers via Lambda@Edge rewrite. - GET /cdn-spa-shell — lightweight SPA shell with convention-level OG only, no @page/@event DB lookups. CloudFront serves this to regular users instead of hitting the full SPA controller. Lambda@Edge (lambda/cloudfront-og-router/): - Origin-request handler that rewrites crawler User-Agents to /og-shell and everyone else to /cdn-spa-shell. Terraform (terraform/modules/cloudfront_intercode/): - Reusable module for the full CloudFront distribution: assets origin (/packs/*), Rails origin, ordered cache behaviors, Lambda@Edge wiring, Host-header forwarding for multi-tenant convention routing. Direct access to the Rails app (without CloudFront) continues to work unchanged — single_page_app#root still serves full per-resource OG tags. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds missing pass-through CloudFront behaviours for: /users/*, /oauth/*, /reports/*, /calendars/*, /csv_exports/*, /user_con_profiles/*, /stripe_account/*, /email_forwarders/*, /sitemap.xml. Also adds a RAILS_PATH_RE bypass in the Lambda@Edge function as a second line of defence so requests to real Rails routes are never accidentally rewritten to /cdn-spa-shell or /og-shell if a new route is added without a corresponding CloudFront behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extracts the SQS queues, S3 uploads bucket, and IAM user/group/policy from neil-terraform into a reusable module alongside the existing cloudfront_intercode module. Creates: default/mailers/ahoy/dead-letter SQS queues, S3 bucket with CORS config, IAM group+user+access key with policy covering SQS, S3, SES, CloudWatch scheduler provisioning, and optional SNS/KMS access. Also creates a CloudWatch alarm that fires when the oldest message in any non-DLQ queue exceeds 10 minutes. inbox_bucket_arn, inbox_sns_topic_arn, and kms_key_arn are optional variables for resources defined outside the module. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pulls in ses-receiving.tf from neil-terraform: inbox S3 bucket with 14-day lifecycle, SES receipt rule set + active rule, SNS topic for delivery notifications with HTTPS webhook subscription, SNS feedback IAM roles, and a SES configuration set with CloudWatch event tracking. Since the inbox bucket and SNS topic are now created internally: - Removes inbox_bucket_arn, inbox_sns_topic_arn, kms_key_arn variables - Adds inbox_bucket_name and sns_notification_endpoint variables - IAM policy references internal resources directly; KMS access uses the AWS-managed alias/aws/ses key instead of a hardcoded key ID Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves the SES inbox bucket, receipt rules, SNS topic + webhook subscription, and SNS feedback IAM roles out of intercode_aws_resources and into a new ses_email_receiving module. intercode_aws_resources is restored to accepting optional inbox_bucket_arn, inbox_sns_topic_arn, and kms_key_arn variables, so it works with ses_email_receiving or any other email handling module via output wiring. ses_email_receiving exposes kms_key_arn as an output (the AWS-managed alias/aws/ses key) so callers can feed it straight into intercode_aws_resources without hardcoding key IDs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ses_email_receiving now takes iam_group_name as a variable and attaches its own aws_iam_group_policy covering S3 inbox access, sns:ConfirmSubscription, and kms:Decrypt (alias/aws/ses) directly to the app's IAM group. intercode_aws_resources no longer needs inbox_bucket_arn, inbox_sns_topic_arn, or kms_key_arn variables. It exposes iam_group_name as an output so any email module can self-attach its policy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Data-only module that fetches domain verification records from the forwardemail.net API (paginated). Outputs a domain => verification_code map for use with forwardemail_receiving_domain DNS modules. No AWS resources or IAM policies — forwardemail receiving is DNS-only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Contributor
Code Coverage Report: Only Changed Files listed
Minimum allowed coverage is |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
The SPA controller currently serves per-resource Open Graph metadata (event titles, page descriptions) by doing CMS Liquid rendering and DB lookups on every request. The goal is to eventually serve a fully static HTML shell for SPA routes, but we can't do that without some way to still give crawlers correct OG tags.
This PR adds the infrastructure to make that work with CloudFront. The existing
single_page_app#rootbehavior is completely unchanged — direct access to the Rails app still serves full per-resource OG tags as before. CloudFront is optional, not required.Changes
💻 New Rails endpoints
GET /og-shell?path=<path>— minimal bare HTML (no layout) with per-resource OG tags. Looks up the event or CMS page from the path, pullscached_og_descriptionfor pages, usesshort_blurbfor events. This is what crawlers get through CloudFront.GET /cdn-spa-shell— lightweight SPA shell with convention-level OG only (name, image, favicon). No@page/@eventDB lookups. This is what regular CloudFront users get instead of hitting the full SPA controller.💻 Lambda@Edge function (
lambda/cloudfront-og-router/)Origin-request handler that rewrites based on
User-Agent: known crawlers go to/og-shell?path=<original-path>, everyone else goes to/cdn-spa-shell. ~30 lines of JS, no dependencies.💻 Terraform module (
terraform/modules/cloudfront_intercode/)Reusable module for the full distribution. Handles the assets origin (
/packs/*with long-TTL immutable caching), pass-through behaviors for API/auth endpoints, cache policies keyed onHostheader (important for multi-tenant convention routing), and the Lambda@Edge wiring on the default behavior.Risks
The
Hostheader forwarding is the trickiest part — Intercode resolves conventions by domain, so CloudFront needs to forward the viewer'sHostto the Rails origin rather than substituting the origin hostname. The origin request policies in the module handle this, but it's worth verifying in staging before pointing any real convention domains at it.Release plan and notes
Nothing to ship yet — this is groundwork. The next step is wiring up an actual CloudFront distribution using the module and pointing a convention domain at it to validate the setup.
🤖 Generated with Claude Code