name: Weekly Menu Scrape & Publish on: schedule: # Monday 7:30am EST = 12:30 UTC - cron: '30 12 * * 1' # Monday 7:30am EDT = 11:30 UTC - cron: '30 11 * * 1' workflow_dispatch: permissions: id-token: write contents: read concurrency: group: weekly-menu cancel-in-progress: false jobs: scrape-and-publish: runs-on: ubuntu-latest # A hung Playwright scrape would otherwise hold the weekly-menu concurrency # group for the 360-minute default. timeout-minutes: 30 env: AWS_REGION: us-east-1 steps: - name: Timezone guard if: github.event_name == 'schedule' env: CRON: ${{ github.event.schedule }} run: | OFFSET=$(TZ='America/New_York' date +%z) echo "Cron: $CRON | Eastern offset: $OFFSET" if { [ "$OFFSET" = "-0400" ] && [ "$CRON" = "30 12 * * 1" ]; } || \ { [ "$OFFSET" = "-0500" ] && [ "$CRON" = "30 11 * * 1" ]; }; then echo "Wrong-timezone cron fired — skipping" echo "SKIP_RUN=true" >> "$GITHUB_ENV" fi - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: env.SKIP_RUN != 'true' - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 if: env.SKIP_RUN != 'true' with: python-version: '3.12.14' - name: Install dependencies if: env.SKIP_RUN != 'true' run: | pip install -r requirements.txt playwright install chromium --with-deps - name: Configure AWS credentials if: env.SKIP_RUN != 'true' uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0 with: role-to-assume: ${{ secrets.AWS_WEEKLY_MENU_ROLE_ARN }} aws-region: us-east-1 - name: Scrape menu if: env.SKIP_RUN != 'true' run: python3 src/scraper/scrape_menu.py # Deploy targets come from Parameter Store, written by Terraform # (terraform/ssm.tf). They replace the CloudFormation stack outputs this # job used to read; there is no CloudFormation stack any more. - name: Get deploy parameters if: env.SKIP_RUN != 'true' id: stack run: | set -euo pipefail get_param() { aws ssm get-parameter --name "$1" --query 'Parameter.Value' --output text } API_URL=$(get_param /meal-order-manager/deploy/api-url) FORM_BUCKET=$(get_param /meal-order-manager/deploy/form-bucket) DIST_ID=$(get_param /meal-order-manager/deploy/distribution-id) FORM_URL=$(get_param /meal-order-manager/deploy/form-url) for v in "$API_URL" "$FORM_BUCKET" "$DIST_ID" "$FORM_URL"; do if [ -z "$v" ] || [ "$v" = "None" ]; then echo "A /meal-order-manager/deploy/* parameter is missing; has Terraform been applied?" >&2 exit 1 fi done echo "api_url=$API_URL" >> "$GITHUB_OUTPUT" echo "form_bucket=$FORM_BUCKET" >> "$GITHUB_OUTPUT" echo "dist_id=$DIST_ID" >> "$GITHUB_OUTPUT" echo "form_url=$FORM_URL" >> "$GITHUB_OUTPUT" - name: Get discount settings if: env.SKIP_RUN != 'true' id: discount env: API_URL: ${{ steps.stack.outputs.api_url }} run: | set -euo pipefail MEALS_PUBLISH_KEY=$(aws ssm get-parameter \ --name /meal-order-manager/publish-key \ --with-decryption \ --query 'Parameter.Value' \ --output text) SETTINGS=$(MEALS_PUBLISH_KEY="$MEALS_PUBLISH_KEY" python3 scripts/upload_menu.py settings --api-url "$API_URL") BULK=$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["bulk_discount_percent"])' "$SETTINGS") SUBSIDY=$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["company_subsidy_percent"])' "$SETTINGS") echo "bulk_discount=$BULK" >> "$GITHUB_OUTPUT" echo "company_subsidy=$SUBSIDY" >> "$GITHUB_OUTPUT" - name: Get Google Client ID if: env.SKIP_RUN != 'true' id: google run: | GOOGLE_CLIENT_ID=$(aws ssm get-parameter \ --name /meal-order-manager/google-client-id \ --query 'Parameter.Value' \ --output text) if [ "$GOOGLE_CLIENT_ID" = "None" ] || [ -z "$GOOGLE_CLIENT_ID" ]; then echo "Google client ID is required for cloud form generation" >&2 exit 1 fi echo "client_id=$GOOGLE_CLIENT_ID" >> "$GITHUB_OUTPUT" - name: Generate order form if: env.SKIP_RUN != 'true' env: BULK_DISCOUNT: ${{ steps.discount.outputs.bulk_discount }} COMPANY_SUBSIDY: ${{ steps.discount.outputs.company_subsidy }} GOOGLE_CLIENT_ID: ${{ steps.google.outputs.client_id }} run: | # Relative /api paths so the form stays same-origin on CloudFront # after the ALB origin swap. Do not bake the ALB DNS into HTML. python3 src/server/generate_form.py \ --bulk-discount "$BULK_DISCOUNT" \ --company-subsidy "$COMPANY_SUBSIDY" \ --google-client-id "$GOOGLE_CLIENT_ID" - name: Publish menu through API if: env.SKIP_RUN != 'true' env: API_URL: ${{ steps.stack.outputs.api_url }} run: | set -euo pipefail MEALS_PUBLISH_KEY=$(aws ssm get-parameter \ --name /meal-order-manager/publish-key \ --with-decryption \ --query 'Parameter.Value' \ --output text) MEALS_PUBLISH_KEY="$MEALS_PUBLISH_KEY" python3 scripts/upload_menu.py publish --api-url "$API_URL" - name: Upload form to S3 if: env.SKIP_RUN != 'true' env: FORM_BUCKET: ${{ steps.stack.outputs.form_bucket }} run: | WEEK=$(date +%Y-W%U) aws s3 cp "output/order-form-$WEEK.html" \ "s3://${FORM_BUCKET}/index.html" \ --content-type "text/html" \ --cache-control "no-cache" aws s3 cp "output/order-form-$WEEK.html" \ "s3://${FORM_BUCKET}/archive/$WEEK.html" \ --content-type "text/html" - name: Invalidate CloudFront cache if: env.SKIP_RUN != 'true' env: DIST_ID: ${{ steps.stack.outputs.dist_id }} run: | aws cloudfront create-invalidation \ --distribution-id "$DIST_ID" \ --paths "/index.html" - name: Notify Slack if: env.SKIP_RUN != 'true' env: FORM_URL: ${{ steps.stack.outputs.form_url }} run: python3 scripts/notify_slack.py "$FORM_URL"