feat(*): refactor metadata handling; move metadata to backend where possible, refine client views

This commit is contained in:
Cory Dransfeldt 2025-05-25 20:15:45 -07:00
parent 929bc9f9f8
commit 9687509e4a
No known key found for this signature in database
35 changed files with 506 additions and 339 deletions

View file

@ -1,13 +1,13 @@
<?php
namespace App\Classes;
namespace App\Classes;
require __DIR__ . "/BaseHandler.php";
require __DIR__ . "/BaseHandler.php";
abstract class ApiHandler extends BaseHandler
{
protected function ensureCliAccess(): void
abstract class ApiHandler extends BaseHandler
{
if (php_sapi_name() !== 'cli' && $_SERVER['REQUEST_METHOD'] !== 'POST') $this->sendErrorResponse("Not Found", 404);
protected function ensureCliAccess(): void
{
if (php_sapi_name() !== 'cli' && $_SERVER['REQUEST_METHOD'] !== 'POST') $this->sendErrorResponse("Not Found", 404);
}
}
}

View file

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

View file

@ -1,102 +1,102 @@
<?php
namespace App\Classes;
namespace App\Classes;
require __DIR__ . "/../../vendor/autoload.php";
require __DIR__ . "/../../vendor/autoload.php";
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
abstract class BaseHandler
{
protected string $postgrestUrl;
protected string $postgrestApiKey;
protected ?\Redis $cache = null;
public function __construct()
abstract class BaseHandler
{
$this->loadEnvironment();
$this->initializeCache();
}
protected string $postgrestUrl;
protected string $postgrestApiKey;
protected ?\Redis $cache = null;
private function loadEnvironment(): void
{
$this->postgrestUrl = $_ENV["POSTGREST_URL"] ?? getenv("POSTGREST_URL") ?? "";
$this->postgrestApiKey = $_ENV["POSTGREST_API_KEY"] ?? getenv("POSTGREST_API_KEY") ?? "";
}
public function __construct()
{
$this->loadEnvironment();
$this->initializeCache();
}
protected function initializeCache(): void
{
if (class_exists("Redis")) {
try {
$redis = new \Redis();
$redis->connect("127.0.0.1", 6379);
private function loadEnvironment(): void
{
$this->postgrestUrl = $_ENV["POSTGREST_URL"] ?? getenv("POSTGREST_URL") ?? "";
$this->postgrestApiKey = $_ENV["POSTGREST_API_KEY"] ?? getenv("POSTGREST_API_KEY") ?? "";
}
$this->cache = $redis;
} catch (\Exception $e) {
error_log("Redis connection failed: " . $e->getMessage());
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;
}
} 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);
}
}
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,22 +1,24 @@
<?php
namespace App\Classes;
namespace App\Classes;
class BookFetcher extends PageFetcher
{
public function fetch(string $url): ?array
class BookFetcher extends PageFetcher
{
$cacheKey = "book_" . md5($url);
$cached = $this->cacheGet($cacheKey);
public function fetch(string $url): ?array
{
$cacheKey = "book_" . md5($url);
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
if ($cached) return $cached;
$book = $this->fetchSingleFromApi("optimized_books", $url);
$book = $this->fetchSingleFromApi("optimized_books", $url);
if (!$book) return null;
if (!$book) return null;
$this->cacheSet($cacheKey, $book);
$book['globals'] = $this->getGlobals();
return $book;
$this->cacheSet($cacheKey, $book);
return $book;
}
}
}

View file

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

View file

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

View file

@ -3,23 +3,29 @@
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;
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));
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;
}
@ -27,4 +33,14 @@ abstract class PageFetcher extends BaseHandler
{
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,22 +1,24 @@
<?php
namespace App\Classes;
namespace App\Classes;
class ShowFetcher extends PageFetcher
{
public function fetch(string $url): ?array
class ShowFetcher extends PageFetcher
{
$cacheKey = "show_" . md5($url);
$cached = $this->cacheGet($cacheKey);
public function fetch(string $url): ?array
{
$cacheKey = "show_" . md5($url);
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
if ($cached) return $cached;
$show = $this->fetchSingleFromApi("optimized_shows", $url);
$show = $this->fetchSingleFromApi("optimized_shows", $url);
if (!$show) return null;
if (!$show) return null;
$this->cacheSet($cacheKey, $show);
$show['globals'] = $this->getGlobals();
return $show;
$this->cacheSet($cacheKey, $show);
return $show;
}
}
}

View file

@ -20,6 +20,8 @@ class TagFetcher extends PageFetcher
if (!$results || count($results) === 0) return null;
$results[0]['globals'] = $this->getGlobals();
$this->cacheSet($cacheKey, $results);
return $results;