feat(core): add CellarEntry model + migration (VPA-17) - #862
Open
vpatrin wants to merge 1 commit into
Open
Conversation
Table tracking bottles a user owns at home — one row per (user, SKU) with a quantity, not one row per bottle. Enabler for the Cellar endpoints and UI (VPA-18..22). user_id is an Integer FK to users.id rather than the channel-prefixed String used by Watch and TastingNote: a brand-new table has no reason to reproduce that legacy shape and migrate it twice (VPA-42 tracks migrating the existing ones). Consequence: cellar service code cannot be copy-pasted from watches.py — it must resolve the integer user id. No standalone index on user_id — the (user_id, sku) unique constraint's composite btree already covers user_id-only lookups via leftmost prefix. Closes VPA-17
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the
cellar_entriestable — one row per(user_id, sku)with aquantity, not one row per bottle. This is the persistence layer only: no endpoints, no UI. Enabler for the Cellar feature (VPA-18..22).Related issue(s)
Fixes VPA-17
Changes
core/db/models.py— newCellarEntrymodel:id(PK),user_id(Integer FK →users.id,ondelete="CASCADE"),sku(String FK →products.sku, indexed),quantity(default 1),added_atcore/alembic/versions/a321897f6ef7_add_cellar_entries_table.py— migration, chains onto prior headd54ff3a506b5backend/tests/test_cellar_entry_model.py— two constraint tests (unique(user_id, sku),quantity > 0)user_idisInteger, not the legacyStringpatternWatch.user_id/TastingNote.user_idare channel-prefixedStringcolumns (tg:123456), inherited from Telegram-first auth (ADR 0004, superseded by ADR 0008 OAuth) — VPA-42 tracks migrating those.CellarEntryis a brand-new table with no reason to reproduce that shape and migrate it twice, so it goes straight toInteger, ForeignKey("users.id").Consequence for VPA-18: the cellar service/repo code cannot be copy-pasted from
watches.py— it has to resolve the authenticated user's integerid, not build a channel-prefixed string.No standalone index on
user_idThe
(user_id, sku)unique constraint's composite btree already coversuser_id-only lookups via leftmost prefix, so a separate index would be redundant. Deliberate divergence fromWatch, which carries that redundancy.skukeeps its own index — it's an FK-referencing column, and an unindexed one would seq-scancellar_entrieson a futureproductshard-delete.Migration review checklist
uq_cellar_entries_user_sku,ck_cellar_entries_quantityondelete="CASCADE"on theuser_idFK only —skustays a bare FK (products soft-delete viadelisted_at, never hard-deleted)ix_cellar_entries_skudowngrade()is a real drop (index, then table), not a stubTesting limitation
The tests use a sync SQLite session that creates only
CellarEntry.__table__, not the full schema — the full schema fails under SQLite becauseproducts.tasting_profileisJSONB.UNIQUEandCHECKare genuinely exercised (both are falsifiable — remove either constraint and the corresponding test fails). FK behavior andondelete="CASCADE"are not covered by these tests; that lands with VPA-18, once real rows exist to delete a parent through. CI'smigratejob (fresh Postgres,alembic upgrade head) proves the DDL itself is valid.The tests live under
backend/tests/rather thancore/—corehas no pytest dependency, no test directory, and no CI job that would execute tests placed there.How to test
Fresh-DB apply is already covered by CI's
migratejob on this PR. Outstanding manual step (not done yet, owed separately): apply to a copy of prod data.Confirm exit 0, then inspect the table:
psql -c "\d cellar_entries"