M12b: category contract v2 on the server (schema 2, fourteen defaults, finer import mapping)
- migrations/0002_categories_v2.sql, byte-identical to contracts/schema/migrations/0002_categories_v2.sql (SPEC §14.1.1), with an identity test; SCHEMA_VERSION = 2 (E-1 reports it, the importer's check follows). - Seed test on a fresh database (the fourteen rows of AC-7.2, server_version 11 to 14) and an upgrade test of a v1 database in use (0001 applied through the migrator, then users, a custom, a renamed and an archived default, an account, a recurring template, expenses): every earlier row unchanged, the four rows above the old sequence, foreign_key_check empty, schema_version 2, a pull from the old cursor returns exactly the four rows. - DefaultCategoryKey with the fourteen keys (wire enum and contracts/openapi.yaml); icons 33 and colours 14 (§9.7) in validation. - The two category examples M12a held back (E-26 response-default, sync/pull-page-categories-v2) and its contracts-check patch for them and the enum. - Importer: splitwise_categories.csv per the §10.8 v0.8 table (D-053), DEFAULT_KEYS of fourteen, synthetic fixtures for the finer mapping (Insurance/Försäkringar to insurance among them), a schema-1 refusal test. - Test harnesses seed users at server_version 15/16 (after the fourteen seeds); pull counts 10 -> 14. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
-- Kvitt server schema v2: four new default categories (housing, insurance, children, fees). Do not edit; add a new
|
||||
-- migration and bump the schema version (SPEC §14.8). Applied on top of server-v1.sql (0001_init.sql).
|
||||
--
|
||||
-- The default_key CHECK of categories cannot be altered in place in SQLite, so the table is rebuilt: copy the rows
|
||||
-- aside, drop the table, recreate it with the new CHECK and copy the rows back, ids and server_version unchanged.
|
||||
-- The migration runs inside one transaction (sqlx wraps every migration in one) with foreign_keys = ON (SPEC §8.9).
|
||||
-- defer_foreign_keys = ON lets expenses and recurring_templates point at categories while the table is briefly
|
||||
-- absent; the deferred check at commit still fails the migration if any reference is left dangling. It resets
|
||||
-- itself when the transaction ends. No other table, index or trigger refers to categories by anything but name.
|
||||
PRAGMA defer_foreign_keys = ON;
|
||||
|
||||
CREATE TABLE categories_v1_copy AS SELECT * FROM categories;
|
||||
DROP TABLE categories;
|
||||
|
||||
CREATE TABLE categories (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
default_key TEXT UNIQUE CHECK (default_key IS NULL OR default_key IN
|
||||
('groceries', 'restaurants', 'home', 'utilities', 'transport',
|
||||
'travel', 'entertainment', 'health', 'shopping', 'other',
|
||||
'housing', 'insurance', 'children', 'fees')),
|
||||
name TEXT CHECK (name IS NULL OR length(name) BETWEEN 1 AND 40),
|
||||
icon TEXT NOT NULL,
|
||||
color TEXT NOT NULL,
|
||||
sort_order INTEGER NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
archived_at TEXT,
|
||||
deleted_at TEXT,
|
||||
server_version INTEGER NOT NULL UNIQUE,
|
||||
CHECK (name IS NOT NULL OR default_key IS NOT NULL)
|
||||
) STRICT;
|
||||
|
||||
INSERT INTO categories (id, default_key, name, icon, color, sort_order, created_at, updated_at, archived_at, deleted_at, server_version)
|
||||
SELECT id, default_key, name, icon, color, sort_order, created_at, updated_at, archived_at, deleted_at, server_version
|
||||
FROM categories_v1_copy;
|
||||
DROP TABLE categories_v1_copy;
|
||||
|
||||
-- Each new row takes the next server_version from the global sequence, as every server write does (SPEC §14.1), so
|
||||
-- phones receive the rows on their next pull whatever their cursor.
|
||||
UPDATE sync_sequence SET value = value + 1 WHERE id = 1;
|
||||
INSERT INTO categories (id, default_key, name, icon, color, sort_order, created_at, updated_at, archived_at, deleted_at, server_version)
|
||||
SELECT '01920000-0000-7000-8000-000000000011', 'housing', NULL, 'key', 'indigo', 25,
|
||||
'2026-10-18T00:00:00.000Z', '2026-10-18T00:00:00.000Z', NULL, NULL, value
|
||||
FROM sync_sequence WHERE id = 1;
|
||||
|
||||
UPDATE sync_sequence SET value = value + 1 WHERE id = 1;
|
||||
INSERT INTO categories (id, default_key, name, icon, color, sort_order, created_at, updated_at, archived_at, deleted_at, server_version)
|
||||
SELECT '01920000-0000-7000-8000-000000000012', 'insurance', NULL, 'shield', 'slate', 45,
|
||||
'2026-10-18T00:00:00.000Z', '2026-10-18T00:00:00.000Z', NULL, NULL, value
|
||||
FROM sync_sequence WHERE id = 1;
|
||||
|
||||
UPDATE sync_sequence SET value = value + 1 WHERE id = 1;
|
||||
INSERT INTO categories (id, default_key, name, icon, color, sort_order, created_at, updated_at, archived_at, deleted_at, server_version)
|
||||
SELECT '01920000-0000-7000-8000-000000000013', 'children', NULL, 'child_care', 'lime', 85,
|
||||
'2026-10-18T00:00:00.000Z', '2026-10-18T00:00:00.000Z', NULL, NULL, value
|
||||
FROM sync_sequence WHERE id = 1;
|
||||
|
||||
UPDATE sync_sequence SET value = value + 1 WHERE id = 1;
|
||||
INSERT INTO categories (id, default_key, name, icon, color, sort_order, created_at, updated_at, archived_at, deleted_at, server_version)
|
||||
SELECT '01920000-0000-7000-8000-000000000014', 'fees', NULL, 'account_balance', 'amber', 95,
|
||||
'2026-10-18T00:00:00.000Z', '2026-10-18T00:00:00.000Z', NULL, NULL, value
|
||||
FROM sync_sequence WHERE id = 1;
|
||||
|
||||
UPDATE meta SET value = '2' WHERE key = 'schema_version';
|
||||
Reference in New Issue
Block a user