From 59a3bb8382b0cf52aa57cae419e48ef809bed94f Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Sun, 12 Apr 2026 17:55:10 -0700 Subject: [PATCH] =?UTF-8?q?feat(community):=20migration=20026=20=E2=80=94?= =?UTF-8?q?=20community=5Fpseudonyms=20table=20in=20per-user=20kiwi.db?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/026_community_pseudonyms.sql | 21 ++++++++ tests/db/test_migration_026.py | 49 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 app/db/migrations/026_community_pseudonyms.sql create mode 100644 tests/db/test_migration_026.py diff --git a/app/db/migrations/026_community_pseudonyms.sql b/app/db/migrations/026_community_pseudonyms.sql new file mode 100644 index 0000000..ed6ac3f --- /dev/null +++ b/app/db/migrations/026_community_pseudonyms.sql @@ -0,0 +1,21 @@ +-- 026_community_pseudonyms.sql +-- Per-user pseudonym store: maps the user's chosen community display name +-- to their Directus user ID. This table lives in per-user kiwi.db only. +-- It is NEVER replicated to the community PostgreSQL — pseudonym isolation is by design. +-- +-- A user may have one active pseudonym. Old pseudonyms are retained for reference +-- (posts published under them keep their pseudonym attribution) but only one is +-- flagged as current (is_current = 1). + +CREATE TABLE IF NOT EXISTS community_pseudonyms ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + pseudonym TEXT NOT NULL, + directus_user_id TEXT NOT NULL, + is_current INTEGER NOT NULL DEFAULT 1 CHECK (is_current IN (0, 1)), + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +-- Only one pseudonym can be current at a time per user +CREATE UNIQUE INDEX IF NOT EXISTS idx_community_pseudonyms_current + ON community_pseudonyms (directus_user_id) + WHERE is_current = 1; diff --git a/tests/db/test_migration_026.py b/tests/db/test_migration_026.py new file mode 100644 index 0000000..596be38 --- /dev/null +++ b/tests/db/test_migration_026.py @@ -0,0 +1,49 @@ +# tests/db/test_migration_026.py +import pytest +from pathlib import Path +from app.db.store import Store + + +def test_migration_026_adds_community_pseudonyms(tmp_path): + """Migration 026 adds community_pseudonyms table to per-user kiwi.db.""" + store = Store(tmp_path / "test.db") + cur = store.conn.execute( + "SELECT name FROM sqlite_master WHERE type='table' AND name='community_pseudonyms'" + ) + assert cur.fetchone() is not None, "community_pseudonyms table must exist after migrations" + store.close() + + +def test_migration_026_unique_index_enforces_single_current_pseudonym(tmp_path): + """Only one is_current=1 pseudonym per directus_user_id is allowed.""" + store = Store(tmp_path / "test.db") + store.conn.execute( + "INSERT INTO community_pseudonyms (pseudonym, directus_user_id, is_current) VALUES (?, ?, 1)", + ("PastaWitch", "user-abc-123"), + ) + store.conn.commit() + + import sqlite3 + with pytest.raises(sqlite3.IntegrityError): + store.conn.execute( + "INSERT INTO community_pseudonyms (pseudonym, directus_user_id, is_current) VALUES (?, ?, 1)", + ("NoodleNinja", "user-abc-123"), + ) + store.conn.commit() + + store.close() + + +def test_migration_026_allows_historical_pseudonyms(tmp_path): + """Multiple is_current=0 pseudonyms are allowed for the same user.""" + store = Store(tmp_path / "test.db") + store.conn.executemany( + "INSERT INTO community_pseudonyms (pseudonym, directus_user_id, is_current) VALUES (?, ?, 0)", + [("OldName1", "user-abc-123"), ("OldName2", "user-abc-123")], + ) + store.conn.commit() + cur = store.conn.execute( + "SELECT COUNT(*) FROM community_pseudonyms WHERE directus_user_id='user-abc-123'" + ) + assert cur.fetchone()[0] == 2 + store.close()