#![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!()
}
}
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())),
}
}
}