about summary refs log tree commit diff
path: root/src/database/memory.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/database/memory.rs')
-rw-r--r--src/database/memory.rs249
1 files changed, 249 insertions, 0 deletions
diff --git a/src/database/memory.rs b/src/database/memory.rs
new file mode 100644
index 0000000..6339e7a
--- /dev/null
+++ b/src/database/memory.rs
@@ -0,0 +1,249 @@
+#![allow(clippy::todo)]
+use async_trait::async_trait;
+use futures_util::FutureExt;
+use serde_json::json;
+use std::collections::HashMap;
+use std::sync::Arc;
+use tokio::sync::RwLock;
+
+use crate::database::{ErrorKind, MicropubChannel, Result, settings, Storage, StorageError};
+
+#[derive(Clone, Debug)]
+pub struct MemoryStorage {
+    pub mapping: Arc<RwLock<HashMap<String, serde_json::Value>>>,
+    pub channels: Arc<RwLock<HashMap<String, Vec<String>>>>,
+}
+
+#[async_trait]
+impl Storage for MemoryStorage {
+    async fn post_exists(&self, url: &str) -> Result<bool> {
+        return Ok(self.mapping.read().await.contains_key(url));
+    }
+
+    async fn get_post(&self, url: &str) -> Result<Option<serde_json::Value>> {
+        let mapping = self.mapping.read().await;
+        match mapping.get(url) {
+            Some(val) => {
+                if let Some(new_url) = val["see_other"].as_str() {
+                    match mapping.get(new_url) {
+                        Some(val) => Ok(Some(val.clone())),
+                        None => {
+                            drop(mapping);
+                            self.mapping.write().await.remove(url);
+                            Ok(None)
+                        }
+                    }
+                } else {
+                    Ok(Some(val.clone()))
+                }
+            }
+            _ => Ok(None),
+        }
+    }
+
+    async fn put_post(&self, post: &'_ serde_json::Value, _user: &'_ str) -> Result<()> {
+        let mapping = &mut self.mapping.write().await;
+        let key: &str = match post["properties"]["uid"][0].as_str() {
+            Some(uid) => uid,
+            None => {
+                return Err(StorageError::from_static(
+                    ErrorKind::Other,
+                    "post doesn't have a UID",
+                ))
+            }
+        };
+        mapping.insert(key.to_string(), post.clone());
+        if post["properties"]["url"].is_array() {
+            for url in post["properties"]["url"]
+                .as_array()
+                .unwrap()
+                .iter()
+                .map(|i| i.as_str().unwrap().to_string())
+            {
+                if url != key {
+                    mapping.insert(url, json!({ "see_other": key }));
+                }
+            }
+        }
+        if post["type"]
+            .as_array()
+            .unwrap()
+            .iter()
+            .any(|i| i == "h-feed")
+        {
+            // This is a feed. Add it to the channels array if it's not already there.
+            println!("{:#}", post);
+            self.channels
+                .write()
+                .await
+                .entry(
+                    post["properties"]["author"][0]
+                        .as_str()
+                        .unwrap()
+                        .to_string(),
+                )
+                .or_insert_with(Vec::new)
+                .push(key.to_string())
+        }
+        Ok(())
+    }
+
+    async fn update_post(&self, url: &'_ str, update: crate::micropub::MicropubUpdate) -> Result<()> {
+        let mut guard = self.mapping.write().await;
+        let mut post = guard.get_mut(url).ok_or(StorageError::from_static(ErrorKind::NotFound, "The specified post wasn't found in the database."))?;
+
+        use crate::micropub::MicropubPropertyDeletion;
+
+        let mut add_keys: HashMap<String, Vec<serde_json::Value>> = HashMap::new();
+        let mut remove_keys: Vec<String> = vec![];
+        let mut remove_values: HashMap<String, Vec<serde_json::Value>> = HashMap::new();
+
+        if let Some(MicropubPropertyDeletion::Properties(delete)) = update.delete {
+            remove_keys.extend(delete.iter().cloned());
+        } else if let Some(MicropubPropertyDeletion::Values(delete)) = update.delete {
+            for (k, v) in delete {
+                remove_values
+                    .entry(k.to_string())
+                    .or_default()
+                    .extend(v.clone());
+            }
+        }
+        if let Some(add) = update.add {
+            for (k, v) in add {
+                add_keys.insert(k.to_string(), v.clone());
+            }
+        }
+        if let Some(replace) = update.replace {
+            for (k, v) in replace {
+                remove_keys.push(k.to_string());
+                add_keys.insert(k.to_string(), v.clone());
+            }
+        }
+
+        if let Some(props) = post["properties"].as_object_mut() {
+            for k in remove_keys {
+                props.remove(&k);
+            }
+        }
+        for (k, v) in remove_values {
+            let k = &k;
+            let props = if k == "children" {
+                &mut post
+            } else {
+                &mut post["properties"]
+            };
+            v.iter().for_each(|v| {
+                if let Some(vec) = props[k].as_array_mut() {
+                    if let Some(index) = vec.iter().position(|w| w == v) {
+                        vec.remove(index);
+                    }
+                }
+            });
+        }
+        for (k, v) in add_keys {
+            tracing::debug!("Adding k/v to post: {} => {:?}", k, v);
+            let props = if k == "children" {
+                &mut post
+            } else {
+                &mut post["properties"]
+            };
+            if let Some(prop) = props[&k].as_array_mut() {
+                if k == "children" {
+                    v.into_iter().rev().for_each(|v| prop.insert(0, v));
+                } else {
+                    prop.extend(v.into_iter());
+                }
+            } else {
+                props[&k] = serde_json::Value::Array(v)
+            }
+        }
+
+        Ok(())
+    }
+
+    async fn get_channels(&self, user: &'_ str) -> Result<Vec<MicropubChannel>> {
+        match self.channels.read().await.get(user) {
+            Some(channels) => Ok(futures_util::future::join_all(
+                channels
+                    .iter()
+                    .map(|channel| {
+                        self.get_post(channel).map(|result| result.unwrap()).map(
+                            |post: Option<serde_json::Value>| {
+                                post.map(|post| MicropubChannel {
+                                    uid: post["properties"]["uid"][0].as_str().unwrap().to_string(),
+                                    name: post["properties"]["name"][0]
+                                        .as_str()
+                                        .unwrap()
+                                        .to_string(),
+                                })
+                            },
+                        )
+                    })
+                    .collect::<Vec<_>>(),
+            )
+            .await
+            .into_iter()
+            .flatten()
+            .collect::<Vec<_>>()),
+            None => Ok(vec![]),
+        }
+    }
+
+    #[allow(unused_variables)]
+    async fn read_feed_with_limit(
+        &self,
+        url: &'_ str,
+        after: &'_ Option<String>,
+        limit: usize,
+        user: &'_ Option<String>,
+    ) -> Result<Option<serde_json::Value>> {
+        todo!()
+    }
+
+    #[allow(unused_variables)]
+    async fn read_feed_with_cursor(
+        &self,
+        url: &'_ str,
+        cursor: Option<&'_ str>,
+        limit: usize,
+        user: Option<&'_ str>
+    ) -> Result<Option<(serde_json::Value, Option<String>)>> {
+        todo!()
+    }
+
+    async fn delete_post(&self, url: &'_ str) -> Result<()> {
+        self.mapping.write().await.remove(url);
+        Ok(())
+    }
+
+    #[allow(unused_variables)]
+    async fn get_setting<S: settings::Setting<'a>, 'a>(&'_ self, user: &'_ str) -> Result<S> {
+        todo!()
+    }
+
+    #[allow(unused_variables)]
+    async fn set_setting<S: settings::Setting<'a> + 'a, 'a>(&self, user: &'a str, value: S::Data) -> Result<()> {
+        todo!()
+    }
+
+    #[allow(unused_variables)]
+    async fn add_or_update_webmention(&self, target: &str, mention_type: kittybox_util::MentionType, mention: serde_json::Value) -> Result<()> {
+        todo!()
+    }
+
+}
+
+impl Default for MemoryStorage {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl MemoryStorage {
+    pub fn new() -> Self {
+        Self {
+            mapping: Arc::new(RwLock::new(HashMap::new())),
+            channels: Arc::new(RwLock::new(HashMap::new())),
+        }
+    }
+}