meal-order-manager/terraform/locals.tf
Adam Moussa d7ad49d00f
Some checks are pending
Deploy API / Resolve target (push) Waiting to run
Deploy API / Deploy API to (push) Blocked by required conditions
feat(api): add OpenAPI Redocly contract and VPC outputs (DEV-289) (#206)
* feat(infra): export attached VPC ids and lock prod to afterhours (DEV-289)

Prod must keep existing_vpc_id pointed at the afterhours VPC. Outputs
expose the resolved vpc_id and public subnet IDs.

Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>

* feat(api): add OpenAPI 3.1 and Redocly lint in CI (DEV-289)

Same extends: recommended ruleset and @redocly/cli 2.52.1 as
internal-portal. Documents current { error: string } JSON errors.

Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>

* fix(api): document 4xx and reject invalid form-status weeks (DEV-289)

Health, form-status, and roster document 400. form-status now maps
current and returns 400 for a week that is not current or YYYY-WNN.
Redocly treats 302 as a success response, matching the portal.

Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>

* style(test): format VPC contract assertions for ruff (DEV-289)

Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>

* fix(api): fail Redocly on missing 4xx and 2xx/3xx (DEV-289)

Promote operation-4xx-response and the 2xx-or-3xx success rule to error.
Replace unused health and roster 400s with 403, matching portal health.
Form-status keeps its real 400 for invalid week.

Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>

* fix(api): split week params and allow live menu nulls (DEV-289)

Menu and form-status take current or YYYY-WNN. Orders take YYYY-WNN or a
calendar date and reject current. Menu payloads may emit null menu_url,
calories, protein, and image_url.

Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>

* fix(infra): fail prod apply without the afterhours VPC (DEV-289)

Prod never creates the 10.60 fallback VPC. A terraform_data precondition
fails plan and apply when existing_vpc_id is empty, instead of a check
block that only warns.

Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-09-22 00:33:48 +00:00

114 lines
4.4 KiB
HCL

locals {
project = "meal-order-manager"
is_prod = var.environment == "prod"
account_id = local.is_prod ? "011934824531" : "710827005802"
hcp_project = "seahaven-${var.environment}"
hcp_workspace = "${local.project}-${var.environment}"
github_oidc_provider_arn = "arn:aws:iam::${local.account_id}:oidc-provider/token.actions.githubusercontent.com"
# Created only when existing_vpc_id is empty. Prod attaches to afterhours 10.70.
# 10.60 is the unused fallback CIDR, not a second prod VPC.
manage_vpc = var.existing_vpc_id == "" && !local.is_prod
vpc_cidr = "10.60.0.0/16"
public_subnet_cidrs = ["10.60.0.0/24", "10.60.1.0/24"]
form_bucket_name = "${local.project}-form-${local.account_id}"
reports_bucket_name = "${local.project}-reports-${local.account_id}"
table_name = "${local.project}-orders"
# Pre-DNS: use the CloudFront domain. After cutover (attach_custom_domain),
# use the public custom domain.
form_url = var.attach_custom_domain ? "https://${var.domain_name}" : "https://${aws_cloudfront_distribution.form.domain_name}"
ssm_prefix = "/${local.project}"
slack_channel_param = "${local.ssm_prefix}/slack-channel-id"
# google-client-id is created and rotated out-of-band. Terraform reads it
# (data.tf) but never owns it.
google_client_id_param = "${local.ssm_prefix}/google-client-id"
portal_cognito_issuer_param = "${local.ssm_prefix}/portal-cognito-issuer"
portal_cognito_audience_param = "${local.ssm_prefix}/portal-cognito-audience"
portal_cognito_trust_param = "${local.ssm_prefix}/portal-cognito-trust"
# shared/slack.py resolves the token by NAME, while the IAM grant is scoped to
# the ARN in var.slack_bot_secret_arn. Both must refer to the same secret.
slack_bot_secret_name = "${local.project}/slack-bot-token"
ssm_parameter_arn_wildcard = "arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter${local.ssm_prefix}/*"
cors_origins = [
"https://orders.seahaven.com",
"https://internal.seahaven.com",
"https://internal.dev.seahaven.com",
"http://localhost:5173",
"http://localhost:4173",
]
# AWS managed CachingDisabled / AllViewerExceptHostHeader. Authenticated
# portal calls must not be cached. ALB origin uses Host of the load balancer
# DNS name (http-only).
api_cache_policy_id = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad"
api_origin_request_policy_id = "b689b0a8-53d0-40ab-baf2-68738e2966ac"
cloudfront_all_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
# HTTP routes served by the Fargate Flask app. authorizer is in-process now.
api_routes = {
submit_order = {
route_key = "POST /api/submit-order"
authorizer = "NONE"
permission_source = "POST/api/submit-order"
}
menu = {
route_key = "GET /api/menu/{week}"
authorizer = "NONE"
permission_source = "GET/api/menu/*"
}
my_orders = {
route_key = "GET /api/orders/{week}"
authorizer = "NONE"
permission_source = "GET/api/orders/*"
}
form_status = {
route_key = "GET /api/form-status/{week}"
authorizer = "NONE"
permission_source = "GET/api/form-status/*"
}
roster = {
route_key = "GET /api/roster"
authorizer = "NONE"
permission_source = "GET/api/roster"
}
publish_settings = {
route_key = "GET /api/publish/settings"
authorizer = "HMAC"
permission_source = "GET/api/publish/settings"
}
publish_menu = {
route_key = "POST /api/publish/menu"
authorizer = "HMAC"
permission_source = "POST/api/publish/menu"
}
admin_orders_get = {
route_key = "GET /api/admin/orders"
authorizer = "BEARER"
permission_source = "GET/api/admin/orders"
}
admin_orders_put = {
route_key = "PUT /api/admin/orders"
authorizer = "BEARER"
permission_source = "PUT/api/admin/orders"
}
admin_orders_delete = {
route_key = "DELETE /api/admin/orders"
authorizer = "BEARER"
permission_source = "DELETE/api/admin/orders"
}
admin_summary_pdf = {
route_key = "GET /api/admin/summary-pdf"
authorizer = "BEARER"
permission_source = "GET/api/admin/summary-pdf"
}
}
}