Make the homepage an actual template.

This commit is contained in:
Greg Sarjeant 2025-06-01 23:30:24 -04:00
parent d3271e43a0
commit 51595c13eb
20 changed files with 229 additions and 99 deletions

View File

@ -22,6 +22,15 @@ server {
alias /var/www/html/public; alias /var/www/html/public;
index index.php; index index.php;
# Cache static files
# Note that I don't actually serve most of this (just js and css to start)
# but including them all will let caching work later if I add images or something
location ~* ^/tkr/.+\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# index.php is the entry point # index.php is the entry point
# It needs to be sent to php-fpm # It needs to be sent to php-fpm
# But if someone tries to directly access index.php, that file will throw a 404 # But if someone tries to directly access index.php, that file will throw a 404
@ -60,15 +69,6 @@ server {
fastcgi_param QUERY_STRING $query_string; fastcgi_param QUERY_STRING $query_string;
} }
# Cache static files
# Note that I don't actually serve most of this (just js and css to start)
# but including them all will let caching work later if I add images or something
location ~* ^/tkr/.+\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# Deny access to sensitive directories # Deny access to sensitive directories
location ~ ^/tkr/(storage|lib|vendor|config) { location ~ ^/tkr/(storage|lib|vendor|config) {
deny all; deny all;

View File

@ -1,11 +1,11 @@
<?php <?php
define('APP_ROOT', dirname(__FILE__)); #define('APP_ROOT', dirname(__FILE__));
define('CLASSES_DIR', APP_ROOT . '/classes'); #define('CLASSES_DIR', APP_ROOT . '/classes');
define('LIB_DIR', APP_ROOT . '/lib'); #define('LIB_DIR', APP_ROOT . '/lib');
define('TICKS_DIR', APP_ROOT . '/storage/ticks'); #define('TICKS_DIR', APP_ROOT . '/storage/ticks');
define('DATA_DIR', APP_ROOT . '/storage/db'); #define('DATA_DIR', APP_ROOT . '/storage/db');
define('DB_FILE', DATA_DIR . '/tkr.sqlite'); #define('DB_FILE', DATA_DIR . '/tkr.sqlite');
function verify_data_dir(string $dir, bool $allow_create = false): void { function verify_data_dir(string $dir, bool $allow_create = false): void {
if (!is_dir($dir)) { if (!is_dir($dir)) {

View File

@ -1,7 +1,7 @@
<?php <?php
require_once __DIR__ . '/../bootstrap.php'; #require_once __DIR__ . '/../bootstrap.php';
confirm_setup(); #confirm_setup();
// Made this a class so it could be more obvious where config settings are coming from. // Made this a class so it could be more obvious where config settings are coming from.
// Felt too much like magic constants in other files before. // Felt too much like magic constants in other files before.

View File

@ -1,7 +1,7 @@
<?php <?php
require_once __DIR__ . '/../bootstrap.php'; #require_once __DIR__ . '/../bootstrap.php';
confirm_setup(); #confirm_setup();
// Made this a class so it could be more obvious where config settings are coming from. // Made this a class so it could be more obvious where config settings are coming from.
// Felt too much like magic constants in other files before. // Felt too much like magic constants in other files before.

View File

@ -1,9 +1,9 @@
<?php <?php
require_once __DIR__ . '/../bootstrap.php'; #require_once __DIR__ . '/../bootstrap.php';
require_once CLASSES_DIR . '/Config.php'; #require_once CLASSES_DIR . '/Config.php';
require_once CLASSES_DIR . '/User.php'; #require_once CLASSES_DIR . '/User.php';
require LIB_DIR . '/emoji.php'; #require LIB_DIR . '/emoji.php';
function save_mood(string $mood): void { function save_mood(string $mood): void {
$config = Config::load(); $config = Config::load();

View File

@ -3,8 +3,6 @@ if (session_status() === PHP_SESSION_NONE) {
session_start(); session_start();
} }
$isLoggedIn = isset($_SESSION['username']);
function generateCsrfToken() { function generateCsrfToken() {
if (empty($_SESSION['csrf_token'])) { if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32)); $_SESSION['csrf_token'] = bin2hex(random_bytes(32));

View File

@ -1,5 +1,5 @@
<?php <?php
require_once __DIR__ . '/../bootstrap.php'; #require_once __DIR__ . '/../bootstrap.php';
function save_tick(string $tick): void { function save_tick(string $tick): void {
// build the tick path and filename from the current time // build the tick path and filename from the current time

View File

@ -38,3 +38,101 @@ function relative_time(string $tickTime): string {
} }
return $diff->s . ' second' . ($diff->s != 1 ? 's' : '') . ' ago'; return $diff->s . ' second' . ($diff->s != 1 ? 's' : '') . ' ago';
} }
function verify_data_dir(string $dir, bool $allow_create = false): void {
if (!is_dir($dir)) {
if ($allow_create) {
if (!mkdir($dir, 0770, true)) {
http_response_code(500);
echo "Failed to create required directory: $dir";
exit;
}
} else {
http_response_code(500);
echo "Required directory does not exist: $dir";
exit;
}
}
if (!is_writable($dir)) {
http_response_code(500);
echo "Directory is not writable: $dir";
exit;
}
}
// Verify that setup is complete (i.e. the databse is populated).
// Redirect to setup.php if it isn't.
function confirm_setup(): void {
$db = get_db();
// Ensure required tables exist
$db->exec("CREATE TABLE IF NOT EXISTS user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
display_name TEXT NOT NULL,
password_hash TEXT NOT NULL,
about TEXT NULL,
website TEXT NULL,
mood TEXT NULL
)");
$db->exec("CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY,
site_title TEXT NOT NULL,
site_description TEXT NULL,
base_path TEXT NOT NULL,
items_per_page INTEGER NOT NULL
)");
// See if there's any data in the tables
$user_count = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn();
$settings_count = (int) $db->query("SELECT COUNT(*) FROM settings")->fetchColumn();
// If either table has no records and we aren't on setup.php, redirect to setup.php
if ($user_count === 0 || $settings_count === 0){
if (basename($_SERVER['PHP_SELF']) !== 'setup.php'){
header('Location: setup.php');
exit;
}
} else {
// If setup is complete and we are on setup.php, redirect to index.php.
if (basename($_SERVER['PHP_SELF']) === 'setup.php'){
header('Location: index.php');
exit;
}
};
}
function get_db(): PDO {
verify_data_dir(DATA_DIR, true);
try {
$db = new PDO("sqlite:" . DB_FILE);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
die("Database connection failed: " . $e->getMessage());
}
return $db;
}
function render_template(string $templateFile, array $vars = []): string {
#$templatePath = TEMPLATES_DIR . '/' . ltrim($templateFile, '/');
if (!file_exists($templateFile)) {
throw new RuntimeException("Template not found: $templatePath");
}
// Extract variables into local scope
extract($vars, EXTR_SKIP);
// Start output buffering
ob_start();
// Include the template (with extracted variables in scope)
include $templateFile;
// Return rendered output
return ob_get_clean();
}

View File

@ -1,7 +1,36 @@
<?php <?php
#require_once __DIR__ . '/../bootstrap.php';
define('APP_ROOT', dirname(dirname(__FILE__)));
define('CLASSES_DIR', APP_ROOT . '/classes');
define('LIB_DIR', APP_ROOT . '/lib');
define('STORAGE_DIR', APP_ROOT . '/storage');
define('TEMPLATES_DIR', APP_ROOT . '/templates');
define('TICKS_DIR', STORAGE_DIR . '/ticks');
define('DATA_DIR', STORAGE_DIR . '/db');
define('DB_FILE', DATA_DIR . '/tkr.sqlite');
$include_dirs = [
LIB_DIR,
CLASSES_DIR,
];
foreach ($include_dirs as $include_dir){
foreach (glob($include_dir . '/*.php') as $file) {
require_once $file;
}
}
confirm_setup();
$isLoggedIn = isset($_SESSION['user_id']);
$config = Config::load();
$user = User::load();
// Define your base path (subdirectory) // Define your base path (subdirectory)
$basePath = '/tkr'; #$basePath = '/tkr';
// Get HTTP data // Get HTTP data
$method = $_SERVER['REQUEST_METHOD']; $method = $_SERVER['REQUEST_METHOD'];
@ -11,8 +40,15 @@ $request = $_SERVER['REQUEST_URI'];
// and strip the trailing slash from the resulting route // and strip the trailing slash from the resulting route
$path = parse_url($request, PHP_URL_PATH); $path = parse_url($request, PHP_URL_PATH);
if (strpos($path, $basePath) === 0) { // return a 404 if s request for a .php file gets this far.
$path = substr($path, strlen($basePath)); if (preg_match('/\.php$/', $path)) {
http_response_code(404);
echo '<h1>404 Not Found</h1>';
exit;
}
if (strpos($path, $config->basePath) === 0) {
$path = substr($path, strlen($config->basePath));
} }
$path = trim($path, '/'); $path = trim($path, '/');
@ -42,7 +78,21 @@ header('Content-Type: text/html; charset=utf-8');
echo "Path: " . $path; echo "Path: " . $path;
// Define your routes // Define your routes
route('', function() { route('', function() use ($isLoggedIn, $config, $user) {
echo '<h1>Home Page</h1>'; #include TEMPLATES_DIR . "/home.php";
echo '<p>Welcome to the home page!</p>'; #echo render_home_page($isLoggedIn, $config, $user);
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$limit = $config->itemsPerPage;
$offset = ($page - 1) * $limit;
$ticks = iterator_to_array(stream_ticks($limit, $offset));
$vars = [
'isLoggedIn' => $isLoggedIn,
'config' => $config,
'user' => $user,
'ticks' => $ticks,
];
echo render_template(TEMPLATES_DIR . "/home.php", $vars);
}); });
//isset($_SESSION['user_id'])

View File

@ -1,14 +0,0 @@
<?php
require_once __DIR__ . '/../bootstrap.php';
confirm_setup();
require_once CLASSES_DIR . '/Config.php';
require LIB_DIR . '/session.php';
$config = Config::load();
$_SESSION = [];
session_destroy();
header('Location: ' . $config->basePath);
exit;

View File

@ -1,12 +1,12 @@
<?php <?php
require_once __DIR__ . '/../bootstrap.php'; #require_once __DIR__ . '/../bootstrap.php';
confirm_setup(); #confirm_setup();
require_once CLASSES_DIR . '/Config.php'; #require_once CLASSES_DIR . '/Config.php';
require LIB_DIR . '/session.php'; #require LIB_DIR . '/session.php';
require LIB_DIR . '/ticks.php'; #require LIB_DIR . '/ticks.php';
require LIB_DIR . '/util.php'; #require LIB_DIR . '/util.php';
// ticks must be sent via POST // ticks must be sent via POST

View File

@ -1,10 +1,10 @@
<?php <?php
require_once __DIR__ . '/../bootstrap.php'; #require_once __DIR__ . '/../bootstrap.php';
confirm_setup(); #confirm_setup();
require_once CLASSES_DIR . '/Config.php'; #require_once CLASSES_DIR . '/Config.php';
require LIB_DIR . '/session.php'; #require LIB_DIR . '/session.php';
if (!$isLoggedIn){ if (!$isLoggedIn){
header('Location: ' . $config->basePath . 'login.php'); header('Location: ' . $config->basePath . 'login.php');

View File

@ -1,10 +1,10 @@
<?php <?php
require_once __DIR__ . '/../../bootstrap.php'; #require_once __DIR__ . '/../../bootstrap.php';
confirm_setup(); #confirm_setup();
require_once CLASSES_DIR . '/Config.php'; #require_once CLASSES_DIR . '/Config.php';
require_once LIB_DIR . '/ticks.php'; #require_once LIB_DIR . '/ticks.php';
$config = Config::load(); $config = Config::load();
$ticks = iterator_to_array(stream_ticks($config->itemsPerPage)); $ticks = iterator_to_array(stream_ticks($config->itemsPerPage));

View File

@ -1,31 +1,15 @@
<?php <?php /** @var bool $isLoggedIn */ ?>
require_once __DIR__ . '/../bootstrap.php'; <?php /** @var Config $config */ ?>
<?php /** @var User $user */ ?>
<?php /** @var array $ticks */ ?>
confirm_setup();
require_once CLASSES_DIR . '/Config.php';
require_once CLASSES_DIR . '/User.php';
require LIB_DIR . '/session.php';
require LIB_DIR . '/ticks.php';
require LIB_DIR . '/util.php';
$config = Config::load();
// I can get away with this before login because there's only one user.
$user = User::load();
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$limit = $config->itemsPerPage;
$offset = ($page - 1) * $limit;
$ticks = iterator_to_array(stream_ticks($limit, $offset));
?>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title><?= $config->siteTitle ?></title> <title><?= $config->siteTitle ?></title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="<?= htmlspecialchars($config->basePath) ?>css/tkr.css?v=<?= time() ?>"> <link rel="stylesheet" href="<?= htmlspecialchars($config->basePath) ?>/css/tkr.css?v=<?= time() ?>">
</head> </head>
<body> <body>
<div class="home-navbar"> <div class="home-navbar">

View File

@ -1,10 +1,10 @@
<?php <?php
require_once __DIR__ . '/../bootstrap.php'; #require_once __DIR__ . '/../bootstrap.php';
confirm_setup(); #confirm_setup();
require_once CLASSES_DIR . '/Config.php'; #require_once CLASSES_DIR . '/Config.php';
require LIB_DIR . '/session.php'; #require LIB_DIR . '/session.php';
$config = Config::load(); $config = Config::load();

14
src/templates/logout.php Normal file
View File

@ -0,0 +1,14 @@
<?php
#require_once __DIR__ . '/../bootstrap.php';
#confirm_setup();
#require_once CLASSES_DIR . '/Config.php';
#require LIB_DIR . '/session.php';
$config = Config::load();
$_SESSION = [];
session_destroy();
header('Location: ' . $config->basePath);
exit;

View File

@ -1,10 +1,10 @@
<?php <?php
require_once __DIR__ . '/../../bootstrap.php'; #require_once __DIR__ . '/../../bootstrap.php';
confirm_setup(); #confirm_setup();
require_once CLASSES_DIR . '/Config.php'; #require_once CLASSES_DIR . '/Config.php';
require_once LIB_DIR . '/ticks.php'; #require_once LIB_DIR . '/ticks.php';
$config = Config::load(); $config = Config::load();
$ticks = iterator_to_array(stream_ticks($config->itemsPerPage)); $ticks = iterator_to_array(stream_ticks($config->itemsPerPage));

View File

@ -1,11 +1,11 @@
<?php <?php
require_once __DIR__ . '/../bootstrap.php'; #require_once __DIR__ . '/../bootstrap.php';
confirm_setup(); #confirm_setup();
require_once CLASSES_DIR . '/Config.php'; #require_once CLASSES_DIR . '/Config.php';
require LIB_DIR . '/session.php'; #require LIB_DIR . '/session.php';
require LIB_DIR . '/mood.php'; #require LIB_DIR . '/mood.php';
// get the config // get the config

View File

@ -1,7 +1,7 @@
<?php <?php
require_once __DIR__ . '/../bootstrap.php'; #require_once __DIR__ . '/../bootstrap.php';
confirm_setup(); #confirm_setup();
// If we got past confirm_setup(), then setup isn't complete. // If we got past confirm_setup(), then setup isn't complete.
$db = get_db(); $db = get_db();

View File

@ -1,9 +1,9 @@
<?php <?php
require_once __DIR__ . '/../bootstrap.php'; #require_once __DIR__ . '/../bootstrap.php';
confirm_setup(); #confirm_setup();
require LIB_DIR . '/util.php'; #require LIB_DIR . '/util.php';
$path = $_GET['path'] ?? ''; $path = $_GET['path'] ?? '';
$parts = explode('/', $path); $parts = explode('/', $path);