-- 001_init.sql -- -- Initial schema for Chicken Babies R Us. Authoritative copy of the -- tables + indexes + check constraints documented in -- ``docs/ROADMAP.md`` (see "SQLite Schema (authoritative)"). -- -- Idempotency: every statement uses IF NOT EXISTS so re-running the -- file on a partially-migrated database is still safe. The migration -- runner also gates execution via the schema_migrations tracker, so -- this belt-and-braces approach is defensive only. -- -- No PRAGMA statements here: journal_mode = WAL and foreign_keys = ON -- are applied per-connection via the SQLAlchemy connect-event -- listener in ``app/db.py``. Setting them inside a migration file -- would be a no-op on every connection except the one that ran the -- migration, which is the opposite of what we want. CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, email TEXT NOT NULL UNIQUE, display_name TEXT NOT NULL, created_at TEXT NOT NULL, last_login_at TEXT, active INTEGER NOT NULL DEFAULT 1 ); CREATE TABLE IF NOT EXISTS magic_link_tokens ( id INTEGER PRIMARY KEY, email TEXT NOT NULL, token_hash TEXT NOT NULL UNIQUE, created_at TEXT NOT NULL, expires_at TEXT NOT NULL, used_at TEXT, request_ip TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_magic_email_created ON magic_link_tokens(email, created_at); CREATE TABLE IF NOT EXISTS sessions ( id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id), token_hash TEXT NOT NULL UNIQUE, created_at TEXT NOT NULL, expires_at TEXT NOT NULL, ip TEXT NOT NULL, user_agent TEXT NOT NULL, revoked_at TEXT ); CREATE TABLE IF NOT EXISTS pages ( id INTEGER PRIMARY KEY, slug TEXT NOT NULL UNIQUE, title TEXT NOT NULL, body_md TEXT NOT NULL, body_html_cached TEXT NOT NULL, updated_at TEXT NOT NULL, published INTEGER NOT NULL DEFAULT 1 ); CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY, slug TEXT NOT NULL UNIQUE, title TEXT NOT NULL, body_md TEXT NOT NULL, body_html_cached TEXT NOT NULL, status TEXT NOT NULL CHECK (status IN ('draft','published')), published_at TEXT, updated_at TEXT NOT NULL, author_user_id INTEGER NOT NULL REFERENCES users(id) ); CREATE INDEX IF NOT EXISTS idx_posts_status_pub ON posts(status, published_at DESC); CREATE TABLE IF NOT EXISTS media ( id INTEGER PRIMARY KEY, filename TEXT NOT NULL UNIQUE, original_filename TEXT NOT NULL, content_type TEXT NOT NULL, size_bytes INTEGER NOT NULL, stored_path TEXT NOT NULL, alt_text TEXT NOT NULL DEFAULT '', uploaded_by INTEGER NOT NULL REFERENCES users(id), uploaded_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS contact_submissions ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL, message TEXT NOT NULL, ip TEXT NOT NULL, user_agent TEXT NOT NULL, submitted_at TEXT NOT NULL, handled INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS auth_events ( id INTEGER PRIMARY KEY, event_type TEXT NOT NULL, email TEXT, user_id INTEGER REFERENCES users(id), ip TEXT NOT NULL, user_agent TEXT NOT NULL, created_at TEXT NOT NULL, detail TEXT NOT NULL DEFAULT '{}' ); CREATE INDEX IF NOT EXISTS idx_auth_events_created ON auth_events(created_at DESC);