#![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::new( 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: serde_json::Value) -> Result<()> { let mut add_keys: HashMap = HashMap::new(); let mut remove_keys: Vec = vec![]; let mut remove_values: HashMap> = HashMap::new(); if let Some(delete) = update["delete"].as_array() { remove_keys.extend( delete .iter() .filter_map(|v| v.as_str()) .map(|v| v.to_string()), ); } else if let Some(delete) = update["delete"].as_object() { for (k, v) in delete { if let Some(v) = v.as_array() { remove_values .entry(k.to_string()) .or_default() .extend(v.clone()); } else { return Err(StorageError::new( ErrorKind::BadRequest, "Malformed update object", )); } } } if let Some(add) = update["add"].as_object() { for (k, v) in add { if v.is_array() { add_keys.insert(k.to_string(), v.clone()); } else { return Err(StorageError::new( ErrorKind::BadRequest, "Malformed update object", )); } } } if let Some(replace) = update["replace"].as_object() { for (k, v) in replace { remove_keys.push(k.to_string()); add_keys.insert(k.to_string(), v.clone()); } } let mut mapping = self.mapping.write().await; if let Some(mut post) = mapping.get(url) { if let Some(url) = post["see_other"].as_str() { if let Some(new_post) = mapping.get(url) { post = new_post } else { return Err(StorageError::new( ErrorKind::NotFound, "The post you have requested is not found in the database.", )); } } let mut post = post.clone(); for k in remove_keys { post["properties"].as_object_mut().unwrap().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 { let props = if k == "children" { &mut post } else { &mut post["properties"] }; let k = &k; if let Some(prop) = props[k].as_array_mut() { if k == "children" { v.as_array() .unwrap() .iter() .cloned() .rev() .for_each(|v| prop.insert(0, v)); } else { prop.extend(v.as_array().unwrap().iter().cloned()); } } else { post["properties"][k] = v } } mapping.insert( post["properties"]["uid"][0].as_str().unwrap().to_string(), post, ); } else { return Err(StorageError::new( ErrorKind::NotFound, "The designated post wasn't found in the database.", )); } 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!() } 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>(&self, user: &'_ str, value: S::Data) -> 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())), } } }