|
| 1 | +# Issue #660 — Implementation Summary |
| 2 | +## [P1] Audit constraints on reminder, tag, and ledger tables |
| 3 | + |
| 4 | +**Date:** 2026-06-02 |
| 5 | +**Branch:** your branch name here |
| 6 | +**Status:** Complete |
| 7 | + |
| 8 | +--- |
| 9 | + |
| 10 | +## What this issue asked for |
| 11 | + |
| 12 | +The `tags`, `subscription_tags`, and `gift_card_ledger` tables had almost no |
| 13 | +safety rules (constraints). This meant the database could accept bad data like |
| 14 | +blank tag names, duplicate tag assignments, or gift card deductions that would |
| 15 | +make a user's balance go negative. |
| 16 | + |
| 17 | +This issue asked us to: |
| 18 | +1. Audit what constraints were missing |
| 19 | +2. Add those constraints via a migration |
| 20 | +3. Update the app to handle new constraint errors gracefully |
| 21 | +4. Write tests to prove everything works |
| 22 | + |
| 23 | +--- |
| 24 | +## Files changed |
| 25 | + |
| 26 | +### 1. New migration |
| 27 | +`supabase/migrations/20260602000000_audit_constraints_tags_and_ledger.sql` |
| 28 | + |
| 29 | +Creates two brand new tables and adds missing constraints to an existing one. |
| 30 | + |
| 31 | +### 2. New utility |
| 32 | +`backend/src/utils/db-constraint-errors.ts` |
| 33 | + |
| 34 | +Maps Postgres constraint violation errors to typed HTTP errors so routes can |
| 35 | +return friendly 4xx responses instead of raw 500s. |
| 36 | + |
| 37 | +### 3. Updated route |
| 38 | +`backend/src/routes/tags.ts` |
| 39 | + |
| 40 | +- Added `parseDbError` to the `POST /api/tags` handler |
| 41 | +- Added `parseDbError` to the `POST /api/subscriptions/:id/tags` handler |
| 42 | +- Added `parseDbError` to the `DELETE /api/subscriptions/:id/tags/:tagId` handler |
| 43 | +- Fixed table name from `subscription_tag_assignments` to `subscription_tags` |
| 44 | +- Fixed table name from `subscription_tags` to `tags` in the GET and POST handlers |
| 45 | + |
| 46 | +### 4. Updated route |
| 47 | +`backend/src/routes/gift-card-ledger.ts` |
| 48 | + |
| 49 | +- Added `parseDbError` to the `POST /api/gift-card-ledger/top-up` handler |
| 50 | + |
| 51 | +### 5. New tests |
| 52 | +`backend/tests/db-constraint-errors.test.ts` |
| 53 | + |
| 54 | +13 unit tests covering every constraint mapping and the generic fallback. |
| 55 | +No database connection required — all tests use fake Postgres error objects. |
| 56 | + |
| 57 | +--- |
| 58 | + |
| 59 | +## What the migration does |
| 60 | + |
| 61 | +### tags table (created from scratch) |
| 62 | +| Constraint | Rule | |
| 63 | +|---|---| |
| 64 | +| `tags_user_id_fkey` | user_id must refer to a real user | |
| 65 | +| `tags_user_id_name_lower_key` | no duplicate tag names per user (case-insensitive) | |
| 66 | +| `tags_name_length_check` | name must be 1–50 characters | |
| 67 | +| `tags_color_check` | color must be a valid hex code like #FF5733 or empty | |
| 68 | + |
| 69 | +### subscription_tags table (created from scratch) |
| 70 | +| Constraint | Rule | |
| 71 | +|---|---| |
| 72 | +| `subscription_tags_pkey` | composite primary key prevents duplicate tag assignments | |
| 73 | +| `subscription_tags_subscription_id_fkey` | subscription_id must refer to a real subscription | |
| 74 | +| `subscription_tags_tag_id_fkey` | tag_id must refer to a real tag | |
| 75 | + |
| 76 | +### gift_card_ledger table (updated) |
| 77 | +| Constraint | Rule | |
| 78 | +|---|---| |
| 79 | +| `gift_card_ledger_amount_check` | amount must never be zero | |
| 80 | +| `gift_card_ledger_type_check` | type expanded to include refund, adjustment, expiry | |
| 81 | +| `gift_card_ledger_type_amount_sign_check` | top_up/refund must be positive, deduction/expiry must be negative | |
| 82 | +| `gift_card_ledger_currency_check` | currency must be a valid 3-letter ISO code | |
| 83 | +| `gift_card_ledger_balance_after_check` | balance can never go below zero | |
| 84 | +| `gift_card_ledger_reference_id_user_idx` | reference_id must be unique per user | |
| 85 | + |
| 86 | +--- |
| 87 | + |
| 88 | +## How to apply the migration |
| 89 | + |
| 90 | +```bash |
| 91 | +# From the repo root |
| 92 | +supabase db push |
| 93 | +``` |
| 94 | + |
| 95 | +--- |
| 96 | + |
| 97 | +## How to run the tests |
| 98 | + |
| 99 | +```bash |
| 100 | +# From the backend folder |
| 101 | +cd backend |
| 102 | +npx jest --testPathPattern="db-constraint-errors" |
| 103 | +``` |
| 104 | + |
| 105 | +--- |
| 106 | + |
| 107 | +## Security notes |
| 108 | + |
| 109 | +- No RLS policies were removed or weakened |
| 110 | +- All new tables have RLS enabled with owner-only policies |
| 111 | +- No new environment variables required |
| 112 | +- Migration is safe to re-run — all statements use IF NOT EXISTS guards |
| 113 | + |
| 114 | +--- |
| 115 | + |
| 116 | +## PR checklist |
| 117 | + |
| 118 | +- [x] Constraint inventory produced |
| 119 | +- [x] Missing constraints added via migration |
| 120 | +- [x] Application code updated to handle new constraint errors |
| 121 | +- [x] Tests added and passing |
| 122 | +- [x] Documentation written |
| 123 | +- [x] No security regressions |
0 commit comments