PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_TIMEOUT => 5 ]); // Enable WAL mode for concurrency and performance $pdo->exec('PRAGMA journal_mode = WAL;'); $pdo->exec('PRAGMA foreign_keys = ON;'); // Initialize tables if needed init_db_schema($pdo); return $pdo; } function init_db_schema(PDO $pdo): void { $pdo->exec(" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'admin', is_enabled INTEGER NOT NULL DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS announcements ( id INTEGER PRIMARY KEY AUTOINCREMENT, is_active INTEGER NOT NULL DEFAULT 1, type TEXT NOT NULL DEFAULT 'holiday', headline TEXT NOT NULL, message TEXT NOT NULL, start_date TEXT NULL, end_date TEXT NULL, show_banner INTEGER NOT NULL DEFAULT 1, show_in_hours INTEGER NOT NULL DEFAULT 1, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS menu_categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, description TEXT NULL, sort_order INTEGER NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS menu_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, category_id INTEGER NOT NULL REFERENCES menu_categories(id) ON DELETE CASCADE, name TEXT NOT NULL, description TEXT NULL, price TEXT NOT NULL, badge TEXT NULL, is_available INTEGER NOT NULL DEFAULT 1, sort_order INTEGER NOT NULL DEFAULT 0, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS pub_hours ( day_of_week INTEGER PRIMARY KEY, is_open INTEGER NOT NULL DEFAULT 0, open_time TEXT NULL, close_time TEXT NULL, kitchen_close_time TEXT NULL, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- Single-row table (id is pinned to 1) for site-wide toggles that -- don't belong to any one record — starting with whether the -- public menu page shows its items at all. CREATE TABLE IF NOT EXISTS site_settings ( id INTEGER PRIMARY KEY CHECK (id = 1), menu_enabled INTEGER NOT NULL DEFAULT 1, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- Failed admin logins, used by inc/login-throttle.php. Rows older -- than the throttle window are pruned on every new failure. CREATE TABLE IF NOT EXISTS login_attempts ( id INTEGER PRIMARY KEY AUTOINCREMENT, ip TEXT NOT NULL, username TEXT NOT NULL, attempted_at INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_login_attempts_ip ON login_attempts (ip, attempted_at); CREATE INDEX IF NOT EXISTS idx_login_attempts_username ON login_attempts (username, attempted_at); "); // users.is_enabled shipped after the initial release — CREATE TABLE IF // NOT EXISTS above is a no-op against an existing table, so an install // from before this needs the column added explicitly, once. $hasIsEnabled = false; foreach ($pdo->query("PRAGMA table_info(users)") as $col) { if ($col['name'] === 'is_enabled') { $hasIsEnabled = true; break; } } if (!$hasIsEnabled) { $pdo->exec("ALTER TABLE users ADD COLUMN is_enabled INTEGER NOT NULL DEFAULT 1"); } // No default admin is seeded — a fresh database has no users until one // is created from the CLI (run as www-data so the sqlite file stays // writable by Apache): // docker exec -it -u www-data the-old-fitz php /var/www/html/bin/create-user.php // Seed default weekly hours if the table is empty: closed Sun–Wed, // 4–10pm Thu–Sat with the kitchen closing at 9 — matches the hours the // site originally shipped with as hardcoded copy. $stmt = $pdo->query("SELECT COUNT(*) as count FROM pub_hours"); $hoursCount = $stmt->fetchColumn(); if ($hoursCount == 0) { $insert = $pdo->prepare(" INSERT INTO pub_hours (day_of_week, is_open, open_time, close_time, kitchen_close_time) VALUES (?, ?, ?, ?, ?) "); // 0=Sunday ... 6=Saturday $defaults = [ 0 => null, // Sunday — closed 1 => null, // Monday — closed 2 => null, // Tuesday — closed 3 => null, // Wednesday — closed 4 => ['16:00', '22:00', '21:00'], // Thursday 5 => ['16:00', '22:00', '21:00'], // Friday 6 => ['16:00', '22:00', '21:00'], // Saturday ]; foreach ($defaults as $day => $times) { if ($times === null) { $insert->execute([$day, 0, null, null, null]); } else { $insert->execute([$day, 1, $times[0], $times[1], $times[2]]); } } } // Seed the singleton settings row (menu on by default) if it doesn't exist yet. $stmt = $pdo->query("SELECT COUNT(*) as count FROM site_settings"); $settingsCount = $stmt->fetchColumn(); if ($settingsCount == 0) { $pdo->exec("INSERT INTO site_settings (id, menu_enabled) VALUES (1, 1)"); } }