mirror of
https://github.com/Sea-Haven-Industries/meal-order-manager.git
synced 2026-09-30 19:23:13 +00:00
* fix(auth): require Google authentication in cloud mode Remove the public shared-key mechanism and fail closed on Google auth while adding submit-route throttling. * fix(auth): address review follow-ups Fail closed on whitespace-only Google configuration and centralize shared authentication behavior. * test(auth): use non-secret Google client fixture Make the public test identifier explicit so secret scanning does not misclassify it as an API key. * test(auth): avoid OAuth-shaped fixture Use a format-neutral audience value so secret scanning can distinguish the fixture from a real client identifier. * chore(security): suppress public OAuth fixture Document the scanner false positive without suppressing any runtime credential flow.
160 lines
5.8 KiB
YAML
160 lines
5.8 KiB
YAML
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'
|
|
run: |
|
|
CRON="${{ github.event.schedule }}"
|
|
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'
|
|
|
|
- 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@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6
|
|
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
|
|
|
|
- name: Get stack outputs
|
|
if: env.SKIP_RUN != 'true'
|
|
id: stack
|
|
run: |
|
|
API_URL=$(aws cloudformation describe-stacks \
|
|
--stack-name meal-order-manager \
|
|
--query 'Stacks[0].Outputs[?OutputKey==`ApiUrl`].OutputValue' \
|
|
--output text)
|
|
FORM_BUCKET=$(aws cloudformation describe-stacks \
|
|
--stack-name meal-order-manager \
|
|
--query 'Stacks[0].Outputs[?OutputKey==`FormBucketName`].OutputValue' \
|
|
--output text)
|
|
DIST_ID=$(aws cloudformation describe-stacks \
|
|
--stack-name meal-order-manager \
|
|
--query 'Stacks[0].Outputs[?OutputKey==`DistributionId`].OutputValue' \
|
|
--output text)
|
|
FORM_URL=$(aws cloudformation describe-stacks \
|
|
--stack-name meal-order-manager \
|
|
--query 'Stacks[0].Outputs[?OutputKey==`FormUrl`].OutputValue' \
|
|
--output text)
|
|
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
|
|
run: |
|
|
RESULT=$(aws dynamodb get-item \
|
|
--table-name meal-order-manager-orders \
|
|
--key '{"PK":{"S":"CONFIG"},"SK":{"S":"SETTINGS"}}' \
|
|
--output json 2>/dev/null || echo '{}')
|
|
BULK=$(echo "$RESULT" | python3 -c "
|
|
import sys, json
|
|
d = json.load(sys.stdin)
|
|
print(d.get('Item',{}).get('bulk_discount_percent',{}).get('N','0'))
|
|
" 2>/dev/null || echo "0")
|
|
SUBSIDY=$(echo "$RESULT" | python3 -c "
|
|
import sys, json
|
|
d = json.load(sys.stdin)
|
|
print(d.get('Item',{}).get('company_subsidy_percent',{}).get('N','0'))
|
|
" 2>/dev/null || echo "0")
|
|
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'
|
|
run: |
|
|
python3 src/server/generate_form.py \
|
|
--api-url "${{ steps.stack.outputs.api_url }}" \
|
|
--bulk-discount "${{ steps.discount.outputs.bulk_discount }}" \
|
|
--company-subsidy "${{ steps.discount.outputs.company_subsidy }}" \
|
|
--google-client-id "${{ steps.google.outputs.client_id }}"
|
|
|
|
- name: Upload menu to DynamoDB
|
|
if: env.SKIP_RUN != 'true'
|
|
run: python3 scripts/upload_menu.py
|
|
|
|
- name: Upload form to S3
|
|
if: env.SKIP_RUN != 'true'
|
|
run: |
|
|
WEEK=$(date +%Y-W%U)
|
|
aws s3 cp "output/order-form-$WEEK.html" \
|
|
"s3://${{ steps.stack.outputs.form_bucket }}/index.html" \
|
|
--content-type "text/html" \
|
|
--cache-control "no-cache"
|
|
aws s3 cp "output/order-form-$WEEK.html" \
|
|
"s3://${{ steps.stack.outputs.form_bucket }}/archive/$WEEK.html" \
|
|
--content-type "text/html"
|
|
|
|
- name: Invalidate CloudFront cache
|
|
if: env.SKIP_RUN != 'true'
|
|
run: |
|
|
aws cloudfront create-invalidation \
|
|
--distribution-id "${{ steps.stack.outputs.dist_id }}" \
|
|
--paths "/index.html"
|
|
|
|
- name: Notify Slack
|
|
if: env.SKIP_RUN != 'true'
|
|
run: python3 scripts/notify_slack.py "${{ steps.stack.outputs.form_url }}"
|