443 lines
14 KiB
Rust
443 lines
14 KiB
Rust
use super::{React, ReplyReact, StoreError, SubmissionReact, Undo};
|
|
use chrono::{DateTime, Utc};
|
|
use sled::{Db, Transactional, Tree};
|
|
use std::{collections::HashSet, io::Cursor};
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Store {
|
|
react_tree: Tree,
|
|
react_kind_tree: Tree,
|
|
react_kind_count_tree: Tree,
|
|
submission_tree: Tree,
|
|
submission_created_tree: Tree,
|
|
comment_tree: Tree,
|
|
comment_created_tree: Tree,
|
|
profile_created_tree: Tree,
|
|
count_tree: Tree,
|
|
}
|
|
|
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
|
struct StoredReact<'a> {
|
|
id: Uuid,
|
|
submission_id: Uuid,
|
|
profile_id: Uuid,
|
|
comment_id: Option<Uuid>,
|
|
react: &'a str,
|
|
published: DateTime<Utc>,
|
|
created_at: DateTime<Utc>,
|
|
updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
|
|
struct StoredReactKindHash {
|
|
reacts: HashSet<String>,
|
|
}
|
|
|
|
impl Store {
|
|
pub fn build(db: &Db) -> Result<Self, sled::Error> {
|
|
Ok(Store {
|
|
react_tree: db.open_tree("profiles/reacts")?,
|
|
react_kind_tree: db.open_tree("profiles/reacts/kind")?,
|
|
react_kind_count_tree: db.open_tree("profiles/reacts/kind/count")?,
|
|
submission_tree: db.open_tree("profiles/reacts/submission")?,
|
|
submission_created_tree: db.open_tree("/profiles/reacts/submission/created")?,
|
|
comment_tree: db.open_tree("/profiles/reacts/comment")?,
|
|
comment_created_tree: db.open_tree("/profiles/reacts/comment/created")?,
|
|
profile_created_tree: db.open_tree("/profile/reacts/profile/created")?,
|
|
count_tree: db.open_tree("/profiles/reacts/count")?,
|
|
})
|
|
}
|
|
|
|
pub fn create(
|
|
&self,
|
|
submission_id: Uuid,
|
|
profile_id: Uuid,
|
|
comment_id: Option<Uuid>,
|
|
react: &str,
|
|
published: DateTime<Utc>,
|
|
) -> Result<React, StoreError> {
|
|
let mut id;
|
|
let mut stored_react;
|
|
|
|
let now = Utc::now().into();
|
|
let mut stored_react_vec = vec![];
|
|
|
|
while {
|
|
stored_react_vec.clear();
|
|
let cursor = Cursor::new(&mut stored_react_vec);
|
|
id = Uuid::new_v4();
|
|
stored_react = StoredReact {
|
|
id,
|
|
submission_id,
|
|
profile_id,
|
|
comment_id,
|
|
react,
|
|
published,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
serde_json::to_writer(cursor, &stored_react)?;
|
|
self.react_tree
|
|
.compare_and_swap(
|
|
react_key(id),
|
|
None as Option<&[u8]>,
|
|
Some(stored_react_vec.as_slice()),
|
|
)?
|
|
.is_err()
|
|
} {}
|
|
|
|
let kind = react.to_owned();
|
|
|
|
let res = [
|
|
&self.submission_tree,
|
|
&self.submission_created_tree,
|
|
&self.comment_tree,
|
|
&self.comment_created_tree,
|
|
&self.profile_created_tree,
|
|
&self.count_tree,
|
|
&self.react_kind_tree,
|
|
&self.react_kind_count_tree,
|
|
]
|
|
.transaction(move |trees| {
|
|
let submission_tree = &trees[0];
|
|
let submission_created_tree = &trees[1];
|
|
let comment_tree = &trees[2];
|
|
let comment_created_tree = &trees[3];
|
|
let profile_created_tree = &trees[4];
|
|
let count_tree = &trees[5];
|
|
let react_kind_tree = &trees[6];
|
|
let react_kind_count_tree = &trees[7];
|
|
|
|
let count_key;
|
|
let react_kind_key;
|
|
let react_kind_count_key;
|
|
|
|
if let Some(comment_id) = comment_id {
|
|
comment_tree.insert(
|
|
comment_key(comment_id, id).as_bytes(),
|
|
id.to_string().as_bytes(),
|
|
)?;
|
|
comment_created_tree.insert(
|
|
comment_created_key(comment_id, now, id).as_bytes(),
|
|
id.to_string().as_bytes(),
|
|
)?;
|
|
|
|
count_key = comment_count_key(comment_id);
|
|
react_kind_key = comment_kind_key(comment_id);
|
|
react_kind_count_key = comment_kind_count_key(comment_id, &kind);
|
|
} else {
|
|
submission_tree.insert(
|
|
submission_key(submission_id, id).as_bytes(),
|
|
id.to_string().as_bytes(),
|
|
)?;
|
|
submission_created_tree.insert(
|
|
submission_created_key(submission_id, now, id).as_bytes(),
|
|
id.to_string().as_bytes(),
|
|
)?;
|
|
|
|
count_key = submission_count_key(submission_id);
|
|
react_kind_key = submission_kind_key(submission_id);
|
|
react_kind_count_key = submission_kind_count_key(submission_id, &kind);
|
|
}
|
|
|
|
profile_created_tree.insert(
|
|
profile_created_key(profile_id, now, id).as_bytes(),
|
|
id.to_string().as_bytes(),
|
|
)?;
|
|
|
|
super::modify(
|
|
react_kind_tree,
|
|
&react_kind_key,
|
|
|hs: &mut StoredReactKindHash| {
|
|
hs.reacts.insert(kind.clone());
|
|
},
|
|
)?;
|
|
|
|
super::count(react_kind_count_tree, &react_kind_count_key, |c| {
|
|
c.saturating_sub(1)
|
|
})?;
|
|
super::count(count_tree, &profile_count_key(profile_id), |c| {
|
|
c.saturating_add(1)
|
|
})?;
|
|
super::count(count_tree, &count_key, |c| c.saturating_add(1))?;
|
|
|
|
Ok(())
|
|
});
|
|
|
|
if let Err(e) = res {
|
|
self.react_tree.remove(react_key(id))?;
|
|
return Err(e.into());
|
|
}
|
|
|
|
Ok(stored_react.into())
|
|
}
|
|
|
|
pub fn count_for_submission(&self, submission_id: Uuid, kind: &str) -> Result<u64, StoreError> {
|
|
match self
|
|
.react_kind_count_tree
|
|
.get(submission_kind_count_key(submission_id, kind))?
|
|
{
|
|
Some(ivec) => {
|
|
let s = String::from_utf8_lossy(&ivec);
|
|
let count: u64 = s.parse().expect("Count is valid");
|
|
Ok(count)
|
|
}
|
|
None => Ok(0),
|
|
}
|
|
}
|
|
|
|
pub fn count_for_comment(&self, comment_id: Uuid, kind: &str) -> Result<u64, StoreError> {
|
|
match self
|
|
.react_kind_count_tree
|
|
.get(comment_kind_count_key(comment_id, kind))?
|
|
{
|
|
Some(ivec) => {
|
|
let s = String::from_utf8_lossy(&ivec);
|
|
let count: u64 = s.parse().expect("Count is valid");
|
|
Ok(count)
|
|
}
|
|
None => Ok(0),
|
|
}
|
|
}
|
|
|
|
pub fn kinds_for_submission(&self, submission_id: Uuid) -> Result<HashSet<String>, StoreError> {
|
|
match self
|
|
.react_kind_tree
|
|
.get(submission_kind_key(submission_id))?
|
|
{
|
|
Some(ivec) => {
|
|
let item: StoredReactKindHash = serde_json::from_slice(&ivec)?;
|
|
Ok(item.reacts)
|
|
}
|
|
None => Ok(HashSet::new()),
|
|
}
|
|
}
|
|
|
|
pub fn kinds_for_comment(&self, comment_id: Uuid) -> Result<HashSet<String>, StoreError> {
|
|
match self.react_kind_tree.get(comment_kind_key(comment_id))? {
|
|
Some(ivec) => {
|
|
let item: StoredReactKindHash = serde_json::from_slice(&ivec)?;
|
|
Ok(item.reacts)
|
|
}
|
|
None => Ok(HashSet::new()),
|
|
}
|
|
}
|
|
|
|
pub fn for_submission(&self, submission_id: Uuid) -> impl DoubleEndedIterator<Item = Uuid> {
|
|
self.submission_tree
|
|
.scan_prefix(submission_prefix(submission_id))
|
|
.values()
|
|
.filter_map(|res| res.ok())
|
|
.filter_map(|ivec| {
|
|
let s = String::from_utf8_lossy(&ivec);
|
|
s.parse::<Uuid>().ok()
|
|
})
|
|
}
|
|
|
|
pub fn for_comment(&self, comment_id: Uuid) -> impl DoubleEndedIterator<Item = Uuid> {
|
|
self.comment_tree
|
|
.scan_prefix(comment_prefix(comment_id))
|
|
.values()
|
|
.filter_map(|res| res.ok())
|
|
.filter_map(|ivec| {
|
|
let s = String::from_utf8_lossy(&ivec);
|
|
s.parse::<Uuid>().ok()
|
|
})
|
|
}
|
|
|
|
pub fn by_id(&self, id: Uuid) -> Result<Option<React>, StoreError> {
|
|
let ivec = match self.react_tree.get(react_key(id))? {
|
|
Some(ivec) => ivec,
|
|
None => return Ok(None),
|
|
};
|
|
|
|
let stored_react: StoredReact = serde_json::from_slice(&ivec)?;
|
|
|
|
Ok(Some(stored_react.into()))
|
|
}
|
|
|
|
pub fn delete(&self, react_id: Uuid) -> Result<Option<Undo<React>>, StoreError> {
|
|
let ivec = match self.react_tree.get(react_key(react_id))? {
|
|
Some(ivec) => ivec,
|
|
None => return Ok(None),
|
|
};
|
|
|
|
let stored_react: StoredReact = serde_json::from_slice(&ivec)?;
|
|
let submission_id = stored_react.submission_id;
|
|
let profile_id = stored_react.profile_id;
|
|
let comment_id = stored_react.comment_id;
|
|
let created_at = stored_react.created_at;
|
|
let react = stored_react.react.to_owned();
|
|
|
|
[
|
|
&self.react_tree,
|
|
&self.submission_tree,
|
|
&self.submission_created_tree,
|
|
&self.comment_tree,
|
|
&self.comment_created_tree,
|
|
&self.profile_created_tree,
|
|
&self.count_tree,
|
|
&self.react_kind_tree,
|
|
&self.react_kind_count_tree,
|
|
]
|
|
.transaction(move |trees| {
|
|
let react_tree = &trees[0];
|
|
let submission_tree = &trees[1];
|
|
let submission_created_tree = &trees[2];
|
|
let comment_tree = &trees[3];
|
|
let comment_created_tree = &trees[4];
|
|
let profile_created_tree = &trees[5];
|
|
let count_tree = &trees[6];
|
|
let react_kind_tree = &trees[7];
|
|
let react_kind_count_tree = &trees[8];
|
|
|
|
react_tree.remove(react_key(react_id).as_bytes())?;
|
|
|
|
let count_key;
|
|
let react_kind_key;
|
|
let react_kind_count_key;
|
|
|
|
if let Some(comment_id) = comment_id {
|
|
comment_tree.remove(comment_key(comment_id, react_id).as_bytes())?;
|
|
comment_created_tree
|
|
.remove(comment_created_key(comment_id, created_at, react_id).as_bytes())?;
|
|
|
|
count_key = comment_count_key(comment_id);
|
|
react_kind_key = comment_kind_key(comment_id);
|
|
react_kind_count_key = comment_kind_count_key(comment_id, &react);
|
|
} else {
|
|
submission_tree.remove(submission_key(submission_id, react_id).as_bytes())?;
|
|
submission_created_tree.remove(
|
|
submission_created_key(submission_id, created_at, react_id).as_bytes(),
|
|
)?;
|
|
|
|
count_key = submission_count_key(submission_id);
|
|
react_kind_key = submission_kind_key(submission_id);
|
|
react_kind_count_key = submission_kind_count_key(submission_id, &react);
|
|
}
|
|
|
|
profile_created_tree
|
|
.remove(profile_created_key(profile_id, created_at, react_id).as_bytes())?;
|
|
|
|
super::modify(
|
|
react_kind_tree,
|
|
&react_kind_key,
|
|
|hs: &mut StoredReactKindHash| {
|
|
hs.reacts.remove(&react);
|
|
},
|
|
)?;
|
|
|
|
super::count(react_kind_count_tree, &react_kind_count_key, |c| {
|
|
c.saturating_sub(1)
|
|
})?;
|
|
super::count(count_tree, &profile_count_key(profile_id), |c| {
|
|
c.saturating_sub(1)
|
|
})?;
|
|
super::count(count_tree, &count_key, |c| c.saturating_sub(1))?;
|
|
|
|
Ok(())
|
|
})?;
|
|
|
|
Ok(Some(Undo(stored_react.into())))
|
|
}
|
|
}
|
|
|
|
fn react_key(id: Uuid) -> String {
|
|
format!("/reacts/{}/data", id)
|
|
}
|
|
|
|
fn submission_kind_key(submission_id: Uuid) -> String {
|
|
format!("/submission/{}/kinds", submission_id)
|
|
}
|
|
|
|
fn comment_kind_key(comment_id: Uuid) -> String {
|
|
format!("/comment/{}/kinds", comment_id)
|
|
}
|
|
|
|
fn submission_kind_count_key(submission_id: Uuid, kind: &str) -> String {
|
|
format!("/submission/{}/kind/{}/count", submission_id, kind)
|
|
}
|
|
|
|
fn comment_kind_count_key(comment_id: Uuid, kind: &str) -> String {
|
|
format!("/comment/{}/kind/{}/count", comment_id, kind)
|
|
}
|
|
|
|
fn submission_key(submission_id: Uuid, id: Uuid) -> String {
|
|
format!("/submission/{}/react/{}", submission_id, id)
|
|
}
|
|
|
|
fn submission_prefix(submission_id: Uuid) -> String {
|
|
format!("/submission/{}/reacts", submission_id)
|
|
}
|
|
|
|
fn submission_created_key(submission_id: Uuid, created_at: DateTime<Utc>, id: Uuid) -> String {
|
|
format!(
|
|
"/submission/{}/created/{}/react/{}",
|
|
submission_id,
|
|
created_at.to_rfc3339(),
|
|
id
|
|
)
|
|
}
|
|
|
|
fn comment_key(comment_id: Uuid, id: Uuid) -> String {
|
|
format!("/comment/{}/react/{}", comment_id, id)
|
|
}
|
|
|
|
fn comment_prefix(comment_id: Uuid) -> String {
|
|
format!("/comment/{}/react", comment_id)
|
|
}
|
|
|
|
fn comment_created_key(comment_id: Uuid, created_at: DateTime<Utc>, id: Uuid) -> String {
|
|
format!(
|
|
"/comment/{}/created/{}/react/{}",
|
|
comment_id,
|
|
created_at.to_rfc3339(),
|
|
id,
|
|
)
|
|
}
|
|
|
|
fn submission_count_key(submission_id: Uuid) -> String {
|
|
format!("/submission/{}/count", submission_id,)
|
|
}
|
|
|
|
fn comment_count_key(comment_id: Uuid) -> String {
|
|
format!("/comment/{}/count", comment_id)
|
|
}
|
|
|
|
fn profile_created_key(profile_id: Uuid, created_at: DateTime<Utc>, id: Uuid) -> String {
|
|
format!(
|
|
"/profile/{}/created/{}/react/{}",
|
|
profile_id,
|
|
created_at.to_rfc3339(),
|
|
id
|
|
)
|
|
}
|
|
|
|
fn profile_count_key(profile_id: Uuid) -> String {
|
|
format!("/profile/{}/count", profile_id)
|
|
}
|
|
|
|
impl<'a> From<StoredReact<'a>> for React {
|
|
fn from(sl: StoredReact) -> Self {
|
|
if let Some(comment_id) = sl.comment_id {
|
|
React::Reply(ReplyReact {
|
|
id: sl.id,
|
|
submission_id: sl.submission_id,
|
|
profile_id: sl.profile_id,
|
|
comment_id,
|
|
react: sl.react.to_owned(),
|
|
published: sl.published,
|
|
})
|
|
} else {
|
|
React::Submission(SubmissionReact {
|
|
id: sl.id,
|
|
submission_id: sl.submission_id,
|
|
profile_id: sl.profile_id,
|
|
react: sl.react.to_owned(),
|
|
published: sl.published,
|
|
})
|
|
}
|
|
}
|
|
}
|