File: /home/mmickelson/view-once.com/src/db.php
<?php
// Database bootstrap and migrations
function get_db(): PDO {
  ini_set('display_errors', 0);
  error_reporting(E_ALL);
  header_remove('X-Powered-By');
  // Ensure data dirs
  $dataDir = dirname(DB_FILE);
  if (!is_dir($dataDir)) { mkdir($dataDir, 0755, true); }
  if (!is_dir(FILES_DIR)) { mkdir(FILES_DIR, 0755, true); }
  $db = new PDO('sqlite:' . DB_FILE, null, null, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
  ]);
  // Base table
  $db->exec('CREATE TABLE IF NOT EXISTS secrets (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    token TEXT UNIQUE NOT NULL,
    body TEXT NOT NULL,
    created_at INTEGER NOT NULL,
    expires_at INTEGER NOT NULL,
    is_file INTEGER DEFAULT 0,
    filename TEXT,
    mime_type TEXT,
    file_size INTEGER
  )');
  // Migration: expires_at
  try {
    $db->exec('SELECT expires_at FROM secrets LIMIT 1');
  } catch (PDOException $e) {
    $db->exec('ALTER TABLE secrets ADD COLUMN expires_at INTEGER NOT NULL DEFAULT 0');
    $db->exec('UPDATE secrets SET expires_at = created_at + 86400 WHERE expires_at = 0');
  }
  // Migration: file columns
  try {
    $db->exec('SELECT is_file FROM secrets LIMIT 1');
  } catch (PDOException $e) {
    $db->exec('ALTER TABLE secrets ADD COLUMN is_file INTEGER DEFAULT 0');
    $db->exec('ALTER TABLE secrets ADD COLUMN filename TEXT');
    $db->exec('ALTER TABLE secrets ADD COLUMN mime_type TEXT');
    $db->exec('ALTER TABLE secrets ADD COLUMN file_size INTEGER');
  }
  // Indexes
  $db->exec('CREATE INDEX IF NOT EXISTS idx_token ON secrets(token)');
  $db->exec('CREATE INDEX IF NOT EXISTS idx_expires ON secrets(expires_at)');
  return $db;
}