feat: add jersey size selection to checkout flow #142
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/basketball-api!142
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "141-jersey-size-selection"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Add jersey size selection during checkout so parents can specify their child's jersey size. Size is required for paid options (reversible, jersey_warmup), ignored for opt-out, and persisted via Stripe session metadata on payment confirmation.
Changes
src/basketball_api/models.py— AddedJerseySizeenum (YS, YM, YL, YXL, AS, AM, AL, AXL) andjersey_sizenullable column on Playersrc/basketball_api/routes/jersey.py— AddedGET /jersey/sizesendpoint, addedsizefield toJerseyCheckoutRequest, added size validation (required for paid options, 422 if missing/invalid), passjersey_sizein Stripe session metadatasrc/basketball_api/routes/webhooks.py— Webhook readsjersey_sizefrom Stripe metadata and saves toplayer.jersey_sizeon payment confirmation; backwards-compatible (missing size = NULL)alembic/versions/015_add_jersey_size_to_player.py— New migration addingjerseysizeenum type andjersey_sizecolumn to players tabletests/test_jersey.py— 12 new/updated tests: sizes endpoint, size validation (missing, invalid, opt-out bypass), webhook persistence with size, backwards compat without sizeTest Plan
pytest tests/ -k jersey— 35 tests passGET /jersey/sizesreturns 8 sizes with labelsPOST /jersey/checkoutrejects paid options without size (422)Review Checklist
Related
Self-Review: PASS
Files reviewed: 5 changed files, +213/-6 lines
Checklist
JerseySizeenum with 8 values,jersey_sizenullable column on Playerjerseysizeenum type and column, downgrade drops bothGET /jersey/sizes: returns 8 sizes with human-readable labels, no auth requiredPOST /jersey/checkout: size required for paid options (422 if missing/invalid), ignored for opt-out, passed in Stripe metadatajersey_sizefrom Stripe metadata, handles missing size (backwards compat) and invalid size (logs warning)Notes
test_invalid_option_returns_422intentionally omitssize-- option validation fires first, which is correctjersey_sizein metadata = NULL)PR #142 Review
DOMAIN REVIEW
Tech stack: Python / FastAPI / SQLAlchemy / Alembic / Stripe / pytest
Python/FastAPI/SQLAlchemy assessment:
Model layer (
models.py):JerseySizeenum is clean, follows the same pattern as existing enums (JerseyOption,JerseyOrderStatus). Thejersey_sizecolumn is correctly typed asMapped[JerseySize | None]withnullable=True. PEP 484 type hints are correct.Route layer (
routes/jersey.py):GET /jersey/sizesendpoint is simple and correct. Returns Pydantic-validated responses.JerseyCheckoutRequestaddssize: str | None = None-- optional field with correct default._VALID_SIZESset for O(1) lookup -- good.body.sizeis passed as-is into Stripe metadata (string), which is the correct Stripe pattern.Webhook layer (
routes/webhooks.py):jersey_sizefrom Stripe metadata, converts viaJerseySize(value)with try/except for invalid values.jersey_sizein metadata gracefully results inNone(no crash).Migration (
015_add_jersey_size_to_player.py):014(down_revision).checkfirst=Truebefore adding the column -- safe for re-runs.Pydantic schemas:
JerseySizeResponseis properly typed.JerseyCheckoutRequest.sizeusesstr | Nonewhich is correct for the validation pattern (validated manually against_VALID_SIZESrather than using Pydantic enum validation, which keeps the error messages user-friendly).BLOCKERS
None.
NITS
Triple definition of size values: The size values appear in (a)
JerseySizeenum inmodels.py, (b)JERSEY_SIZESlist injersey.py, and (c) the migration. The migration is a frozen snapshot (correct), butJERSEY_SIZEScould be derived from the enum to prevent future drift. Consider:and building
JERSEY_SIZESwith a label map. Not blocking since the values are static and match today.Comment typo in
jersey.pyline 125:"rejected for opt-out is N/A"-- reads awkwardly. Minor.Opt-out with size silently ignored: When a user sends
{"option": "opt_out", "size": "YL"}, the size is silently ignored. This is arguably correct (opt-out doesn't need a size), but a stricter approach would reject the request or log a warning. Non-blocking.SOP COMPLIANCE
141-jersey-size-selectionreferences issue #141plan-wkqfeat: add jersey size selection to checkout flow)PROCESS OBSERVATIONS
pytestpassing. Unchecked items are manual verification steps for staging, which is appropriate for a Stripe integration.plan-wkqper SOP. This is a process nit, not a code blocker.VERDICT: APPROVED