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 <?php
namespace App\Classes; namespace App\Classes;
require __DIR__ . "/BaseHandler.php"; require __DIR__ . "/BaseHandler.php";
abstract class ApiHandler extends BaseHandler abstract class ApiHandler extends BaseHandler
{ {
protected function ensureCliAccess(): void 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

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

View file

@ -1,14 +1,14 @@
<?php <?php
namespace App\Classes; namespace App\Classes;
require __DIR__ . "/../../vendor/autoload.php"; require __DIR__ . "/../../vendor/autoload.php";
use GuzzleHttp\Client; use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Exception\RequestException;
abstract class BaseHandler abstract class BaseHandler
{ {
protected string $postgrestUrl; protected string $postgrestUrl;
protected string $postgrestApiKey; protected string $postgrestApiKey;
protected ?\Redis $cache = null; protected ?\Redis $cache = null;
@ -99,4 +99,4 @@ abstract class BaseHandler
{ {
$this->sendResponse(["error" => $message], $statusCode); $this->sendResponse(["error" => $message], $statusCode);
} }
} }

View file

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

View file

@ -1,9 +1,9 @@
<?php <?php
namespace App\Classes; namespace App\Classes;
class GenreFetcher extends PageFetcher class GenreFetcher extends PageFetcher
{ {
public function fetch(string $url): ?array public function fetch(string $url): ?array
{ {
$cacheKey = "genre_" . md5($url); $cacheKey = "genre_" . md5($url);
@ -15,8 +15,10 @@ class GenreFetcher extends PageFetcher
if (!$genre) return null; if (!$genre) return null;
$genre['globals'] = $this->getGlobals();
$this->cacheSet($cacheKey, $genre); $this->cacheSet($cacheKey, $genre);
return $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,9 +1,9 @@
<?php <?php
namespace App\Classes; namespace App\Classes;
class MovieFetcher extends PageFetcher class MovieFetcher extends PageFetcher
{ {
public function fetch(string $url): ?array public function fetch(string $url): ?array
{ {
$cacheKey = "movie_" . md5($url); $cacheKey = "movie_" . md5($url);
@ -15,8 +15,10 @@ class MovieFetcher extends PageFetcher
if (!$movie) return null; if (!$movie) return null;
$movie['globals'] = $this->getGlobals();
$this->cacheSet($cacheKey, $movie); $this->cacheSet($cacheKey, $movie);
return $movie; return $movie;
} }
} }

View file

@ -3,23 +3,29 @@
namespace App\Classes; namespace App\Classes;
use App\Classes\BaseHandler; use App\Classes\BaseHandler;
use App\Classes\GlobalsFetcher;
abstract class PageFetcher extends BaseHandler abstract class PageFetcher extends BaseHandler
{ {
protected ?array $globals = null;
protected function cacheGet(string $key): mixed 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 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 protected function fetchSingleFromApi(string $endpoint, string $url): ?array
{ {
$data = $this->fetchFromApi($endpoint, "url=eq./{$url}"); $data = $this->fetchFromApi($endpoint, "url=eq./{$url}");
return $data[0] ?? null; return $data[0] ?? null;
} }
@ -27,4 +33,14 @@ abstract class PageFetcher extends BaseHandler
{ {
return $this->makeRequest("POST", $endpoint, ['json' => $body]); 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,9 +1,9 @@
<?php <?php
namespace App\Classes; namespace App\Classes;
class ShowFetcher extends PageFetcher class ShowFetcher extends PageFetcher
{ {
public function fetch(string $url): ?array public function fetch(string $url): ?array
{ {
$cacheKey = "show_" . md5($url); $cacheKey = "show_" . md5($url);
@ -15,8 +15,10 @@ class ShowFetcher extends PageFetcher
if (!$show) return null; if (!$show) return null;
$show['globals'] = $this->getGlobals();
$this->cacheSet($cacheKey, $show); $this->cacheSet($cacheKey, $show);
return $show; return $show;
} }
} }

View file

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

14
composer.lock generated
View file

@ -753,16 +753,16 @@
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
"version": "v3.5.1", "version": "v3.6.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git", "url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -775,7 +775,7 @@
"name": "symfony/contracts" "name": "symfony/contracts"
}, },
"branch-alias": { "branch-alias": {
"dev-main": "3.5-dev" "dev-main": "3.6-dev"
} }
}, },
"autoload": { "autoload": {
@ -800,7 +800,7 @@
"description": "A generic function and convention to trigger deprecation notices", "description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
}, },
"funding": [ "funding": [
{ {
@ -816,7 +816,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-09-25T14:20:29+00:00" "time": "2024-09-25T14:21:43+00:00"
}, },
{ {
"name": "voku/html-min", "name": "voku/html-min",

View file

@ -1,11 +1,13 @@
import feeds from "./feeds.js" import feeds from "./feeds.js"
import general from "./general.js"; import general from "./general.js";
import media from "./media.js"; import media from "./media.js";
import metadata from "./metadata.js";
import navigation from "./navigation.js"; import navigation from "./navigation.js";
export default { export default {
...feeds, ...feeds,
...general, ...general,
...media, ...media,
...metadata,
...navigation, ...navigation,
}; };

View file

@ -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}`
};
}
};

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "coryd.dev", "name": "coryd.dev",
"version": "7.0.8", "version": "8.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "coryd.dev", "name": "coryd.dev",
"version": "7.0.8", "version": "8.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"minisearch": "^7.1.2", "minisearch": "^7.1.2",

View file

@ -1,6 +1,6 @@
{ {
"name": "coryd.dev", "name": "coryd.dev",
"version": "7.0.8", "version": "8.0.0",
"description": "The source for my personal site. Built using 11ty (and other tools).", "description": "The source for my personal site. Built using 11ty (and other tools).",
"type": "module", "type": "module",
"engines": { "engines": {

View file

@ -165,12 +165,26 @@ SELECT
END END
) AS feed, ) AS feed,
json_build_object( 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 'open_graph_image', CASE
WHEN df.filename_disk IS NOT NULL AND df.filename_disk != '' AND df.filename_disk != '/' THEN CONCAT('/', df.filename_disk) WHEN df.filename_disk IS NOT NULL AND df.filename_disk != '' AND df.filename_disk != '/' THEN
ELSE NULL CONCAT('/', df.filename_disk)
END ELSE globals.metadata->>'open_graph_image'
END,
'url', CONCAT(globals.url, p.slug),
'type', 'article'
) AS metadata ) AS metadata
FROM posts p FROM posts p
LEFT JOIN directus_files df ON p.image = df.id LEFT JOIN directus_files df ON p.image = df.id
CROSS JOIN optimized_globals globals 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';

View file

@ -5,11 +5,24 @@ SELECT
p.permalink, p.permalink,
p.description, p.description,
json_build_object( 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 'open_graph_image', CASE
WHEN df.filename_disk IS NOT NULL AND df.filename_disk != '' AND df.filename_disk != '/' THEN WHEN df.filename_disk IS NOT NULL AND df.filename_disk != '' AND df.filename_disk != '/' THEN
CONCAT('/', df.filename_disk) CONCAT('/', df.filename_disk)
ELSE NULL ELSE globals.metadata->>'open_graph_image'
END END,
'url', CONCAT(globals.url, p.permalink),
'type', 'page'
) AS metadata, ) AS metadata,
p.updated, p.updated,
( (
@ -62,4 +75,7 @@ FROM
GROUP BY GROUP BY
p.id, p.id,
df.filename_disk, df.filename_disk,
globals.cdn_url; globals.cdn_url,
globals.site_name,
globals.url,
globals.metadata->>'open_graph_image';

View file

@ -173,12 +173,25 @@ SELECT
END AS feed, END AS feed,
(SELECT TO_CHAR(days_read, 'FM999G999G999') FROM reading_streak LIMIT 1) AS days_read, (SELECT TO_CHAR(days_read, 'FM999G999G999') FROM reading_streak LIMIT 1) AS days_read,
json_build_object( 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 'open_graph_image', CASE
WHEN df.filename_disk IS NOT NULL AND df.filename_disk != '' AND df.filename_disk != '/' THEN CONCAT('/', df.filename_disk) WHEN df.filename_disk IS NOT NULL AND df.filename_disk != '' AND df.filename_disk != '/' THEN CONCAT('/', df.filename_disk)
ELSE NULL ELSE NULL
END END,
'url', CONCAT(globals.url, b.slug),
'type', 'book'
) AS metadata ) AS metadata
FROM books b FROM books b
LEFT JOIN directus_files df ON b.art = df.id LEFT JOIN directus_files df ON b.art = df.id
CROSS JOIN optimized_globals globals 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;

View file

@ -179,15 +179,28 @@ SELECT
ELSE NULL ELSE NULL
END AS feed, END AS feed,
json_build_object( 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 'open_graph_image', CASE
WHEN df2.filename_disk IS NOT NULL AND df2.filename_disk != '' AND df2.filename_disk != '/' THEN WHEN df2.filename_disk IS NOT NULL AND df2.filename_disk != '' AND df2.filename_disk != '/' THEN
CONCAT('/', df2.filename_disk) CONCAT('/', df2.filename_disk)
ELSE NULL ELSE NULL
END END,
'url', CONCAT(globals.url, m.slug),
'type', 'movie'
) AS metadata ) AS metadata
FROM movies m FROM movies m
LEFT JOIN directus_files df ON m.art = df.id LEFT JOIN directus_files df ON m.art = df.id
LEFT JOIN directus_files df2 ON m.backdrop = df2.id LEFT JOIN directus_files df2 ON m.backdrop = df2.id
CROSS JOIN optimized_globals globals 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; ORDER BY m.last_watched DESC;

View file

@ -186,13 +186,26 @@ SELECT
WHERE ra.artists_id = ar.id WHERE ra.artists_id = ar.id
) AS related_artists, ) AS related_artists,
json_build_object( 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 'open_graph_image', CASE
WHEN df.filename_disk IS NOT NULL AND df.filename_disk != '' AND df.filename_disk != '/' THEN CONCAT('/', df.filename_disk) WHEN df.filename_disk IS NOT NULL AND df.filename_disk != '' AND df.filename_disk != '/' THEN CONCAT('/', df.filename_disk)
ELSE NULL ELSE NULL
END END,
'url', CONCAT(globals.url, ar.slug),
'type', 'artist'
) AS metadata ) AS metadata
FROM artists ar FROM artists ar
LEFT JOIN directus_files df ON ar.art = df.id LEFT JOIN directus_files df ON ar.art = df.id
LEFT JOIN genres g ON ar.genres = g.id LEFT JOIN genres g ON ar.genres = g.id
CROSS JOIN optimized_globals globals 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;

View file

@ -106,6 +106,20 @@ SELECT
WHERE pg.genres_id = g.id WHERE pg.genres_id = g.id
) AS posts, ) AS posts,
json_build_object( 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', ( 'open_graph_image', (
SELECT CONCAT('/', df_artist.filename_disk) SELECT CONCAT('/', df_artist.filename_disk)
FROM artists a FROM artists a
@ -115,7 +129,9 @@ SELECT
AND df_artist.filename_disk != '' AND df_artist.filename_disk != ''
ORDER BY a.total_plays DESC ORDER BY a.total_plays DESC
LIMIT 1 LIMIT 1
) ),
'url', CONCAT(globals.url, g.slug),
'type', 'genre'
) AS metadata ) AS metadata
FROM genres g FROM genres g
CROSS JOIN optimized_globals globals CROSS JOIN optimized_globals globals

View file

@ -179,15 +179,28 @@ SELECT
) AS artists, ) AS artists,
MAX(e.last_watched_at) AS last_watched_at, MAX(e.last_watched_at) AS last_watched_at,
json_build_object( 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 '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) 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 ELSE NULL
END END,
'url', CONCAT(globals.url, s.slug),
'type', 'show'
) AS metadata ) AS metadata
FROM shows s FROM shows s
LEFT JOIN episodes e ON s.id = e.show 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_art ON s.art = df_art.id
LEFT JOIN directus_files df_backdrop ON s.backdrop = df_backdrop.id LEFT JOIN directus_files df_backdrop ON s.backdrop = df_backdrop.id
CROSS JOIN optimized_globals globals 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; ORDER BY MAX(e.last_watched_at) DESC;

View file

@ -1,6 +1,7 @@
<?php <?php
require_once "icons.php"; require_once "icons.php";
require_once "media.php"; require_once "media.php";
require_once "metadata.php";
require_once "paginator.php"; require_once "paginator.php";
require_once "routing.php"; require_once "routing.php";
require_once "strings.php"; require_once "strings.php";

33
server/utils/metadata.php Normal file
View file

@ -0,0 +1,33 @@
<?php
function setupPageMetadata(array $page, string $requestUri): array {
$globals = $page['globals'] ?? [];
$title = htmlspecialchars($page["metadata"]["title"] ?? "", ENT_QUOTES, "UTF-8");
$description = htmlspecialchars($page["metadata"]["description"] ?? "", ENT_QUOTES, "UTF-8");
$image = htmlspecialchars(
$page["metadata"]["open_graph_image"] ?? $globals["metadata"]["open_graph_image"] ?? "",
ENT_QUOTES,
"UTF-8"
);
$fullUrl = $globals["url"] . $requestUri;
$oembedUrl = $globals["url"] . "/oembed" . $requestUri;
return [
'pageTitle' => $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');
}
?>

View file

@ -68,11 +68,4 @@
return $string . 's' . ($trailing ? $trailing : ''); 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');
}
?> ?>

View file

@ -11,18 +11,18 @@
if (strpos($url, "music/artists/") !== 0) redirectTo404(); if (strpos($url, "music/artists/") !== 0) redirectTo404();
$artist = (new ArtistFetcher())->fetch($url); $fetcher = new ArtistFetcher();
$artist = $fetcher->fetch($url);
if (!$artist) redirectTo404(); if (!$artist) redirectTo404();
$artist["description"] = parseMarkdown($artist["description"]); $artist["description_html"] = parseMarkdown($artist["description"]);
$pageTitle = htmlspecialchars("Artists • " . $artist["name"], ENT_QUOTES, "UTF-8"); $artist["globals"] = $fetcher->getGlobals();
$pageDescription = truncateText(htmlspecialchars(strip_tags($artist["description"]), ENT_QUOTES, "UTF-8"), 250); $page = $artist;
$ogImage = htmlspecialchars($artist["metadata"]["open_graph_image"], ENT_QUOTES, "UTF-8");
$fullUrl = "https://www.coryd.dev" . $requestUri;
$oembedUrl = "https://www.coryd.dev/oembed" . $requestUri;
extract(setupPageMetadata($page, $requestUri));
ob_start(); ob_start();
header("Cache-Control: public, max-age=3600"); header("Cache-Control: public, max-age=3600");
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT"); header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT");
?> ?>

View file

@ -11,18 +11,19 @@
if (!preg_match('/^reading\/books\/([\dXx-]+)$/', $url)) redirectTo404(); if (!preg_match('/^reading\/books\/([\dXx-]+)$/', $url)) redirectTo404();
$book = (new BookFetcher())->fetch($url); $fetcher = new BookFetcher();
$book = $fetcher->fetch($url);
if (!$book) redirectTo404(); if (!$book) redirectTo404();
$book["description"] = parseMarkdown($book["description"]); $book["description_html"] = parseMarkdown($book["description"]);
$pageTitle = htmlspecialchars("Books • {$book["title"]} by {$book["author"]}", ENT_QUOTES, "UTF-8"); $book["globals"] = $fetcher->getGlobals();
$pageDescription = truncateText(htmlspecialchars(strip_tags($book["description"]), ENT_QUOTES, "UTF-8"), 250); $page = $book;
$ogImage = htmlspecialchars($book["metadata"]["open_graph_image"], ENT_QUOTES, "UTF-8"); $globals = $page["globals"];
$fullUrl = "https://www.coryd.dev" . $requestUri;
$oembedUrl = "https://www.coryd.dev/oembed" . $requestUri;
extract(setupPageMetadata($page, $requestUri));
ob_start(); ob_start();
header("Cache-Control: public, max-age=3600"); header("Cache-Control: public, max-age=3600");
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT"); header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT");
?> ?>

View file

@ -11,20 +11,19 @@
if (!preg_match('/^music\/genres\/[\w-]+$/', $url)) redirectTo404(); if (!preg_match('/^music\/genres\/[\w-]+$/', $url)) redirectTo404();
$genre = (new GenreFetcher())->fetch($url); $fetcher = new GenreFetcher();
$genre = $fetcher->fetch($url);
if (!$genre) redirectTo404(); if (!$genre) redirectTo404();
$pageTitle = htmlspecialchars("Genres • " . $genre["name"], ENT_QUOTES, "UTF-8"); $genre["globals"] = $fetcher->getGlobals();
$pageDescription = truncateText( $page = $genre;
htmlspecialchars(strip_tags($genre["description"]), ENT_QUOTES, "UTF-8"), $globals = $page["globals"];
250
); extract(setupPageMetadata($page, $requestUri));
$ogImage = htmlspecialchars($genre["metadata"]["open_graph_image"] ?? "", ENT_QUOTES, "UTF-8");
$fullUrl = "https://www.coryd.dev" . $requestUri;
$oembedUrl = "https://www.coryd.dev/oembed" . $requestUri;
ob_start(); ob_start();
header("Cache-Control: public, max-age=3600"); header("Cache-Control: public, max-age=3600");
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT"); header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT");
?> ?>

View file

@ -11,18 +11,19 @@
if (!preg_match('/^watching\/movies\/[\w-]+$/', $url)) redirectTo404(); if (!preg_match('/^watching\/movies\/[\w-]+$/', $url)) redirectTo404();
$movie = (new MovieFetcher())->fetch($url); $fetcher = new MovieFetcher();
$movie = $fetcher->fetch($url);
if (!$movie) redirectTo404(); if (!$movie) redirectTo404();
$movie["description"] = parseMarkdown($movie["description"]); $movie["description_html"] = parseMarkdown($movie["description"]);
$pageTitle = htmlspecialchars("Movies • " . $movie["title"], ENT_QUOTES, "UTF-8"); $movie["globals"] = $fetcher->getGlobals();
$pageDescription = truncateText(htmlspecialchars(strip_tags($movie["description"]), ENT_QUOTES, "UTF-8"), 250); $page = $movie;
$ogImage = htmlspecialchars($movie["metadata"]["open_graph_image"], ENT_QUOTES, "UTF-8"); $globals = $page["globals"];
$fullUrl = "https://www.coryd.dev" . $requestUri;
$oembedUrl = "https://www.coryd.dev/oembed" . $requestUri;
extract(setupPageMetadata($page, $requestUri));
ob_start(); ob_start();
header("Cache-Control: public, max-age=3600"); header("Cache-Control: public, max-age=3600");
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT"); header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT");
?> ?>

View file

@ -11,20 +11,19 @@
if (!preg_match('/^watching\/shows\/[\w-]+$/', $url)) redirectTo404(); if (!preg_match('/^watching\/shows\/[\w-]+$/', $url)) redirectTo404();
$show = (new ShowFetcher())->fetch($url); $fetcher = new ShowFetcher();
$show = $fetcher->fetch($url);
if (!$show) redirectTo404(); if (!$show) redirectTo404();
$pageTitle = htmlspecialchars("Show • " . $show["title"], ENT_QUOTES, "UTF-8"); $show["description_html"] = parseMarkdown($show["description"]);
$pageDescription = truncateText( $show["globals"] = $fetcher->getGlobals();
htmlspecialchars(strip_tags($show["description"]), ENT_QUOTES, "UTF-8"), $page = $show;
250 $globals = $page["globals"];
);
$ogImage = htmlspecialchars($show["metadata"]["open_graph_image"], ENT_QUOTES, "UTF-8");
$fullUrl = "https://www.coryd.dev" . $requestUri;
$oembedUrl = "https://www.coryd.dev/oembed" . $requestUri;
extract(setupPageMetadata($page, $requestUri));
ob_start(); ob_start();
header("Cache-Control: public, max-age=3600"); header("Cache-Control: public, max-age=3600");
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT"); header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT");
?> ?>

View file

@ -4,6 +4,7 @@
require __DIR__ . "/../server/utils/init.php"; require __DIR__ . "/../server/utils/init.php";
use App\Classes\TagFetcher; use App\Classes\TagFetcher;
use App\Classes\GlobalsFetcher;
use voku\helper\HtmlMin; use voku\helper\HtmlMin;
$requestUri = $_SERVER["REQUEST_URI"]; $requestUri = $_SERVER["REQUEST_URI"];
@ -25,9 +26,10 @@
if (!preg_match('/^[\p{L}\p{N} _\.\-\&]+$/u', $tag)) redirectTo404(); 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; $pageSize = 20;
$tagged = (new TagFetcher())->fetch($tag, $page, $pageSize); $fetcher = new TagFetcher();
$tagged = $fetcher->fetch($tag, $pageNum, $pageSize);
if (!$tagged || count($tagged) === 0) { if (!$tagged || count($tagged) === 0) {
header("Location: /404/", true, 302); header("Location: /404/", true, 302);
@ -37,21 +39,32 @@
$totalCount = $tagged[0]['total_count'] ?? 0; $totalCount = $tagged[0]['total_count'] ?? 0;
$totalPages = max(ceil($totalCount / $pageSize), 1); $totalPages = max(ceil($totalCount / $pageSize), 1);
$pagination = [ $pagination = [
'pageNumber' => $page, 'pageNumber' => $pageNum,
'pages' => range(1, $totalPages), 'pages' => range(1, $totalPages),
'href' => [ 'href' => [
'previous' => $page > 1 ? "/tags/{$tag}/" . ($page - 1) : null, 'previous' => $pageNum > 1 ? "/tags/{$tag}/" . ($pageNum - 1) : null,
'next' => $page < $totalPages ? "/tags/{$tag}/" . ($page + 1) : null 'next' => $pageNum < $totalPages ? "/tags/{$tag}/" . ($pageNum + 1) : null
], ],
'links' => range(1, $totalPages) '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)); extract(setupPageMetadata($page, $requestUri));
$pageDescription = "All content tagged with #" . strtolower(ucfirst($tag)) . ".";
$fullUrl = "https://www.coryd.dev" . $requestUri;
$oembedUrl = "https://www.coryd.dev/oembed" . $requestUri;
ob_start(); ob_start();
header("Cache-Control: public, max-age=3600"); header("Cache-Control: public, max-age=3600");
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT"); header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT");
?> ?>

View file

@ -1,11 +1,11 @@
<title><?= cleanMeta($pageTitle ?? "{{ pageTitle }}") ?> • {{ globals.site_name }}</title> <title><?= cleanMeta($page['metadata']['title']) ?></title>
<meta name="description" content="<?= cleanMeta($pageDescription ?? '{{ pageDescription | escape }}') ?>" /> <meta name="description" content="<?= cleanMeta($page['metadata']['description']) ?>" />
<meta property="og:title" content="<?= cleanMeta($pageTitle ?? '{{ pageTitle }}') ?> • {{ globals.site_name }}" /> <meta property="og:title" content="<?= cleanMeta($page['metadata']['title']) ?>" />
<meta property="og:description" content="<?= cleanMeta($pageDescription ?? '{{ pageDescription | escape }}') ?>" /> <meta property="og:description" content="<?= cleanMeta($page['metadata']['description']) ?>" />
<meta property="og:image" content="{{ globals.url }}<?= cleanMeta("/og/w800/{% appVersion %}" . ($ogImage ?? '{{ ogImage }}')) ?>" /> <meta property="og:image" content="<?= cleanMeta($globals['url'] . '/og/w800' . $page['metadata']['open_graph_image']) ?>" />
<meta property="og:url" content="<?= cleanMeta($fullUrl ?? '{{ fullUrl }}') ?>" /> <meta property="og:url" content="<?= cleanMeta($page['metadata']['url'] ?? $fullUrl) ?>" />
<link rel="alternate" type="application/json+oembed" href="<?= cleanMeta($oembedUrl ?? '{{ oembedUrl }}') ?>" title="<?= cleanMeta($pageTitle ?? '{{ pageTitle }}') ?> • {{ globals.site_name }}"> <link rel="alternate" type="application/json+oembed" href="<?= cleanMeta($oembedUrl) ?>" title="<?= cleanMeta($page['metadata']['title']) ?>">
<link rel="canonical" href="<?= cleanMeta($fullUrl ?? '{{ fullUrl }}') ?>" /> <link rel="canonical" href="<?= cleanMeta($page['metadata']['url'] ?? $fullUrl) ?>" />
<?php if (!empty($pagination)): ?> <?php if (!empty($pagination)): ?>
<?php if (!empty($pagination['href']['next'])): ?> <?php if (!empty($pagination['href']['next'])): ?>
<link rel="next" href="<?= cleanMeta($pagination['href']['next']) ?>"> <link rel="next" href="<?= cleanMeta($pagination['href']['next']) ?>">

View file

@ -1,103 +1,63 @@
{%- assign fullUrl = globals.url | append: page.url -%}
{%- assign oembedUrl = globals.url | append: "/oembed" | append: page.url -%}
{%- capture appVersionString -%}{% appVersion %}{%- endcapture -%} {%- capture appVersionString -%}{% appVersion %}{%- endcapture -%}
{%- assign ogImageBaseUrl = globals.url | append: "/og/w800/" | append: appVersionString -%} {%- assign source = page -%}
{%- capture pageTitle -%}
{%- if page.title -%}
{{ page.title | append: ' • ' | append: globals.site_name }}
{%- elsif title -%}
{{ title | append: ' • ' | append: globals.site_name }}
{%- else -%}
{{ globals.site_name }}
{%- endif -%}
{%- endcapture -%}
{%- capture pageDescription -%}
{%- if page.description -%}
{{ page.description }}
{%- elsif description -%}
{{ description }}
{%- else -%}
{{ globals.site_description }}
{%- endif -%}
{%- endcapture -%}
{%- assign ogImage = ogImageBaseUrl | append: globals.metadata.open_graph_image -%}
{%- case schema -%} {%- case schema -%}
{%- when 'artist' -%} {%- when 'artist', 'genre', 'book', 'movie', 'show', 'tags' -%}
{% render "fetchers/artist.php.liquid" %} {% render "fetchers/{{ schema }}.php.liquid" %}
{%- when 'genre' -%}
{% render "fetchers/genre.php.liquid" %}
{%- when 'book' -%}
{% render "fetchers/book.php.liquid" %}
{%- when 'movie' -%}
{% render "fetchers/movie.php.liquid" %}
{%- when 'show' -%}
{% render "fetchers/show.php.liquid" %}
{%- when 'tags' -%}
{% render "fetchers/tags.php.liquid" %}
{%- when 'blog' -%} {%- when 'blog' -%}
{%- assign pageTitle = post.title -%} {%- assign source = post -%}
{%- assign pageDescription = post.description -%}
{%- assign ogImage = ogImageBaseUrl | append: post.metadata.open_graph_image -%}
{%- when 'music-index', 'music-week-artists' -%} {%- when 'music-index', 'music-week-artists' -%}
{%- assign ogImage = ogImageBaseUrl | append: music.week.artists[0].metadata.open_graph_image -%} {%- assign source = music.week.artists[0] -%}
{%- when 'music-week-albums', 'music-week-tracks' -%} {%- when 'music-week-albums', 'music-week-tracks' -%}
{%- assign ogImage = ogImageBaseUrl | append: music.week.albums[0].metadata.open_graph_image -%} {%- assign source = music.week.albums[0] -%}
{%- when 'music-month-artists' -%} {%- when 'music-month-artists' -%}
{%- assign ogImage = ogImageBaseUrl | append: music.month.artists[0].metadata.open_graph_image -%} {%- assign source = music.month.artists[0] -%}
{%- when 'music-month-albums' -%} {%- when 'music-month-albums' -%}
{%- assign ogImage = ogImageBaseUrl | append: music.month.albums[0].metadata.open_graph_image -%} {%- assign source = music.month.albums[0] -%}
{%- when 'music-releases' -%} {%- when 'music-releases' -%}
{%- assign ogImage = ogImageBaseUrl | append: albumReleases.upcoming[0].metadata.open_graph_image -%} {%- assign source = albumReleases.upcoming[0] -%}
{%- when 'books' -%} {%- when 'books' -%}
{%- assign overviewBook = books.all | filterBooksByStatus: 'started' | reverse | first %} {%- assign source = books.all | filterBooksByStatus: 'started' | reverse | first -%}
{%- assign ogImage = ogImageBaseUrl | append: overviewBook.metadata.open_graph_image -%}
{%- when 'reading-year' -%} {%- when 'reading-year' -%}
{%- assign pageTitle = 'Books' | append: ' • ' | append: year.value | append: ' • ' | append: globals.site_name -%} {%- assign title = 'Books • ' | append: year.value | append: ' • ' | append: globals.site_name -%}
{%- capture pageDescription -%} {%- assign description = "Here's what I read in " | append: year.value | append: '.' -%}
Here's what I read in {{ year.value }}. {%- assign bookYear = year.data | filterBooksByStatus: 'finished' | shuffleArray | first -%}
{%- endcapture -%} {%- assign source = bookYear -%}
{%- assign bookData = year.data | filterBooksByStatus: 'finished' -%}
{%- assign bookYear = bookData | shuffleArray | first -%}
{%- assign ogImage = ogImageBaseUrl | append: bookYear.metadata.open_graph_image -%}
{%- when 'favorite-movies' -%} {%- when 'favorite-movies' -%}
{%- assign favoriteMovie = movies.favorites | shuffleArray | first %} {%- assign source = movies.favorites | shuffleArray | first -%}
{%- assign ogImage = ogImageBaseUrl | append: favoriteMovie.metadata.open_graph_image -%}
{%- when 'favorite-shows' -%} {%- when 'favorite-shows' -%}
{%- assign favoriteShow = tv.favorites | shuffleArray | first %} {%- assign source = tv.favorites | shuffleArray | first -%}
{%- assign ogImage = ogImageBaseUrl | append: favoriteShow.metadata.open_graph_image -%}
{%- when 'watching' -%} {%- when 'watching' -%}
{%- assign mergedMovies = movies.recentlyWatched | mergeArray: movies.favorites %} {%- assign mergedMovies = movies.recentlyWatched | mergeArray: movies.favorites -%}
{%- assign mergedShows = tv.recentlyWatched | mergeArray: tv.favorites %} {%- assign mergedShows = tv.recentlyWatched | mergeArray: tv.favorites -%}
{%- assign overviewWatched = mergedMovies | mergeArray: mergedShows | shuffleArray | first -%} {%- assign source = mergedMovies | mergeArray: mergedShows | shuffleArray | first -%}
{%- assign ogImage = ogImageBaseUrl | append: overviewWatched.metadata.open_graph_image -%}
{%- when 'upcoming-shows' -%} {%- when 'upcoming-shows' -%}
{%- assign upcomingShow = upcomingShows.watching | shuffleArray | first %} {%- assign source = upcomingShows.watching | shuffleArray | first -%}
{%- assign ogImage = ogImageBaseUrl | append: upcomingShow.metadata.open_graph_image -%} {%- endcase %}
{%- when 'page' -%} {%- assign meta = source | getMetadata: globals, page, title, description, schema -%}
{%- assign pageDescription = page.description -%} {%- assign fullUrl = meta.url -%}
{% endcase %} {%- assign oembedUrl = globals.url | append: "/oembed" | append: page.url -%}
{%- if type == 'dynamic' -%} {%- if type == 'dynamic' -%}
{% render "metadata/dynamic.php.liquid" {% render "metadata/dynamic.php.liquid"
fullUrl:fullUrl, fullUrl: fullUrl,
oembedUrl:oembedUrl, oembedUrl: oembedUrl,
pageTitle:pageTitle, pageTitle: meta.title,
pageDescription:pageDescription, pageDescription: meta.description,
ogImage:globals.metadata.open_graph_image, ogImage: meta.open_graph_image,
globals:globals, globals: globals,
%} %}
{%- else -%} {%- else -%}
{% render "metadata/static.liquid" {% render "metadata/static.liquid"
fullUrl:fullUrl, fullUrl: fullUrl,
oembedUrl:oembedUrl, oembedUrl: oembedUrl,
pageTitle:pageTitle, pageTitle: meta.title,
pageDescription:pageDescription, pageDescription: meta.description,
ogImage:ogImage, ogImage: meta.open_graph_image,
globals:globals, globals: globals,
%} %}
{%- endif -%} {%- endif %}
{% render "metadata/base.liquid" {% render "metadata/base.liquid"
pageTitle:pageTitle, pageTitle: meta.title,
globals:globals, globals: globals,
eleventy:eleventy, eleventy: eleventy,
appVersion:appVersion, appVersion: appVersion,
%} %}

View file

@ -1,10 +1,9 @@
{%- assign description = pageDescription | markdown | strip_html | htmlTruncate | escape -%}
<title>{{ pageTitle }}</title> <title>{{ pageTitle }}</title>
<meta name="description" content="{{ pageDescription | markdown | strip_html | htmlTruncate | escape }}" />
<meta property="og:title" content="{{ pageTitle }}" /> <meta property="og:title" content="{{ pageTitle }}" />
<meta name="description" content="{{ description }}" /> <meta property="og:description" content="{{ pageDescription | markdown | strip_html | htmlTruncate | escape }}" />
<meta property="og:description" content="{{ description }}" /> <meta property="og:type" content="{{ page.metadata.type | default: 'article' }}" />
<meta property="og:type" content="article" />
<meta property="og:url" content="{{ fullUrl }}" /> <meta property="og:url" content="{{ fullUrl }}" />
<link rel="alternate" type="application/json+oembed" href="{{ oembedUrl }}" title="{{ pageTitle }}">
<link rel="canonical" href="{{ fullUrl }}" />
<meta property="og:image" content="{{ ogImage }}" /> <meta property="og:image" content="{{ ogImage }}" />
<link rel="alternate" type="application/json+oembed" href="{{ oembedUrl }}" title="{{ pageTitle }}" />
<link rel="canonical" href="{{ fullUrl }}" />

View file

@ -3,7 +3,6 @@ title: Search
permalink: /search/index.html permalink: /search/index.html
description: Search through posts and other content on my site. description: Search through posts and other content on my site.
--- ---
<h2 class="page-title">Search</h2> <h2 class="page-title">Search</h2>
<p>You can find <a href="/posts">posts</a>, <a href="/links">links</a>, <a href="/music/#artists">artists</a>, genres, <a href="/watching#movies">movies</a>, <a href="/watching#tv">shows</a> and <a href="/reading">books</a> via the field below (though it only surfaces movies and shows I've watched and books I've written something about). <a href="/tags">You can also browse my tags list</a>.</p> <p>You can find <a href="/posts">posts</a>, <a href="/links">links</a>, <a href="/music/#artists">artists</a>, genres, <a href="/watching#movies">movies</a>, <a href="/watching#tv">shows</a> and <a href="/reading">books</a> via the field below (though it only surfaces movies and shows I've watched and books I've written something about). <a href="/tags">You can also browse my tags list</a>.</p>
{% render "blocks/top-tags.liquid" {% render "blocks/top-tags.liquid"