Commit graph

41 commits

Author SHA1 Message Date
28ec13584a
fix(auth): distinguish portal verification outages 2026-09-15 17:52:49 -04:00
387bf64b6b
feat(auth): accept portal Cognito ID tokens 2026-09-15 17:49:36 -04:00
Adam Moussa
8489dc3986
feat(meals): dual-read week keys and meal-to-Flex join (PLAT-134) (#182)
Some checks failed
Build Lambda Layer / build (push) Has been cancelled
* feat(meals): dual-read week keys and meal-to-Flex join (PLAT-134)

* style(meals): apply ruff format (PLAT-134)

* docs(meals): clarify week-key dual-read is same-instant (PLAT-134)
2026-09-02 00:01:44 +00:00
renovate[bot]
432203f04d
chore(deps): update dependency boto3 to v1.43.78 (#168) 2026-08-24 20:32:21 +00:00
Adam Moussa
9407a3e6d7
chore(deps): batch minor and patch updates (#165)
Some checks are pending
Build Lambda Layer / build (push) Waiting to run
2026-08-24 19:47:00 +00:00
dependabot[bot]
ed5249c20e
chore(deps): bump the minor-and-patch group (#145)
Bumps the minor-and-patch group in /src/shared with 2 updates: [boto3](https://github.com/boto/boto3) and [fpdf2](https://github.com/py-pdf/fpdf2).


Updates `boto3` from 1.43.66 to 1.43.71
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.66...1.43.71)

Updates `fpdf2` from 2.8.7 to 2.8.8
- [Release notes](https://github.com/py-pdf/fpdf2/releases)
- [Changelog](https://github.com/py-pdf/fpdf2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/py-pdf/fpdf2/compare/2.8.7...2.8.8)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.71
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
- dependency-name: fpdf2
  dependency-version: 2.8.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-17 18:55:53 +00:00
Adam Moussa
58b9d4f83b
fix(pdf): fit weekly summaries on one page without a broken title (#138)
Some checks failed
Build Lambda Layer / build (push) Has been cancelled
Helvetica cannot encode an em dash, so the header rendered a question mark. Tighter single-column spacing keeps a 12-person week on page 1 while still paginating large weeks.
2026-08-15 00:17:27 +00:00
dependabot[bot]
9869bca561
chore(deps): bump boto3 in /src/shared in the minor-and-patch group (#130)
Bumps the minor-and-patch group in /src/shared with 1 update: [boto3](https://github.com/boto/boto3).


Updates `boto3` from 1.43.62 to 1.43.66
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.62...1.43.66)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.66
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 17:17:26 +00:00
Adam Moussa
b595744882
fix(form): refine meal description More/Less formatting (DEV-24) (#121)
Some checks failed
Deploy / deploy (push) Has been cancelled
* fix(form): align More/Less spacing with meal card rhythm

* fix(form): lighten More/Less typography underline treatment

* fix(form): add More/Less focus-visible and coarse-pointer hit target

* fix(form): stop meal cards stretching on description expand
2026-08-07 22:04:45 +00:00
dependabot[bot]
a3c83f9d29
chore(deps): bump boto3 in /src/shared in the minor-and-patch group (#113)
Bumps the minor-and-patch group in /src/shared with 1 update: [boto3](https://github.com/boto/boto3).


Updates `boto3` from 1.43.60 to 1.43.62
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.60...1.43.62)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.62
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-04 17:47:27 -04:00
Adam Moussa
a69e6d0a7c
fix(form): restore description toggles after sign-in (#107)
Some checks are pending
Deploy / deploy (push) Waiting to run
* fix(form): restore description toggles after sign-in

* fix(form): reset expanded descriptions on re-auth
2026-08-03 16:19:46 -04:00
Adam Moussa
88399fe153
refactor(weekly-menu): isolate menu writes behind API (#94)
* feat(api): add IAM-authenticated menu publication

Keep weekly menu writes behind Lambda so the GitHub runtime role cannot access the shared DynamoDB table directly.

* refactor(workflow): publish weekly menus through API

Use SigV4 requests for settings and menu publication so the scheduled workflow no longer needs direct DynamoDB access.

* fix: address review comments

* style(python): apply Ruff formatting
2026-08-03 14:27:59 -04:00
Adam Moussa
bcf255b17f
chore(form): add template JavaScript checks (#92)
* 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.

* chore(form): lint template JavaScript in CI

* docs(form): record frontend delivery decisions

* fix: resolve remaining merge conflicts
2026-08-03 14:15:25 -04:00
Adam Moussa
311eab35c0
fix(auth): require Google authentication in cloud mode (#90)
* 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.
2026-08-03 13:51:12 -04:00
dependabot[bot]
a55c56f0ab
chore(deps): bump boto3 in /src/shared in the minor-and-patch group (#104)
Bumps the minor-and-patch group in /src/shared with 1 update: [boto3](https://github.com/boto/boto3).


Updates `boto3` from 1.43.56 to 1.43.60
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.56...1.43.60)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.60
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-03 13:39:40 -04:00
Adam Moussa
adf175daef
feat(form): render admin orders as mobile cards (#86)
Some checks failed
Deploy / deploy (push) Has been cancelled
* feat(form): render admin orders as mobile cards

* fix(form): address mobile admin review findings

* fix(tests): remove unused mobile fixture state
2026-07-31 10:15:43 -04:00
Adam Moussa
602b0c7fd0
fix(form): accessibility for qty, status, and descriptions (#81)
* fix(form): add status regions, live total, and a11y CSS

Dual alert/status slots for form and admin, aria-live on the sticky
total, success heading focus target, More/Less affordance styles, and
44px coarse-pointer chip padding.

* fix(form): wire a11y labels, status helper, and desc expand

Meal-scoped qty labels at card create time, aria-pressed filter chips,
dual-node showStatus replacing all alert() calls (confirm retained),
overflow-gated More/Less, and success-heading focus after submit.

* test(form): cover a11y labels, status region, and desc toggle

Structural checks for dual status nodes and no alert(); Playwright for
init-time qty labels, aria-pressed chips, overflow More/Less, and
failed-submit text landing in role=alert.

* fix: address review comments
2026-07-31 10:15:43 -04:00
Adam Moussa
83077f7aa9
test(form): cover sticky footer at narrow widths (#85)
* test(form): cover sticky footer at narrow widths

* fix(form): address review findings

* fix(form): guard stale admin edit responses

* test(form): stub admin lookup in browser tests
2026-07-31 10:15:42 -04:00
Adam Moussa
30fa7fe352
fix(form): wrap Google user bar at phone widths (#84)
* fix(form): wrap Google user bar at phone widths

Allow the signed-in Google identity and admin controls to wrap below 480px while preserving the desktop row, with generated-form Playwright coverage for phone widths.

* fix(form): preserve 480px user bar boundary

Keep the mobile layout below 480px and lock both sides of the breakpoint with browser coverage.

* style(tests): apply ruff formatting

* test(form): guarantee Playwright browser cleanup

* test(form): strengthen phone-width coverage

* fix: add @classmethod and use cls in tests/test_generate_form.py

Co-authored-by: seahaven-openswe[bot] <296972425+seahaven-openswe[bot]@users.noreply.github.com>

* fix(tests): restore class fixture discovery

---------

Co-authored-by: seahaven-openswe[bot] <296972425+seahaven-openswe[bot]@users.noreply.github.com>
2026-07-31 10:15:42 -04:00
Adam Moussa
216618a862
refactor(form): extract Jinja templates and lock form JS in CI (#77)
Some checks are pending
Deploy / deploy (push) Waiting to run
* refactor(form): extract Jinja templates and lock form JS in CI

Split the monolithic generate_form f-string into form.html.j2/css/js
plus admin.js, inject a single window.CONFIG blob, and add structural
plus Playwright coverage so qty delegation and clamp stay green in CI.

* Update src/server/generate_form.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix(form): isolate admin script bindings

* fix(form): address admin and form review findings

* fix(form): resolve remaining review nitpicks

* ci(workflow): restore required check context

Keep the reusable workflow caller job compatible with the organization-required ci / ci status check.

* fix(form): address remaining review findings

* fix: apply CodeRabbit auto-fixes

Fixed 1 file(s) based on 1 unresolved review comment.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
2026-07-30 19:19:51 -04:00
043eea1443
fix(form): use single-column layout for meals without images on mobile 2026-07-30 11:18:21 -04:00
2a985c1940
fix(form): improve mobile layout for order form and admin toolbar
Stop phone-width sideways scroll from the admin toolbar, stack meal cards
and bump touch targets on coarse pointers, and respect safe-area insets.
2026-07-29 19:19:55 -04:00
Adam Moussa
03e902da6c
chore(deps): bump boto3 from 1.43.51 to 1.43.56 in multiple files (#68)
* chore(deps): bump boto3

Bumps the minor-and-patch group in /functions/admin_authorizer with 1 update: [boto3](https://github.com/boto/boto3).


Updates `boto3` from 1.43.51 to 1.43.56
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.51...1.43.56)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.56
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump boto3

Bumps the minor-and-patch group in /functions/aggregate_orders with 1 update: [boto3](https://github.com/boto/boto3).


Updates `boto3` from 1.43.51 to 1.43.56
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.51...1.43.56)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.56
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump boto3

Bumps the minor-and-patch group in /functions/close_form with 1 update: [boto3](https://github.com/boto/boto3).


Updates `boto3` from 1.43.51 to 1.43.56
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.51...1.43.56)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.56
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump boto3

Bumps the minor-and-patch group in /functions/email_report with 1 update: [boto3](https://github.com/boto/boto3).


Updates `boto3` from 1.43.51 to 1.43.56
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.51...1.43.56)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.56
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump boto3

Bumps the minor-and-patch group in /functions/slack_notifier with 1 update: [boto3](https://github.com/boto/boto3).


Updates `boto3` from 1.43.51 to 1.43.56
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.51...1.43.56)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.56
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump boto3

Bumps the minor-and-patch group in /functions/submit_order with 1 update: [boto3](https://github.com/boto/boto3).


Updates `boto3` from 1.43.51 to 1.43.56
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.51...1.43.56)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.56
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump boto3 in /src/shared in the minor-and-patch group

Bumps the minor-and-patch group in /src/shared with 1 update: [boto3](https://github.com/boto/boto3).


Updates `boto3` from 1.43.51 to 1.43.56
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.51...1.43.56)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.56
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 15:03:46 +00:00
Adam Moussa
09052fa91a
chore: resolve open code scanning alerts (#58)
Some checks failed
Deploy / deploy (push) Has been cancelled
* ci: add least-privilege permissions blocks to workflow callers
Resolves code scanning alerts #9 and #11 (actions/missing-workflow-permissions). Both callable workflows only need contents: read; the dependency-review callable already declares it internally, this caps the caller token to match.

* fix: turn off debug mode in Flask app configuration.

Resolves code scanning alert #2 (Flask app is run in debug mode)

* ci: bump reusable workflow pin to f71002a (ruff 0.15.22 pin)

Picks up Sea-Haven-Industries/.github#88, which pins ruff in
ci-python-sam so unpinned installs no longer float to new releases
with changed default rule sets (0.16.0 broke CI with 89 pre-existing
findings). Refs Sea-Haven-Industries/.github#87.
2026-07-23 20:00:47 +00:00
Adam Moussa
4079d57757
chore: Bump boto3 from 1.43.41 and 1.43.46 to 1.43.51 (#56)
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-07-20 16:56:49 +00:00
dependabot[bot]
65bbe6f8b5
Bump boto3 from 1.43.41 to 1.43.46 in /functions/email_report in the minor-and-patch group (#43)
Some checks are pending
Deploy / deploy (push) Waiting to run
* Bump boto3 in /functions/email_report in the minor-and-patch group

Bumps the minor-and-patch group in /functions/email_report with 1 update: [boto3](https://github.com/boto/boto3).


Updates `boto3` from 1.43.41 to 1.43.46
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.41...1.43.46)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.46
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump boto3 in /src/shared in the minor-and-patch group (#47)

Bumps the minor-and-patch group in /src/shared with 1 update: [boto3](https://github.com/boto/boto3).


Updates `boto3` from 1.43.41 to 1.43.46
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.41...1.43.46)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.46
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump boto3 in /functions/slack_notifier in the minor-and-patch group (#46)

Bumps the minor-and-patch group in /functions/slack_notifier with 1 update: [boto3](https://github.com/boto/boto3).


Updates `boto3` from 1.43.41 to 1.43.46
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.41...1.43.46)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.46
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump boto3 in /functions/close_form in the minor-and-patch group (#44)

Bumps the minor-and-patch group in /functions/close_form with 1 update: [boto3](https://github.com/boto/boto3).


Updates `boto3` from 1.43.41 to 1.43.46
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.41...1.43.46)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.46
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump boto3 in /functions/submit_order in the minor-and-patch group (#45)

Bumps the minor-and-patch group in /functions/submit_order with 1 update: [boto3](https://github.com/boto/boto3).


Updates `boto3` from 1.43.41 to 1.43.46
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.43.41...1.43.46)

---
updated-dependencies:
- dependency-name: boto3
  dependency-version: 1.43.46
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Adam Moussa <166072409+amoussa1229@users.noreply.github.com>
2026-07-13 16:32:14 +00:00
Adam Moussa
b8fef8b4f4
chore(deps): pin Python requirements to == for reproducible builds (INFRA-62) (#36) 2026-07-06 18:27:02 -04:00
53a1e503b3 Fix put_summary float serialization so SUMMARY records persist
aggregate_orders uploads the weekly PDF/CSVs to S3 and then calls
put_summary(), but put_summary spread the summary dict (which contains
float prices/totals) straight into put_item without Decimal conversion.
boto3 rejects floats (TypeError: Float types are not supported), so the
SUMMARY DynamoDB item was never written for any week (W20-W23).

The summary-PDF download endpoint gates on get_summary(week), so it got
None and returned 404 -- 'No summary PDF for <week> yet' -- even though
the PDF was sitting in S3.

Convert via _to_decimal in put_summary, matching put_order/put_settings.
2026-06-12 15:07:15 -04:00
dependabot[bot]
24c464bbb2
Update fpdf2 requirement from >=2.7 to >=2.8.7 in /src/shared (#30)
Updates the requirements on [fpdf2](https://github.com/py-pdf/fpdf2) to permit the latest version.
- [Release notes](https://github.com/py-pdf/fpdf2/releases)
- [Changelog](https://github.com/py-pdf/fpdf2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/py-pdf/fpdf2/compare/2.7.0...2.8.7)

---
updated-dependencies:
- dependency-name: fpdf2
  dependency-version: 2.8.7
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-11 14:31:33 -04:00
b6733703ab Add admin summary-PDF download endpoint and button
The weekly summary PDF generated at Thursday close was stored in the
reports bucket with no way to reach it from the UI — admins had to pull
it from S3 manually. Surface it in the admin panel:

- submit_order: GET /api/admin/summary-pdf?week= (admin-gated) returns
  a 5-minute presigned URL from the SUMMARY item's stamped PDF key;
  404 for weeks that haven't closed.
- template.yaml: REPORTS_BUCKET env var + read-only s3:GetObject on
  reports/* for SubmitOrderFunction (needed so the presigned URL is
  signed with sufficient permissions).
- generate_form.py: "Download summary PDF" button in the admin header;
  explains Thursday-close timing on 404.
- Tests: presign happy path, 404 open week, 400 missing week, 401
  unauthenticated.
- README updated.
2026-06-05 18:41:55 -04:00
Adam Moussa
10d135e32e
Add weekly summary PDF and admin order-list download (#16) (#17)
Some checks are pending
Deploy / deploy (push) Waiting to run
Generate a per-person weekly summary PDF at Thursday close and store it
alongside the CSV reports, plus a client-side admin download that rolls
orders up into item -> total quantity for bulk ordering.

- shared/pdf.py: build_weekly_summary_pdf() via fpdf2 (pure-Python,
  ARM64-safe; first non-boto3 layer dep). Per-person employee -> item ->
  quantity, no pricing.
- aggregate_orders: write reports/{week}/weekly-summary-{week}.pdf
  (application/pdf) and stamp weekly_summary_pdf_s3_key on the SUMMARY.
  No new IAM (existing S3CrudPolicy). No email/Slack delivery.
- generate_form.py: "Download order list" admin button aggregates the
  loaded week's orders into an item->qty CSV (no per-employee breakdown,
  no prices) via a Blob download. Works for open weeks too.
- Tests: tests/test_pdf.py; aggregate happy-path now asserts 3 S3
  uploads + the pdf key.
- README updated.
2026-06-01 18:49:58 -04:00
Adam Moussa
c52f608974 Fix admin bypass: defer closed overlay until after Google auth
Some checks failed
Deploy / deploy (push) Has been cancelled
The closed overlay (z-index 2000) was blocking Google sign-in from
firing, so the admin check never ran. Now the overlay is deferred
when Google auth is configured — checkAdmin() shows or bypasses
it after auth completes.
2026-05-22 12:35:36 -04:00
Adam Moussa
1aa28b38fd
Add admin form bypass and send order summary to admin DMs (#15)
Admins (by email in settings) can now view and submit orders even
after the form closes. The orders-aggregated Slack summary is sent
as a DM to each admin instead of posting to the channel.
2026-05-22 12:24:21 -04:00
Adam Moussa
5db95ce9d1
Add admin panel, fix dual-domain auth, harden scrape schedule (#14)
Some checks failed
Deploy / deploy (push) Has been cancelled
* Add admin panel, fix dual-domain auth, harden weekly scrape schedule

Accept both seahavenind.com and seahaven.com Google Workspace domains
for employee sign-in. Add admin panel with order management (view by
week, edit quantities, add/remove items, delete orders) behind Google
auth + DynamoDB admin_emails allowlist. Shift weekly menu scrape from
8:00am to 7:30am ET and add timezone guard to prevent duplicate runs
from dual EST/EDT crons.

* Rename Secrets Manager env vars to avoid CI false positive

The reusable CI workflow greps for keywords like TOKEN and API_KEY in
Lambda environment variables. Our env vars hold Secrets Manager lookup
names, not actual secrets, but the heuristic matched the SM key name
meal-order-manager/slack-bot-token. Rename SLACK_BOT_TOKEN_SECRET to
SLACK_BOT_SM_NAME and FORM_API_KEY_SECRET to FORM_APIKEY_SM_NAME, and
reorder the Globals block so the value falls outside the grep window.
2026-05-19 16:32:19 -04:00
Adam Moussa
a752c24e0f
Add discount pricing, Google auth, and order hardening (#10)
Some checks failed
Deploy / deploy (push) Has been cancelled
* Add discount settings and two-tier pricing to order aggregation

Settings CRUD (get_settings/put_settings) for DynamoDB CONFIG#SETTINGS item.
Aggregation now tracks bulk_price and employee_price separately, with
grand_total (company cost) and employee_total (payroll deductions).

* 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.

* Update SAM template for Google auth, Slack invocation, and deadline change

Add SLACK_NOTIFIER_ARN and GOOGLE_CLIENT_ID_PARAM env vars to submit order
function with lambda:InvokeFunction policy. Move close-form schedule to
Thursday 11:59pm EST/EDT.

* Update order form UI and CI workflow for new features

Form now shows discount pricing, responsive grid layout, Google Sign-In
overlay, and closed-orders page with countdown timer. CI workflow fetches
discount settings from DynamoDB and Google Client ID from SSM.

* Add SSM GetParameter permission to submit order Lambda

Required for reading the Google Client ID from Parameter Store
at /meal-order-manager/google-client-id.

* Harden auth, pricing, and reliability in order handlers

Enforce Google auth when configured (reject missing tokens with 403),
return 503 on token verification outages, switch to Decimal with
ROUND_HALF_UP for financial precision, clamp discount bounds 0-100,
use email-based slugs, add 5-min cache TTL with time.monotonic(),
wrap Slack invocation in try/except, add reopen_at timestamp to
closed form status, add reminder dedup guards for dual EST/EDT crons,
escape Slack mrkdwn special characters, and handle empty employee names.

* Fix XSS risks and add closed-form UX to order page

Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.

* Document CORS, cron idempotency, and SSM config in template

Add comments explaining CORS dev server strategy, dual EST/EDT cron
idempotency, and manual SSM parameter creation for Google Client ID.

* Add unit tests for submit, notify, and aggregate handlers

50 tests covering pricing pipeline (Decimal rounding, clamping, totals),
Google auth (enforcement, bypass prevention, audience/domain validation,
503 on outage), email slug generation, form status with reopen_at,
input validation, Slack failure resilience, reminder dedup guards,
order confirmation DMs, aggregated summaries, CSV generation, and
mrkdwn escaping.

* Use full email as order slug for defense-in-depth

Replace email-prefix slug with full lowercase email to eliminate any
possibility of cross-domain collisions, per senior review sign-off.

* Remove unused imports flagged by ruff

* Apply ruff formatting

* Fix PR review findings: auth, rounding, and close-form guard

- Remove dead elif branch in submit_order auth (always returned 403)
- Catch HTTPError before URLError so expired tokens return 403 not 503
- Wrap SSM get_parameter in try/except for fresh deployments
- Add wall-clock guard to close_form handler (Friday >= 11 PM ET)
- Add epsilon nudge to JS employeePrice for IEEE 754 boundary match
- Switch Flask dev server from round() to Decimal ROUND_HALF_UP
- Add tests for HTTPError handling and close_form guard (6 new tests)

* Fix close-form weekday guard and SSM auth fail-open

- Close form guard: check weekday == 3 (Thursday), not 4 (Friday) — the
  crons fire at Thursday 11:59 PM ET, when weekday() is 3
- SSM fail-closed: separate _google_auth_configured() (checks env var) from
  _get_google_client_id() (fetches value). If auth is configured but the SSM
  fetch fails, return 503 instead of silently falling back to manual auth
- Update close_form tests to use Thursday dates
- Add test_ssm_failure_fails_closed

* Harden Flask dev server auth and escaping

- Add hosted domain check to _verify_google_token (mirror Lambda)
- Gate auth on config (client_id presence), not request body — prevents
  bypass by omitting google_id_token when auth is configured
- Add discount percentage clamping to match Lambda handler
- Add </script> escaping to google_client_id_json

* fix: Email order filenames, SSM param TTL, DST-safe reopen_at

- Flask dev server: persist orders under lowercase email slug (match Lambda/Dynamo)
- shared.secrets: split secret vs SSM caches; expire get_parameter entries every 5 minutes
- form-status reopen_at: calendar Monday + datetime.combine for 8am ET (not 24h timedelta)
- Add _eastern_now() for testability; tests for SSM TTL and DST weekend edge case

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

* Apply ruff formatting to submit_order handler

* fix(server): retry SSM for Google client id after TTL on failure

Transient SSM errors no longer cache empty client id for the process lifetime;
matches Lambda handler refresh behavior (300s TTL).

Co-authored-by: Cursor <cursoragent@cursor.com>

* style(server): ruff-format Google client id cache helper

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(close-form): accept Fri 00–03 ET catch-up after Thu close cron

EventBridge can deliver past midnight ET; widen the wall-clock guard so a
delayed Thursday 23:59 UTC cron still closes the form. Idempotent when already
closed. Adds test for early Friday; past-window skip now starts Fri 04:00 ET.

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

* fix(submit-order): bill from Dynamo menu retail, not client JSON

Load authoritative meal prices from get_menu(week); reject unknown meal names
and return 503 when the menu has no priced meals. Use meal_name in the pricing
loop to avoid shadowing the employee name. Adds regression tests for tampering,
unknown meals, and empty menu meals.

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

* fix: use single braces in loadRoster JS nested string

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix Eastern fallback countdown

* Fix pricing validation and JWT display decoding

* Fix optional Google auth detection

* Format app.py line length for ruff compliance

* Fix auth config check and URL escaping in form

- _google_auth_configured() now checks env var presence (intent), not
  the fetched SSM value — prevents silent auth bypass if SSM param is
  deleted
- Add </script> escaping to URL values in generate_form.py for
  consistency with other injected values

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-13 18:00:21 -04:00
Adam Moussa
a8adbdc116
Switch to orders.seahaven.com, add roster dropdown, fix deadline (#9)
Some checks are pending
Deploy / deploy (push) Waiting to run
- Change custom domain from orders.seahavenind.com to
  orders.seahaven.com to match other subdomain conventions
- Add GET /api/roster endpoint returning employee names/emails
- Replace name/email text inputs with dropdown populated from
  roster API (falls back to embedded roster for local dev)
- Fix order deadline text from Wednesday to Thursday 11:59 PM
- Fix Slack API calls: use form-urlencoded for conversations and
  users methods that reject JSON body encoding
2026-05-12 20:16:46 -04:00
Adam Moussa
11ea599bbd
Fix Slack API calls that require form-urlencoded encoding (#7)
Some checks are pending
Deploy / deploy (push) Waiting to run
conversations.members, conversations.open, and users.info reject
JSON body with 'missing required field'. Use form-urlencoded for
these methods while keeping JSON for chat.postMessage and
files.upload which require it for structured blocks/payloads.
2026-05-12 20:05:48 -04:00
Adam Moussa
440117c9a1
Fix ruff lint and format violations, update README (#5)
Apply ruff check --fix and ruff format across all Python files to
pass CI pipeline. Remove unused imports (os, sys), fix f-strings
without placeholders. Update README to reflect sync-roster Lambda,
corrected shared layer path, and current project structure.
2026-05-12 19:31:27 -04:00
Adam Moussa
8935fc8f2d
Add roster sync Lambda, move API key to Secrets Manager, add Slack manifest (#3)
- Add sync-roster Lambda that auto-syncs employee roster from Slack
  channel membership (runs Monday 6:55am ET before menu publish)
- Move FormApiKey from CloudFormation parameter/env var to Secrets
  Manager (meal-order-manager/form-api-key) per security conventions
- Add Slack app manifest with required bot scopes
- Add get_channel_members() and get_user_info() to shared Slack module
- Add Lambda function ARN outputs to CloudFormation
- Add log group for sync-roster Lambda (60-day retention)
2026-05-12 19:21:47 -04:00
Adam Moussa
360a0435e8
Fix shared layer structure and DynamoDB Decimal type error (#2)
Move shared layer source from src/shared/python/shared/ to
src/shared/shared/ to prevent SAM from creating a double
python/python/ directory in the layer artifact. Add _to_decimal()
helper to convert floats to Decimal for DynamoDB compatibility
in put_order.
2026-05-12 19:09:44 -04:00
Adam Moussa
d332affd56
Initial commit: meal ordering automation system (#1)
Playwright-based menu scraper for Redefine Meals, self-contained HTML
order form with S3/CloudFront hosting, DynamoDB-backed order submission
via API Gateway, and automated payroll deduction reports via SES.
2026-05-12 18:25:15 -04:00