From 4cc9cfdc8c06f335acb052f57aaa0beb84a04167 Mon Sep 17 00:00:00 2001 From: Sascha Nitsch Date: Wed, 11 Jun 2025 03:20:35 +0200 Subject: [PATCH] converted from date field to timestamp fixed reloading logic on paging --- php/federator/api/fedusers/outbox.php | 27 +++++---- php/federator/cache/cache.php | 5 +- php/federator/connector/connector.php | 7 ++- php/federator/dio/posts.php | 80 ++++++++++++++------------- plugins/federator/contentnation.php | 21 +++---- plugins/federator/dummyconnector.php | 7 ++- plugins/federator/rediscache.php | 15 +++-- 7 files changed, 89 insertions(+), 73 deletions(-) diff --git a/php/federator/api/fedusers/outbox.php b/php/federator/api/fedusers/outbox.php index 65b1dfc..036c1b5 100644 --- a/php/federator/api/fedusers/outbox.php +++ b/php/federator/api/fedusers/outbox.php @@ -56,34 +56,33 @@ class Outbox implements \Federator\Api\FedUsers\FedUsersInterface // get posts from user $outbox = new \Federator\Data\ActivityPub\Common\Outbox(); - $min = $this->main->extractFromURI("min", ""); - $max = $this->main->extractFromURI("max", ""); + $min = intval($this->main->extractFromURI('min', '0'), 10); + $max = intval($this->main->extractFromURI('max', '0'), 10); $page = $this->main->extractFromURI("page", ""); if ($page !== "") { - $items = \Federator\DIO\Posts::getPostsByUser($dbh, $user->id, $connector, $cache, $min, $max); + $items = \Federator\DIO\Posts::getPostsByUser($dbh, $user->id, $connector, $cache, $min, $max, 20); $outbox->setItems($items); } else { + $tmpitems = \Federator\DIO\Posts::getPostsByUser($dbh, $user->id, $connector, $cache, $min, $max, 99999); + $outbox->setTotalItems(sizeof($tmpitems)); $items = []; } $config = $this->main->getConfig(); $domain = $config['generic']['externaldomain']; - $id = 'https://' . $domain . '/users/' . $_user . '/outbox'; + $id = 'https://' . $domain . '/' . $_user . '/outbox'; $outbox->setPartOf($id); $outbox->setID($id); - if ($page !== '') { - $id .= '?page=' . urlencode($page); - } else { + if ($page === '') { $outbox->setType('OrderedCollection'); } - if ($page === '' || $outbox->count() == 0) { - $outbox->setFirst($id . '?page=0'); - $outbox->setLast($id . '&min=0'); + if ($page === '' || $outbox->getCount() == 0) { + $outbox->setFirst($id . '?page=true'); } if (sizeof($items) > 0) { - $newestId = $items[0]->getPublished(); - $oldestId = $items[sizeof($items) - 1]->getPublished(); - $outbox->setNext($id . '&max=' . $newestId); - $outbox->setPrev($id . '&min=' . $oldestId); + $oldestTS = $items[0]->getPublished(); + $newestTS = $items[sizeof($items) - 1]->getPublished(); + $outbox->setNext($id . '?page=true&max=' . $newestTS); + $outbox->setPrev($id . '?page=true&min=' . $oldestTS); } $obj = $outbox->toObject(); return json_encode($obj, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); diff --git a/php/federator/cache/cache.php b/php/federator/cache/cache.php index 4a979bf..f9ba480 100644 --- a/php/federator/cache/cache.php +++ b/php/federator/cache/cache.php @@ -35,10 +35,13 @@ interface Cache extends \Federator\Connector\Connector * save remote posts by user * * @param string $user user name + * @param int $min min timestamp + * @param int $max max timestamp + * @param int $limit limit results * @param \Federator\Data\ActivityPub\Common\APObject[]|false $posts user posts * @return void */ - public function saveRemotePostsByUser($user, $posts); + public function saveRemotePostsByUser($user, $min, $max, $limit, $posts); /** * save remote stats diff --git a/php/federator/connector/connector.php b/php/federator/connector/connector.php index c51e42a..fc69bb6 100644 --- a/php/federator/connector/connector.php +++ b/php/federator/connector/connector.php @@ -35,12 +35,13 @@ interface Connector * get posts by given user * * @param string $id user id - * @param string $min min date - * @param string $max max date + * @param int $min min value + * @param int $max max value + * @param int $limit maximum number of results * @return \Federator\Data\ActivityPub\Common\Activity[]|false */ - public function getRemotePostsByUser($id, $min, $max); + public function getRemotePostsByUser($id, $min, $max, $limit); /** * get remote user by given name diff --git a/php/federator/dio/posts.php b/php/federator/dio/posts.php index 5527482..ee4fabb 100644 --- a/php/federator/dio/posts.php +++ b/php/federator/dio/posts.php @@ -24,22 +24,24 @@ class Posts * connector to fetch use with * @param \Federator\Cache\Cache|null $cache * optional caching service - * @param string $min - * minimum date - * @param string $max - * maximum date + * @param int $min + * minimum timestamp + * @param int $max + * maximum timestamp + * @param int $limit + * maximum number of results * @return \Federator\Data\ActivityPub\Common\Activity[] */ - public static function getPostsByUser($dbh, $userid, $connector, $cache, $min, $max) + public static function getPostsByUser($dbh, $userid, $connector, $cache, $min, $max, $limit) { // ask cache if ($cache !== null) { - $posts = $cache->getRemotePostsByUser($userid, $min, $max); + $posts = $cache->getRemotePostsByUser($userid, $min, $max, $limit); if ($posts !== false) { return $posts; } } - $posts = self::getPostsFromDb($dbh, $userid, $min, $max); + $posts = self::getPostsFromDb($dbh, $userid, $min, $max, $limit); if ($posts === false) { $posts = []; } @@ -52,9 +54,8 @@ class Posts foreach ($posts as $post) { $published = $post->getPublished(); if ($published != null) { - $publishedStr = gmdate('Y-m-d H:i:s', $published); - if ($latestPublished === null || $publishedStr > $latestPublished) { - $latestPublished = $publishedStr; + if ($latestPublished === null || $published > $latestPublished) { + $latestPublished = $published; } } } @@ -63,17 +64,24 @@ class Posts } } - // Always fetch newer posts from connector (if any) - $newPosts = $connector->getRemotePostsByUser($userid, $remoteMin, $max); - if ($newPosts !== false && is_array($newPosts)) { - // Merge new posts with DB posts, avoiding duplicates by ID - $existingIds = []; - foreach ($posts as $post) { - $existingIds[$post->getID()] = true; - } - foreach ($newPosts as $newPost) { - if (!isset($existingIds[$newPost->getID()])) { - $posts[] = $newPost; + // Fetch newer posts from connector (if any) if max is not set and limit not reached + if ($max == 0 && sizeof($posts) < $limit) { + $newPosts = $connector->getRemotePostsByUser($userid, $remoteMin, $max, $limit); + if ($newPosts !== false && is_array($newPosts)) { + // Merge new posts with DB posts, avoiding duplicates by ID + $existingIds = []; + foreach ($posts as $post) { + $existingIds[$post->getID()] = true; + } + foreach ($newPosts as $newPost) { + if (!isset($existingIds[$newPost->getID()])) { + if ($newPost->getID() !== "") { + self::savePost($dbh, $userid, $newPost); + } + if (sizeof($posts) < $limit) { + $posts[] = $newPost; + } + } } } } @@ -95,11 +103,8 @@ class Posts $originUrl = 'localhost'; // Fallback to localhost if no origin is set } - // save posts to DB + // optionally convert from article to note foreach ($posts as $post) { - if ($post->getID() !== "") { - self::savePost($dbh, $userid, $post); - } switch (strtolower($post->getType())) { case 'undo': $object = $post->getObject(); @@ -134,7 +139,7 @@ class Posts } if ($cache !== null) { - $cache->saveRemotePostsByUser($userid, $posts); + $cache->saveRemotePostsByUser($userid, $min, $max, $limit, $posts); } return $posts; } @@ -144,26 +149,27 @@ class Posts * * @param \mysqli $dbh * @param string $userId - * @param string|null $min - * @param string|null $max + * @param int $min min timestamp + * @param int $max max timestamp + * @param int $limit * @return \Federator\Data\ActivityPub\Common\Activity[]|false */ - public static function getPostsFromDb($dbh, $userId, $min = null, $max = null) + public static function getPostsFromDb($dbh, $userId, $min, $max, $limit = 20) { - $sql = 'SELECT `id`, `url`, `user_id`, `actor`, `type`, `object`, `to`, `cc`, `published` FROM posts WHERE user_id = ?'; + $sql = 'SELECT `id`, `url`, `user_id`, `actor`, `type`, `object`, `to`, `cc`, unix_timestamp(`published`) as published FROM posts WHERE user_id = ?'; $params = [$userId]; $types = 's'; - if ($min !== null && $min !== "") { - $sql .= ' AND published >= ?'; + if ($min > 0) { + $sql .= ' AND published >= from_unixtime(?)'; $params[] = $min; $types .= 's'; } - if ($max !== null && $max !== "") { - $sql .= ' AND published <= ?'; + if ($max > 0) { + $sql .= ' AND published <= from_unixtime(?)'; $params[] = $max; $types .= 's'; } - $sql .= ' ORDER BY published DESC'; + $sql .= ' ORDER BY published DESC LIMIT ' . $limit; $stmt = $dbh->prepare($sql); if ($stmt === false) { @@ -193,9 +199,7 @@ class Posts } if (isset($row['published']) && $row['published'] !== null) { // If it's numeric, keep as int. If it's a string, try to parse as ISO 8601. - if (is_numeric($row['published'])) { - $row['published'] = intval($row['published'], 10); - } else { + if (!is_numeric($row['published'])) { // Try to parse as datetime string $timestamp = strtotime($row['published']); $row['published'] = $timestamp !== false ? $timestamp : null; diff --git a/plugins/federator/contentnation.php b/plugins/federator/contentnation.php index a19aa9a..d639a90 100644 --- a/plugins/federator/contentnation.php +++ b/plugins/federator/contentnation.php @@ -109,21 +109,23 @@ class ContentNation implements Connector * get posts by given user * * @param string $userId user id - * @param string $min min date - * @param string $max max date + * @param int $min min date + * @param int $max max date + * @param int $limit limit results + * @unused-param $limit * @return \Federator\Data\ActivityPub\Common\Activity[]|false */ - public function getRemotePostsByUser($userId, $min, $max) + public function getRemotePostsByUser($userId, $min, $max, $limit) { if (preg_match("#^([^@]+)@([^/]+)#", $userId, $matches) == 1) { $userId = $matches[1]; } $remoteURL = $this->service . '/api/profile/' . urlencode($userId) . '/activities'; - if ($min !== '') { - $remoteURL .= '&minTS=' . urlencode($min); + if ($min > 0) { + $remoteURL .= '&minTS=' . intval($min, 10); } - if ($max !== '') { - $remoteURL .= '&maxTS=' . urlencode($max); + if ($max > 0) { + $remoteURL .= '&maxTS=' . intval($max, 10); } [$response, $info] = \Federator\Main::getFromRemote($remoteURL, []); if ($info['http_code'] != 200) { @@ -148,12 +150,11 @@ class ContentNation implements Connector case 'Article': $create = new \Federator\Data\ActivityPub\Common\Create(); $create->setAActor($ourUrl . '/' . $userId); - $create->setID($activity['id']) - ->setPublished($activity['published'] ?? $activity['timestamp']) + $create->setPublished($activity['published'] ?? $activity['timestamp']) ->addTo($ourUrl . '/' . $userId . '/followers') ->addCC("https://www.w3.org/ns/activitystreams#Public"); $create->setURL($ourUrl . '/' . $activity['profilename'] . '/' . $activity['name']); - $create->setID($ourUrl . '/' . $activity['profilename'] . '/' . $activity['id']); + $create->setID($ourUrl . '/' . $activity['profilename'] . '/' . $activity['name']); $apArticle = new \Federator\Data\ActivityPub\Common\Article(); if (array_key_exists('tags', $activity)) { foreach ($activity['tags'] as $tag) { diff --git a/plugins/federator/dummyconnector.php b/plugins/federator/dummyconnector.php index d4ecb8f..e57cc2a 100644 --- a/plugins/federator/dummyconnector.php +++ b/plugins/federator/dummyconnector.php @@ -46,11 +46,12 @@ class DummyConnector implements Connector * get posts by given user * * @param string $id user id @unused-param - * @param string $min min date @unused-param - * @param string $max max date @unused-param + * @param int $min min timestamp @unused-param + * @param int $max max timestamp @unused-param + * @param int $limit limit number of results @unused-param * @return \Federator\Data\ActivityPub\Common\Activity[]|false */ - public function getRemotePostsByUser($id, $min, $max) + public function getRemotePostsByUser($id, $min, $max, $limit) { return false; } diff --git a/plugins/federator/rediscache.php b/plugins/federator/rediscache.php index bc3cf16..69b93b9 100644 --- a/plugins/federator/rediscache.php +++ b/plugins/federator/rediscache.php @@ -139,12 +139,13 @@ class RedisCache implements Cache * get posts by given user * * @param string $id user id @unused-param - * @param string $min min date @unused-param - * @param string $max max date @unused-param + * @param int $min min timestamp @unused-param + * @param int $max max timestamp @unused-param + * @param int $limit limit results @unused-param * @return \Federator\Data\ActivityPub\Common\Activity[]|false */ - public function getRemotePostsByUser($id, $min, $max) + public function getRemotePostsByUser($id, $min, $max, $limit) { error_log("rediscache::getRemotePostsByUser not implemented"); return false; @@ -273,10 +274,13 @@ class RedisCache implements Cache * save remote posts by user * * @param string $user user name @unused-param + * @param int $min min timestamp @unused-param + * @param int $max max timestamp @unused-param + * @param int $limit limit results @unused-param * @param \Federator\Data\ActivityPub\Common\APObject[]|false $posts user posts @unused-param * @return void */ - public function saveRemotePostsByUser($user, $posts) + public function saveRemotePostsByUser($user, $min, $max, $limit, $posts) { error_log("rediscache::saveRemotePostsByUser not implemented"); } @@ -305,6 +309,9 @@ class RedisCache implements Cache */ public function saveRemoteUserByName($_name, $user) { + if (!$this->connected) { + $this->connect(); + } $key = self::createKey('u', $_name); $serialized = $user->toJson(); $this->redis->setEx($key, $this->userTTL, $serialized);