feat(*): refactor dynamic vs. static structure and distinctions; make additional elements dynamic

This commit is contained in:
Cory Dransfeldt 2025-06-11 20:27:41 -07:00
parent ca57082f01
commit c021ea54ae
No known key found for this signature in database
140 changed files with 1001 additions and 985 deletions

View file

@ -1,14 +0,0 @@
<?php
namespace App\Classes;
require __DIR__ . "/BaseHandler.php";
require __DIR__ . '/../../server/utils/init.php';
abstract class ApiHandler extends BaseHandler
{
protected function ensureCliAccess(): void
{
if (php_sapi_name() !== 'cli' && $_SERVER['REQUEST_METHOD'] !== 'POST') redirectTo404();
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace App\Classes;
class ArtistFetcher extends PageFetcher
{
public function fetch(string $url): ?array
{
$cacheKey = "artist_" . md5($url);
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
$artist = $this->fetchSingleFromApi("optimized_artists", $url);
if (!$artist) return null;
$artist['globals'] = $this->getGlobals();
$this->cacheSet($cacheKey, $artist);
return $artist;
}
}

View file

@ -1,102 +0,0 @@
<?php
namespace App\Classes;
require __DIR__ . "/../../vendor/autoload.php";
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
abstract class BaseHandler
{
protected string $postgrestUrl;
protected string $postgrestApiKey;
protected ?\Redis $cache = null;
public function __construct()
{
$this->loadEnvironment();
$this->initializeCache();
}
private function loadEnvironment(): void
{
$this->postgrestUrl = $_ENV["POSTGREST_URL"] ?? getenv("POSTGREST_URL") ?? "";
$this->postgrestApiKey = $_ENV["POSTGREST_API_KEY"] ?? getenv("POSTGREST_API_KEY") ?? "";
}
protected function initializeCache(): void
{
if (class_exists("Redis")) {
try {
$redis = new \Redis();
$redis->connect("127.0.0.1", 6379);
$this->cache = $redis;
} catch (\Exception $e) {
error_log("Redis connection failed: " . $e->getMessage());
$this->cache = null;
}
} else {
error_log("Redis extension not found — caching disabled.");
$this->cache = null;
}
}
protected function makeRequest(string $method, string $endpoint, array $options = []): array
{
$client = new Client();
$url = rtrim($this->postgrestUrl, "/") . "/" . ltrim($endpoint, "/");
try {
$response = $client->request($method, $url, array_merge_recursive([
"headers" => [
"Authorization" => "Bearer {$this->postgrestApiKey}",
"Content-Type" => "application/json",
]
], $options));
$responseBody = $response->getBody()->getContents();
if (empty($responseBody)) return [];
$data = json_decode($responseBody, true);
if (json_last_error() !== JSON_ERROR_NONE) throw new \Exception("Invalid JSON: " . json_last_error_msg());
return $data;
} catch (RequestException $e) {
$response = $e->getResponse();
$statusCode = $response ? $response->getStatusCode() : 'N/A';
$responseBody = $response ? $response->getBody()->getContents() : 'No response';
throw new \Exception("HTTP {$method} {$url} failed with status {$statusCode}: {$responseBody}");
} catch (\Exception $e) {
throw new \Exception("Request error: " . $e->getMessage());
}
}
protected function fetchFromApi(string $endpoint, string $query = ""): array
{
$url = $endpoint . ($query ? "?{$query}" : "");
return $this->makeRequest("GET", $url);
}
protected function sendResponse(array $data, int $statusCode = 200): void
{
http_response_code($statusCode);
header("Content-Type: application/json");
echo json_encode($data);
exit();
}
protected function sendErrorResponse(string $message, int $statusCode = 500): void
{
$this->sendResponse(["error" => $message], $statusCode);
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace App\Classes;
class BookFetcher extends PageFetcher
{
public function fetch(string $url): ?array
{
$cacheKey = "book_" . md5($url);
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
$book = $this->fetchSingleFromApi("optimized_books", $url);
if (!$book) return null;
$book['globals'] = $this->getGlobals();
$this->cacheSet($cacheKey, $book);
return $book;
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace App\Classes;
class GenreFetcher extends PageFetcher
{
public function fetch(string $url): ?array
{
$cacheKey = "genre_" . md5($url);
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
$genre = $this->fetchSingleFromApi("optimized_genres", $url);
if (!$genre) return null;
$genre['globals'] = $this->getGlobals();
$this->cacheSet($cacheKey, $genre);
return $genre;
}
}

View file

@ -1,22 +0,0 @@
<?php
namespace App\Classes;
class GlobalsFetcher extends PageFetcher
{
public function fetch(): ?array
{
$cacheKey = "globals";
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
$globals = $this->fetchFromApi("optimized_globals");
if (empty($globals)) return null;
$this->cacheSet($cacheKey, $globals[0]);
return $globals[0];
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace App\Classes;
class MovieFetcher extends PageFetcher
{
public function fetch(string $url): ?array
{
$cacheKey = "movie_" . md5($url);
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
$movie = $this->fetchSingleFromApi("optimized_movies", $url);
if (!$movie) return null;
$movie['globals'] = $this->getGlobals();
$this->cacheSet($cacheKey, $movie);
return $movie;
}
}

View file

@ -1,46 +0,0 @@
<?php
namespace App\Classes;
use App\Classes\BaseHandler;
use App\Classes\GlobalsFetcher;
abstract class PageFetcher extends BaseHandler
{
protected ?array $globals = null;
protected function cacheGet(string $key): mixed
{
return $this->cache && $this->cache->exists($key)
? json_decode($this->cache->get($key), true)
: null;
}
protected function cacheSet(string $key, mixed $value, int $ttl = 3600): void
{
if ($this->cache) {
$this->cache->setex($key, $ttl, json_encode($value));
}
}
protected function fetchSingleFromApi(string $endpoint, string $url): ?array
{
$data = $this->fetchFromApi($endpoint, "url=eq./{$url}");
return $data[0] ?? null;
}
protected function fetchPostRpc(string $endpoint, array $body): ?array
{
return $this->makeRequest("POST", $endpoint, ['json' => $body]);
}
public function getGlobals(): ?array
{
if ($this->globals !== null) return $this->globals;
$fetcher = new GlobalsFetcher();
$this->globals = $fetcher->fetch();
return $this->globals;
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace App\Classes;
class ShowFetcher extends PageFetcher
{
public function fetch(string $url): ?array
{
$cacheKey = "show_" . md5($url);
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
$show = $this->fetchSingleFromApi("optimized_shows", $url);
if (!$show) return null;
$show['globals'] = $this->getGlobals();
$this->cacheSet($cacheKey, $show);
return $show;
}
}

View file

@ -1,29 +0,0 @@
<?php
namespace App\Classes;
class TagFetcher extends PageFetcher
{
public function fetch(string $tag, int $page = 1, int $pageSize = 20): ?array
{
$offset = ($page - 1) * $pageSize;
$cacheKey = "tag_" . md5("{$tag}_{$page}");
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
$results = $this->fetchPostRpc("rpc/get_tagged_content", [
"tag_query" => $tag,
"page_size" => $pageSize,
"page_offset" => $offset
]);
if (!$results || count($results) === 0) return null;
$results[0]['globals'] = $this->getGlobals();
$this->cacheSet($cacheKey, $results);
return $results;
}
}

View file

@ -1,3 +0,0 @@
<?php
require_once "media.php";
?>

View file

@ -1,8 +0,0 @@
<?php
function sanitizeMediaString(string $str): string
{
$sanitizedString = preg_replace("/[^a-zA-Z0-9\s-]/", "", iconv("UTF-8", "ASCII//TRANSLIT", $str));
return strtolower(trim(preg_replace("/[\s-]+/", "-", $sanitizedString), "-"));
}
?>

View file

@ -1,7 +1,6 @@
<?php
require __DIR__ . "/../vendor/autoload.php";
require __DIR__ . "/Utils/init.php";
require_once __DIR__ . '/../bootstrap.php';
use App\Classes\ApiHandler;
use GuzzleHttp\Client;

View file

@ -1,6 +1,6 @@
<?php
require __DIR__ . "/../vendor/autoload.php";
require_once __DIR__ . '/../bootstrap.php';
use App\Classes\ApiHandler;
use GuzzleHttp\Client;

View file

@ -1,6 +1,6 @@
<?php
require __DIR__ . "/../vendor/autoload.php";
require_once __DIR__ . '/../bootstrap.php';
use App\Classes\BaseHandler;
use GuzzleHttp\Client;

View file

@ -1,6 +1,6 @@
<?php
require __DIR__ . "/../vendor/autoload.php";
require_once __DIR__ . '/../bootstrap.php';
use App\Classes\ApiHandler;
use GuzzleHttp\Client;

View file

@ -1,7 +1,6 @@
<?php
require __DIR__ . "/../vendor/autoload.php";
require __DIR__ . '/../server/utils/init.php';
require_once __DIR__ . '/../bootstrap.php';
use App\Classes\BaseHandler;
use GuzzleHttp\Client;

View file

@ -1,6 +1,6 @@
<?php
require __DIR__ . '/../server/utils/init.php';
require_once __DIR__ . '/../bootstrap.php';
$id = $_GET['id'] ?? null;
$class = $_GET['class'] ?? null;

View file

@ -1,78 +0,0 @@
<?php
require __DIR__ . "/../vendor/autoload.php";
use App\Classes\BaseHandler;
class LatestListenHandler extends BaseHandler
{
protected int $cacheTTL = 60;
public function __construct()
{
parent::__construct();
$this->initializeCache();
}
public function handleRequest(): void
{
try {
$cachedData = $this->cache ? $this->cache->get("latest_listen") : null;
if ($cachedData) {
$this->sendResponse(json_decode($cachedData, true));
return;
}
$data = $this->makeRequest("GET", "optimized_latest_listen?select=*");
if (!is_array($data) || empty($data[0])) {
$this->sendResponse(["message" => "No recent tracks found"], 404);
return;
}
$latestListen = $this->formatLatestListen($data[0]);
if ($this->cache) $this->cache->set(
"latest_listen",
json_encode($latestListen),
$this->cacheTTL
);
$this->sendResponse($latestListen);
} catch (\Exception $e) {
error_log("LatestListenHandler Error: " . $e->getMessage());
$this->sendErrorResponse("Internal Server Error: " . $e->getMessage(), 500);
}
}
private function formatLatestListen(array $latestListen): array
{
$emoji = $latestListen["artist_emoji"] ?? ($latestListen["genre_emoji"] ?? "🎧");
$trackName = htmlspecialchars(
$latestListen["track_name"] ?? "Unknown Track",
ENT_QUOTES,
"UTF-8"
);
$artistName = htmlspecialchars(
$latestListen["artist_name"] ?? "Unknown Artist",
ENT_QUOTES,
"UTF-8"
);
$url = htmlspecialchars($latestListen["url"] ?? "/", ENT_QUOTES, "UTF-8");
return [
"content" => sprintf(
'%s %s by <a href="https://www.coryd.dev%s">%s</a>',
$emoji,
$trackName,
$url,
$artistName
),
];
}
}
$handler = new LatestListenHandler();
$handler->handleRequest();

View file

@ -1,7 +1,6 @@
<?php
require __DIR__ . "/../vendor/autoload.php";
require __DIR__ . '/../server/utils/init.php';
require_once __DIR__ . '/../bootstrap.php';
use App\Classes\BaseHandler;
@ -20,7 +19,7 @@ class QueryHandler extends BaseHandler
$referer = $_SERVER['HTTP_REFERER'] ?? '';
$hostAllowed = fn($url) => in_array(parse_url($url, PHP_URL_HOST), $allowedHosts, true);
if (!$hostAllowed($origin) && !$hostAllowed($referer)) $this->sendErrorResponse("Forbidden invalid origin", 403);
if (!$hostAllowed($origin) && !$hostAllowed($referer)) $this->sendErrorResponse("Forbidden: invalid origin", 403);
$allowedSource = $origin ?: $referer;
$scheme = parse_url($allowedSource, PHP_URL_SCHEME) ?? 'https';

View file

@ -1,7 +1,6 @@
<?php
require __DIR__ . "/../vendor/autoload.php";
require __DIR__ . "/Utils/init.php";
require_once __DIR__ . '/../bootstrap.php';
use App\Classes\ApiHandler;
use GuzzleHttp\Client;

View file

@ -1,7 +1,6 @@
<?php
require __DIR__ . "/../vendor/autoload.php";
require __DIR__ . '/../server/utils/init.php';
require_once __DIR__ . '/../bootstrap.php';
use App\Classes\BaseHandler;

View file

@ -1,6 +1,6 @@
<?php
require __DIR__ . "/../vendor/autoload.php";
require_once __DIR__ . '/../bootstrap.php';
use App\Classes\ApiHandler;
use GuzzleHttp\Client;

View file

@ -1,95 +1,95 @@
<?php
$umamiHost = 'https://stats.coryd.dev';
$requestUri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];
$proxyPrefix = '/assets/scripts';
$forwardPath = parse_url($requestUri, PHP_URL_PATH);
$forwardPath = str_replace($proxyPrefix, '', $forwardPath);
$targetUrl = $umamiHost . $forwardPath;
$umamiHost = 'https://stats.coryd.dev';
$requestUri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];
$proxyPrefix = '/assets/scripts';
$forwardPath = parse_url($requestUri, PHP_URL_PATH);
$forwardPath = str_replace($proxyPrefix, '', $forwardPath);
$targetUrl = $umamiHost . $forwardPath;
if (isset($contentType) && preg_match('#^(application/json|text/|application/javascript)#', $contentType)) {
ob_start('ob_gzhandler');
} else {
ob_start();
}
if (isset($contentType) && preg_match('#^(application/json|text/|application/javascript)#', $contentType)) {
ob_start('ob_gzhandler');
} else {
ob_start();
}
if ($method === 'GET' && preg_match('#^/utils\.js$#', $forwardPath)) {
$remoteUrl = $umamiHost . '/script.js';
$cacheKey = 'remote_stats_script';
$ttl = 3600;
$js = null;
$code = 200;
$redis = null;
if ($method === 'GET' && preg_match('#^/utils\.js$#', $forwardPath)) {
$remoteUrl = $umamiHost . '/script.js';
$cacheKey = 'remote_stats_script';
$ttl = 3600;
$js = null;
$code = 200;
$redis = null;
try {
if (extension_loaded('redis')) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
try {
if (extension_loaded('redis')) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
if ($redis->exists($cacheKey)) $js = $redis->get($cacheKey);
if ($redis->exists($cacheKey)) $js = $redis->get($cacheKey);
}
} catch (Exception $e) {
error_log("Redis unavailable: " . $e->getMessage());
}
} catch (Exception $e) {
error_log("Redis unavailable: " . $e->getMessage());
if (!is_string($js)) {
$ch = curl_init($remoteUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
$js = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($redis && $code === 200 && $js) $redis->setex($cacheKey, $ttl, $js);
}
if (!is_string($js) || trim($js) === '') {
$js = '// Failed to fetch remote script';
$code = 502;
}
http_response_code($code);
header('Content-Type: application/javascript; charset=UTF-8');
echo $js;
exit;
}
if (!is_string($js)) {
$ch = curl_init($remoteUrl);
$headers = [
'Content-Type: application/json',
'Accept: application/json',
];
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
if (isset($_SERVER['HTTP_USER_AGENT'])) $headers[] = 'User-Agent: ' . $_SERVER['HTTP_USER_AGENT'];
$js = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$ch = curl_init($targetUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_close($ch);
if ($method === 'POST') {
$body = file_get_contents('php://input');
$data = json_decode($body, true);
if ($redis && $code === 200 && $js) $redis->setex($cacheKey, $ttl, $js);
if (strpos($forwardPath, '/api/send') === 0 && is_array($data)) $data['payload'] = array_merge($data['payload'] ?? [], [
'ip' => $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0',
]);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
} else {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
if (!is_string($js) || trim($js) === '') {
$js = '// Failed to fetch remote script';
$code = 502;
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
http_response_code($code);
header('Content-Type: application/javascript; charset=UTF-8');
echo $js;
exit;
}
curl_close($ch);
http_response_code($httpCode);
$headers = [
'Content-Type: application/json',
'Accept: application/json',
];
if ($contentType) header("Content-Type: $contentType");
if (isset($_SERVER['HTTP_USER_AGENT'])) $headers[] = 'User-Agent: ' . $_SERVER['HTTP_USER_AGENT'];
$ch = curl_init($targetUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($method === 'POST') {
$body = file_get_contents('php://input');
$data = json_decode($body, true);
if (strpos($forwardPath, '/api/send') === 0 && is_array($data)) $data['payload'] = array_merge($data['payload'] ?? [], [
'ip' => $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0',
]);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
} else {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);
http_response_code($httpCode);
if ($contentType) header("Content-Type: $contentType");
echo $response ?: '';
echo $response ?: '';

View file

@ -1,6 +1,6 @@
<?php
require __DIR__ . "/../vendor/autoload.php";
require_once __DIR__ . '/../bootstrap.php';
use App\Classes\ApiHandler;
use GuzzleHttp\Client;