From b5e91d9d0d5f6af95cfe4113495340c871545369 Mon Sep 17 00:00:00 2001 From: Jon Seager Date: Thu, 5 Feb 2026 21:06:37 +0000 Subject: [PATCH] refactor(db): consolidate 21 migrations into 4 clean files No production instances exist, so safe to rewrite from scratch. Drops columns that were added then removed (notes, password_hash, cup rating), standardises timestamp format and primary key style, and creates all tables in their final form without incremental ALTER TABLE churn. --- migrations/0001_auth.sql | 51 ++++++++++++++ migrations/0001_init.sql | 43 ------------ migrations/0002_auth.sql | 20 ------ migrations/0002_coffee.sql | 51 ++++++++++++++ migrations/0003_activities.sql | 42 +++++++++++ migrations/0003_sessions.sql | 13 ---- migrations/0004_add_bags.sql | 15 ---- migrations/0004_timeline_and_usage.sql | 30 ++++++++ migrations/0005_add_action_to_timeline.sql | 1 - migrations/0006_add_gear.sql | 11 --- ...07_update_timeline_constraint_for_gear.sql | 29 -------- migrations/0008_remove_gear_notes.sql | 1 - migrations/0009_add_brews.sql | 38 ---------- migrations/0010_denormalize_timeline.sql | 69 ------------------- migrations/0011_add_filter_paper.sql | 22 ------ migrations/0012_add_cafes.sql | 42 ----------- migrations/0013_add_cups.sql | 39 ----------- migrations/0014_remove_cafe_notes.sql | 1 - migrations/0015_remove_cup_notes.sql | 1 - migrations/0016_remove_roaster_notes.sql | 1 - migrations/0017_passkey_auth.sql | 34 --------- migrations/0018_remove_password_hash.sql | 3 - migrations/0019_add_ai_usage.sql | 13 ---- migrations/0020_remove_cup_rating.sql | 1 - migrations/0021_add_brew_notes.sql | 1 - 25 files changed, 174 insertions(+), 398 deletions(-) create mode 100644 migrations/0001_auth.sql delete mode 100644 migrations/0001_init.sql delete mode 100644 migrations/0002_auth.sql create mode 100644 migrations/0002_coffee.sql create mode 100644 migrations/0003_activities.sql delete mode 100644 migrations/0003_sessions.sql delete mode 100644 migrations/0004_add_bags.sql create mode 100644 migrations/0004_timeline_and_usage.sql delete mode 100644 migrations/0005_add_action_to_timeline.sql delete mode 100644 migrations/0006_add_gear.sql delete mode 100644 migrations/0007_update_timeline_constraint_for_gear.sql delete mode 100644 migrations/0008_remove_gear_notes.sql delete mode 100644 migrations/0009_add_brews.sql delete mode 100644 migrations/0010_denormalize_timeline.sql delete mode 100644 migrations/0011_add_filter_paper.sql delete mode 100644 migrations/0012_add_cafes.sql delete mode 100644 migrations/0013_add_cups.sql delete mode 100644 migrations/0014_remove_cafe_notes.sql delete mode 100644 migrations/0015_remove_cup_notes.sql delete mode 100644 migrations/0016_remove_roaster_notes.sql delete mode 100644 migrations/0017_passkey_auth.sql delete mode 100644 migrations/0018_remove_password_hash.sql delete mode 100644 migrations/0019_add_ai_usage.sql delete mode 100644 migrations/0020_remove_cup_rating.sql delete mode 100644 migrations/0021_add_brew_notes.sql diff --git a/migrations/0001_auth.sql b/migrations/0001_auth.sql new file mode 100644 index 0000000..798ed15 --- /dev/null +++ b/migrations/0001_auth.sql @@ -0,0 +1,51 @@ +-- Authentication and authorisation tables + +CREATE TABLE users ( + id INTEGER PRIMARY KEY, + username TEXT NOT NULL UNIQUE, + uuid TEXT, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) +); + +CREATE TABLE tokens ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + token_hash TEXT NOT NULL UNIQUE, + name TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), + last_used_at TEXT, + revoked_at TEXT +); +CREATE INDEX idx_tokens_user_id ON tokens(user_id); +CREATE INDEX idx_tokens_token_hash ON tokens(token_hash); + +CREATE TABLE sessions ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + session_token_hash TEXT NOT NULL, + created_at TEXT NOT NULL, + expires_at TEXT NOT NULL +); +CREATE INDEX idx_sessions_token_hash ON sessions(session_token_hash); +CREATE INDEX idx_sessions_user_id ON sessions(user_id); +CREATE INDEX idx_sessions_expires_at ON sessions(expires_at); + +CREATE TABLE passkey_credentials ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + credential_json TEXT NOT NULL, + name TEXT NOT NULL DEFAULT 'default', + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), + last_used_at TEXT +); +CREATE INDEX idx_passkey_credentials_user_id ON passkey_credentials(user_id); + +CREATE TABLE registration_tokens ( + id INTEGER PRIMARY KEY, + token_hash TEXT NOT NULL UNIQUE, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), + expires_at TEXT NOT NULL, + used_at TEXT, + used_by_user_id INTEGER REFERENCES users(id) +); +CREATE INDEX idx_registration_tokens_token_hash ON registration_tokens(token_hash); diff --git a/migrations/0001_init.sql b/migrations/0001_init.sql deleted file mode 100644 index ff27701..0000000 --- a/migrations/0001_init.sql +++ /dev/null @@ -1,43 +0,0 @@ -PRAGMA foreign_keys = ON; - -CREATE TABLE roasters ( - id INTEGER PRIMARY KEY, - name TEXT NOT NULL, - country TEXT NOT NULL, - city TEXT, - homepage TEXT, - notes TEXT, - slug TEXT, - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) -); - -CREATE UNIQUE INDEX idx_roasters_slug ON roasters(slug); - -CREATE TABLE roasts ( - id INTEGER PRIMARY KEY, - roaster_id INTEGER NOT NULL REFERENCES roasters(id) ON DELETE CASCADE, - name TEXT NOT NULL, - origin TEXT, - region TEXT, - producer TEXT, - process TEXT, - tasting_notes TEXT, - slug TEXT, - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) -); - -CREATE INDEX idx_roasts_roaster_id ON roasts(roaster_id); -CREATE UNIQUE INDEX idx_roasts_roaster_slug ON roasts(roaster_id, slug); - -CREATE TABLE timeline_events ( - id INTEGER PRIMARY KEY, - entity_type TEXT NOT NULL CHECK (entity_type IN ('roaster', 'roast', 'bag')), - entity_id INTEGER NOT NULL, - occurred_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), - title TEXT NOT NULL, - details_json TEXT, - tasting_notes_json TEXT -); - -CREATE INDEX idx_timeline_events_entity ON timeline_events(entity_type, entity_id); -CREATE INDEX idx_timeline_events_occurred_at ON timeline_events(occurred_at); diff --git a/migrations/0002_auth.sql b/migrations/0002_auth.sql deleted file mode 100644 index b309ed6..0000000 --- a/migrations/0002_auth.sql +++ /dev/null @@ -1,20 +0,0 @@ --- migrate:up -CREATE TABLE users ( - id INTEGER PRIMARY KEY, - username TEXT NOT NULL UNIQUE, - password_hash TEXT NOT NULL, - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) -); - -CREATE TABLE tokens ( - id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, - token_hash TEXT NOT NULL UNIQUE, - name TEXT NOT NULL, - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), - last_used_at TEXT, - revoked_at TEXT -); - -CREATE INDEX idx_tokens_user_id ON tokens(user_id); -CREATE INDEX idx_tokens_token_hash ON tokens(token_hash); diff --git a/migrations/0002_coffee.sql b/migrations/0002_coffee.sql new file mode 100644 index 0000000..2921865 --- /dev/null +++ b/migrations/0002_coffee.sql @@ -0,0 +1,51 @@ +-- Coffee supply chain and equipment tables + +CREATE TABLE roasters ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + country TEXT NOT NULL, + city TEXT, + homepage TEXT, + slug TEXT, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) +); +CREATE UNIQUE INDEX idx_roasters_slug ON roasters(slug); + +CREATE TABLE roasts ( + id INTEGER PRIMARY KEY, + roaster_id INTEGER NOT NULL REFERENCES roasters(id) ON DELETE CASCADE, + name TEXT NOT NULL, + origin TEXT, + region TEXT, + producer TEXT, + process TEXT, + tasting_notes TEXT, + slug TEXT, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) +); +CREATE INDEX idx_roasts_roaster_id ON roasts(roaster_id); +CREATE UNIQUE INDEX idx_roasts_roaster_slug ON roasts(roaster_id, slug); + +CREATE TABLE bags ( + id INTEGER PRIMARY KEY, + roast_id INTEGER NOT NULL REFERENCES roasts(id) ON DELETE CASCADE, + roast_date TEXT, + amount REAL NOT NULL, + remaining REAL NOT NULL, + closed BOOLEAN NOT NULL DEFAULT FALSE, + finished_at TEXT, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), + updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) +); +CREATE INDEX idx_bags_roast_id ON bags(roast_id); +CREATE INDEX idx_bags_closed ON bags(closed); + +CREATE TABLE gear ( + id INTEGER PRIMARY KEY, + category TEXT NOT NULL CHECK (category IN ('grinder', 'brewer', 'filter_paper')), + make TEXT NOT NULL, + model TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), + updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) +); +CREATE INDEX idx_gear_category ON gear(category); diff --git a/migrations/0003_activities.sql b/migrations/0003_activities.sql new file mode 100644 index 0000000..9a5c9df --- /dev/null +++ b/migrations/0003_activities.sql @@ -0,0 +1,42 @@ +-- Brew, cafe, and cup tables + +CREATE TABLE brews ( + id INTEGER PRIMARY KEY, + bag_id INTEGER NOT NULL REFERENCES bags(id) ON DELETE CASCADE, + coffee_weight REAL NOT NULL, + grinder_id INTEGER NOT NULL REFERENCES gear(id) ON DELETE RESTRICT, + grind_setting REAL NOT NULL, + brewer_id INTEGER NOT NULL REFERENCES gear(id) ON DELETE RESTRICT, + filter_paper_id INTEGER REFERENCES gear(id) ON DELETE SET NULL, + water_volume INTEGER NOT NULL, + water_temp REAL NOT NULL, + quick_notes TEXT, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), + updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) +); +CREATE INDEX idx_brews_bag_id ON brews(bag_id); +CREATE INDEX idx_brews_created_at ON brews(created_at DESC); + +CREATE TABLE cafes ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + slug TEXT NOT NULL, + city TEXT NOT NULL, + country TEXT NOT NULL, + latitude REAL NOT NULL, + longitude REAL NOT NULL, + website TEXT, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), + updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) +); +CREATE UNIQUE INDEX idx_cafes_slug ON cafes(slug); + +CREATE TABLE cups ( + id INTEGER PRIMARY KEY, + roast_id INTEGER NOT NULL REFERENCES roasts(id) ON DELETE RESTRICT, + cafe_id INTEGER NOT NULL REFERENCES cafes(id) ON DELETE RESTRICT, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), + updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) +); +CREATE INDEX idx_cups_roast_id ON cups(roast_id); +CREATE INDEX idx_cups_cafe_id ON cups(cafe_id); diff --git a/migrations/0003_sessions.sql b/migrations/0003_sessions.sql deleted file mode 100644 index 4903a54..0000000 --- a/migrations/0003_sessions.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Add sessions table for web authentication -CREATE TABLE IF NOT EXISTS sessions ( - id INTEGER PRIMARY KEY NOT NULL, - user_id INTEGER NOT NULL, - session_token_hash TEXT NOT NULL, - created_at TEXT NOT NULL, - expires_at TEXT NOT NULL, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS idx_sessions_token_hash ON sessions(session_token_hash); -CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id); -CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at); diff --git a/migrations/0004_add_bags.sql b/migrations/0004_add_bags.sql deleted file mode 100644 index 884a1a8..0000000 --- a/migrations/0004_add_bags.sql +++ /dev/null @@ -1,15 +0,0 @@ -CREATE TABLE bags ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - roast_id INTEGER NOT NULL, - roast_date DATE, - amount REAL NOT NULL, - remaining REAL NOT NULL, - closed BOOLEAN NOT NULL DEFAULT FALSE, - finished_at DATE, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (roast_id) REFERENCES roasts (id) ON DELETE CASCADE -); - -CREATE INDEX idx_bags_roast_id ON bags(roast_id); -CREATE INDEX idx_bags_closed ON bags(closed); diff --git a/migrations/0004_timeline_and_usage.sql b/migrations/0004_timeline_and_usage.sql new file mode 100644 index 0000000..4cd4930 --- /dev/null +++ b/migrations/0004_timeline_and_usage.sql @@ -0,0 +1,30 @@ +-- Timeline events and AI usage tracking + +CREATE TABLE timeline_events ( + id INTEGER PRIMARY KEY, + entity_type TEXT NOT NULL CHECK (entity_type IN ('roaster', 'roast', 'bag', 'gear', 'brew', 'cafe', 'cup')), + entity_id INTEGER NOT NULL, + action TEXT NOT NULL, + occurred_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), + title TEXT NOT NULL, + details_json TEXT, + tasting_notes_json TEXT, + slug TEXT, + roaster_slug TEXT, + brew_data_json TEXT +); +CREATE INDEX idx_timeline_events_entity ON timeline_events(entity_type, entity_id); +CREATE INDEX idx_timeline_events_occurred_at ON timeline_events(occurred_at DESC); + +CREATE TABLE ai_usage ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + model TEXT NOT NULL, + endpoint TEXT NOT NULL, + prompt_tokens INTEGER NOT NULL, + completion_tokens INTEGER NOT NULL, + total_tokens INTEGER NOT NULL, + cost REAL NOT NULL, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) +); +CREATE INDEX idx_ai_usage_user_id ON ai_usage(user_id); diff --git a/migrations/0005_add_action_to_timeline.sql b/migrations/0005_add_action_to_timeline.sql deleted file mode 100644 index 5151eb9..0000000 --- a/migrations/0005_add_action_to_timeline.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE timeline_events ADD COLUMN action TEXT NOT NULL DEFAULT 'added'; diff --git a/migrations/0006_add_gear.sql b/migrations/0006_add_gear.sql deleted file mode 100644 index 3c6b29e..0000000 --- a/migrations/0006_add_gear.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE gear ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - category TEXT NOT NULL CHECK (category IN ('grinder', 'brewer')), - make TEXT NOT NULL, - model TEXT NOT NULL, - notes TEXT, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX idx_gear_category ON gear(category); diff --git a/migrations/0007_update_timeline_constraint_for_gear.sql b/migrations/0007_update_timeline_constraint_for_gear.sql deleted file mode 100644 index 84e02ac..0000000 --- a/migrations/0007_update_timeline_constraint_for_gear.sql +++ /dev/null @@ -1,29 +0,0 @@ --- Update timeline_events CHECK constraint to include 'gear' --- SQLite requires recreating the table to modify CHECK constraints - --- Step 1: Rename old table -ALTER TABLE timeline_events RENAME TO timeline_events_old; - --- Step 2: Create new table with updated constraint -CREATE TABLE timeline_events ( - id INTEGER PRIMARY KEY, - entity_type TEXT NOT NULL CHECK (entity_type IN ('roaster', 'roast', 'bag', 'gear')), - entity_id INTEGER NOT NULL, - occurred_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), - title TEXT NOT NULL, - details_json TEXT, - tasting_notes_json TEXT, - action TEXT NOT NULL DEFAULT 'added' -); - --- Step 3: Copy data from old table -INSERT INTO timeline_events (id, entity_type, entity_id, occurred_at, title, details_json, tasting_notes_json, action) -SELECT id, entity_type, entity_id, occurred_at, title, details_json, tasting_notes_json, action -FROM timeline_events_old; - --- Step 4: Drop old table -DROP TABLE timeline_events_old; - --- Step 5: Recreate indexes -CREATE INDEX idx_timeline_events_entity ON timeline_events(entity_type, entity_id); -CREATE INDEX idx_timeline_events_occurred_at ON timeline_events(occurred_at); diff --git a/migrations/0008_remove_gear_notes.sql b/migrations/0008_remove_gear_notes.sql deleted file mode 100644 index 80522c5..0000000 --- a/migrations/0008_remove_gear_notes.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE gear DROP COLUMN notes; diff --git a/migrations/0009_add_brews.sql b/migrations/0009_add_brews.sql deleted file mode 100644 index a74c750..0000000 --- a/migrations/0009_add_brews.sql +++ /dev/null @@ -1,38 +0,0 @@ --- Brews table for logging individual coffee brews -CREATE TABLE brews ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - bag_id INTEGER NOT NULL REFERENCES bags(id) ON DELETE CASCADE, - coffee_weight REAL NOT NULL, - grinder_id INTEGER NOT NULL REFERENCES gear(id) ON DELETE RESTRICT, - grind_setting REAL NOT NULL, - brewer_id INTEGER NOT NULL REFERENCES gear(id) ON DELETE RESTRICT, - water_volume INTEGER NOT NULL, - water_temp REAL NOT NULL, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX idx_brews_bag_id ON brews(bag_id); -CREATE INDEX idx_brews_created_at ON brews(created_at DESC); - --- Update timeline constraint to include brew entity type -DROP TABLE IF EXISTS timeline_events_new; -CREATE TABLE timeline_events_new ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - entity_type TEXT NOT NULL CHECK (entity_type IN ('roaster', 'roast', 'bag', 'gear', 'brew')), - entity_id INTEGER NOT NULL, - action TEXT NOT NULL, - occurred_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), - title TEXT NOT NULL, - details_json TEXT, - tasting_notes_json TEXT -); - -INSERT INTO timeline_events_new (id, entity_type, entity_id, action, occurred_at, title, details_json, tasting_notes_json) -SELECT id, entity_type, entity_id, action, occurred_at, title, details_json, tasting_notes_json -FROM timeline_events; - -DROP TABLE timeline_events; -ALTER TABLE timeline_events_new RENAME TO timeline_events; -CREATE INDEX idx_timeline_events_entity ON timeline_events(entity_type, entity_id); -CREATE INDEX idx_timeline_events_occurred_at ON timeline_events(occurred_at DESC); diff --git a/migrations/0010_denormalize_timeline.sql b/migrations/0010_denormalize_timeline.sql deleted file mode 100644 index 96376eb..0000000 --- a/migrations/0010_denormalize_timeline.sql +++ /dev/null @@ -1,69 +0,0 @@ --- Add denormalized slug columns to timeline_events --- This eliminates the need for 9-way JOINs when listing timeline events - --- Step 1: Add the new columns -ALTER TABLE timeline_events ADD COLUMN slug TEXT; -ALTER TABLE timeline_events ADD COLUMN roaster_slug TEXT; -ALTER TABLE timeline_events ADD COLUMN brew_data_json TEXT; - --- Step 2: Backfill roaster slugs for roaster events -UPDATE timeline_events -SET slug = (SELECT slug FROM roasters WHERE roasters.id = timeline_events.entity_id) -WHERE entity_type = 'roaster'; - --- Step 3: Backfill slugs for roast events -UPDATE timeline_events -SET slug = (SELECT slug FROM roasts WHERE roasts.id = timeline_events.entity_id), - roaster_slug = ( - SELECT ro.slug FROM roasts r - JOIN roasters ro ON r.roaster_id = ro.id - WHERE r.id = timeline_events.entity_id - ) -WHERE entity_type = 'roast'; - --- Step 4: Backfill slugs for bag events -UPDATE timeline_events -SET slug = ( - SELECT r.slug FROM bags b - JOIN roasts r ON b.roast_id = r.id - WHERE b.id = timeline_events.entity_id - ), - roaster_slug = ( - SELECT ro.slug FROM bags b - JOIN roasts r ON b.roast_id = r.id - JOIN roasters ro ON r.roaster_id = ro.id - WHERE b.id = timeline_events.entity_id - ) -WHERE entity_type = 'bag'; - --- Step 5: Backfill slugs and brew_data for brew events -UPDATE timeline_events -SET slug = ( - SELECT r.slug FROM brews br - JOIN bags b ON br.bag_id = b.id - JOIN roasts r ON b.roast_id = r.id - WHERE br.id = timeline_events.entity_id - ), - roaster_slug = ( - SELECT ro.slug FROM brews br - JOIN bags b ON br.bag_id = b.id - JOIN roasts r ON b.roast_id = r.id - JOIN roasters ro ON r.roaster_id = ro.id - WHERE br.id = timeline_events.entity_id - ), - brew_data_json = ( - SELECT json_object( - 'bag_id', br.bag_id, - 'grinder_id', br.grinder_id, - 'brewer_id', br.brewer_id, - 'coffee_weight', br.coffee_weight, - 'grind_setting', br.grind_setting, - 'water_volume', br.water_volume, - 'water_temp', br.water_temp - ) - FROM brews br - WHERE br.id = timeline_events.entity_id - ) -WHERE entity_type = 'brew'; - --- Gear events have no slug (entity_type = 'gear' gets NULL for both slug columns) diff --git a/migrations/0011_add_filter_paper.sql b/migrations/0011_add_filter_paper.sql deleted file mode 100644 index ff7d8e2..0000000 --- a/migrations/0011_add_filter_paper.sql +++ /dev/null @@ -1,22 +0,0 @@ --- Add 'filter_paper' gear category and optional filter_paper_id on brews. --- --- SQLite does not support altering CHECK constraints, so we recreate the gear --- table with the updated constraint. - -CREATE TABLE gear_new ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - category TEXT NOT NULL CHECK (category IN ('grinder', 'brewer', 'filter_paper')), - make TEXT NOT NULL, - model TEXT NOT NULL, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -INSERT INTO gear_new (id, category, make, model, created_at, updated_at) -SELECT id, category, make, model, created_at, updated_at FROM gear; - -DROP TABLE gear; -ALTER TABLE gear_new RENAME TO gear; -CREATE INDEX idx_gear_category ON gear(category); - -ALTER TABLE brews ADD COLUMN filter_paper_id INTEGER REFERENCES gear(id) ON DELETE SET NULL; diff --git a/migrations/0012_add_cafes.sql b/migrations/0012_add_cafes.sql deleted file mode 100644 index e485b20..0000000 --- a/migrations/0012_add_cafes.sql +++ /dev/null @@ -1,42 +0,0 @@ --- Add cafes table for tracking coffee shops -CREATE TABLE cafes ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - slug TEXT NOT NULL, - city TEXT NOT NULL, - country TEXT NOT NULL, - latitude REAL NOT NULL, - longitude REAL NOT NULL, - website TEXT, - notes TEXT, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE UNIQUE INDEX idx_cafes_slug ON cafes(slug); - --- Update timeline_events CHECK constraint to include 'cafe' --- SQLite requires recreating the table to modify CHECK constraints -DROP TABLE IF EXISTS timeline_events_new; -CREATE TABLE timeline_events_new ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - entity_type TEXT NOT NULL CHECK (entity_type IN ('roaster', 'roast', 'bag', 'gear', 'brew', 'cafe')), - entity_id INTEGER NOT NULL, - action TEXT NOT NULL, - occurred_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), - title TEXT NOT NULL, - details_json TEXT, - tasting_notes_json TEXT, - slug TEXT, - roaster_slug TEXT, - brew_data_json TEXT -); - -INSERT INTO timeline_events_new (id, entity_type, entity_id, action, occurred_at, title, details_json, tasting_notes_json, slug, roaster_slug, brew_data_json) -SELECT id, entity_type, entity_id, action, occurred_at, title, details_json, tasting_notes_json, slug, roaster_slug, brew_data_json -FROM timeline_events; - -DROP TABLE timeline_events; -ALTER TABLE timeline_events_new RENAME TO timeline_events; -CREATE INDEX idx_timeline_events_entity ON timeline_events(entity_type, entity_id); -CREATE INDEX idx_timeline_events_occurred_at ON timeline_events(occurred_at DESC); diff --git a/migrations/0013_add_cups.sql b/migrations/0013_add_cups.sql deleted file mode 100644 index 5d02837..0000000 --- a/migrations/0013_add_cups.sql +++ /dev/null @@ -1,39 +0,0 @@ --- Add cups table for tracking roasts consumed at cafes -CREATE TABLE cups ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - roast_id INTEGER NOT NULL REFERENCES roasts(id) ON DELETE RESTRICT, - cafe_id INTEGER NOT NULL REFERENCES cafes(id) ON DELETE RESTRICT, - notes TEXT, - rating INTEGER CHECK (rating BETWEEN 1 AND 5), - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX idx_cups_roast_id ON cups(roast_id); -CREATE INDEX idx_cups_cafe_id ON cups(cafe_id); - --- Update timeline_events CHECK constraint to include 'cup' --- SQLite requires recreating the table to modify CHECK constraints -DROP TABLE IF EXISTS timeline_events_new; -CREATE TABLE timeline_events_new ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - entity_type TEXT NOT NULL CHECK (entity_type IN ('roaster', 'roast', 'bag', 'gear', 'brew', 'cafe', 'cup')), - entity_id INTEGER NOT NULL, - action TEXT NOT NULL, - occurred_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), - title TEXT NOT NULL, - details_json TEXT, - tasting_notes_json TEXT, - slug TEXT, - roaster_slug TEXT, - brew_data_json TEXT -); - -INSERT INTO timeline_events_new (id, entity_type, entity_id, action, occurred_at, title, details_json, tasting_notes_json, slug, roaster_slug, brew_data_json) -SELECT id, entity_type, entity_id, action, occurred_at, title, details_json, tasting_notes_json, slug, roaster_slug, brew_data_json -FROM timeline_events; - -DROP TABLE timeline_events; -ALTER TABLE timeline_events_new RENAME TO timeline_events; -CREATE INDEX idx_timeline_events_entity ON timeline_events(entity_type, entity_id); -CREATE INDEX idx_timeline_events_occurred_at ON timeline_events(occurred_at DESC); diff --git a/migrations/0014_remove_cafe_notes.sql b/migrations/0014_remove_cafe_notes.sql deleted file mode 100644 index 9554899..0000000 --- a/migrations/0014_remove_cafe_notes.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE cafes DROP COLUMN notes; diff --git a/migrations/0015_remove_cup_notes.sql b/migrations/0015_remove_cup_notes.sql deleted file mode 100644 index 1ee068f..0000000 --- a/migrations/0015_remove_cup_notes.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE cups DROP COLUMN notes; diff --git a/migrations/0016_remove_roaster_notes.sql b/migrations/0016_remove_roaster_notes.sql deleted file mode 100644 index d387d88..0000000 --- a/migrations/0016_remove_roaster_notes.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE roasters DROP COLUMN notes; diff --git a/migrations/0017_passkey_auth.sql b/migrations/0017_passkey_auth.sql deleted file mode 100644 index 340c1e2..0000000 --- a/migrations/0017_passkey_auth.sql +++ /dev/null @@ -1,34 +0,0 @@ --- Add UUID column to users for WebAuthn user handle -ALTER TABLE users ADD COLUMN uuid TEXT; - --- Backfill existing users with random v4 UUIDs -UPDATE users SET uuid = - lower(hex(randomblob(4))) || '-' || - lower(hex(randomblob(2))) || '-' || - '4' || substr(lower(hex(randomblob(2))), 2) || '-' || - substr('89ab', abs(random()) % 4 + 1, 1) || substr(lower(hex(randomblob(2))), 2) || '-' || - lower(hex(randomblob(6))); - --- Passkey credential storage -CREATE TABLE passkey_credentials ( - id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, - credential_json TEXT NOT NULL, - name TEXT NOT NULL DEFAULT 'default', - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), - last_used_at TEXT -); - -CREATE INDEX idx_passkey_credentials_user_id ON passkey_credentials(user_id); - --- One-time registration tokens for bootstrap and invite flows -CREATE TABLE registration_tokens ( - id INTEGER PRIMARY KEY, - token_hash TEXT NOT NULL UNIQUE, - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), - expires_at TEXT NOT NULL, - used_at TEXT, - used_by_user_id INTEGER REFERENCES users(id) -); - -CREATE INDEX idx_registration_tokens_token_hash ON registration_tokens(token_hash); diff --git a/migrations/0018_remove_password_hash.sql b/migrations/0018_remove_password_hash.sql deleted file mode 100644 index e504618..0000000 --- a/migrations/0018_remove_password_hash.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Remove password_hash column from users (passkey-only auth) --- SQLite 3.35+ supports ALTER TABLE DROP COLUMN -ALTER TABLE users DROP COLUMN password_hash; diff --git a/migrations/0019_add_ai_usage.sql b/migrations/0019_add_ai_usage.sql deleted file mode 100644 index 410f1c1..0000000 --- a/migrations/0019_add_ai_usage.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE ai_usage ( - id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, - model TEXT NOT NULL, - endpoint TEXT NOT NULL, - prompt_tokens INTEGER NOT NULL, - completion_tokens INTEGER NOT NULL, - total_tokens INTEGER NOT NULL, - cost REAL NOT NULL, - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) -); - -CREATE INDEX idx_ai_usage_user_id ON ai_usage(user_id); diff --git a/migrations/0020_remove_cup_rating.sql b/migrations/0020_remove_cup_rating.sql deleted file mode 100644 index b639543..0000000 --- a/migrations/0020_remove_cup_rating.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE cups DROP COLUMN rating; diff --git a/migrations/0021_add_brew_notes.sql b/migrations/0021_add_brew_notes.sql deleted file mode 100644 index 5784907..0000000 --- a/migrations/0021_add_brew_notes.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE brews ADD COLUMN quick_notes TEXT;