about summary refs log tree commit diff
path: root/kittybox-rs/src/database/mod.rs
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2023-07-22 12:24:08 +0300
committerVika <vika@fireburn.ru>2023-07-22 12:24:08 +0300
commit39ddd3689aa4ef38580ea90087e1e204b55fcfc7 (patch)
treedfdef357c0e97543d8957c4d700da670081fe018 /kittybox-rs/src/database/mod.rs
parent6a88707be1651f457df0960ec0ddc78dadf8ed3a (diff)
downloadkittybox-39ddd3689aa4ef38580ea90087e1e204b55fcfc7.tar.zst
database: add "add_or_update_webmention" operation
This is an operation that atomically adds or updates a webmention cite
attached to a post. This is used so a database backend can optimize
for it (for example, using a transaction or shifting the JSON
modification operation to the database)
Diffstat (limited to 'kittybox-rs/src/database/mod.rs')
-rw-r--r--kittybox-rs/src/database/mod.rs74
1 files changed, 74 insertions, 0 deletions
diff --git a/kittybox-rs/src/database/mod.rs b/kittybox-rs/src/database/mod.rs
index 231fd26..1d1cf15 100644
--- a/kittybox-rs/src/database/mod.rs
+++ b/kittybox-rs/src/database/mod.rs
@@ -2,6 +2,7 @@
 use std::borrow::Cow;
 
 use async_trait::async_trait;
+use kittybox_util::MentionType;
 
 mod file;
 pub use crate::database::file::FileStorage;
@@ -296,6 +297,22 @@ pub trait Storage: std::fmt::Debug + Clone + Send + Sync {
 
     /// Commits a setting to the setting store.
     async fn set_setting<S: Setting<'a> + 'a, 'a>(&self, user: &'a str, value: S::Data) -> Result<()>;
+
+    /// Add (or update) a webmention on a certian post.
+    ///
+    /// The MF2 object describing the webmention content will always
+    /// be of type `h-cite`, and the `uid` property on the object will
+    /// always be set.
+    ///
+    /// The rationale for this function is as follows: webmentions
+    /// might be duplicated, and we need to deduplicate them first. As
+    /// we lack support for transactions and locking posts on the
+    /// database, the only way is to implement the operation on the
+    /// database itself.
+    ///
+    /// Besides, it may even allow for nice tricks like storing the
+    /// webmentions separately and rehydrating them on feed reads.
+    async fn add_or_update_webmention(&self, target: &str, mention_type: MentionType, mention: serde_json::Value) -> Result<()>;
 }
 
 #[cfg(test)]
@@ -303,6 +320,7 @@ mod tests {
     use super::settings;
 
     use super::{MicropubChannel, Storage};
+    use kittybox_util::MentionType;
     use serde_json::json;
 
     async fn test_basic_operations<Backend: Storage>(backend: Backend) {
@@ -499,6 +517,37 @@ mod tests {
         post
     }
 
+    fn gen_random_mention(domain: &str, mention_type: MentionType, url: &str) -> serde_json::Value {
+        use faker_rand::lorem::{Paragraphs, Word};
+
+        let uid = format!(
+            "https://{domain}/posts/{}-{}-{}",
+            rand::random::<Word>(),
+            rand::random::<Word>(),
+            rand::random::<Word>()
+        );
+
+        let time = chrono::Local::now().to_rfc3339();
+        let post = json!({
+            "type": ["h-cite"],
+            "properties": {
+                "content": [rand::random::<Paragraphs>().to_string()],
+                "uid": [&uid],
+                "url": [&uid],
+                "published": [&time],
+                (match mention_type {
+                    MentionType::Reply => "in-reply-to",
+                    MentionType::Like => "like-of",
+                    MentionType::Repost => "repost-of",
+                    MentionType::Bookmark => "bookmark-of",
+                    MentionType::Mention => unimplemented!(),
+                }): [url]
+            }
+        });
+
+        post
+    }
+
     async fn test_feed_pagination<Backend: Storage>(backend: Backend) {
         let posts = {
             let mut posts = std::iter::from_fn(
@@ -626,6 +675,30 @@ mod tests {
         .expect("Operation should not hang: see https://gitlab.com/kittybox/kittybox/-/issues/4");
     }
 
+    async fn test_webmention_addition<Backend: Storage>(db: Backend) {
+        let post = gen_random_post("fireburn.ru");
+
+        db.put_post(&post, "fireburn.ru").await.unwrap();
+        const TYPE: MentionType = MentionType::Reply;
+
+        let target = post["properties"]["uid"][0].as_str().unwrap();
+        let mut reply = gen_random_mention("aaronparecki.com", TYPE, target);
+
+        let (read_post, _) = db.read_feed_with_cursor(target, None, 20, None).await.unwrap().unwrap();
+        assert_eq!(post, read_post);
+
+        db.add_or_update_webmention(target, TYPE, reply.clone()).await.unwrap();
+
+        let (read_post, _) = db.read_feed_with_cursor(target, None, 20, None).await.unwrap().unwrap();
+        assert_eq!(read_post["properties"]["reply"][0], reply);
+
+        reply["properties"]["content"][0] = json!(rand::random::<faker_rand::lorem::Paragraphs>().to_string());
+
+        db.add_or_update_webmention(target, TYPE, reply.clone()).await.unwrap();
+        let (read_post, _) = db.read_feed_with_cursor(target, None, 20, None).await.unwrap().unwrap();
+        assert_eq!(read_post["properties"]["reply"][0], reply);
+    }
+
     /// Automatically generates a test suite for
     macro_rules! test_all {
         ($func_name:ident, $mod_name:ident) => {
@@ -635,6 +708,7 @@ mod tests {
                 $func_name!(test_settings);
                 $func_name!(test_update);
                 $func_name!(test_feed_pagination);
+                $func_name!(test_webmention_addition);
             }
         };
     }