Initial commit: RSS feed dashboard aggregating YouTube, Reddit and RT

- PHP 8 + PDO SQLite cache with on-demand TTL refresh and dedup by link hash
- laminas-feed + Guzzle for RSS/Atom parsing and HTTP
- Bootstrap 5 UI with source and time-range filters, AJAX load-more
- User cron-free deployment under Apache/php-fpm; .htaccess hardening
This commit is contained in:
Harwinder Sekhon
2026-10-05 17:07:48 -04:00
commit ad4dc6b984
21 changed files with 2732 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace App;
final class View
{
public static function e(?string $value): string
{
return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
public static function sourceLabel(string $source): string
{
return match ($source) {
'youtube' => 'YouTube',
'reddit' => 'Reddit',
'rt' => 'RT',
default => ucfirst($source),
};
}
public static function sourceBadge(string $source): string
{
return match ($source) {
'youtube' => 'text-bg-danger',
'reddit' => 'text-bg-warning',
'rt' => 'text-bg-dark',
default => 'text-bg-secondary',
};
}
public static function sourceColor(string $source): string
{
return match ($source) {
'youtube' => '#ff1f4b',
'reddit' => '#ff6a00',
'rt' => '#00c389',
default => '#6a3dff',
};
}
public static function rangeLabel(string $range): string
{
return match ($range) {
'1h' => '1 hour',
'2h' => '2 hours',
'6h' => '6 hours',
'24h' => '24 hours',
'7d' => '7 days',
default => 'All time',
};
}
public static function render(string $template, array $data = []): string
{
$path = dirname(__DIR__) . '/templates/' . $template . '.php';
if (!is_file($path)) {
throw new \RuntimeException(sprintf('Template "%s" not found.', $template));
}
extract($data, EXTR_SKIP);
ob_start();
include $path;
return (string) ob_get_clean();
}
public static function timeAgo(?string $iso): string
{
if ($iso === null || $iso === '') {
return '';
}
$timestamp = strtotime($iso);
if ($timestamp === false) {
return '';
}
$diff = time() - $timestamp;
if ($diff < 60) {
return 'just now';
}
$units = [
31536000 => 'y',
2592000 => 'mo',
604800 => 'w',
86400 => 'd',
3600 => 'h',
60 => 'm',
];
foreach ($units as $seconds => $label) {
if ($diff >= $seconds) {
return floor($diff / $seconds) . $label . ' ago';
}
}
return 'just now';
}
}