feat(*.php, *.psql): deduplicate API code + performance improvements

This commit is contained in:
Cory Dransfeldt 2025-04-22 12:39:42 -07:00
parent cf3dac8a46
commit 4bad005e58
No known key found for this signature in database
31 changed files with 502 additions and 666 deletions

View file

@ -10,13 +10,8 @@ use GuzzleHttp\Client;
header("Content-Type: application/json");
$authHeader = $_SERVER["HTTP_AUTHORIZATION"] ?? "";
$expectedToken = "Bearer " . getenv("NAVIDROME_SCROBBLE_TOKEN");
class NavidromeScrobbleHandler extends ApiHandler
{
private string $postgrestApiUrl;
private string $postgrestApiToken;
private string $navidromeApiUrl;
private string $navidromeAuthToken;
private string $forwardEmailApiKey;
@ -28,14 +23,12 @@ class NavidromeScrobbleHandler extends ApiHandler
{
parent::__construct();
$this->ensureCliAccess();
$this->loadEnvironment();
$this->loadExternalServiceKeys();
$this->validateAuthorization();
}
private function loadEnvironment(): void
private function loadExternalServiceKeys(): void
{
$this->postgrestApiUrl = getenv("POSTGREST_URL");
$this->postgrestApiToken = getenv("POSTGREST_API_KEY");
$this->navidromeApiUrl = getenv("NAVIDROME_API_URL");
$this->navidromeAuthToken = getenv("NAVIDROME_API_TOKEN");
$this->forwardEmailApiKey = getenv("FORWARDEMAIL_API_KEY");
@ -95,7 +88,7 @@ class NavidromeScrobbleHandler extends ApiHandler
private function isTrackAlreadyScrobbled(array $track): bool
{
$playDate = strtotime($track["playDate"]);
$existingListen = $this->fetchFromPostgREST("listens", "listened_at=eq.{$playDate}&limit=1");
$existingListen = $this->fetchFromApi("listens", "listened_at=eq.{$playDate}&limit=1");
return !empty($existingListen);
}
@ -121,61 +114,52 @@ class NavidromeScrobbleHandler extends ApiHandler
private function getOrCreateArtist(string $artistName): array
{
if (!$this->isDatabaseAvailable()) {
error_log("Skipping artist insert: database is unavailable.");
return [];
}
if (!$this->isDatabaseAvailable()) return [];
if (isset($this->artistCache[$artistName])) return $this->artistCache[$artistName];
$encodedArtist = rawurlencode($artistName);
$existingArtist = $this->fetchFromPostgREST("artists", "name_string=eq.{$encodedArtist}&limit=1");
$existingArtist = $this->fetchFromApi("artists", "name_string=eq.{$encodedArtist}&limit=1");
if (!empty($existingArtist)) {
$this->artistCache[$artistName] = $existingArtist[0];
return $existingArtist[0];
return $this->artistCache[$artistName] = $existingArtist[0];
}
$this->fetchFromPostgREST("artists", "", "POST", [
"mbid" => "",
"art" => "4cef75db-831f-4f5d-9333-79eaa5bb55ee",
"name_string" => $artistName,
"slug" => "/music",
"country" => "",
"description" => "",
"tentative" => true,
"favorite" => false,
"tattoo" => false,
"total_plays" => 0
$this->makeRequest("POST", "artists", [
"json" => [
"mbid" => "",
"art" => "4cef75db-831f-4f5d-9333-79eaa5bb55ee",
"name_string" => $artistName,
"slug" => "/music",
"country" => "",
"description" => "",
"tentative" => true,
"favorite" => false,
"tattoo" => false,
"total_plays" => 0
]
]);
$this->sendFailureEmail("New tentative artist record", "A new tentative artist record was inserted for: $artistName");
$artistData = $this->fetchFromPostgREST("artists", "name_string=eq.{$encodedArtist}&limit=1");
$artistData = $this->fetchFromApi("artists", "name_string=eq.{$encodedArtist}&limit=1");
$this->artistCache[$artistName] = $artistData[0] ?? [];
return $this->artistCache[$artistName];
return $this->artistCache[$artistName] = $artistData[0] ?? [];
}
private function getOrCreateAlbum(string $albumName, array $artistData): array
{
if (!$this->isDatabaseAvailable()) {
error_log("Skipping album insert: database is unavailable.");
return [];
}
if (!$this->isDatabaseAvailable()) return [];
$albumKey = $this->generateAlbumKey($artistData["name_string"], $albumName);
if (isset($this->albumCache[$albumKey])) return $this->albumCache[$albumKey];
$encodedAlbumKey = rawurlencode($albumKey);
$existingAlbum = $this->fetchFromPostgREST("albums", "key=eq.{$encodedAlbumKey}&limit=1");
$existingAlbum = $this->fetchFromApi("albums", "key=eq.{$encodedAlbumKey}&limit=1");
if (!empty($existingAlbum)) {
$this->albumCache[$albumKey] = $existingAlbum[0];
return $existingAlbum[0];
return $this->albumCache[$albumKey] = $existingAlbum[0];
}
$artistId = $artistData["id"] ?? null;
@ -185,35 +169,37 @@ class NavidromeScrobbleHandler extends ApiHandler
return [];
}
$this->fetchFromPostgREST("albums", "", "POST", [
"mbid" => null,
"art" => "4cef75db-831f-4f5d-9333-79eaa5bb55ee",
"key" => $albumKey,
"name" => $albumName,
"tentative" => true,
"total_plays" => 0,
"artist" => $artistId
$this->makeRequest("POST", "albums", [
"json" => [
"mbid" => null,
"art" => "4cef75db-831f-4f5d-9333-79eaa5bb55ee",
"key" => $albumKey,
"name" => $albumName,
"tentative" => true,
"total_plays" => 0,
"artist" => $artistId
]
]);
$this->sendFailureEmail("New tentative album record", "A new tentative album record was inserted:\n\nAlbum: $albumName\nKey: $albumKey");
$albumData = $this->fetchFromPostgREST("albums", "key=eq.{$encodedAlbumKey}&limit=1");
$albumData = $this->fetchFromApi("albums", "key=eq.{$encodedAlbumKey}&limit=1");
$this->albumCache[$albumKey] = $albumData[0] ?? [];
return $this->albumCache[$albumKey];
return $this->albumCache[$albumKey] = $albumData[0] ?? [];
}
private function insertListen(array $track, string $albumKey): void
{
$playDate = strtotime($track["playDate"]);
$this->fetchFromPostgREST("listens", "", "POST", [
"artist_name" => $track["artist"],
"album_name" => $track["album"],
"track_name" => $track["title"],
"listened_at" => $playDate,
"album_key" => $albumKey
$this->makeRequest("POST", "listens", [
"json" => [
"artist_name" => $track["artist"],
"album_name" => $track["album"],
"track_name" => $track["title"],
"listened_at" => $playDate,
"album_key" => $albumKey
]
]);
}
@ -221,24 +207,18 @@ class NavidromeScrobbleHandler extends ApiHandler
{
$artistKey = sanitizeMediaString($artistName);
$albumKey = sanitizeMediaString($albumName);
return "{$artistKey}-{$albumKey}";
}
private function sendFailureEmail(string $subject, string $message): void
{
if (!$this->isDatabaseAvailable()) {
error_log("Skipping email: database is unavailable.");
return;
}
if (!$this->isDatabaseAvailable()) return;
$authHeader = "Basic " . base64_encode($this->forwardEmailApiKey . ":");
$client = new Client([
"base_uri" => "https://api.forwardemail.net/",
]);
$client = new Client(["base_uri" => "https://api.forwardemail.net/"]);
try {
$response = $client->post("v1/emails", [
$client->post("v1/emails", [
"headers" => [
"Authorization" => $authHeader,
"Content-Type" => "application/x-www-form-urlencoded",
@ -250,12 +230,10 @@ class NavidromeScrobbleHandler extends ApiHandler
"text" => $message,
],
]);
} catch (\GuzzleHttp\Exception\RequestException $e) {
error_log("Request Exception: " . $e->getMessage());
if ($e->hasResponse()) {
$errorResponse = (string) $e->getResponse()->getBody();
error_log("Error Response: " . $errorResponse);
error_log("Error Response: " . (string) $e->getResponse()->getBody());
}
} catch (\Exception $e) {
error_log("General Exception: " . $e->getMessage());
@ -265,9 +243,9 @@ class NavidromeScrobbleHandler extends ApiHandler
private function isDatabaseAvailable(): bool
{
try {
$response = $this->fetchFromPostgREST("listens", "limit=1");
$response = $this->fetchFromApi("listens", "limit=1");
return is_array($response);
} catch (Exception $e) {
} catch (\Exception $e) {
error_log("Database check failed: " . $e->getMessage());
return false;
}
@ -277,7 +255,7 @@ class NavidromeScrobbleHandler extends ApiHandler
try {
$handler = new NavidromeScrobbleHandler();
$handler->runScrobbleCheck();
} catch (Exception $e) {
} catch (\Exception $e) {
http_response_code(500);
echo json_encode(["error" => $e->getMessage()]);
}