Add SQLite-backed announcement system and admin portal

- Add SQLite database initialization and management in inc/db.php
- Implement session authentication and CSRF security helpers in inc/auth.php
- Implement announcement querying and saving in inc/announcements.php
- Add management dashboard, login, and logout in admin/
- Protect db/ directory with Apache access controls
This commit is contained in:
David Dyer committed 2026-08-29 21:57:42 -04:00
1 parent b9d3262233
commit 9b7d9ceccb
7 files changed
+761

No files matched your search

+80
View File
@@ -0,0 +1,80 @@
<?php
/**
* Announcements helper functions
*/
require_once __DIR__ . '/db.php';
function get_active_announcement(): ?array {
$pdo = get_db();
$today = date('Y-m-d');
$sql = "
SELECT * FROM announcements
WHERE is_active = 1
AND (start_date IS NULL OR start_date = '' OR start_date <= :today1)
AND (end_date IS NULL OR end_date = '' OR end_date >= :today2)
ORDER BY id DESC
LIMIT 1
";
$stmt = $pdo->prepare($sql);
$stmt->execute([
':today1' => $today,
':today2' => $today
]);
$result = $stmt->fetch();
return $result ?: null;
}
function get_all_announcements(): array {
$pdo = get_db();
$stmt = $pdo->query("SELECT * FROM announcements ORDER BY id DESC");
return $stmt->fetchAll();
}
function save_announcement(array $data, ?int $id = null): int {
$pdo = get_db();
$isActive = !empty($data['is_active']) ? 1 : 0;
$type = in_array($data['type'] ?? '', ['holiday', 'special', 'closure']) ? $data['type'] : 'holiday';
$headline = trim($data['headline'] ?? '');
$message = trim($data['message'] ?? '');
$startDate = !empty($data['start_date']) ? trim($data['start_date']) : null;
$endDate = !empty($data['end_date']) ? trim($data['end_date']) : null;
$showBanner = !empty($data['show_banner']) ? 1 : 0;
$showInHours = !empty($data['show_in_hours']) ? 1 : 0;
if ($id) {
$stmt = $pdo->prepare("
UPDATE announcements SET
is_active = ?,
type = ?,
headline = ?,
message = ?,
start_date = ?,
end_date = ?,
show_banner = ?,
show_in_hours = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
");
$stmt->execute([
$isActive, $type, $headline, $message, $startDate, $endDate,
$showBanner, $showInHours, $id
]);
return $id;
} else {
$stmt = $pdo->prepare("
INSERT INTO announcements (
is_active, type, headline, message, start_date, end_date, show_banner, show_in_hours
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$isActive, $type, $headline, $message, $startDate, $endDate,
$showBanner, $showInHours
]);
return (int)$pdo->lastInsertId();
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
/**
* Auth & session helper
*/
if (session_status() === PHP_SESSION_NONE) {
// Configure secure session cookie defaults
session_set_cookie_params([
'lifetime' => 86400 * 7, // 7 days
'path' => '/',
'httponly' => true,
'samesite' => 'Lax'
]);
session_start();
}
require_once __DIR__ . '/db.php';
function is_authenticated(): bool {
return !empty($_SESSION['admin_user_id']);
}
function require_auth(): void {
if (!is_authenticated()) {
header('Location: login.php');
exit;
}
}
function get_logged_in_user(): ?array {
if (!is_authenticated()) {
return null;
}
return [
'id' => $_SESSION['admin_user_id'],
'username' => $_SESSION['admin_username'] ?? 'admin',
'role' => $_SESSION['admin_role'] ?? 'admin'
];
}
function authenticate_user(string $username, string $password): bool {
$pdo = get_db();
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([trim($username)]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
session_regenerate_id(true);
$_SESSION['admin_user_id'] = $user['id'];
$_SESSION['admin_username'] = $user['username'];
$_SESSION['admin_role'] = $user['role'];
return true;
}
return false;
}
function logout_user(): void {
$_SESSION = [];
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
session_destroy();
}
function get_csrf_token(): string {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function verify_csrf_token(?string $token): bool {
if (empty($_SESSION['csrf_token']) || empty($token)) {
return false;
}
return hash_equals($_SESSION['csrf_token'], $token);
}
+82
View File
@@ -0,0 +1,82 @@
<?php
/**
* Database connection & schema management for SQLite
*/
function get_db(): PDO {
static $pdo = null;
if ($pdo !== null) {
return $pdo;
}
$dbDir = __DIR__ . '/../db';
if (!is_dir($dbDir)) {
mkdir($dbDir, 0755, true);
}
$dbPath = $dbDir . '/fitz.sqlite';
$isNew = !file_exists($dbPath);
$pdo = new PDO('sqlite:' . $dbPath, null, null, [
PDO::ATTR_ERRMODE => 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',
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
);
");
// Seed default admin user if table is empty (username: admin / password: password)
$stmt = $pdo->query("SELECT COUNT(*) as count FROM users");
$userCount = $stmt->fetchColumn();
if ($userCount == 0) {
$defaultPassHash = password_hash('password', PASSWORD_DEFAULT);
$insert = $pdo->prepare("INSERT INTO users (username, password_hash, role) VALUES (?, ?, 'admin')");
$insert->execute(['admin', $defaultPassHash]);
}
// Seed default Labor Day announcement row if table is empty
$stmtAnn = $pdo->query("SELECT COUNT(*) as count FROM announcements");
$annCount = $stmtAnn->fetchColumn();
if ($annCount == 0) {
$insertAnn = $pdo->prepare("
INSERT INTO announcements (
is_active, type, headline, message, start_date, end_date, show_banner, show_in_hours
) VALUES (
1, 'holiday', 'Labor Day Closure', 'The Old Fitz will be closed Thursday, Sept 3 through Saturday, Sept 5 for the holiday weekend. Doors reopen Thursday, Sept 10 at 4:00 PM!', NULL, NULL, 1, 1
)
");
$insertAnn->execute();
}
}