Naming fixes. Add partials. Get single ticks working with clean urls.

This commit is contained in:
Greg Sarjeant 2025-06-03 21:22:20 -04:00
parent 97e2c205a3
commit 78ebb67fc0
22 changed files with 119 additions and 99 deletions

View File

@ -1,4 +1,16 @@
<?php <?php
// Store and validate request data
$method = $_SERVER['REQUEST_METHOD'];
$request = $_SERVER['REQUEST_URI'];
$path = parse_url($request, PHP_URL_PATH);
// return a 404 if a request for a .php file gets this far.
if (preg_match('/\.php$/', $path)) {
http_response_code(404);
echo '<h1>404 Not Found</h1>';
exit;
}
// Define all the important paths // Define all the important paths
define('APP_ROOT', dirname(dirname(__FILE__))); define('APP_ROOT', dirname(dirname(__FILE__)));
define('SRC_DIR', APP_ROOT . '/src'); define('SRC_DIR', APP_ROOT . '/src');
@ -34,18 +46,6 @@ Session::start();
Session::generateCsrfToken(); Session::generateCsrfToken();
$config = Config::load(); $config = Config::load();
// Get request data
$method = $_SERVER['REQUEST_METHOD'];
$request = $_SERVER['REQUEST_URI'];
$path = parse_url($request, PHP_URL_PATH);
// return a 404 if a request for a .php file gets this far.
if (preg_match('/\.php$/', $path)) {
http_response_code(404);
echo '<h1>404 Not Found</h1>';
exit;
}
// Remove the base path from the URL // Remove the base path from the URL
// and strip the trailing slash from the resulting route // and strip the trailing slash from the resulting route
if (strpos($path, $config->basePath) === 0) { if (strpos($path, $config->basePath) === 0) {
@ -100,6 +100,7 @@ $routeHandlers = [
['mood', 'MoodController@handleMood', ['POST']], ['mood', 'MoodController@handleMood', ['POST']],
['feed/rss', 'FeedController@rss'], ['feed/rss', 'FeedController@rss'],
['feed/atom', 'FeedController@atom'], ['feed/atom', 'FeedController@atom'],
['tick/{y}/{m}/{d}/{h}/{i}/{s}', 'TickController'],
]; ];
// Set content type // Set content type

View File

@ -18,8 +18,8 @@ class AdminController extends Controller {
// POST handler // POST handler
// save updated settings // save updated settings
public function handleSave(){ public function handleSave(){
$isLoggedIn = isset($_SESSION['user_id']); //$isLoggedIn = isset($_SESSION['user_id']);
if (!$isLoggedIn){ if (!Session::isLoggedIn()){
header('Location: ' . $config->basePath . 'login.php'); header('Location: ' . $config->basePath . 'login.php');
exit; exit;
} }

View File

@ -6,7 +6,6 @@ class HomeController extends Controller {
// renders the homepage view. // renders the homepage view.
public function index(){ public function index(){
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1; $page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$isLoggedIn = isset($_SESSION['user_id']);
$config = Config::load(); $config = Config::load();
$user = User::load(); $user = User::load();
@ -18,10 +17,9 @@ class HomeController extends Controller {
$tickList = $view->renderTicksSection($config->siteDescription, $ticks, $page, $limit); $tickList = $view->renderTicksSection($config->siteDescription, $ticks, $page, $limit);
$vars = [ $vars = [
'isLoggedIn' => $isLoggedIn,
'config' => $config, 'config' => $config,
'user' => $user, 'user' => $user,
'tickList' => $tickList, 'tickList' => $tickList,
]; ];
$this->render("home.php", $vars); $this->render("home.php", $vars);

View File

@ -0,0 +1,11 @@
<?php
class TickController extends Controller{
// every tick is identified by its timestamp
public function index(string $year, string $month, string $day, string $hour, string $minute, string $second){
$model = new Tick();
$tick = $model->get($year, $month, $day, $hour, $minute, $second);
$this->render('tick.php', $tick);
}
}

View File

@ -24,6 +24,10 @@ class Session {
return $_SESSION['csrf_token']; return $_SESSION['csrf_token'];
} }
public static function isLoggedIn(): bool {
return isset($_SESSION['user_id']);
}
public static function end(): void { public static function end(): void {
$_SESSION = []; $_SESSION = [];
session_destroy(); session_destroy();

View File

@ -104,6 +104,17 @@ class Util {
}; };
} }
public static function tick_time_to_tick_path($tickTime){
[$date, $time] = explode(' ', $tickTime);
$dateParts = explode('-', $date);
$timeParts = explode(':', $time);
[$year, $month, $day] = $dateParts;
[$hour, $minute, $second] = $timeParts;
return "$year/$month/$day/$hour/$minute/$second";
}
// TODO: Move to model base class? // TODO: Move to model base class?
public static function get_db(): PDO { public static function get_db(): PDO {
Util::verify_data_dir(DATA_DIR, true); Util::verify_data_dir(DATA_DIR, true);

View File

@ -1,6 +1,7 @@
<?php <?php
class Tick { class Tick {
// Everything in this class just reads from and writes to the filesystem
// It doesn't maintain state, so everything's just a static function
public static function streamTicks(int $limit, int $offset = 0): Generator { public static function streamTicks(int $limit, int $offset = 0): Generator {
$tick_files = glob(TICKS_DIR . '/*/*/*.txt'); $tick_files = glob(TICKS_DIR . '/*/*/*.txt');
usort($tick_files, fn($a, $b) => strcmp($b, $a)); // sort filenames in reverse chronological order usort($tick_files, fn($a, $b) => strcmp($b, $a)); // sort filenames in reverse chronological order
@ -73,5 +74,30 @@ class Tick {
// write the tick to the file (the file will be created if it doesn't exist) // write the tick to the file (the file will be created if it doesn't exist)
$content = $time . "|" . $tick . "\n"; $content = $time . "|" . $tick . "\n";
file_put_contents($filename, $content, FILE_APPEND); file_put_contents($filename, $content, FILE_APPEND);
} }
public static function get(string $y, string $m, string $d, string $H, string $i, string $s): array{
$tickTime = new DateTime("$y-$m-$d $H:$i:$s");
$timestamp = "$H:$i:$s";
$file = TICKS_DIR . "/$y/$m/$d.txt";
if (!file_exists($file)) {
http_response_code(404);
echo "Tick not found: $file.";
exit;
}
$lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
if (str_starts_with($line, $timestamp)) {
$tick = Util::escape_and_linkify(explode('|', $line)[1]);
return [
'tickTime' => $tickTime,
'tick' => $tick,
'config' => Config::load(),
];
}
}
}
} }

View File

@ -1,17 +1,14 @@
<?php /** @var Config $config */ ?> <?php /** @var Config $config */ ?>
<?php /** @var User $user */ ?> <?php /** @var User $user */ ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html>
<head> <head>
<title><?= $config->siteTitle ?></title> <?php include TEMPLATES_DIR . '/partials/head.php'?>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="<?= htmlspecialchars($config->basePath) ?>css/tkr.css">
</head> </head>
<body> <body>
<?php include TEMPLATES_DIR . '/partials/navbar.php'?>
<html lang="en">
<h1>Admin</h1> <h1>Admin</h1>
<div><a href="<?= $config->basePath ?>">Back to home</a></div>
<div> <div>
<form method="post"> <form method="post">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>"> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">

View File

@ -27,9 +27,9 @@ echo '<?xml version="1.0" encoding="utf-8"?>' . "\n";
[$hour, $minute, $second] = $timeParts; [$hour, $minute, $second] = $timeParts;
$tickPath = "$year/$month/$day/$hour/$minute/$second"; $tickPath = "$year/$month/$day/$hour/$minute/$second";
$tickUrl = htmlspecialchars($basePath . "tick.php?path=" . $tickPath); $tickUrl = htmlspecialchars($basePath . "tick/$tickPath");
$tickTime = date(DATE_ATOM, strtotime($tick['timestamp'])); $tickTime = date(DATE_ATOM, strtotime($tick['timestamp']));
$tickText = htmlspecialchars($tick['tick']); $tickText = htmlspecialchars($tick['tick']);
?> ?>
<entry> <entry>
<title><?= $tickText ?></title> <title><?= $tickText ?></title>

View File

@ -15,7 +15,6 @@ echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
<language>en-us</language> <language>en-us</language>
<lastBuildDate><?php echo date(DATE_RSS); ?></lastBuildDate> <lastBuildDate><?php echo date(DATE_RSS); ?></lastBuildDate>
<?php foreach ($ticks as $tick): <?php foreach ($ticks as $tick):
// TODO: Make this a util function.
[$date, $time] = explode(' ', $tick['timestamp']); [$date, $time] = explode(' ', $tick['timestamp']);
$dateParts = explode('-', $date); $dateParts = explode('-', $date);
$timeParts = explode(':', $time); $timeParts = explode(':', $time);
@ -27,7 +26,7 @@ echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
?> ?>
<item> <item>
<title><?php echo htmlspecialchars($tick['tick']); ?></title> <title><?php echo htmlspecialchars($tick['tick']); ?></title>
<link><?php echo htmlspecialchars($config->basePath . "tick.php?path=$tickPath"); ?></link> <link><?php echo htmlspecialchars($config->basePath . "tick/$tickPath"); ?></link>
<description><?php echo htmlspecialchars($tick['tick']); ?></description> <description><?php echo htmlspecialchars($tick['tick']); ?></description>
<pubDate><?php echo date(DATE_RSS, strtotime($tick['timestamp'])); ?></pubDate> <pubDate><?php echo date(DATE_RSS, strtotime($tick['timestamp'])); ?></pubDate>
<guid><?php echo htmlspecialchars($tickPath); ?></guid> <guid><?php echo htmlspecialchars($tickPath); ?></guid>

View File

@ -2,26 +2,13 @@
<?php /** @var Config $config */ ?> <?php /** @var Config $config */ ?>
<?php /** @var User $user */ ?> <?php /** @var User $user */ ?>
<?php /** @var string $tickList */ ?> <?php /** @var string $tickList */ ?>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title><?= $config->siteTitle ?></title> <?php include TEMPLATES_DIR . '/partials/head.php'?>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="<?= htmlspecialchars($config->basePath) ?>css/tkr.css?v=<?= time() ?>">
</head> </head>
<body> <body>
<div class="home-navbar"> <?php include TEMPLATES_DIR . '/partials/navbar.php'?>
<a href="<?= $config->basePath ?>feed/rss">rss</a>
<a href="<?= $config->basePath ?>feed/atom">atom</a>
<?php if (!$isLoggedIn): ?>
<a href="<?= $config->basePath ?>login">login</a>
<?php else: ?>
<a href="<?= $config->basePath ?>admin">admin</a>
<a href="<?= $config->basePath ?>logout">logout</a>
<?php endif; ?>
</div>
<div class="home-container"> <div class="home-container">
<section id="sidebar" class="home-sidebar"> <section id="sidebar" class="home-sidebar">
<div class="home-header"> <div class="home-header">
@ -32,12 +19,12 @@
<div class="profile-row"> <div class="profile-row">
<div class="mood-bar"> <div class="mood-bar">
<span>Current mood: <?= $user->mood ?></span> <span>Current mood: <?= $user->mood ?></span>
<?php if ($isLoggedIn): ?> <?php if (Session::isLoggedIn()): ?>
<a href="<?= $config->basePath ?>mood">Change</a> <a href="<?= $config->basePath ?>mood">Change</a>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
<?php if ($isLoggedIn): ?> <?php if (Session::isLoggedIn()): ?>
<hr/> <hr/>
<div class="profile-row"> <div class="profile-row">
<form class="tick-form" method="post"> <form class="tick-form" method="post">

View File

@ -1,16 +1,13 @@
<?php /** @var Config $config */ ?> <?php /** @var Config $config */ ?>
<?php /** @var string $csrfToken */ ?> <?php /** @var string $csrfToken */ ?>
<?php /** @var string $error */ ?> <?php /** @var string $error */ ?>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title><?= $config->siteTitle ?></title> <?php include TEMPLATES_DIR . '/partials/head.php'?>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="<?= htmlspecialchars($config->basePath) ?>css/tkr.css?v=<?= time() ?>">
</head> </head>
<body> <body>
<?php include TEMPLATES_DIR . '/partials/navbar.php'?>
<h2>Login</h2> <h2>Login</h2>
<?php if ($error): ?> <?php if ($error): ?>
<p style="color:red"><?= htmlspecialchars($error) ?></p> <p style="color:red"><?= htmlspecialchars($error) ?></p>

View File

@ -1,19 +1,13 @@
<?php /** @var Config $config */ ?> <?php /** @var Config $config */ ?>
<?php /** @var string $moodPicker */ ?> <?php /** @var string $moodPicker */ ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html>
<head> <head>
<title><?= $config->siteTitle ?></title> <?php include TEMPLATES_DIR . '/partials/head.php'?>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="<?= htmlspecialchars($config->basePath) ?>css/tkr.css">
</head> </head>
<body> <body>
<?php include TEMPLATES_DIR . '/partials/navbar.php'?>
<h2>How are you feeling?</h2> <h2>How are you feeling?</h2>
<?php echo $moodPicker; ?> <?php echo $moodPicker; ?>
<a class="back-link" href="<?= htmlspecialchars($config->basePath) ?>">Back to home</a>
</body> </body>
</html> </html>

View File

@ -0,0 +1,5 @@
<?php /** @var Config $config */ ?>
<title><?= $config->siteTitle ?></title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="<?= htmlspecialchars($config->basePath) ?>css/tkr.css?v=<?= time() ?>">

View File

@ -0,0 +1,12 @@
<?php /** @var Config $config */ ?>
<div class="navbar">
<a href="<?= $config->basePath ?>">home</a>
<a href="<?= $config->basePath ?>feed/rss">rss</a>
<a href="<?= $config->basePath ?>feed/atom">atom</a>
<?php if (!Session::isLoggedIn()): ?>
<a href="<?= $config->basePath ?>login">login</a>
<?php else: ?>
<a href="<?= $config->basePath ?>admin">admin</a>
<a href="<?= $config->basePath ?>logout">logout</a>
<?php endif; ?>
</div>

View File

@ -1,10 +1,5 @@
<?php <?php
#require_once __DIR__ . '/../bootstrap.php'; $db = Utli::get_db();
#confirm_setup();
// If we got past confirm_setup(), then setup isn't complete.
$db = get_db();
// Handle submitted form // Handle submitted form
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {

View File

@ -1,32 +1,15 @@
<?php <?php /** @var Config $config */ ?>
<?php /** @var Date $tickTime */ ?>
$path = $_GET['path'] ?? ''; <?php /** @var string $tick */ ?>
$parts = explode('/', $path); <!DOCTYPE html>
<html>
if (count($parts) !== 6) { <head>
http_response_code(400); <?php include TEMPLATES_DIR . '/partials/head.php'?>
echo "Invalid tick path."; </head>
exit; <body>
} <?php include TEMPLATES_DIR . '/partials/navbar.php'?>
<!DOCTYPE html>
[$y, $m, $d, $H, $i, $s] = $parts; <h1>Tick from <?= $tickTime->format('Y-m-d H:i:s'); ?></h1>
$timestamp = "$H:$i:$s"; <p><?= $tick ?></p>
$file = TICKS_DIR . "/$y/$m/$d.txt"; </body>
</html>
if (!file_exists($file)) {
http_response_code(404);
echo "Tick not found.";
exit;
}
$lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
if (str_starts_with($line, $timestamp)) {
echo "<h1>Tick from $timestamp on $y-$m-$d</h1>";
echo "<p>" . escape_and_linkify(explode('|', $line)[1]) . "</p>";
exit;
}
}
http_response_code(404);
echo "Tick not found.";