diff --git a/api/Classes/ApiHandler.php b/api/Classes/ApiHandler.php index a50a88b..157db3f 100644 --- a/api/Classes/ApiHandler.php +++ b/api/Classes/ApiHandler.php @@ -1,13 +1,13 @@ sendErrorResponse("Not Found", 404); + protected function ensureCliAccess(): void + { + if (php_sapi_name() !== 'cli' && $_SERVER['REQUEST_METHOD'] !== 'POST') $this->sendErrorResponse("Not Found", 404); + } } -} diff --git a/api/Classes/ArtistFetcher.php b/api/Classes/ArtistFetcher.php index 74d1925..c4dfa21 100644 --- a/api/Classes/ArtistFetcher.php +++ b/api/Classes/ArtistFetcher.php @@ -1,22 +1,24 @@ 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; + } } -} diff --git a/api/Classes/BaseHandler.php b/api/Classes/BaseHandler.php index d8c4168..bed8f40 100644 --- a/api/Classes/BaseHandler.php +++ b/api/Classes/BaseHandler.php @@ -1,102 +1,102 @@ 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); - } -} diff --git a/api/Classes/BookFetcher.php b/api/Classes/BookFetcher.php index bf1d35b..e43a13e 100644 --- a/api/Classes/BookFetcher.php +++ b/api/Classes/BookFetcher.php @@ -1,22 +1,24 @@ 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; + } } -} diff --git a/api/Classes/GenreFetcher.php b/api/Classes/GenreFetcher.php index a6b754b..faf337d 100644 --- a/api/Classes/GenreFetcher.php +++ b/api/Classes/GenreFetcher.php @@ -1,22 +1,24 @@ 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; + } } -} diff --git a/api/Classes/GlobalsFetcher.php b/api/Classes/GlobalsFetcher.php new file mode 100644 index 0000000..e189108 --- /dev/null +++ b/api/Classes/GlobalsFetcher.php @@ -0,0 +1,22 @@ +cacheGet($cacheKey); + + if ($cached) return $cached; + + $globals = $this->fetchFromApi("optimized_globals"); + + if (empty($globals)) return null; + + $this->cacheSet($cacheKey, $globals[0]); + + return $globals[0]; + } + } diff --git a/api/Classes/MovieFetcher.php b/api/Classes/MovieFetcher.php index 1cb058f..77fb861 100644 --- a/api/Classes/MovieFetcher.php +++ b/api/Classes/MovieFetcher.php @@ -1,22 +1,24 @@ 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; + } } -} diff --git a/api/Classes/PageFetcher.php b/api/Classes/PageFetcher.php index de92144..aa6b1f3 100644 --- a/api/Classes/PageFetcher.php +++ b/api/Classes/PageFetcher.php @@ -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; + } } diff --git a/api/Classes/ShowFetcher.php b/api/Classes/ShowFetcher.php index 16743a7..4f5e3cb 100644 --- a/api/Classes/ShowFetcher.php +++ b/api/Classes/ShowFetcher.php @@ -1,22 +1,24 @@ 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; + } } -} diff --git a/api/Classes/TagFetcher.php b/api/Classes/TagFetcher.php index 6379564..73f90cb 100644 --- a/api/Classes/TagFetcher.php +++ b/api/Classes/TagFetcher.php @@ -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; diff --git a/composer.lock b/composer.lock index dbe2da7..cadb410 100644 --- a/composer.lock +++ b/composer.lock @@ -753,16 +753,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -775,7 +775,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -800,7 +800,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -816,7 +816,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "voku/html-min", diff --git a/config/filters/index.js b/config/filters/index.js index f74601f..974395a 100644 --- a/config/filters/index.js +++ b/config/filters/index.js @@ -1,11 +1,13 @@ import feeds from "./feeds.js" import general from "./general.js"; import media from "./media.js"; +import metadata from "./metadata.js"; import navigation from "./navigation.js"; export default { ...feeds, ...general, ...media, + ...metadata, ...navigation, }; diff --git a/config/filters/metadata.js b/config/filters/metadata.js new file mode 100644 index 0000000..1fbb395 --- /dev/null +++ b/config/filters/metadata.js @@ -0,0 +1,19 @@ +export default { + getMetadata: (data = {}, globals = {}, page = {}, title = "", description = "", schema = "") => { + const metadata = data?.metadata; + const baseUrl = globals.url || "" + const ogPath = "/og/w800"; + const image = metadata?.open_graph_image || globals.metadata?.open_graph_image || globals.avatar; + const rawTitle = title || page.title || metadata?.title || globals.site_name; + const resolvedTitle = `${rawTitle} • ${globals.site_name}`; + const resolvedDescription = description || metadata?.description || page.description || globals.site_description; + const url = metadata?.url || (page.url ? `${baseUrl}${page.url}` : "#"); + + return { + title: resolvedTitle, + description: resolvedDescription, + url, + open_graph_image: `${baseUrl}${ogPath}${image}` + }; + } +}; diff --git a/package-lock.json b/package-lock.json index bc60309..6d04c1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "coryd.dev", - "version": "7.0.8", + "version": "8.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "coryd.dev", - "version": "7.0.8", + "version": "8.0.0", "license": "MIT", "dependencies": { "minisearch": "^7.1.2", diff --git a/package.json b/package.json index 270c7d1..b30f408 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coryd.dev", - "version": "7.0.8", + "version": "8.0.0", "description": "The source for my personal site. Built using 11ty (and other tools).", "type": "module", "engines": { diff --git a/queries/views/content/posts.sql b/queries/views/content/posts.sql index 24d01e2..5c8e051 100644 --- a/queries/views/content/posts.sql +++ b/queries/views/content/posts.sql @@ -165,12 +165,26 @@ SELECT END ) AS feed, json_build_object( + 'title', p.title, + 'description', LEFT( + regexp_replace( + regexp_replace( + regexp_replace(p.description, E'[*_`~#>-]', '', 'g'), + E'\\[(.*?)\\]\\((.*?)\\)', E'\\1', 'g' + ), + E'!\\[(.*?)\\]\\((.*?)\\)', '', 'g' + ), + 250 + ), 'open_graph_image', CASE - WHEN df.filename_disk IS NOT NULL AND df.filename_disk != '' AND df.filename_disk != '/' THEN CONCAT('/', df.filename_disk) - ELSE NULL - END + WHEN df.filename_disk IS NOT NULL AND df.filename_disk != '' AND df.filename_disk != '/' THEN + CONCAT('/', df.filename_disk) + ELSE globals.metadata->>'open_graph_image' + END, + 'url', CONCAT(globals.url, p.slug), + 'type', 'article' ) AS metadata FROM posts p LEFT JOIN directus_files df ON p.image = df.id CROSS JOIN optimized_globals globals -GROUP BY p.id, df.filename_disk, globals.cdn_url; +GROUP BY p.id, df.filename_disk, globals.cdn_url, globals.site_name, globals.url, globals.metadata->>'open_graph_image'; diff --git a/queries/views/globals/pages.sql b/queries/views/globals/pages.sql index a36c41e..12b7e85 100644 --- a/queries/views/globals/pages.sql +++ b/queries/views/globals/pages.sql @@ -5,11 +5,24 @@ SELECT p.permalink, p.description, json_build_object( + 'title', CONCAT(p.title, ' • ', globals.site_name), + 'description', LEFT( + regexp_replace( + regexp_replace( + regexp_replace(p.description, E'[*_`~#>-]', '', 'g'), + E'\\[(.*?)\\]\\((.*?)\\)', '\\1', 'g' + ), + E'!\\[(.*?)\\]\\((.*?)\\)', '', 'g' + ), + 250 + ), 'open_graph_image', CASE WHEN df.filename_disk IS NOT NULL AND df.filename_disk != '' AND df.filename_disk != '/' THEN CONCAT('/', df.filename_disk) - ELSE NULL - END + ELSE globals.metadata->>'open_graph_image' + END, + 'url', CONCAT(globals.url, p.permalink), + 'type', 'page' ) AS metadata, p.updated, ( @@ -62,4 +75,7 @@ FROM GROUP BY p.id, df.filename_disk, - globals.cdn_url; + globals.cdn_url, + globals.site_name, + globals.url, + globals.metadata->>'open_graph_image'; diff --git a/queries/views/media/books.sql b/queries/views/media/books.sql index bd05c93..5818b51 100644 --- a/queries/views/media/books.sql +++ b/queries/views/media/books.sql @@ -173,12 +173,25 @@ SELECT END AS feed, (SELECT TO_CHAR(days_read, 'FM999G999G999') FROM reading_streak LIMIT 1) AS days_read, json_build_object( + 'title', CONCAT('Book • ', b.title, ' by ', b.author, ' • ', globals.site_name), + 'description', LEFT( + regexp_replace( + regexp_replace( + regexp_replace( b.description, E'[*_`~#>-]', '', 'g'), + E'\\[(.*?)\\]\\((.*?)\\)', E'\\1', 'g' + ), + E'!\\[(.*?)\\]\\((.*?)\\)', '', 'g' + ), + 250 + ), 'open_graph_image', CASE WHEN df.filename_disk IS NOT NULL AND df.filename_disk != '' AND df.filename_disk != '/' THEN CONCAT('/', df.filename_disk) ELSE NULL - END + END, + 'url', CONCAT(globals.url, b.slug), + 'type', 'book' ) AS metadata FROM books b LEFT JOIN directus_files df ON b.art = df.id CROSS JOIN optimized_globals globals -GROUP BY b.id, df.filename_disk, globals.cdn_url; +GROUP BY b.id, df.filename_disk, globals.cdn_url, globals.site_name, globals.url; diff --git a/queries/views/media/movies.sql b/queries/views/media/movies.sql index b47c794..4abda69 100644 --- a/queries/views/media/movies.sql +++ b/queries/views/media/movies.sql @@ -179,15 +179,28 @@ SELECT ELSE NULL END AS feed, json_build_object( + 'title', CONCAT('Movie • ', m.title, ' • ', globals.site_name), + 'description', LEFT( + regexp_replace( + regexp_replace( + regexp_replace(m.description, E'[*_`~#>-]', '', 'g'), + E'\\[(.*?)\\]\\((.*?)\\)', E'\\1', 'g' + ), + E'!\\[(.*?)\\]\\((.*?)\\)', '', 'g' + ), + 250 + ), 'open_graph_image', CASE WHEN df2.filename_disk IS NOT NULL AND df2.filename_disk != '' AND df2.filename_disk != '/' THEN CONCAT('/', df2.filename_disk) ELSE NULL - END + END, + 'url', CONCAT(globals.url, m.slug), + 'type', 'movie' ) AS metadata FROM movies m LEFT JOIN directus_files df ON m.art = df.id LEFT JOIN directus_files df2 ON m.backdrop = df2.id CROSS JOIN optimized_globals globals -GROUP BY m.id, df.filename_disk, df2.filename_disk, globals.cdn_url +GROUP BY m.id, df.filename_disk, df2.filename_disk, globals.cdn_url, globals.site_name, globals.url ORDER BY m.last_watched DESC; diff --git a/queries/views/media/music/artists.sql b/queries/views/media/music/artists.sql index 1d7ee60..818711e 100644 --- a/queries/views/media/music/artists.sql +++ b/queries/views/media/music/artists.sql @@ -186,13 +186,26 @@ SELECT WHERE ra.artists_id = ar.id ) AS related_artists, json_build_object( + 'title', CONCAT('Artist • ', ar.name_string, ' • ', globals.site_name), + 'description', LEFT( + regexp_replace( + regexp_replace( + regexp_replace(ar.description, E'[*_`~#>-]', '', 'g'), + E'\\[(.*?)\\]\\((.*?)\\)', E'\\1', 'g' + ), + E'!\\[(.*?)\\]\\((.*?)\\)', '', 'g' + ), + 250 + ), 'open_graph_image', CASE WHEN df.filename_disk IS NOT NULL AND df.filename_disk != '' AND df.filename_disk != '/' THEN CONCAT('/', df.filename_disk) ELSE NULL - END + END, + 'url', CONCAT(globals.url, ar.slug), + 'type', 'artist' ) AS metadata FROM artists ar LEFT JOIN directus_files df ON ar.art = df.id LEFT JOIN genres g ON ar.genres = g.id CROSS JOIN optimized_globals globals -GROUP BY ar.id, df.filename_disk, g.name, g.slug, g.emoji, globals.cdn_url; +GROUP BY ar.id, df.filename_disk, g.name, g.slug, g.emoji, globals.cdn_url, globals.site_name, globals.url; diff --git a/queries/views/media/music/genres.sql b/queries/views/media/music/genres.sql index 1ee9f90..d8f1073 100644 --- a/queries/views/media/music/genres.sql +++ b/queries/views/media/music/genres.sql @@ -106,6 +106,20 @@ SELECT WHERE pg.genres_id = g.id ) AS posts, json_build_object( + 'title', CONCAT('Genre • ', g.name, ' • ', globals.site_name), + 'description', LEFT( + regexp_replace( + regexp_replace( + regexp_replace( + g.description, + E'[*_`~#>-]', '', 'g' + ), + E'\\[(.*?)\\]\\((.*?)\\)', E'\\1', 'g' + ), + E'!\\[(.*?)\\]\\((.*?)\\)', '', 'g' + ), + 250 + ), 'open_graph_image', ( SELECT CONCAT('/', df_artist.filename_disk) FROM artists a @@ -115,7 +129,9 @@ SELECT AND df_artist.filename_disk != '' ORDER BY a.total_plays DESC LIMIT 1 - ) + ), + 'url', CONCAT(globals.url, g.slug), + 'type', 'genre' ) AS metadata FROM genres g CROSS JOIN optimized_globals globals diff --git a/queries/views/media/shows/shows.sql b/queries/views/media/shows/shows.sql index e950410..1a04389 100644 --- a/queries/views/media/shows/shows.sql +++ b/queries/views/media/shows/shows.sql @@ -179,15 +179,28 @@ SELECT ) AS artists, MAX(e.last_watched_at) AS last_watched_at, json_build_object( + 'title', CONCAT('Show • ', s.title, ' • ', globals.site_name), + 'description', LEFT( + regexp_replace( + regexp_replace( + regexp_replace(s.description, E'[*_`~#>-]', '', 'g'), + E'\\[(.*?)\\]\\((.*?)\\)', E'\\1', 'g' + ), + E'!\\[(.*?)\\]\\((.*?)\\)', '', 'g' + ), + 250 + ), 'open_graph_image', CASE WHEN df_backdrop.filename_disk IS NOT NULL AND df_backdrop.filename_disk != '' AND df_backdrop.filename_disk != '/' THEN CONCAT('/', df_backdrop.filename_disk) ELSE NULL - END + END, + 'url', CONCAT(globals.url, s.slug), + 'type', 'show' ) AS metadata FROM shows s LEFT JOIN episodes e ON s.id = e.show LEFT JOIN directus_files df_art ON s.art = df_art.id LEFT JOIN directus_files df_backdrop ON s.backdrop = df_backdrop.id CROSS JOIN optimized_globals globals -GROUP BY s.id, df_art.filename_disk, df_backdrop.filename_disk, globals.cdn_url +GROUP BY s.id, df_art.filename_disk, df_backdrop.filename_disk, globals.cdn_url, globals.site_name, globals.url ORDER BY MAX(e.last_watched_at) DESC; diff --git a/server/utils/init.php b/server/utils/init.php index b00f9c9..f447853 100644 --- a/server/utils/init.php +++ b/server/utils/init.php @@ -1,6 +1,7 @@ $title, + 'pageDescription' => $description, + 'ogImage' => $image, + 'fullUrl' => $fullUrl, + 'oembedUrl' => $oembedUrl, + 'globals' => $globals + ]; + } + + function cleanMeta($value) { + $value = trim($value ?? ''); + $value = str_replace(["\r", "\n"], ' ', $value); + $value = preg_replace('/\s+/', ' ', $value); + return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); + } + +?> diff --git a/server/utils/strings.php b/server/utils/strings.php index 7c71975..fc7f85c 100644 --- a/server/utils/strings.php +++ b/server/utils/strings.php @@ -68,11 +68,4 @@ return $string . 's' . ($trailing ? $trailing : ''); } - function cleanMeta($value) { - $value = trim($value ?? ''); - $value = str_replace(["\r", "\n"], ' ', $value); - $value = preg_replace('/\s+/', ' ', $value); - return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); - } - ?> diff --git a/src/includes/fetchers/artist.php.liquid b/src/includes/fetchers/artist.php.liquid index 9152f50..d67020b 100644 --- a/src/includes/fetchers/artist.php.liquid +++ b/src/includes/fetchers/artist.php.liquid @@ -11,18 +11,18 @@ if (strpos($url, "music/artists/") !== 0) redirectTo404(); - $artist = (new ArtistFetcher())->fetch($url); + $fetcher = new ArtistFetcher(); + $artist = $fetcher->fetch($url); if (!$artist) redirectTo404(); - $artist["description"] = parseMarkdown($artist["description"]); - $pageTitle = htmlspecialchars("Artists • " . $artist["name"], ENT_QUOTES, "UTF-8"); - $pageDescription = truncateText(htmlspecialchars(strip_tags($artist["description"]), ENT_QUOTES, "UTF-8"), 250); - $ogImage = htmlspecialchars($artist["metadata"]["open_graph_image"], ENT_QUOTES, "UTF-8"); - $fullUrl = "https://www.coryd.dev" . $requestUri; - $oembedUrl = "https://www.coryd.dev/oembed" . $requestUri; + $artist["description_html"] = parseMarkdown($artist["description"]); + $artist["globals"] = $fetcher->getGlobals(); + $page = $artist; + extract(setupPageMetadata($page, $requestUri)); ob_start(); header("Cache-Control: public, max-age=3600"); header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT"); + ?> diff --git a/src/includes/fetchers/book.php.liquid b/src/includes/fetchers/book.php.liquid index bec9af7..ca034c2 100644 --- a/src/includes/fetchers/book.php.liquid +++ b/src/includes/fetchers/book.php.liquid @@ -11,18 +11,19 @@ if (!preg_match('/^reading\/books\/([\dXx-]+)$/', $url)) redirectTo404(); - $book = (new BookFetcher())->fetch($url); + $fetcher = new BookFetcher(); + $book = $fetcher->fetch($url); if (!$book) redirectTo404(); - $book["description"] = parseMarkdown($book["description"]); - $pageTitle = htmlspecialchars("Books • {$book["title"]} by {$book["author"]}", ENT_QUOTES, "UTF-8"); - $pageDescription = truncateText(htmlspecialchars(strip_tags($book["description"]), ENT_QUOTES, "UTF-8"), 250); - $ogImage = htmlspecialchars($book["metadata"]["open_graph_image"], ENT_QUOTES, "UTF-8"); - $fullUrl = "https://www.coryd.dev" . $requestUri; - $oembedUrl = "https://www.coryd.dev/oembed" . $requestUri; + $book["description_html"] = parseMarkdown($book["description"]); + $book["globals"] = $fetcher->getGlobals(); + $page = $book; + $globals = $page["globals"]; + extract(setupPageMetadata($page, $requestUri)); ob_start(); header("Cache-Control: public, max-age=3600"); header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT"); + ?> diff --git a/src/includes/fetchers/genre.php.liquid b/src/includes/fetchers/genre.php.liquid index 239e33c..3d0d003 100644 --- a/src/includes/fetchers/genre.php.liquid +++ b/src/includes/fetchers/genre.php.liquid @@ -11,20 +11,19 @@ if (!preg_match('/^music\/genres\/[\w-]+$/', $url)) redirectTo404(); - $genre = (new GenreFetcher())->fetch($url); + $fetcher = new GenreFetcher(); + $genre = $fetcher->fetch($url); if (!$genre) redirectTo404(); - $pageTitle = htmlspecialchars("Genres • " . $genre["name"], ENT_QUOTES, "UTF-8"); - $pageDescription = truncateText( - htmlspecialchars(strip_tags($genre["description"]), ENT_QUOTES, "UTF-8"), - 250 - ); - $ogImage = htmlspecialchars($genre["metadata"]["open_graph_image"] ?? "", ENT_QUOTES, "UTF-8"); - $fullUrl = "https://www.coryd.dev" . $requestUri; - $oembedUrl = "https://www.coryd.dev/oembed" . $requestUri; + $genre["globals"] = $fetcher->getGlobals(); + $page = $genre; + $globals = $page["globals"]; + + extract(setupPageMetadata($page, $requestUri)); ob_start(); header("Cache-Control: public, max-age=3600"); header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT"); + ?> diff --git a/src/includes/fetchers/movie.php.liquid b/src/includes/fetchers/movie.php.liquid index a633377..ef54706 100644 --- a/src/includes/fetchers/movie.php.liquid +++ b/src/includes/fetchers/movie.php.liquid @@ -11,18 +11,19 @@ if (!preg_match('/^watching\/movies\/[\w-]+$/', $url)) redirectTo404(); - $movie = (new MovieFetcher())->fetch($url); + $fetcher = new MovieFetcher(); + $movie = $fetcher->fetch($url); if (!$movie) redirectTo404(); - $movie["description"] = parseMarkdown($movie["description"]); - $pageTitle = htmlspecialchars("Movies • " . $movie["title"], ENT_QUOTES, "UTF-8"); - $pageDescription = truncateText(htmlspecialchars(strip_tags($movie["description"]), ENT_QUOTES, "UTF-8"), 250); - $ogImage = htmlspecialchars($movie["metadata"]["open_graph_image"], ENT_QUOTES, "UTF-8"); - $fullUrl = "https://www.coryd.dev" . $requestUri; - $oembedUrl = "https://www.coryd.dev/oembed" . $requestUri; + $movie["description_html"] = parseMarkdown($movie["description"]); + $movie["globals"] = $fetcher->getGlobals(); + $page = $movie; + $globals = $page["globals"]; + extract(setupPageMetadata($page, $requestUri)); ob_start(); header("Cache-Control: public, max-age=3600"); header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT"); + ?> diff --git a/src/includes/fetchers/show.php.liquid b/src/includes/fetchers/show.php.liquid index 558ac3a..865123f 100644 --- a/src/includes/fetchers/show.php.liquid +++ b/src/includes/fetchers/show.php.liquid @@ -11,20 +11,19 @@ if (!preg_match('/^watching\/shows\/[\w-]+$/', $url)) redirectTo404(); - $show = (new ShowFetcher())->fetch($url); + $fetcher = new ShowFetcher(); + $show = $fetcher->fetch($url); if (!$show) redirectTo404(); - $pageTitle = htmlspecialchars("Show • " . $show["title"], ENT_QUOTES, "UTF-8"); - $pageDescription = truncateText( - htmlspecialchars(strip_tags($show["description"]), ENT_QUOTES, "UTF-8"), - 250 - ); - $ogImage = htmlspecialchars($show["metadata"]["open_graph_image"], ENT_QUOTES, "UTF-8"); - $fullUrl = "https://www.coryd.dev" . $requestUri; - $oembedUrl = "https://www.coryd.dev/oembed" . $requestUri; + $show["description_html"] = parseMarkdown($show["description"]); + $show["globals"] = $fetcher->getGlobals(); + $page = $show; + $globals = $page["globals"]; + extract(setupPageMetadata($page, $requestUri)); ob_start(); header("Cache-Control: public, max-age=3600"); header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT"); + ?> diff --git a/src/includes/fetchers/tags.php.liquid b/src/includes/fetchers/tags.php.liquid index 326916c..267e86a 100644 --- a/src/includes/fetchers/tags.php.liquid +++ b/src/includes/fetchers/tags.php.liquid @@ -4,6 +4,7 @@ require __DIR__ . "/../server/utils/init.php"; use App\Classes\TagFetcher; + use App\Classes\GlobalsFetcher; use voku\helper\HtmlMin; $requestUri = $_SERVER["REQUEST_URI"]; @@ -25,9 +26,10 @@ if (!preg_match('/^[\p{L}\p{N} _\.\-\&]+$/u', $tag)) redirectTo404(); - $page = isset($matches[2]) ? max(1, (int)$matches[2]) : 1; + $pageNum = isset($matches[2]) ? max(1, (int)$matches[2]) : 1; $pageSize = 20; - $tagged = (new TagFetcher())->fetch($tag, $page, $pageSize); + $fetcher = new TagFetcher(); + $tagged = $fetcher->fetch($tag, $pageNum, $pageSize); if (!$tagged || count($tagged) === 0) { header("Location: /404/", true, 302); @@ -37,21 +39,32 @@ $totalCount = $tagged[0]['total_count'] ?? 0; $totalPages = max(ceil($totalCount / $pageSize), 1); $pagination = [ - 'pageNumber' => $page, + 'pageNumber' => $pageNum, 'pages' => range(1, $totalPages), 'href' => [ - 'previous' => $page > 1 ? "/tags/{$tag}/" . ($page - 1) : null, - 'next' => $page < $totalPages ? "/tags/{$tag}/" . ($page + 1) : null + 'previous' => $pageNum > 1 ? "/tags/{$tag}/" . ($pageNum - 1) : null, + 'next' => $pageNum < $totalPages ? "/tags/{$tag}/" . ($pageNum + 1) : null ], 'links' => range(1, $totalPages) ]; + $globals = (new GlobalsFetcher())->fetch(); + $page = [ + 'tag' => $tag, + 'items' => $tagged, + 'pagination' => $pagination, + 'metadata' => [ + 'title' => '#' . ucfirst($tag) . ' • ' . $globals['site_name'], + 'description' => 'All content tagged with #' . ucfirst($tag) . '.', + 'open_graph_image' => $globals['metadata']['open_graph_image'], + 'url' => $globals['url'] . $requestUri, + 'type' => 'tag' + ], + 'globals' => $globals + ]; - $pageTitle = "#" . strtolower(ucfirst($tag)); - $pageDescription = "All content tagged with #" . strtolower(ucfirst($tag)) . "."; - $fullUrl = "https://www.coryd.dev" . $requestUri; - $oembedUrl = "https://www.coryd.dev/oembed" . $requestUri; - + extract(setupPageMetadata($page, $requestUri)); ob_start(); header("Cache-Control: public, max-age=3600"); header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT"); + ?> diff --git a/src/includes/metadata/dynamic.php.liquid b/src/includes/metadata/dynamic.php.liquid index 9c6cd7f..5081be3 100644 --- a/src/includes/metadata/dynamic.php.liquid +++ b/src/includes/metadata/dynamic.php.liquid @@ -1,11 +1,11 @@ -
You can find posts, links, artists, genres, movies, shows and books via the field below (though it only surfaces movies and shows I've watched and books I've written something about). You can also browse my tags list.
{% render "blocks/top-tags.liquid"