Automated weekly meal ordering from Redefine Meals — scraper, order form, payroll deductions
Find a file
Adam Moussa 81543c3b7f Add Google OAuth, server-side discounts, and Slack order confirmations
Submit order Lambda now verifies Google ID tokens via tokeninfo endpoint,
calculates two-tier discount pricing server-side, and async-invokes the
Slack notifier for per-employee order confirmation DMs. Deadlines updated
to Thursday 11:59pm across all Slack messages.
2026-05-13 11:35:56 -04:00
.github/workflows Fix deploy workflow to match org CD pattern (#6) 2026-05-12 19:39:34 -04:00
functions Add Google OAuth, server-side discounts, and Slack order confirmations 2026-05-13 11:35:56 -04:00
scripts Fix ruff lint and format violations, update README (#5) 2026-05-12 19:31:27 -04:00
src Add Google OAuth, server-side discounts, and Slack order confirmations 2026-05-13 11:35:56 -04:00
.gitignore Initial commit: meal ordering automation system (#1) 2026-05-12 18:25:15 -04:00
config.json Add discount settings and two-tier pricing to order aggregation 2026-05-13 11:35:48 -04:00
README.md Switch to orders.seahaven.com, add roster dropdown, fix deadline (#9) 2026-05-12 20:16:46 -04:00
requirements.txt Initial commit: meal ordering automation system (#1) 2026-05-12 18:25:15 -04:00
samconfig.toml.example Switch to orders.seahaven.com, add roster dropdown, fix deadline (#9) 2026-05-12 20:16:46 -04:00
slack-app-manifest.yml Fix Slack manifest long_description to meet 174-char minimum (#4) 2026-05-12 19:22:46 -04:00
template.yaml Switch to orders.seahaven.com, add roster dropdown, fix deadline (#9) 2026-05-12 20:16:46 -04:00

meal-order-manager

Automates weekly meal ordering from Redefine Meals for Sea Haven Industries employees. Scrapes the menu, generates an order form, collects individual orders, and produces payroll deduction reports.

Architecture

Monday 8am ET                        Employees (Mon–Thu)               Thursday 6pm ET
┌─────────────────┐                  ┌──────────────────┐              ┌──────────────────┐
│  GitHub Actions  │                 │  orders.seahaven │              │  EventBridge      │
│  - Scrape menu   │────S3 upload───▶│  ind.com         │              │  - Close form     │
│  - Generate form │  + DynamoDB     │  (CloudFront+S3) │──POST───┐    │  - Aggregate      │
│  - Slack notify  │                 └──────────────────┘         │    │  - Slack summary  │
└─────────────────┘                                               ▼    └──────────────────┘
                                                          ┌──────────┐
Monday 7am ET                                             │ API GW + │
┌──────────────────┐                                      │ Lambda   │
│  EventBridge     │                                      │ submit   │
│  - Email payroll │                                      └────┬─────┘
│    deductions    │                                           ▼
└──────────────────┘                                      ┌──────────┐
                                                          │ DynamoDB │
Thu 10am: Slack DM                                        │ orders   │
reminders to employees                                    └──────────┘
who haven't ordered

Weekly Flow

When What How
Monday 6:55am ET Sync employee roster from Slack channel membership EventBridge → Lambda → DynamoDB
Monday 7am ET Email previous week's payroll deductions to payroll@ EventBridge → Lambda → SES
Monday 8am ET Scrape menu, generate form, upload to S3, post link to Slack GitHub Actions cron
Mon–Thu Employees visit orders.seahaven.com and submit orders S3 static form → API Gateway → Lambda → DynamoDB
Thursday 10am ET DM employees who haven't ordered yet EventBridge → Lambda → Slack DM
Thursday 6pm ET Close form, aggregate orders, post Redefine order summary to Slack EventBridge → Lambda chain

AWS Resources

Stack name: meal-order-manager (us-east-1)

  • S3 — meal-order-manager-form-* (static form hosting), meal-order-manager-reports-* (CSV reports)
  • CloudFront — HTTPS distribution with custom domain orders.seahaven.com
  • DynamoDB — meal-order-manager-orders (orders, menu, roster, config)
  • API Gateway — HttpApi for order submission
  • Lambda — 6 functions: submit-order, close-form, aggregate-orders, slack-notifier, sync-roster, email-report
  • EventBridge — scheduled rules (dual EST/EDT) for close, reminders, payroll email
  • Secrets Manager — Slack bot token, form API key
  • SES — payroll deduction emails

Setup

Local development

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
playwright install chromium

Deploy to AWS

cp samconfig.toml.example samconfig.toml
# Edit samconfig.toml with your certificate ARN, etc.
sam build
sam deploy

Post-deploy

  1. Create the Slack bot token secret: aws secretsmanager create-secret --name meal-order-manager/slack-bot-token --secret-string "xoxb-..."
  2. Create the form API key secret: aws secretsmanager create-secret --name meal-order-manager/form-api-key --secret-string "$(openssl rand -hex 32)"
  3. Update the Slack channel SSM parameter: aws ssm put-parameter --name /meal-order-manager/slack-channel-id --value "C0XXXXXXX" --overwrite
  4. Verify SES sender identity for adam@seahavenind.com
  5. Set up DNS: CNAME orders.seahaven.com → CloudFront distribution domain
  6. Roster syncs automatically from Slack channel members (runs Monday 6:55am ET), or seed manually: python3 scripts/seed_roster.py

Local Workflow (no AWS)

The scraper, form generator, Flask server, and aggregator still work locally:

python3 src/scraper/scrape_menu.py        # scrape menu
python3 src/server/generate_form.py       # generate form (local mode)
python3 src/server/app.py                 # serve on localhost:5050
python3 src/aggregator/aggregate.py       # generate CSV reports

Configuration

config.json (local dev):

  • menu_url — Redefine Meals menu URL
  • order_deadline — displayed on the form
  • roster — employee list (name, email, slack_user_id)
  • output_dir / orders_dir — local output paths

Project Structure

meal-order-manager/
├── .github/workflows/
│   ├── weekly-menu.yml          # Monday cron: scrape + publish + notify
│   ├── ci.yml                   # PR checks
│   └── deploy.yml               # Push to main: sam deploy
├── src/
│   ├── scraper/                 # Playwright menu scraper
│   ├── server/                  # Form generator + local Flask server
│   ├── aggregator/              # Order aggregation + CSV reports
│   └── shared/shared/           # Lambda layer (db, secrets, slack helpers)
├── functions/                   # Lambda handlers
│   ├── submit_order/
│   ├── close_form/
│   ├── aggregate_orders/
│   ├── slack_notifier/
│   ├── sync_roster/
│   └── email_report/
├── scripts/                     # CI/CD helper scripts
│   ├── upload_menu.py
│   ├── notify_slack.py
│   └── seed_roster.py
├── template.yaml                # SAM template
├── samconfig.toml.example
├── slack-app-manifest.yml       # Slack app manifest (paste into api.slack.com)
└── config.json