feat: add monthly ProductCategory, first_payment EmailType, seed prorated product #370
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!370
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "367-add-monthly-category-first-payment-email"
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
Adds the
monthlyProductCategory andfirst_paymentEmailType enum values needed by the first-payment checkout flow, and seeds a prorated April product.Changes
src/basketball_api/models.py: AddedmonthlytoProductCategoryenum,first_paymenttoEmailTypeenumalembic/versions/037_add_monthly_category_and_first_payment_email.py: New migration that ALTERs both enum types and INSERTs the "Monthly Fee — Prorated April" product (price_cents=0, one_time, tenant_id=1) with ON CONFLICT DO NOTHING for idempotencyTest Plan
TestCheckoutWebhookare on main, unrelated to this changeProductCategory.monthlyandEmailType.first_paymentload correctlyADD VALUE IF NOT EXISTS,ON CONFLICT DO NOTHING)Review Checklist
Related Notes
None — pure schema/migration change.
Related
Closes #367
PR #370 Review
DOMAIN REVIEW
Stack: Python / FastAPI / SQLAlchemy / Alembic (PostgreSQL)
This PR adds two enum values (
ProductCategory.monthly,EmailType.first_payment) and a seed migration for a prorated April product. Two files changed, 37 additions, 0 deletions. Tight scope.Migration correctness (037):
ALTER TYPE ... ADD VALUE IF NOT EXISTSis the correct PostgreSQL pattern for enum extension. Good.down_revision = "036"matches the chain. Migration revision numbering is consistent with the project convention.ON CONFLICT DO NOTHING without a conflict target: The
productstable has no unique constraint onnameor(tenant_id, name)-- only the auto-increment PKid. The bareON CONFLICT DO NOTHINGclause without a column target will only catch PK collisions (which cannot happen with auto-increment). This means the "idempotent" claim in the PR body and code comment is technically incorrect -- if this INSERT were somehow executed twice, it would create duplicate rows. In practice, Alembic migrations run exactly once, so this is not a real-world bug. However, addingON CONFLICT (tenant_id, name) DO NOTHING(after adding a unique constraint) would make the idempotency claim truthful. Verdict: nit, not a blocker, since migrations are single-run by design.Enum placement in models.py:
first_paymentis inserted betweeninterest_notificationandcontract_offer, andmonthlyis appended afterequipment. Both are clean insertions with no ordering issues.Seed data:
price_cents=0is intentional for a prorated month.product_type='one_time'andcategory='monthly'are valid enum values.tenant_id=1is hardcoded but consistent with the single-tenant pattern used throughout this codebase.descriptionis nullable and correctly omitted.BLOCKERS
None.
This is a pure enum extension + seed data migration. The "new functionality with zero test coverage" blocker does not apply here -- there is no new behavior to test. The enum values are schema-level additions consumed by downstream PRs (#368, #369) which will carry the behavioral tests.
NITS
Misleading idempotency comment: The migration comment says "Seed prorated April product (idempotent)" but
ON CONFLICT DO NOTHINGwithout a matching unique constraint does not provide true idempotency. Consider either (a) removing the "(idempotent)" claim, or (b) adding a conflict target likeON CONFLICT ON CONSTRAINT products_pkey DO NOTHING, or (c) using aWHERE NOT EXISTSsubquery instead. This is a documentation accuracy issue, not a runtime bug.No
descriptionfor the seeded product: The product is inserted without adescription. Downstream consumers (checkout page, email templates) may want a human-readable description. Consider whether this should be populated now or deferred to #368.SOP COMPLIANCE
367-add-monthly-category-first-payment-emailfollows{issue-number}-{kebab-case-purpose}conventionPROCESS OBSERVATIONS
TestCheckoutWebhookfailures on main as unrelated. This is good practice for avoiding false review blocks.VERDICT: APPROVED
PR #370 Review
DOMAIN REVIEW
Tech stack: Python / FastAPI / SQLAlchemy / Alembic (PostgreSQL)
Two files changed, both expected:
src/basketball_api/models.py-- addsmonthlytoProductCategoryenum,first_paymenttoEmailTypeenumalembic/versions/037_add_monthly_category_and_first_payment_email.py-- migration withALTER TYPE ... ADD VALUE IF NOT EXISTS, product seed INSERTEnum consistency: Python enum values (
"monthly","first_payment") match the SQL strings exactly. Correct.Product seed values:
tenant_id=1,name='Monthly Fee — Prorated April',price_cents=0,product_type='one_time',category='monthly',active=true. All match requirements.Idempotency:
ALTER TYPE ... ADD VALUE IF NOT EXISTSis genuinely idempotent. TheON CONFLICT DO NOTHINGon the INSERT is technically a no-op because theproductstable has no unique constraint beyond the auto-increment PK -- see NITS below.Migration chain:
revision = "037",down_revision = "036". Main currently ends at030. Migrations 031-036 presumably exist in other in-flight PRs/branches. This is fine as long as the merge order is coordinated.Downgrade: Correctly deletes the seeded product row. Notes that PostgreSQL cannot remove enum values -- appropriate.
Scope: Only models and migration touched. No route or service files. Clean.
BLOCKERS
None.
No new functionality requiring test coverage (enum additions + data migration). No user input. No secrets. No auth changes. No DRY violations.
NITS
ON CONFLICT DO NOTHINGwithout a conflict target is misleading: Theproductstable has no unique constraint on(tenant_id, name)or any other column combination. The only unique column is the auto-incrementidPK. This meansON CONFLICT DO NOTHINGwill never actually skip a duplicate -- it will always insert. In practice this is harmless because Alembic tracks revisions and will never re-run the migration, but calling it "idempotent" in the PR body is technically inaccurate. If true idempotency is desired, consider addingON CONFLICT (id) DO NOTHINGor better yet a unique constraint on(tenant_id, name)in a separate migration.Migration numbering gap (030 -> 037): Six intermediate migrations (031-036) must be merged first or concurrently. If any of those fail to land, this migration will break the Alembic chain. Worth confirming the merge order.
SOP COMPLIANCE
367-add-monthly-category-first-payment-email)PROCESS OBSERVATIONS
Clean, minimal migration PR. Good decomposition -- enum + seed separated from route work (#368) and email work (#369). The migration chain dependency (031-036 must land first) is the only coordination risk. Low change failure risk.
VERDICT: APPROVED