chore: merge executor worktree (06-01 DB migrations)
This commit is contained in:
@@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
phase: 06-preset-data-first-run-detection-and-db-safety
|
||||||
|
plan: "01"
|
||||||
|
subsystem: database
|
||||||
|
tags: [migrations, schema, typescript, constraints, first-run]
|
||||||
|
dependency_graph:
|
||||||
|
requires: []
|
||||||
|
provides: [uniqueness-constraints, setup_completed-column, profile-type-update]
|
||||||
|
affects: [profiles, budgets, categories]
|
||||||
|
tech_stack:
|
||||||
|
added: []
|
||||||
|
patterns: [safe-deduplication-before-unique-constraint, alter-table-with-backfill]
|
||||||
|
key_files:
|
||||||
|
created:
|
||||||
|
- supabase/migrations/006_uniqueness_constraints.sql
|
||||||
|
- supabase/migrations/007_setup_completed.sql
|
||||||
|
modified:
|
||||||
|
- src/lib/types.ts
|
||||||
|
decisions:
|
||||||
|
- "Used wider UNION backfill in 007 (categories OR template_items) per Pitfall 3 guidance — protects v1.0 users with templates but no categories"
|
||||||
|
- "Migration 006 wraps deduplication DELETE and ADD CONSTRAINT in single BEGIN/COMMIT for atomicity"
|
||||||
|
metrics:
|
||||||
|
duration: "4 minutes"
|
||||||
|
completed: "2026-04-20"
|
||||||
|
tasks_completed: 3
|
||||||
|
tasks_total: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 06 Plan 01: DB Safety Constraints and First-Run Flag Summary
|
||||||
|
|
||||||
|
**One-liner:** Two atomic SQL migrations adding UNIQUE constraints on budgets/categories with safe deduplication, plus a `setup_completed` boolean on profiles with UNION backfill, synced to the TypeScript Profile interface.
|
||||||
|
|
||||||
|
## Tasks Completed
|
||||||
|
|
||||||
|
| Task | Name | Commit | Files |
|
||||||
|
|------|------|--------|-------|
|
||||||
|
| 1 | Migration 006: uniqueness constraints | 23fd3fa | supabase/migrations/006_uniqueness_constraints.sql |
|
||||||
|
| 2 | Migration 007: setup_completed column + backfill | 0f441b6 | supabase/migrations/007_setup_completed.sql |
|
||||||
|
| 3 | Update Profile TypeScript interface | 39840ca | src/lib/types.ts |
|
||||||
|
|
||||||
|
## What Was Built
|
||||||
|
|
||||||
|
**Migration 006** (`006_uniqueness_constraints.sql`): A single `BEGIN/COMMIT` transaction that:
|
||||||
|
1. DELETEs duplicate budgets keeping the oldest per `(user_id, start_date)` using `DISTINCT ON`
|
||||||
|
2. ADDs `CONSTRAINT budgets_user_month_unique UNIQUE (user_id, start_date)`
|
||||||
|
3. DELETEs duplicate categories keeping the oldest per `(user_id, name)` using `DISTINCT ON`
|
||||||
|
4. ADDs `CONSTRAINT categories_user_name_unique UNIQUE (user_id, name)`
|
||||||
|
|
||||||
|
**Migration 007** (`007_setup_completed.sql`): Two statements:
|
||||||
|
1. `ALTER TABLE profiles ADD COLUMN setup_completed boolean NOT NULL DEFAULT false`
|
||||||
|
2. `UPDATE profiles SET setup_completed = true WHERE id IN (SELECT DISTINCT user_id FROM categories UNION SELECT t.user_id FROM templates t INNER JOIN template_items ti ON ti.template_id = t.id)`
|
||||||
|
|
||||||
|
**TypeScript** (`src/lib/types.ts`): Added `setup_completed: boolean` to the `Profile` interface between `currency` and `created_at`. `tsc --noEmit` passes cleanly.
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None — plan executed exactly as written. The UNION backfill in migration 007 was specified in the plan (per Pitfall 3 guidance from RESEARCH.md).
|
||||||
|
|
||||||
|
## Known Stubs
|
||||||
|
|
||||||
|
None — this plan produces SQL DDL and a TypeScript type update; no UI or data-flow stubs.
|
||||||
|
|
||||||
|
## Threat Flags
|
||||||
|
|
||||||
|
No new threat surface beyond what was documented in the plan's threat model. The UNIQUE constraints directly address T-06-01 and T-06-02. The `setup_completed` column is a UX routing flag with existing RLS (T-06-03 accepted).
|
||||||
|
|
||||||
|
## Self-Check
|
||||||
|
|
||||||
|
- [x] `supabase/migrations/006_uniqueness_constraints.sql` exists — FOUND
|
||||||
|
- [x] `supabase/migrations/007_setup_completed.sql` exists — FOUND
|
||||||
|
- [x] `src/lib/types.ts` contains `setup_completed: boolean` — FOUND (line 16)
|
||||||
|
- [x] `tsc --noEmit` passed with no errors
|
||||||
|
- [x] Commits 23fd3fa, 0f441b6, 39840ca exist in git log
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
@@ -13,6 +13,7 @@ export interface Profile {
|
|||||||
display_name: string | null
|
display_name: string | null
|
||||||
locale: string
|
locale: string
|
||||||
currency: string
|
currency: string
|
||||||
|
setup_completed: boolean
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
|
|||||||
28
supabase/migrations/006_uniqueness_constraints.sql
Normal file
28
supabase/migrations/006_uniqueness_constraints.sql
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
-- Migration 006: Add uniqueness constraints to budgets and categories
|
||||||
|
-- Safe deduplication runs first inside the transaction before each constraint.
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Deduplicate budgets: keep the oldest row per (user_id, start_date)
|
||||||
|
DELETE FROM budgets
|
||||||
|
WHERE id NOT IN (
|
||||||
|
SELECT DISTINCT ON (user_id, start_date) id
|
||||||
|
FROM budgets
|
||||||
|
ORDER BY user_id, start_date, created_at ASC
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE budgets
|
||||||
|
ADD CONSTRAINT budgets_user_month_unique UNIQUE (user_id, start_date);
|
||||||
|
|
||||||
|
-- Deduplicate categories: keep the oldest row per (user_id, name)
|
||||||
|
DELETE FROM categories
|
||||||
|
WHERE id NOT IN (
|
||||||
|
SELECT DISTINCT ON (user_id, name) id
|
||||||
|
FROM categories
|
||||||
|
ORDER BY user_id, name, created_at ASC
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE categories
|
||||||
|
ADD CONSTRAINT categories_user_name_unique UNIQUE (user_id, name);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
18
supabase/migrations/007_setup_completed.sql
Normal file
18
supabase/migrations/007_setup_completed.sql
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
-- Migration 007: Add setup_completed to profiles
|
||||||
|
-- New signups default to false (not set up).
|
||||||
|
-- Existing users who have any categories are backfilled to true (already set up).
|
||||||
|
-- Wider backfill also includes users with template items to protect against
|
||||||
|
-- edge case where user created template items but skipped category creation.
|
||||||
|
|
||||||
|
ALTER TABLE profiles
|
||||||
|
ADD COLUMN setup_completed boolean NOT NULL DEFAULT false;
|
||||||
|
|
||||||
|
-- Backfill: users with categories OR template items are considered set up
|
||||||
|
UPDATE profiles
|
||||||
|
SET setup_completed = true
|
||||||
|
WHERE id IN (
|
||||||
|
SELECT DISTINCT user_id FROM categories
|
||||||
|
UNION
|
||||||
|
SELECT t.user_id FROM templates t
|
||||||
|
INNER JOIN template_items ti ON ti.template_id = t.id
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user