chore(*): remove duplicate cache rule + cleanup cache headers; cleanup + formatting

This commit is contained in:
Cory Dransfeldt 2025-05-17 13:25:40 -07:00
parent 425fed6ff6
commit 0e565970a5
No known key found for this signature in database
42 changed files with 223 additions and 217 deletions

View file

@ -8,8 +8,6 @@ abstract class ApiHandler extends BaseHandler
{
protected function ensureCliAccess(): void
{
if (php_sapi_name() !== 'cli' && $_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->sendErrorResponse("Not Found", 404);
}
if (php_sapi_name() !== 'cli' && $_SERVER['REQUEST_METHOD'] !== 'POST') $this->sendErrorResponse("Not Found", 404);
}
}

View file

@ -8,12 +8,15 @@ class ArtistFetcher extends PageFetcher
{
$cacheKey = "artist_" . md5($url);
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
$artist = $this->fetchSingleFromApi("optimized_artists", $url);
if (!$artist) return null;
$this->cacheSet($cacheKey, $artist);
return $artist;
}
}

View file

@ -31,13 +31,16 @@ abstract class BaseHandler
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;
}
}
@ -56,12 +59,12 @@ abstract class BaseHandler
], $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());
}
if (json_last_error() !== JSON_ERROR_NONE) throw new \Exception("Invalid JSON: " . json_last_error_msg());
return $data;
} catch (RequestException $e) {
@ -78,6 +81,7 @@ abstract class BaseHandler
protected function fetchFromApi(string $endpoint, string $query = ""): array
{
$url = $endpoint . ($query ? "?{$query}" : "");
return $this->makeRequest("GET", $url);
}
@ -85,7 +89,9 @@ abstract class BaseHandler
{
http_response_code($statusCode);
header("Content-Type: application/json");
echo json_encode($data);
exit();
}

View file

@ -8,12 +8,15 @@ class BookFetcher extends PageFetcher
{
$cacheKey = "book_" . md5($url);
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
$book = $this->fetchSingleFromApi("optimized_books", $url);
if (!$book) return null;
$this->cacheSet($cacheKey, $book);
return $book;
}
}

View file

@ -8,12 +8,15 @@ class GenreFetcher extends PageFetcher
{
$cacheKey = "genre_" . md5($url);
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
$genre = $this->fetchSingleFromApi("optimized_genres", $url);
if (!$genre) return null;
$this->cacheSet($cacheKey, $genre);
return $genre;
}
}

View file

@ -8,12 +8,15 @@ class MovieFetcher extends PageFetcher
{
$cacheKey = "movie_" . md5($url);
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
$movie = $this->fetchSingleFromApi("optimized_movies", $url);
if (!$movie) return null;
$this->cacheSet($cacheKey, $movie);
return $movie;
}
}

View file

@ -19,6 +19,7 @@ abstract class PageFetcher extends BaseHandler
protected function fetchSingleFromApi(string $endpoint, string $url): ?array
{
$data = $this->fetchFromApi($endpoint, "url=eq./{$url}");
return $data[0] ?? null;
}

View file

@ -8,12 +8,15 @@ class ShowFetcher extends PageFetcher
{
$cacheKey = "show_" . md5($url);
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
$show = $this->fetchSingleFromApi("optimized_shows", $url);
if (!$show) return null;
$this->cacheSet($cacheKey, $show);
return $show;
}
}

View file

@ -8,8 +8,8 @@ class TagFetcher extends PageFetcher
{
$offset = ($page - 1) * $pageSize;
$cacheKey = "tag_" . md5("{$tag}_{$page}");
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
$results = $this->fetchPostRpc("rpc/get_tagged_content", [
@ -21,6 +21,7 @@ class TagFetcher extends PageFetcher
if (!$results || count($results) === 0) return null;
$this->cacheSet($cacheKey, $results);
return $results;
}
}

View file

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

View file

@ -16,8 +16,8 @@ class ArtistImportHandler extends ApiHandler
public function __construct()
{
parent::__construct();
$this->ensureCliAccess();
$this->ensureCliAccess();
$this->artistImportToken = getenv("ARTIST_IMPORT_TOKEN");
$this->navidromeApiUrl = getenv("NAVIDROME_API_URL");
$this->navidromeAuthToken = getenv("NAVIDROME_API_TOKEN");
@ -32,13 +32,8 @@ class ArtistImportHandler extends ApiHandler
$providedToken = $input["token"] ?? null;
$artistId = $input["artistId"] ?? null;
if ($providedToken !== $this->artistImportToken) {
$this->sendJsonResponse("error", "Unauthorized access", 401);
}
if (!$artistId) {
$this->sendJsonResponse("error", "Artist ID is required", 400);
}
if ($providedToken !== $this->artistImportToken) $this->sendJsonResponse("error", "Unauthorized access", 401);
if (!$artistId) $this->sendJsonResponse("error", "Artist ID is required", 400);
try {
$artistData = $this->fetchNavidromeArtist($artistId);
@ -56,7 +51,9 @@ class ArtistImportHandler extends ApiHandler
{
http_response_code($statusCode);
header("Content-Type: application/json");
echo json_encode([$key => $message]);
exit();
}
@ -96,9 +93,11 @@ class ArtistImportHandler extends ApiHandler
private function processArtist(object $artistData): bool
{
$artistName = $artistData->name ?? "";
if (!$artistName) throw new \Exception("Artist name is missing.");
$existingArtist = $this->getArtistByName($artistName);
if ($existingArtist) return true;
$artistKey = sanitizeMediaString($artistName);
@ -106,7 +105,6 @@ class ArtistImportHandler extends ApiHandler
$description = strip_tags($artistData->biography ?? "");
$genre = $this->resolveGenreId($artistData->genres[0]->name ?? "");
$starred = $artistData->starred ?? false;
$artistPayload = [
"name_string" => $artistName,
"slug" => $slug,
@ -119,17 +117,18 @@ class ArtistImportHandler extends ApiHandler
];
$this->makeRequest("POST", "artists", ["json" => $artistPayload]);
return true;
}
private function processAlbums(string $artistId, string $artistName): void
{
$artist = $this->getArtistByName($artistName);
if (!$artist) throw new \Exception("Artist not found after insert.");
$existingAlbums = $this->getExistingAlbums($artist["id"]);
$existingAlbumKeys = array_column($existingAlbums, "key");
$navidromeAlbums = $this->fetchNavidromeAlbums($artistId);
foreach ($navidromeAlbums as $album) {
@ -137,9 +136,7 @@ class ArtistImportHandler extends ApiHandler
$releaseYearRaw = $album["date"] ?? null;
$releaseYear = null;
if ($releaseYearRaw && preg_match('/^\d{4}/', $releaseYearRaw, $matches)) {
$releaseYear = (int)$matches[0];
}
if ($releaseYearRaw && preg_match('/^\d{4}/', $releaseYearRaw, $matches)) $releaseYear = (int)$matches[0];
$artistKey = sanitizeMediaString($artistName);
$albumKey = "{$artistKey}-" . sanitizeMediaString($albumName);
@ -170,6 +167,7 @@ class ArtistImportHandler extends ApiHandler
private function getArtistByName(string $nameString): ?array
{
$response = $this->fetchFromApi("artists", "name_string=eq." . urlencode($nameString));
return $response[0] ?? null;
}
@ -181,6 +179,7 @@ class ArtistImportHandler extends ApiHandler
private function resolveGenreId(string $genreName): ?string
{
$genres = $this->fetchFromApi("genres", "name=eq." . urlencode(strtolower($genreName)));
return $genres[0]["id"] ?? null;
}
}

View file

@ -12,6 +12,7 @@ class BookImportHandler extends ApiHandler
public function __construct()
{
parent::__construct();
$this->ensureCliAccess();
$this->bookImportToken = $_ENV["BOOK_IMPORT_TOKEN"] ?? getenv("BOOK_IMPORT_TOKEN");
}
@ -20,20 +21,13 @@ class BookImportHandler extends ApiHandler
{
$input = json_decode(file_get_contents("php://input"), true);
if (!$input) {
$this->sendErrorResponse("Invalid or missing JSON body", 400);
}
if (!$input) $this->sendErrorResponse("Invalid or missing JSON body", 400);
$providedToken = $input["token"] ?? null;
$isbn = $input["isbn"] ?? null;
if ($providedToken !== $this->bookImportToken) {
$this->sendErrorResponse("Unauthorized access", 401);
}
if (!$isbn) {
$this->sendErrorResponse("isbn parameter is required", 400);
}
if ($providedToken !== $this->bookImportToken) $this->sendErrorResponse("Unauthorized access", 401);
if (!$isbn) $this->sendErrorResponse("isbn parameter is required", 400);
try {
$bookData = $this->fetchBookData($isbn);
@ -59,9 +53,7 @@ class BookImportHandler extends ApiHandler
$data = json_decode($response->getBody(), true);
$bookKey = "ISBN:{$isbn}";
if (empty($data[$bookKey])) {
throw new \Exception("Book data not found for ISBN: {$isbn}");
}
if (empty($data[$bookKey])) throw new \Exception("Book data not found for ISBN: {$isbn}");
return $data[$bookKey];
}
@ -75,14 +67,11 @@ class BookImportHandler extends ApiHandler
$author = $bookData["authors"][0]["name"] ?? null;
$description = $bookData["description"] ?? ($bookData["notes"] ?? "");
if (!$isbn || !$title || !$author) {
throw new \Exception("Missing essential book data (title, author, or ISBN).");
}
if (!$isbn || !$title || !$author) throw new \Exception("Missing essential book data (title, author, or ISBN).");
$existingBook = $this->getBookByISBN($isbn);
if ($existingBook) {
throw new \Exception("Book with ISBN {$isbn} already exists.");
}
if ($existingBook) throw new \Exception("Book with ISBN {$isbn} already exists.");
$bookPayload = [
"isbn" => $isbn,
@ -99,6 +88,7 @@ class BookImportHandler extends ApiHandler
private function getBookByISBN(string $isbn): ?array
{
$response = $this->fetchFromApi("books", "isbn=eq." . urlencode($isbn));
return $response[0] ?? null;
}
}

View file

@ -9,13 +9,13 @@ class ContactHandler extends BaseHandler
{
protected string $postgrestUrl;
protected string $postgrestApiKey;
private string $forwardEmailApiKey;
private Client $httpClient;
public function __construct(?Client $httpClient = null)
{
parent::__construct();
$this->httpClient = $httpClient ?? new Client();
$this->forwardEmailApiKey = $_ENV["FORWARDEMAIL_API_KEY"] ?? getenv("FORWARDEMAIL_API_KEY");
}
@ -33,19 +33,16 @@ class ContactHandler extends BaseHandler
if (strpos($contentType, "application/json") !== false) {
$rawBody = file_get_contents("php://input");
$formData = json_decode($rawBody, true);
if (!$formData || !isset($formData["data"])) {
throw new \Exception("Invalid JSON payload.");
}
if (!$formData || !isset($formData["data"])) throw new \Exception("Invalid JSON payload.");
$formData = $formData["data"];
} elseif (
strpos($contentType, "application/x-www-form-urlencoded") !== false
) {
$formData = $_POST;
} else {
$this->sendErrorResponse(
"Unsupported Content-Type. Use application/json or application/x-www-form-urlencoded.",
400
);
$this->sendErrorResponse("Unsupported Content-Type. Use application/json or application/x-www-form-urlencoded.", 400);
}
if (!empty($formData["hp_name"])) $this->sendErrorResponse("Invalid submission.", 400);
@ -65,14 +62,8 @@ class ContactHandler extends BaseHandler
if (empty($name)) $this->sendErrorResponse("Name is required.", 400);
if (!$email) $this->sendErrorResponse("Valid email is required.", 400);
if (empty($message)) $this->sendErrorResponse("Message is required.", 400);
if (strlen($name) > 100) $this->sendErrorResponse(
"Name is too long. Max 100 characters allowed.",
400
);
if (strlen($message) > 1000) $this->sendErrorResponse(
"Message is too long. Max 1000 characters allowed.",
400
);
if (strlen($name) > 100) $this->sendErrorResponse("Name is too long. Max 100 characters allowed.", 400);
if (strlen($message) > 1000) $this->sendErrorResponse("Message is too long. Max 1000 characters allowed.", 400);
if ($this->isBlockedDomain($email)) $this->sendErrorResponse("Submission from blocked domain.", 400);
$contactData = [
@ -87,6 +78,7 @@ class ContactHandler extends BaseHandler
$this->sendRedirect("/contact/success");
} catch (\Exception $e) {
error_log("Error handling contact form submission: " . $e->getMessage());
$this->sendErrorResponse($e->getMessage(), 400);
}
}
@ -95,6 +87,7 @@ class ContactHandler extends BaseHandler
{
$referer = $_SERVER["HTTP_REFERER"] ?? "";
$allowedDomain = "coryd.dev";
if (!str_contains($referer, $allowedDomain)) throw new \Exception("Invalid submission origin.");
}
@ -107,13 +100,12 @@ class ContactHandler extends BaseHandler
if (file_exists($cacheFile)) {
$data = json_decode(file_get_contents($cacheFile), true);
if (
$data["timestamp"] + $rateLimitDuration > time() &&
$data["count"] >= $maxRequests
) {
if ($data["timestamp"] + $rateLimitDuration > time() && $data["count"] >= $maxRequests) {
header("Location: /429", true, 302);
exit();
}
$data["count"]++;
} else {
$data = ["count" => 1, "timestamp" => time()];
@ -130,6 +122,7 @@ class ContactHandler extends BaseHandler
private function isBlockedDomain(string $email): bool
{
$domain = substr(strrchr($email, "@"), 1);
if (!$domain) return false;
$response = $this->httpClient->get(
@ -145,7 +138,6 @@ class ContactHandler extends BaseHandler
],
]
);
$blockedDomains = json_decode($response->getBody(), true);
return !empty($blockedDomains);
@ -163,9 +155,8 @@ class ContactHandler extends BaseHandler
if ($response->getStatusCode() >= 400) {
$errorResponse = json_decode($response->getBody(), true);
throw new \Exception(
"PostgREST error: " . ($errorResponse["message"] ?? "Unknown error")
);
throw new \Exception("PostgREST error: " . ($errorResponse["message"] ?? "Unknown error"));
}
}
@ -206,6 +197,7 @@ class ContactHandler extends BaseHandler
$redirectUrl = "{$protocol}://{$host}{$path}";
header("Location: $redirectUrl", true, 302);
exit();
}
}
@ -215,6 +207,8 @@ try {
$handler->handleRequest();
} catch (\Exception $e) {
error_log("Contact form error: " . $e->getMessage());
echo json_encode(["error" => $e->getMessage()]);
http_response_code(500);
}

View file

@ -10,19 +10,16 @@ class MastodonPostHandler extends ApiHandler
private string $mastodonAccessToken;
private string $rssFeedUrl = "https://www.coryd.dev/feeds/syndication.xml";
private string $baseUrl = "https://www.coryd.dev";
private const MASTODON_API_STATUS = "https://follow.coryd.dev/api/v1/statuses";
private Client $httpClient;
public function __construct(?Client $httpClient = null)
{
parent::__construct();
$this->ensureCliAccess();
$this->ensureCliAccess();
$this->mastodonAccessToken = getenv("MASTODON_ACCESS_TOKEN") ?: $_ENV["MASTODON_ACCESS_TOKEN"] ?? "";
$this->httpClient = $httpClient ?: new Client();
$this->validateAuthorization();
}
@ -49,6 +46,7 @@ class MastodonPostHandler extends ApiHandler
foreach (array_reverse($latestItems) as $item) {
$existing = $this->fetchFromApi("mastodon_posts", "link=eq." . urlencode($item["link"]));
if (!empty($existing)) continue;
$content = $this->truncateContent(
@ -57,7 +55,6 @@ class MastodonPostHandler extends ApiHandler
$item["link"],
500
);
$timestamp = date("Y-m-d H:i:s");
if (!$this->storeInDatabase($item["link"], $timestamp)) {
@ -66,6 +63,7 @@ class MastodonPostHandler extends ApiHandler
}
$postedUrl = $this->postToMastodon($content, $item["image"] ?? null);
if ($postedUrl) {
echo "Posted: {$postedUrl}\n";
} else {
@ -79,6 +77,7 @@ class MastodonPostHandler extends ApiHandler
private function fetchRSSFeed(string $rssFeedUrl): array
{
$rssText = file_get_contents($rssFeedUrl);
if (!$rssText) throw new \Exception("Failed to fetch RSS feed.");
$rss = new \SimpleXMLElement($rssText);
@ -106,9 +105,11 @@ class MastodonPostHandler extends ApiHandler
"headers" => $headers,
"json" => $postData
]);
if ($response->getStatusCode() >= 400) throw new \Exception("Mastodon post failed: {$response->getBody()}");
$body = json_decode($response->getBody()->getContents(), true);
return $body["url"] ?? null;
}
@ -121,9 +122,11 @@ class MastodonPostHandler extends ApiHandler
"created_at" => $timestamp
]
]);
return true;
} catch (\Exception $e) {
echo "Error storing post in DB: " . $e->getMessage() . "\n";
return false;
}
}
@ -132,9 +135,11 @@ class MastodonPostHandler extends ApiHandler
{
try {
$response = $this->fetchFromApi("mastodon_posts", "limit=1");
return is_array($response);
} catch (\Exception $e) {
echo "Database check failed: " . $e->getMessage() . "\n";
return false;
}
}
@ -158,5 +163,6 @@ try {
$handler->handlePost();
} catch (\Exception $e) {
http_response_code(500);
echo json_encode(["error" => $e->getMessage()]);
}

View file

@ -32,6 +32,7 @@ class OembedHandler extends BaseHandler
if ($this->cache && $this->cache->exists($cacheKey)) {
$cachedItem = json_decode($this->cache->get($cacheKey), true);
$this->sendResponse($this->buildResponse(
$cachedItem['title'],
$cachedItem['url'],

View file

@ -27,8 +27,10 @@
if ($httpCode !== 200 || $image === false || strpos($contentType, 'image/') !== 0) {
error_log("Failed to fetch image: $cdnUrl ($httpCode - $contentType)");
header("Location: /404", true, 302);
exit;
}
header("Content-Type: $contentType");
echo $image;

View file

@ -18,6 +18,7 @@ class LatestListenHandler extends BaseHandler
{
try {
$cachedData = $this->cache ? $this->cache->get("latest_listen") : null;
if ($cachedData) {
$this->sendResponse(json_decode($cachedData, true));
return;
@ -41,17 +42,14 @@ class LatestListenHandler extends BaseHandler
$this->sendResponse($latestListen);
} catch (\Exception $e) {
error_log("LatestListenHandler Error: " . $e->getMessage());
$this->sendErrorResponse(
"Internal Server Error: " . $e->getMessage(),
500
);
$this->sendErrorResponse("Internal Server Error: " . $e->getMessage(), 500);
}
}
private function formatLatestListen(array $latestListen): array
{
$emoji =
$latestListen["artist_emoji"] ?? ($latestListen["genre_emoji"] ?? "🎧");
$emoji = $latestListen["artist_emoji"] ?? ($latestListen["genre_emoji"] ?? "🎧");
$trackName = htmlspecialchars(
$latestListen["track_name"] ?? "Unknown Track",
ENT_QUOTES,

View file

@ -18,7 +18,6 @@ class QueryHandler extends BaseHandler
$allowedHosts = ['coryd.dev', 'www.coryd.dev', 'localhost'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
$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);
@ -44,6 +43,7 @@ class QueryHandler extends BaseHandler
if ($this->cache) {
$cached = $this->cache->get($cacheKey);
if ($cached) {
header('Content-Type: application/json');
echo $cached;

View file

@ -13,7 +13,6 @@ class NavidromeScrobbleHandler extends ApiHandler
private string $navidromeApiUrl;
private string $navidromeAuthToken;
private string $forwardEmailApiKey;
private array $artistCache = [];
private array $albumCache = [];
@ -39,7 +38,9 @@ class NavidromeScrobbleHandler extends ApiHandler
if ($authHeader !== $expectedToken) {
http_response_code(401);
echo json_encode(["error" => "Unauthorized."]);
exit();
}
}
@ -60,6 +61,7 @@ class NavidromeScrobbleHandler extends ApiHandler
private function fetchRecentlyPlayed(): array
{
$client = new Client();
try {
$response = $client->request("GET", "{$this->navidromeApiUrl}/api/song", [
"query" => [
@ -76,9 +78,11 @@ class NavidromeScrobbleHandler extends ApiHandler
]);
$data = json_decode($response->getBody()->getContents(), true);
return $data ?? [];
} catch (\Exception $e) {
error_log("Error fetching tracks: " . $e->getMessage());
return [];
}
}
@ -113,15 +117,12 @@ class NavidromeScrobbleHandler extends ApiHandler
private function getOrCreateArtist(string $artistName): array
{
if (!$this->isDatabaseAvailable()) return [];
if (isset($this->artistCache[$artistName])) return $this->artistCache[$artistName];
$encodedArtist = rawurlencode($artistName);
$existingArtist = $this->fetchFromApi("artists", "name_string=eq.{$encodedArtist}&limit=1");
if (!empty($existingArtist)) {
return $this->artistCache[$artistName] = $existingArtist[0];
}
if (!empty($existingArtist)) return $this->artistCache[$artistName] = $existingArtist[0];
$this->makeRequest("POST", "artists", [
"json" => [
@ -137,7 +138,6 @@ class NavidromeScrobbleHandler extends ApiHandler
"total_plays" => 0
]
]);
$this->sendFailureEmail("New tentative artist record", "A new tentative artist record was inserted for: $artistName");
$artistData = $this->fetchFromApi("artists", "name_string=eq.{$encodedArtist}&limit=1");
@ -156,9 +156,7 @@ class NavidromeScrobbleHandler extends ApiHandler
$encodedAlbumKey = rawurlencode($albumKey);
$existingAlbum = $this->fetchFromApi("albums", "key=eq.{$encodedAlbumKey}&limit=1");
if (!empty($existingAlbum)) {
return $this->albumCache[$albumKey] = $existingAlbum[0];
}
if (!empty($existingAlbum)) return $this->albumCache[$albumKey] = $existingAlbum[0];
$artistId = $artistData["id"] ?? null;
@ -205,6 +203,7 @@ class NavidromeScrobbleHandler extends ApiHandler
{
$artistKey = sanitizeMediaString($artistName);
$albumKey = sanitizeMediaString($albumName);
return "{$artistKey}-{$albumKey}";
}
@ -230,9 +229,8 @@ class NavidromeScrobbleHandler extends ApiHandler
]);
} catch (\GuzzleHttp\Exception\RequestException $e) {
error_log("Request Exception: " . $e->getMessage());
if ($e->hasResponse()) {
error_log("Error Response: " . (string) $e->getResponse()->getBody());
}
if ($e->hasResponse()) error_log("Error Response: " . (string) $e->getResponse()->getBody());
} catch (\Exception $e) {
error_log("General Exception: " . $e->getMessage());
}
@ -242,9 +240,11 @@ class NavidromeScrobbleHandler extends ApiHandler
{
try {
$response = $this->fetchFromApi("listens", "limit=1");
return is_array($response);
} catch (\Exception $e) {
error_log("Database check failed: " . $e->getMessage());
return false;
}
}
@ -255,5 +255,6 @@ try {
$handler->runScrobbleCheck();
} catch (\Exception $e) {
http_response_code(500);
echo json_encode(["error" => $e->getMessage()]);
}

View file

@ -24,10 +24,7 @@ class SearchHandler extends BaseHandler
$offset = ($page - 1) * $pageSize;
$cacheKey = $this->generateCacheKey($query, $types, $page, $pageSize);
$results = [];
$results =
$this->getCachedResults($cacheKey) ??
$this->fetchSearchResults($query, $types, $pageSize, $offset);
$results = $this->getCachedResults($cacheKey) ?? $this->fetchSearchResults($query, $types, $pageSize, $offset);
if (empty($results) || empty($results["data"])) {
$this->sendResponse(["results" => [], "total" => 0, "page" => $page, "pageSize" => $pageSize], 200);
@ -35,7 +32,6 @@ class SearchHandler extends BaseHandler
}
$this->cacheResults($cacheKey, $results);
$this->sendResponse(
[
"results" => $results["data"],
@ -57,13 +53,8 @@ class SearchHandler extends BaseHandler
$query = trim($query);
if (strlen($query) > 255) throw new \Exception(
"Invalid 'q' parameter. Exceeds maximum length of 255 characters."
);
if (!preg_match('/^[a-zA-Z0-9\s\-_\'"]+$/', $query)) throw new \Exception(
"Invalid 'q' parameter. Contains unsupported characters."
);
if (strlen($query) > 255) throw new \Exception("Invalid 'q' parameter. Exceeds maximum length of 255 characters.");
if (!preg_match('/^[a-zA-Z0-9\s\-_\'"]+$/', $query)) throw new \Exception("Invalid 'q' parameter. Contains unsupported characters.");
$query = preg_replace("/\s+/", " ", $query);
@ -84,10 +75,7 @@ class SearchHandler extends BaseHandler
);
$invalidTypes = array_diff($types, $allowedTypes);
if (!empty($invalidTypes)) throw new Exception(
"Invalid 'type' parameter. Unsupported types: " .
implode(", ", $invalidTypes)
);
if (!empty($invalidTypes)) throw new Exception("Invalid 'type' parameter. Unsupported types: " . implode(", ", $invalidTypes));
return $types;
}
@ -98,17 +86,14 @@ class SearchHandler extends BaseHandler
int $pageSize,
int $offset
): array {
$typesParam =
$types && count($types) > 0 ? "%7B" . implode(",", $types) . "%7D" : "";
$typesParam = $types && count($types) > 0 ? "%7B" . implode(",", $types) . "%7D" : "";
$endpoint = "rpc/search_optimized_index";
$queryString =
"search_query=" .
urlencode($query) .
"&page_size={$pageSize}&page_offset={$offset}" .
($typesParam ? "&types={$typesParam}" : "");
$data = $this->makeRequest("GET", "{$endpoint}?{$queryString}");
$total = count($data) > 0 ? $data[0]["total_count"] : 0;
$results = array_map(function ($item) {
unset($item["total_count"]);
@ -125,6 +110,7 @@ class SearchHandler extends BaseHandler
int $pageSize
): string {
$typesKey = $types ? implode(",", $types) : "all";
return sprintf(
"search:%s:types:%s:page:%d:pageSize:%d",
md5($query),
@ -138,6 +124,7 @@ class SearchHandler extends BaseHandler
{
if ($this->cache instanceof \Redis) {
$cachedData = $this->cache->get($cacheKey);
return $cachedData ? json_decode($cachedData, true) : null;
} elseif (is_array($this->cache)) {
return $this->cache[$cacheKey] ?? null;

View file

@ -13,38 +13,31 @@ class SeasonImportHandler extends ApiHandler
public function __construct()
{
parent::__construct();
$this->ensureCliAccess();
$this->ensureCliAccess();
$this->tmdbApiKey = getenv("TMDB_API_KEY") ?: $_ENV["TMDB_API_KEY"];
$this->seasonsImportToken = getenv("SEASONS_IMPORT_TOKEN") ?: $_ENV["SEASONS_IMPORT_TOKEN"];
$this->authenticateRequest();
}
private function authenticateRequest(): void
{
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
$this->sendErrorResponse("Method Not Allowed", 405);
}
if ($_SERVER["REQUEST_METHOD"] !== "POST") $this->sendErrorResponse("Method Not Allowed", 405);
$authHeader = $_SERVER["HTTP_AUTHORIZATION"] ?? "";
if (!preg_match('/Bearer\s+(.+)/', $authHeader, $matches)) {
$this->sendErrorResponse("Unauthorized", 401);
}
if (!preg_match('/Bearer\s+(.+)/', $authHeader, $matches)) $this->sendErrorResponse("Unauthorized", 401);
$providedToken = trim($matches[1]);
if ($providedToken !== $this->seasonsImportToken) {
$this->sendErrorResponse("Forbidden", 403);
}
if ($providedToken !== $this->seasonsImportToken) $this->sendErrorResponse("Forbidden", 403);
}
public function importSeasons(): void
{
$ongoingShows = $this->fetchFromApi("optimized_shows", "ongoing=eq.true");
if (empty($ongoingShows)) {
$this->sendResponse(["message" => "No ongoing shows to update"], 200);
}
if (empty($ongoingShows)) $this->sendResponse(["message" => "No ongoing shows to update"], 200);
foreach ($ongoingShows as $show) {
$this->processShowSeasons($show);
@ -86,6 +79,7 @@ class SeasonImportHandler extends ApiHandler
try {
$response = $client->get($url, ["headers" => ["Accept" => "application/json"]]);
return json_decode($response->getBody(), true) ?? [];
} catch (\Exception $e) {
return [];
@ -107,9 +101,11 @@ class SeasonImportHandler extends ApiHandler
private function processSeasonEpisodes(int $showId, string $tmdbId, array $season): void
{
$seasonNumber = $season["season_number"] ?? null;
if ($seasonNumber === null || $seasonNumber == 0) return;
$episodes = $this->fetchSeasonEpisodes($tmdbId, $seasonNumber);
if (empty($episodes)) return;
$watched = $this->fetchWatchedEpisodes($showId);
@ -125,9 +121,9 @@ class SeasonImportHandler extends ApiHandler
foreach ($episodes as $episode) {
$episodeNumber = $episode["episode_number"] ?? null;
if ($episodeNumber === null) continue;
if (in_array($episodeNumber, $scheduledEpisodeNumbers)) continue;
if ($lastWatchedSeason !== null && $seasonNumber < $lastWatchedSeason) return;
if ($seasonNumber == $lastWatchedSeason && $episodeNumber <= $lastWatchedEpisode) continue;
@ -142,6 +138,7 @@ class SeasonImportHandler extends ApiHandler
try {
$response = $client->get($url, ["headers" => ["Accept" => "application/json"]]);
return json_decode($response->getBody(), true)["episodes"] ?? [];
} catch (\Exception $e) {
return [];
@ -151,11 +148,11 @@ class SeasonImportHandler extends ApiHandler
private function addEpisodeToSchedule(int $showId, int $seasonNumber, array $episode): void
{
$airDate = $episode["air_date"] ?? null;
if (!$airDate) return;
$today = date("Y-m-d");
$status = ($airDate < $today) ? "aired" : "upcoming";
$payload = [
"show_id" => $showId,
"season_number" => $seasonNumber,

View file

@ -54,7 +54,6 @@ if ($method === 'GET' && preg_match('#^/utils\.js$#', $forwardPath)) {
http_response_code($code);
header('Content-Type: application/javascript; charset=UTF-8');
header('Cache-Control: public, max-age=60');
echo $js;
exit;
}