#![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>>, pub channels: Arc>>>, } #[async_trait] impl Storage for MemoryStorage { async fn post_exists(&self, url: &str) -> Result { return Ok(self.mapping.read().await.contains_key(url)); } async fn get_post(&self, url: &str) -> Result> { 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> = HashMap::new(); let mut remove_keys: Vec = vec![]; let mut remove_values: HashMap> = 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> { 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| { 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::>(), ) .await .into_iter() .flatten() .collect::>()), None => Ok(vec![]), } } #[allow(unused_variables)] async fn read_feed_with_limit( &self, url: &'_ str, after: &'_ Option, limit: usize, user: &'_ Option, ) -> Result> { todo!() } #[allow(unused_variables)] async fn read_feed_with_cursor( &self, url: &'_ str, cursor: Option<&'_ str>, limit: usize, user: Option<&'_ str> ) -> Result)>> { todo!() } async fn delete_post(&self, url: &'_ str) -> Result<()> { self.mapping.write().await.remove(url); Ok(()) } #[allow(unused_variables)] async fn get_setting, 'a>(&'_ self, user: &'_ str) -> Result { todo!() } #[allow(unused_variables)] async fn set_setting + '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())), } } }