-- ===================================================================== -- DOGFATHER × VanVan Supporter-Abo — Datenbankschema -- Setzt "Dogfather_VanVan_Supporter_Abo.odt" um (Abschnitt 28 empfiehlt -- diese Struktur; hier leicht angepasst an die Konventionen des restlichen -- Workers: TEXT-IDs, ISO-Zeitstempel, Wiederverwendung von audit_log statt -- eines eigenen Änderungsprotokolls). -- -- Bewusst eine EIGENE, von der internen Team-Zugangsverwaltung (users/ -- roles/sessions) komplett GETRENNTE Nutzerwelt — Supporter sind -- öffentliche Fans/Kund:innen, keine Teammitglieder mit Rechten. -- ===================================================================== -- Öffentliche Supporter-Accounts. Nur Name, TikTok-Name, E-Mail bei -- Registrierung (siehe Abschnitt 5/30 — Datensparsamkeit). CREATE TABLE IF NOT EXISTS supporter_users ( id TEXT PRIMARY KEY, display_name TEXT NOT NULL, tiktok_username TEXT, -- ohne "@", normalisiert email TEXT NOT NULL UNIQUE, email_verified INTEGER NOT NULL DEFAULT 0, pending_email TEXT, -- neue E-Mail, bis sie bestätigt wird (Abschnitt 25) status TEXT NOT NULL DEFAULT 'active', -- active | deleted -- Versanddaten werden ERST bei der ersten Prämien-Einlösung abgefragt -- (Abschnitt 17/30), deshalb hier bewusst alles NULL-fähig. ship_name TEXT, ship_street TEXT, ship_zip TEXT, ship_city TEXT, ship_country TEXT, ship_save_address INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, last_login_at TEXT ); -- Einmalcodes für E-Mail-Bestätigung UND Magic-Link-Login (Abschnitt 5/116). CREATE TABLE IF NOT EXISTS supporter_auth_codes ( id TEXT PRIMARY KEY, supporter_id TEXT NOT NULL REFERENCES supporter_users(id), purpose TEXT NOT NULL, -- verify_email | login code_hash TEXT NOT NULL, expires_at TEXT NOT NULL, used_at TEXT, created_at TEXT NOT NULL ); -- Sessions für den öffentlichen Supporter-Bereich — eigenes, von der -- internen Team-Session-Tabelle getrenntes Token-System. CREATE TABLE IF NOT EXISTS supporter_sessions ( token_hash TEXT PRIMARY KEY, supporter_id TEXT NOT NULL REFERENCES supporter_users(id), created_at TEXT NOT NULL, last_active_at TEXT NOT NULL, expires_at TEXT NOT NULL ); -- Eine (aktuell: genau eine) Abo-Stufe pro Supporter. CREATE TABLE IF NOT EXISTS supporter_subscriptions ( id TEXT PRIMARY KEY, supporter_id TEXT NOT NULL REFERENCES supporter_users(id), paypal_subscription_id TEXT UNIQUE, -- erst nach PayPal-Freigabe gesetzt paypal_plan_id TEXT, status TEXT NOT NULL DEFAULT 'pending', -- pending | active | suspended | cancelled | expired started_at TEXT, next_payment_at TEXT, paid_period_end_at TEXT, -- bis wann Zugang nach Kündigung bleibt cancelled_at TEXT, -- Fortschritt (Abschnitt 11-13): Gesamtzahl erfolgreich angerechneter -- Monate bleibt für IMMER erhalten (auch über Kündigung/Reaktivierung -- hinweg, Abschnitt 23) — daraus werden Zyklus/Meilenstein/Kollektion -- rein rechnerisch abgeleitet (siehe lib/supporter-cycle.js), NICHT -- redundant gespeichert, damit nichts auseinanderlaufen kann. total_paid_months INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); -- Jede EINZELNE erfolgreiche/fehlgeschlagene PayPal-Zahlung. Idempotenz -- über paypal_event_id: jedes Webhook-Ereignis wird nur einmal verarbeitet -- (Abschnitt 8, "doppelt empfangenes PayPal-Ereignis: keine doppelte -- Anrechnung"). CREATE TABLE IF NOT EXISTS supporter_payments ( id TEXT PRIMARY KEY, supporter_id TEXT NOT NULL REFERENCES supporter_users(id), subscription_id TEXT NOT NULL REFERENCES supporter_subscriptions(id), paypal_transaction_id TEXT, paypal_event_id TEXT UNIQUE, amount TEXT, currency TEXT DEFAULT 'EUR', payment_status TEXT NOT NULL, -- completed | failed | refunded | reversed is_sandbox INTEGER NOT NULL DEFAULT 0, -- Testzahlungen zählen NIE als echter Monat counted_for_progress INTEGER NOT NULL DEFAULT 0, paid_at TEXT NOT NULL, created_at TEXT NOT NULL ); -- Supporter-Kollektionen (Abschnitt 14) — jeder abgeschlossene 30-Monats- -- Zyklus bekommt ein neues Design. Zyklus-Nummer 1 = Kollektion 01 usw. -- collection_number ist der 1-basierte Zyklus-Index (global, nicht pro -- Supporter — alle Supporter im selben Zyklus teilen sich EIN Design). CREATE TABLE IF NOT EXISTS supporter_collections ( id TEXT PRIMARY KEY, collection_number INTEGER NOT NULL UNIQUE, name TEXT NOT NULL, theme TEXT, description TEXT, cover_image TEXT, small_teddy_design TEXT, medium_teddy_design TEXT, large_teddy_design TEXT, mug_design TEXT, card_design TEXT, hoodie_design TEXT, available_hoodie_sizes TEXT DEFAULT '["S","M","L","XL","XXL"]', -- JSON-Array status TEXT NOT NULL DEFAULT 'draft', -- draft | published created_at TEXT NOT NULL ); -- Eine Zeile pro freigeschaltetem Meilenstein/Prämie eines Supporters. -- milestone_month ∈ {6,12,18,24,30}, cycle_number = welcher 30er-Zyklus. CREATE TABLE IF NOT EXISTS supporter_premiums ( id TEXT PRIMARY KEY, supporter_id TEXT NOT NULL REFERENCES supporter_users(id), collection_id TEXT NOT NULL REFERENCES supporter_collections(id), cycle_number INTEGER NOT NULL, milestone_month INTEGER NOT NULL, -- 6 | 12 | 18 | 24 | 30 premium_type TEXT NOT NULL, -- teddy_klein | teddy_mittel | teddy_gross | tasse_karte | hoodie unlocked_at TEXT NOT NULL, redeem_status TEXT NOT NULL DEFAULT 'verfuegbar', -- verfuegbar | eingeloest | wird_geprueft | wird_vorbereitet -- versandbereit | versendet | zugestellt | erhalten | problem | ersatz_wird_vorbereitet redeemed_at TEXT, hoodie_size TEXT, hoodie_variant TEXT, ship_name TEXT, ship_street TEXT, ship_zip TEXT, ship_city TEXT, ship_country TEXT, tracking_number TEXT, shipped_at TEXT, delivered_at TEXT, admin_note TEXT, UNIQUE (supporter_id, cycle_number, milestone_month) ); CREATE INDEX IF NOT EXISTS idx_supp_sub_supporter ON supporter_subscriptions(supporter_id); CREATE INDEX IF NOT EXISTS idx_supp_pay_supporter ON supporter_payments(supporter_id); CREATE INDEX IF NOT EXISTS idx_supp_pay_sub ON supporter_payments(subscription_id); CREATE INDEX IF NOT EXISTS idx_supp_premiums_supporter ON supporter_premiums(supporter_id); CREATE INDEX IF NOT EXISTS idx_supp_sessions_supporter ON supporter_sessions(supporter_id); CREATE INDEX IF NOT EXISTS idx_supp_codes_supporter ON supporter_auth_codes(supporter_id); -- Kollektion 01 direkt mit anlegen, damit das System sofort nutzbar ist — -- Designs/Bilder können jederzeit im Admin-Bereich nachgepflegt werden. INSERT OR IGNORE INTO supporter_collections (id, collection_number, name, theme, description, status, created_at) VALUES ('collection-01', 1, 'Der Anfang', 'Babyblau & Silber', 'Die allererste Supporter-Kollektion von Dogfather × VanVan.', 'published', datetime('now'));