From 596cc0e1a3ca31cb15be46767358ed3b4f58a5f3 Mon Sep 17 00:00:00 2001 From: David Bailey Date: Mon, 25 Dec 2023 20:13:00 +0100 Subject: [PATCH] feat: :sparkles: add RSS and Atom feeds with caching and nice links --- docker_dev/mysql_schema.sql | 11 +++ www/mysql_adapter.php | 2 +- www/post_adapter.php | 85 +++++++++++++++++++++++ www/router.php | 8 +++ www/templates/fragments/filepath_bar.html | 6 ++ www/templates/pathed_content.html | 5 ++ www/templates/root.html | 4 ++ 7 files changed, 120 insertions(+), 1 deletion(-) diff --git a/docker_dev/mysql_schema.sql b/docker_dev/mysql_schema.sql index 255920f..869935a 100644 --- a/docker_dev/mysql_schema.sql +++ b/docker_dev/mysql_schema.sql @@ -40,6 +40,17 @@ CREATE TABLE path_access_counts ( PRIMARY KEY(access_time, post_path, agent, referrer) ); +CREATE TABLE feed_cache ( + search_path VARCHAR(255), + export_type VARCHAR(255), + + feed_created_on DATETIME DEFAULT CURRENT_TIMESTAMP, + + feed_content MEDIUMTEXT, + + PRIMARY KEY(search_path, export_type) +); + INSERT INTO posts (post_path, post_path_depth, post_metadata, post_content) VALUES ( '/about', diff --git a/www/mysql_adapter.php b/www/mysql_adapter.php index e0f6474..e9bdb5d 100644 --- a/www/mysql_adapter.php +++ b/www/mysql_adapter.php @@ -44,7 +44,7 @@ class MySQLAdapter { return $post_path; } - function _exec($qery, $argtypes, ...$args) { + function _exec($qery, $argtypes = '', ...$args) { $stmt = $this->raw->prepare($qery); if($argtypes != ""){ $stmt->bind_param($argtypes, ...$args); diff --git a/www/post_adapter.php b/www/post_adapter.php index 542b21c..a7e3561 100644 --- a/www/post_adapter.php +++ b/www/post_adapter.php @@ -57,6 +57,11 @@ class PostHandler extends MySQLAdapter { parent::make_post_directory($directory); } + function update_or_create_post(...$args) { + $this->_exec("TRUNCATE feed_cache"); + parent::update_or_create_post(...$args); + } + function save_file($post_path, $file_path) { $this->bump_post($post_path); move_uploaded_file($file_path, $this->data_directory . $post_path); @@ -101,6 +106,86 @@ class PostHandler extends MySQLAdapter { $this->save_file($post_path, $file_path); } } + + function try_get_cached_feed($path, $export_opt) { + $post_cache = $this->_exec("SELECT feed_content, feed_created_on + FROM feed_cache + WHERE search_path=? AND export_type=?", "ss", $path, $export_opt)->fetch_assoc(); + + if(!isset($post_cache)) { + return null; + } + + return ['feed' => $post_cache['feed_content'], 'feed_ts' => $post_cache['feed_created_on']]; + } + + function construct_feed($path) { + $path = $this->_sanitize_path($path); + + $feed = @new Feed; + + $feed->setTitle("DergFeed"); + $feed->setLink("https://lucidragons.de" . $path); + $feed->setFeedLink("https://lucidragons.de/feeds/atom" . $path, "atom"); + + $feed->setDateModified(time()); + + $feed->setDescription("DergenFeed for all your " . $path . " needs <3"); + + $feed_posts = $this->_exec("SELECT + post_path, + post_create_time, post_update_time, + post_content, + post_metadata + FROM posts + WHERE (post_path = ?) OR (post_path LIKE ?) + ORDER BY post_create_time DESC LIMIT 200", + "ss", $path, $path . '/%'); + + while($row = $feed_posts->fetch_array(MYSQLI_ASSOC)) { + $row = $this->_normalize_post_data($row); + $pmeta = $row['post_metadata']; + + if($pmeta['type'] == 'directory') { + continue; + } + + $entry = $feed->createEntry(); + + $entry->setTitle($row['post_path'] . '> ' . $pmeta['title']); + $entry->setLink('https://lucidragons.de' . $row['post_path']); + $entry->setDateModified(strtotime($row['post_update_time'])); + $entry->setDateCreated(strtotime($row['post_create_time'])); + + $entry->setDescription($pmeta['brief'] ?? $pmeta['title']); + + $feed->addEntry($entry); + } + + return $feed; + } + + function get_laminas_feed($path, $export_opt) { + $path = $this->_sanitize_path($path); + + $feed_cache = $this->try_get_cached_feed($path, $export_opt); + + if(isset($feed_cache)) { + return $feed_cache; + } + + $feed = $this->construct_feed($path); + + $this->_exec("INSERT INTO feed_cache + (search_path, export_type, feed_content) + VALUES + (?, 'atom', ?), + (?, 'rss', ?)", + "ssss", $path, $feed->export('atom'), + $path, $feed->export('rss')); + + return $this->try_get_cached_feed($path, $export_opt); + } } ?> \ No newline at end of file diff --git a/www/router.php b/www/router.php index 4d0a219..6d9d00b 100644 --- a/www/router.php +++ b/www/router.php @@ -111,6 +111,14 @@ function generate_website($SURI) { echo $twig->render('upload.html'); } + } elseif(preg_match('/^\/feed(?:\/(rss|atom)(.*))?$/', $SURI, $match)) { + $feed = $adapter->get_laminas_feed($match[2] ?? '/', $match[1] ?? 'rss'); + + header('Content-Type: application/xml'); + header('Cache-Control: max-age=1800'); + header('Etag: W/"' . $SURI . '/' . strtotime($feed['feed_ts']) . '"'); + + echo $feed['feed']; } elseif(preg_match('/^\s*image/', $_SERVER['HTTP_ACCEPT'])) { header('Location: /raw' . $SURI); exit(0); diff --git a/www/templates/fragments/filepath_bar.html b/www/templates/fragments/filepath_bar.html index cff3d04..077345c 100644 --- a/www/templates/fragments/filepath_bar.html +++ b/www/templates/fragments/filepath_bar.html @@ -26,5 +26,11 @@ raw api +
  • + + {{ fa['rss']|raw }} + +
  • \ No newline at end of file diff --git a/www/templates/pathed_content.html b/www/templates/pathed_content.html index cdcb753..7ac1cdf 100644 --- a/www/templates/pathed_content.html +++ b/www/templates/pathed_content.html @@ -2,6 +2,11 @@ {% extends "root.html" %} +{% block feed_links %} + + +{% endblock %} + {% block second_title %}

    {{ post.post_metadata.title }}

    {% endblock %} diff --git a/www/templates/root.html b/www/templates/root.html index 0bb44f5..3302d24 100644 --- a/www/templates/root.html +++ b/www/templates/root.html @@ -7,6 +7,10 @@ + {% block feed_links %} + + {% endblock %} + {% block extra_head %}{% endblock %}