about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2024-03-04 04:40:50 +0300
committerVika <vika@fireburn.ru>2024-03-04 04:40:50 +0300
commitefc10086cbb6726c3728c8edda93b8fd00436717 (patch)
tree749437e3a12181e5380a59e32b16fdbf78e87ba9
parentb7f0140dc05e4b33352e0ea46db4b36c6bfb2894 (diff)
downloadkittybox-efc10086cbb6726c3728c8edda93b8fd00436717.tar.zst
Support ?q=category queries
Warning, untested. But hopefully works!
-rw-r--r--src/database/file/mod.rs14
-rw-r--r--src/database/memory.rs4
-rw-r--r--src/database/mod.rs5
-rw-r--r--src/database/postgres/mod.rs15
4 files changed, 37 insertions, 1 deletions
diff --git a/src/database/file/mod.rs b/src/database/file/mod.rs
index 27d3da1..46660ab 100644
--- a/src/database/file/mod.rs
+++ b/src/database/file/mod.rs
@@ -254,6 +254,20 @@ async fn hydrate_author<S: Storage>(
 #[async_trait]
 impl Storage for FileStorage {
     #[tracing::instrument(skip(self))]
+    async fn categories(&self, url: &str) -> Result<Vec<String>> {
+        // This requires an expensive scan through the entire
+        // directory tree.
+        //
+        // Until this backend has some kind of caching/indexing for
+        // categories (consider using symlinks?), this query won't
+        // perform well.
+        Err(std::io::Error::new(
+            std::io::ErrorKind::Unsupported,
+            "?q=category queries are not implemented due to resource constraints"
+        ))?
+    }
+
+    #[tracing::instrument(skip(self))]
     async fn post_exists(&self, url: &str) -> Result<bool> {
         let path = url_to_path(&self.root_dir, url);
         debug!("Checking if {:?} exists...", path);
diff --git a/src/database/memory.rs b/src/database/memory.rs
index 6339e7a..564f451 100644
--- a/src/database/memory.rs
+++ b/src/database/memory.rs
@@ -16,6 +16,10 @@ pub struct MemoryStorage {
 
 #[async_trait]
 impl Storage for MemoryStorage {
+    async fn categories(&self, _url: &str) -> Result<Vec<String>> {
+        unimplemented!()
+    }
+
     async fn post_exists(&self, url: &str) -> Result<bool> {
         return Ok(self.mapping.read().await.contains_key(url));
     }
diff --git a/src/database/mod.rs b/src/database/mod.rs
index b4b70b2..a6a3b46 100644
--- a/src/database/mod.rs
+++ b/src/database/mod.rs
@@ -215,6 +215,9 @@ pub type Result<T> = std::result::Result<T, StorageError>;
 /// or lock the database so that write conflicts or reading half-written data should not occur.
 #[async_trait]
 pub trait Storage: std::fmt::Debug + Clone + Send + Sync {
+    /// Return the list of categories used in blog posts of a specified blog.
+    async fn categories(&self, url: &str) -> Result<Vec<String>>;
+
     /// Check if a post exists in the database.
     async fn post_exists(&self, url: &str) -> Result<bool>;
 
@@ -236,7 +239,7 @@ pub trait Storage: std::fmt::Debug + Clone + Send + Sync {
     }
     /// Remove post from feed. Some database implementations might have optimized ways to do this.
     #[tracing::instrument(skip(self))]
-    async fn remove_from_feed(&self, feed: &'_ str, post: &'_ str) -> Result<()> {
+    async fn remove_from_feed(&self, feed: &str, post: &str) -> Result<()> {
         tracing::debug!("Removing {} into {} using `update_post`", post, feed);
         self.update_post(feed, serde_json::from_value(
             serde_json::json!({"delete": {"children": [post]}})).unwrap()
diff --git a/src/database/postgres/mod.rs b/src/database/postgres/mod.rs
index ee7dd1c..71c4d58 100644
--- a/src/database/postgres/mod.rs
+++ b/src/database/postgres/mod.rs
@@ -75,6 +75,21 @@ impl PostgresStorage {
 #[async_trait::async_trait]
 impl Storage for PostgresStorage {
     #[tracing::instrument(skip(self))]
+    async fn categories(&self, url: &str) -> Result<Vec<String>> {
+        sqlx::query_scalar::<_, String>("
+SELECT jsonb_array_elements(mf2['properties']['category']) AS category
+FROM kittybox.mf2_json
+WHERE
+    jsonb_typeof(mf2['properties']['category']) = 'array'
+    AND uid LIKE ($1 + '%')
+    GROUP BY category ORDER BY count(*) DESC
+")
+            .bind(url)
+            .fetch_all(&self.db)
+            .await
+            .map_err(|err| err.into())
+    }
+    #[tracing::instrument(skip(self))]
     async fn post_exists(&self, url: &str) -> Result<bool> {
         sqlx::query_as::<_, (bool,)>("SELECT exists(SELECT 1 FROM kittybox.mf2_json WHERE uid = $1 OR mf2['properties']['url'] ? $1)")
             .bind(url)