" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, email TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'user', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, slug TEXT UNIQUE NOT NULL ); CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT NOT NULL, slug TEXT UNIQUE NOT NULL DEFAULT '', author_id INTEGER REFERENCES users(id) ON DELETE SET NULL, category_id INTEGER REFERENCES categories(id) ON DELETE SET NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_posts_author_id ON posts(author_id); CREATE TABLE IF NOT EXISTS media ( id INTEGER PRIMARY KEY AUTOINCREMENT, filename TEXT NOT NULL, url TEXT NOT NULL, hash TEXT NOT NULL, user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_media_user_id ON media(user_id); CREATE INDEX IF NOT EXISTS idx_media_hash_user_id ON media(hash, user_id); CREATE TABLE IF NOT EXISTS password_resets ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, expires_at DATETIME NOT NULL, used_at DATETIME DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS login_attempts ( ip TEXT NOT NULL PRIMARY KEY, attempts INTEGER NOT NULL DEFAULT 0, locked_until TEXT DEFAULT NULL, updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); ", 'down' => " DROP TABLE IF EXISTS login_attempts; DROP TABLE IF EXISTS password_resets; DROP INDEX IF EXISTS idx_media_hash_user_id; DROP INDEX IF EXISTS idx_media_user_id; DROP TABLE IF EXISTS media; DROP INDEX IF EXISTS idx_posts_author_id; DROP TABLE IF EXISTS posts; DROP TABLE IF EXISTS categories; DROP TABLE IF EXISTS users; ", ];