2021-02-09 00:01:24 +00:00
|
|
|
use super::{StoreError, Undo};
|
2021-01-04 17:34:31 +00:00
|
|
|
use chrono::{DateTime, Utc};
|
|
|
|
use sled::{Db, Transactional, Tree};
|
2021-02-02 03:56:41 +00:00
|
|
|
use std::{fmt, io::Cursor};
|
2021-01-04 17:34:31 +00:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
2021-02-02 03:56:41 +00:00
|
|
|
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
|
|
|
|
pub enum Visibility {
|
|
|
|
Public,
|
|
|
|
Unlisted,
|
|
|
|
Followers,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct Submission {
|
|
|
|
id: Uuid,
|
|
|
|
profile_id: Uuid,
|
|
|
|
title: String,
|
|
|
|
title_source: Option<String>,
|
|
|
|
description: Option<String>,
|
|
|
|
description_source: Option<String>,
|
|
|
|
files: Vec<Uuid>,
|
|
|
|
published: Option<DateTime<Utc>>,
|
|
|
|
updated: Option<DateTime<Utc>>,
|
|
|
|
visibility: Visibility,
|
|
|
|
local_only: bool,
|
|
|
|
logged_in_only: bool,
|
2021-02-03 03:19:29 +00:00
|
|
|
sensitive: bool,
|
2021-02-02 03:56:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct SubmissionChanges {
|
|
|
|
id: Uuid,
|
|
|
|
title: Option<String>,
|
|
|
|
title_source: Option<String>,
|
|
|
|
description: Option<String>,
|
|
|
|
description_source: Option<String>,
|
|
|
|
visibility: Option<Visibility>,
|
|
|
|
published: Option<DateTime<Utc>>,
|
|
|
|
updated: Option<DateTime<Utc>>,
|
|
|
|
local_only: Option<bool>,
|
|
|
|
logged_in_only: Option<bool>,
|
2021-02-03 03:19:29 +00:00
|
|
|
sensitive: Option<bool>,
|
2021-02-02 03:56:41 +00:00
|
|
|
original_files: Vec<Uuid>,
|
|
|
|
files: Vec<Uuid>,
|
|
|
|
}
|
|
|
|
|
2021-01-04 17:34:31 +00:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct Store {
|
|
|
|
submission_tree: Tree,
|
|
|
|
profile_tree: Tree,
|
2021-01-11 04:03:20 +00:00
|
|
|
profile_drafted_tree: Tree,
|
|
|
|
published_tree: Tree,
|
|
|
|
profile_published_tree: Tree,
|
2021-01-04 17:34:31 +00:00
|
|
|
count_tree: Tree,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
2021-01-16 04:25:06 +00:00
|
|
|
struct StoredSubmission {
|
2021-01-04 17:34:31 +00:00
|
|
|
id: Uuid,
|
|
|
|
profile_id: Uuid,
|
2021-01-16 04:25:06 +00:00
|
|
|
title: String,
|
2021-02-02 03:56:41 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2021-01-28 02:58:38 +00:00
|
|
|
title_source: Option<String>,
|
2021-02-02 03:56:41 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2021-01-16 04:25:06 +00:00
|
|
|
description: Option<String>,
|
2021-02-02 03:56:41 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2021-01-28 02:58:38 +00:00
|
|
|
description_source: Option<String>,
|
2021-01-04 17:34:31 +00:00
|
|
|
files: Vec<Uuid>,
|
2021-02-02 03:56:41 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2021-01-04 17:34:31 +00:00
|
|
|
published: Option<DateTime<Utc>>,
|
2021-02-02 03:56:41 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
updated: Option<DateTime<Utc>>,
|
2021-01-04 17:34:31 +00:00
|
|
|
visibility: Visibility,
|
2021-02-02 03:56:41 +00:00
|
|
|
#[serde(default)]
|
|
|
|
local_only: bool,
|
|
|
|
#[serde(default)]
|
|
|
|
logged_in_only: bool,
|
2021-02-03 03:19:29 +00:00
|
|
|
#[serde(default)]
|
|
|
|
sensitive: bool,
|
2021-01-11 04:03:20 +00:00
|
|
|
drafted_at: DateTime<Utc>,
|
2021-02-02 03:56:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Submission {
|
|
|
|
pub fn update(&self) -> SubmissionChanges {
|
|
|
|
SubmissionChanges {
|
|
|
|
id: self.id,
|
|
|
|
title: None,
|
|
|
|
title_source: None,
|
|
|
|
description: None,
|
|
|
|
description_source: None,
|
|
|
|
visibility: None,
|
|
|
|
published: self.published,
|
|
|
|
updated: None,
|
|
|
|
local_only: None,
|
|
|
|
logged_in_only: None,
|
2021-02-03 03:19:29 +00:00
|
|
|
sensitive: None,
|
2021-02-02 03:56:41 +00:00
|
|
|
original_files: self.files.clone(),
|
|
|
|
files: self.files.clone(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn id(&self) -> Uuid {
|
|
|
|
self.id
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn profile_id(&self) -> Uuid {
|
|
|
|
self.profile_id
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn title(&self) -> &str {
|
|
|
|
&self.title
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn title_source(&self) -> Option<&str> {
|
|
|
|
self.title_source.as_deref()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn description(&self) -> Option<&str> {
|
|
|
|
self.description.as_ref().map(|d| d.as_str())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn description_source(&self) -> Option<&str> {
|
|
|
|
self.description_source.as_deref()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn files(&self) -> &[Uuid] {
|
|
|
|
&self.files
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn published(&self) -> Option<DateTime<Utc>> {
|
|
|
|
self.published
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn updated(&self) -> Option<DateTime<Utc>> {
|
|
|
|
self.updated
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn visibility(&self) -> Visibility {
|
|
|
|
self.visibility
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_public(&self) -> bool {
|
|
|
|
matches!(self.visibility, Visibility::Public)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_unlisted(&self) -> bool {
|
|
|
|
matches!(self.visibility, Visibility::Unlisted)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_followers_only(&self) -> bool {
|
|
|
|
matches!(self.visibility, Visibility::Followers)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_local_only(&self) -> bool {
|
|
|
|
self.local_only
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_logged_in_only(&self) -> bool {
|
|
|
|
self.logged_in_only
|
|
|
|
}
|
2021-02-03 03:19:29 +00:00
|
|
|
|
|
|
|
pub fn is_sensitive(&self) -> bool {
|
|
|
|
self.sensitive
|
|
|
|
}
|
2021-02-02 03:56:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl SubmissionChanges {
|
|
|
|
pub(crate) fn title(&mut self, title: &str) -> &mut Self {
|
|
|
|
self.title = Some(title.to_owned());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn title_source(&mut self, title_source: &str) -> &mut Self {
|
|
|
|
self.title_source = Some(title_source.to_owned());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn description(&mut self, description: &str) -> &mut Self {
|
|
|
|
self.description = Some(description.to_owned());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn description_source(&mut self, description_source: &str) -> &mut Self {
|
|
|
|
self.description_source = Some(description_source.to_owned());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn visibility(&mut self, visibility: Visibility) -> &mut Self {
|
|
|
|
if self.visibility.is_none() {
|
|
|
|
self.visibility = Some(visibility);
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn published(&mut self, time: Option<DateTime<Utc>>) -> &mut Self {
|
|
|
|
if self.published.is_none() {
|
|
|
|
self.published = time.or_else(|| Some(Utc::now()));
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn updated(&mut self, time: DateTime<Utc>) -> &mut Self {
|
|
|
|
if self.published.is_some() {
|
|
|
|
self.updated = Some(time);
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn local_only(&mut self, local_only: bool) -> &mut Self {
|
|
|
|
if self.published.is_none() {
|
|
|
|
self.local_only = Some(local_only);
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn logged_in_only(&mut self, logged_in_only: bool) -> &mut Self {
|
|
|
|
if self.published.is_none() {
|
|
|
|
self.logged_in_only = Some(logged_in_only);
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-02-03 03:19:29 +00:00
|
|
|
pub(crate) fn sensitive(&mut self, sensitive: bool) -> &mut Self {
|
|
|
|
self.sensitive = Some(sensitive);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-02-09 00:01:24 +00:00
|
|
|
pub(crate) fn add_file(&mut self, file_id: Uuid) -> &mut Self {
|
|
|
|
self.files.push(file_id);
|
2021-02-02 03:56:41 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-02-09 00:01:24 +00:00
|
|
|
pub(crate) fn delete_file(&mut self, file_id: Uuid) -> &mut Self {
|
|
|
|
self.files.retain(|id| *id != file_id);
|
2021-02-02 03:56:41 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn any_changes(&self) -> bool {
|
2021-02-08 23:55:45 +00:00
|
|
|
self.title.is_some()
|
|
|
|
|| self.description.is_some()
|
|
|
|
|| self.published.is_some()
|
|
|
|
|| self.updated.is_some()
|
|
|
|
|| self.visibility.is_some()
|
|
|
|
|| self.local_only.is_some()
|
|
|
|
|| self.logged_in_only.is_some()
|
|
|
|
|| self.sensitive.is_some()
|
|
|
|
|| self.original_files != self.files
|
2021-02-02 03:56:41 +00:00
|
|
|
}
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Store {
|
|
|
|
pub fn build(db: &Db) -> Result<Self, sled::Error> {
|
|
|
|
Ok(Store {
|
|
|
|
submission_tree: db.open_tree("profiles/submissions")?,
|
|
|
|
profile_tree: db.open_tree("profiles/submissions/profile")?,
|
2021-01-11 04:03:20 +00:00
|
|
|
profile_drafted_tree: db.open_tree("/profiles/submissions/profile/drafted")?,
|
|
|
|
published_tree: db.open_tree("/profiles/submission/published")?,
|
|
|
|
profile_published_tree: db.open_tree("/profiles/submission/profile/published")?,
|
2021-01-04 17:34:31 +00:00
|
|
|
count_tree: db.open_tree("/profiles/submissions/count")?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn create(
|
|
|
|
&self,
|
|
|
|
profile_id: Uuid,
|
|
|
|
title: &str,
|
|
|
|
visibility: Visibility,
|
|
|
|
) -> Result<Submission, StoreError> {
|
|
|
|
let mut id;
|
|
|
|
let mut stored_submission;
|
|
|
|
|
|
|
|
let now = Utc::now().into();
|
|
|
|
let mut stored_submission_vec = vec![];
|
|
|
|
|
|
|
|
while {
|
|
|
|
stored_submission_vec.clear();
|
|
|
|
let writer = Cursor::new(&mut stored_submission_vec);
|
|
|
|
id = Uuid::new_v4();
|
|
|
|
stored_submission = StoredSubmission {
|
|
|
|
id,
|
|
|
|
profile_id,
|
2021-01-16 04:25:06 +00:00
|
|
|
title: title.to_owned(),
|
2021-01-28 02:58:38 +00:00
|
|
|
title_source: None,
|
2021-01-04 17:34:31 +00:00
|
|
|
description: None,
|
2021-01-28 02:58:38 +00:00
|
|
|
description_source: None,
|
2021-01-04 17:34:31 +00:00
|
|
|
files: vec![],
|
|
|
|
published: None,
|
2021-02-02 03:56:41 +00:00
|
|
|
updated: None,
|
2021-01-04 17:34:31 +00:00
|
|
|
visibility,
|
2021-02-02 03:56:41 +00:00
|
|
|
local_only: false,
|
|
|
|
logged_in_only: false,
|
2021-01-11 04:03:20 +00:00
|
|
|
drafted_at: now,
|
2021-02-03 03:19:29 +00:00
|
|
|
sensitive: false,
|
2021-01-04 17:34:31 +00:00
|
|
|
};
|
|
|
|
serde_json::to_writer(writer, &stored_submission)?;
|
|
|
|
self.submission_tree
|
|
|
|
.compare_and_swap(
|
|
|
|
id_submission_key(id).as_bytes(),
|
|
|
|
None as Option<&[u8]>,
|
|
|
|
Some(stored_submission_vec.as_slice()),
|
|
|
|
)?
|
|
|
|
.is_err()
|
|
|
|
} {}
|
|
|
|
|
|
|
|
let res = [
|
|
|
|
&self.profile_tree,
|
2021-01-11 04:03:20 +00:00
|
|
|
&self.profile_drafted_tree,
|
2021-01-04 17:34:31 +00:00
|
|
|
&self.count_tree,
|
|
|
|
]
|
|
|
|
.transaction(move |trees| {
|
|
|
|
let profile_tree = &trees[0];
|
2021-01-11 04:03:20 +00:00
|
|
|
let profile_drafted_tree = &trees[1];
|
|
|
|
let count_tree = &trees[2];
|
2021-01-04 17:34:31 +00:00
|
|
|
|
|
|
|
profile_tree.insert(
|
|
|
|
profile_id_key(profile_id, id).as_bytes(),
|
|
|
|
id.to_string().as_bytes(),
|
|
|
|
)?;
|
2021-01-11 04:03:20 +00:00
|
|
|
profile_drafted_tree.insert(
|
|
|
|
profile_id_drafted_submission_key(profile_id, now, id).as_bytes(),
|
2021-01-04 17:34:31 +00:00
|
|
|
id.to_string().as_bytes(),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
super::count(
|
|
|
|
count_tree,
|
|
|
|
&profile_id_submission_count_key(profile_id),
|
|
|
|
|c| c.saturating_add(1),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
});
|
|
|
|
|
|
|
|
if let Err(e) = res {
|
|
|
|
self.submission_tree
|
|
|
|
.remove(id_submission_key(id).as_bytes())?;
|
|
|
|
return Err(e.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(stored_submission.into())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn count(&self, profile_id: Uuid) -> Result<u64, StoreError> {
|
|
|
|
match self
|
|
|
|
.count_tree
|
|
|
|
.get(profile_id_submission_count_key(profile_id))?
|
|
|
|
{
|
|
|
|
Some(ivec) => Ok(String::from_utf8_lossy(&ivec)
|
|
|
|
.parse::<u64>()
|
|
|
|
.expect("Count is valid")),
|
|
|
|
None => Ok(0),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn update(&self, changes: &SubmissionChanges) -> Result<Submission, StoreError> {
|
|
|
|
let stored_submission_ivec =
|
|
|
|
match self.submission_tree.get(id_submission_key(changes.id))? {
|
|
|
|
Some(ivec) => ivec,
|
|
|
|
None => return Err(StoreError::Missing),
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut stored_submission: StoredSubmission =
|
|
|
|
serde_json::from_slice(&stored_submission_ivec)?;
|
|
|
|
|
2021-02-02 03:56:41 +00:00
|
|
|
if let Some(updated) = changes.updated {
|
|
|
|
if let Some(previously_updated) = stored_submission
|
|
|
|
.updated
|
|
|
|
.or_else(|| stored_submission.published)
|
|
|
|
{
|
|
|
|
if updated < previously_updated {
|
|
|
|
return Err(StoreError::Outdated);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-11 04:03:20 +00:00
|
|
|
let already_published = stored_submission.published.is_some();
|
|
|
|
|
2021-01-28 02:58:38 +00:00
|
|
|
if let Some(title) = &changes.title {
|
2021-01-16 04:25:06 +00:00
|
|
|
stored_submission.title = title.to_owned();
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
2021-01-28 02:58:38 +00:00
|
|
|
if let Some(title_source) = &changes.title_source {
|
|
|
|
stored_submission.title_source = Some(title_source.clone());
|
|
|
|
}
|
|
|
|
if let Some(description) = &changes.description {
|
2021-01-16 04:25:06 +00:00
|
|
|
stored_submission.description = Some(description.to_owned());
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
2021-01-28 02:58:38 +00:00
|
|
|
if let Some(description_source) = &changes.description_source {
|
|
|
|
stored_submission.description_source = Some(description_source.clone());
|
|
|
|
}
|
2021-02-02 01:02:15 +00:00
|
|
|
if let Some(visibility) = changes.visibility {
|
|
|
|
stored_submission.visibility = visibility;
|
|
|
|
}
|
2021-02-02 03:56:41 +00:00
|
|
|
if let Some(local_only) = changes.local_only {
|
|
|
|
stored_submission.local_only = local_only;
|
|
|
|
}
|
|
|
|
if let Some(logged_in_only) = changes.logged_in_only {
|
|
|
|
stored_submission.logged_in_only = logged_in_only;
|
|
|
|
}
|
2021-02-03 03:19:29 +00:00
|
|
|
if let Some(sensitive) = changes.sensitive {
|
|
|
|
stored_submission.sensitive = sensitive;
|
|
|
|
}
|
2021-02-02 03:56:41 +00:00
|
|
|
if stored_submission.published.is_some() {
|
|
|
|
if let Some(updated) = changes.updated {
|
|
|
|
stored_submission.updated = Some(updated);
|
|
|
|
}
|
|
|
|
}
|
2021-01-04 17:34:31 +00:00
|
|
|
stored_submission.published = changes.published;
|
2021-02-08 23:55:45 +00:00
|
|
|
stored_submission.files = changes.files.clone();
|
2021-01-04 17:34:31 +00:00
|
|
|
|
|
|
|
let stored_submission_vec = serde_json::to_vec(&stored_submission)?;
|
|
|
|
|
|
|
|
if self
|
|
|
|
.submission_tree
|
|
|
|
.compare_and_swap(
|
|
|
|
id_submission_key(changes.id),
|
|
|
|
Some(&stored_submission_ivec),
|
|
|
|
Some(stored_submission_vec.as_slice()),
|
|
|
|
)?
|
|
|
|
.is_err()
|
|
|
|
{
|
|
|
|
return Err(StoreError::DoubleStore);
|
|
|
|
}
|
|
|
|
|
2021-01-11 04:03:20 +00:00
|
|
|
if !already_published {
|
|
|
|
if let Some(published) = changes.published {
|
|
|
|
self.profile_drafted_tree
|
|
|
|
.remove(profile_id_drafted_submission_key(
|
|
|
|
stored_submission.profile_id,
|
|
|
|
stored_submission.drafted_at,
|
|
|
|
stored_submission.id,
|
|
|
|
))?;
|
|
|
|
|
|
|
|
self.published_tree.insert(
|
|
|
|
published_submission_key(published, stored_submission.id),
|
|
|
|
stored_submission.id.to_string().as_bytes(),
|
|
|
|
)?;
|
|
|
|
self.profile_published_tree.insert(
|
|
|
|
published_profile_submission_key(
|
|
|
|
stored_submission.profile_id,
|
|
|
|
published,
|
|
|
|
stored_submission.id,
|
|
|
|
),
|
|
|
|
stored_submission.id.to_string().as_bytes(),
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-04 17:34:31 +00:00
|
|
|
Ok(stored_submission.into())
|
|
|
|
}
|
|
|
|
|
2021-01-11 04:03:20 +00:00
|
|
|
fn extract<T, F>(&self, submission_id: Uuid, f: F) -> Option<T>
|
|
|
|
where
|
2021-01-16 04:25:06 +00:00
|
|
|
F: FnOnce(StoredSubmission) -> Option<T>,
|
2021-01-11 04:03:20 +00:00
|
|
|
{
|
|
|
|
let ivec = self
|
|
|
|
.submission_tree
|
|
|
|
.get(id_submission_key(submission_id))
|
|
|
|
.ok()??;
|
|
|
|
let stored_submission: StoredSubmission = serde_json::from_slice(&ivec).ok()?;
|
|
|
|
|
|
|
|
(f)(stored_submission)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn published(&self) -> impl DoubleEndedIterator<Item = Uuid> {
|
|
|
|
self.published_tree
|
|
|
|
.scan_prefix(published_prefix())
|
2021-01-04 17:34:31 +00:00
|
|
|
.values()
|
|
|
|
.filter_map(|res| res.ok())
|
2021-01-11 04:03:20 +00:00
|
|
|
.filter_map(uuid_from_ivec)
|
2021-01-10 01:48:04 +00:00
|
|
|
.rev()
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
2021-01-11 04:03:20 +00:00
|
|
|
fn published_date_range<K>(
|
2021-01-04 17:34:31 +00:00
|
|
|
&self,
|
|
|
|
range: impl std::ops::RangeBounds<K>,
|
|
|
|
) -> impl DoubleEndedIterator<Item = Uuid>
|
|
|
|
where
|
|
|
|
K: AsRef<[u8]>,
|
|
|
|
{
|
2021-01-11 04:03:20 +00:00
|
|
|
self.published_tree
|
2021-01-04 17:34:31 +00:00
|
|
|
.range(range)
|
|
|
|
.values()
|
|
|
|
.filter_map(|res| res.ok())
|
2021-01-11 04:03:20 +00:00
|
|
|
.filter_map(uuid_from_ivec)
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
2021-01-11 04:03:20 +00:00
|
|
|
pub fn published_newer_than(
|
|
|
|
&self,
|
|
|
|
submission_id: Uuid,
|
|
|
|
) -> impl DoubleEndedIterator<Item = Uuid> {
|
2021-01-04 17:34:31 +00:00
|
|
|
let this = self.clone();
|
|
|
|
|
2021-01-11 04:03:20 +00:00
|
|
|
self.extract(submission_id, |stored_submission| {
|
|
|
|
stored_submission.published
|
|
|
|
})
|
|
|
|
.into_iter()
|
|
|
|
.flat_map(move |published| {
|
2021-01-25 02:02:03 +00:00
|
|
|
let range_entry = published_submission_range_start(published);
|
|
|
|
let range_entry = range_entry.as_bytes().to_vec();
|
|
|
|
this.published_date_range(range_entry..)
|
2021-01-11 04:03:20 +00:00
|
|
|
})
|
|
|
|
}
|
2021-01-04 17:34:31 +00:00
|
|
|
|
2021-01-11 04:03:20 +00:00
|
|
|
pub fn published_older_than(
|
|
|
|
&self,
|
|
|
|
submission_id: Uuid,
|
|
|
|
) -> impl DoubleEndedIterator<Item = Uuid> {
|
|
|
|
let this = self.clone();
|
|
|
|
|
|
|
|
self.extract(submission_id, |stored_submission| {
|
|
|
|
stored_submission.published
|
|
|
|
})
|
|
|
|
.into_iter()
|
|
|
|
.flat_map(move |published| {
|
2021-01-25 02:02:03 +00:00
|
|
|
let range_entry = published_submission_range_start(published);
|
|
|
|
let range_entry = range_entry.as_bytes().to_vec();
|
|
|
|
this.published_date_range(..range_entry)
|
2021-01-11 04:03:20 +00:00
|
|
|
})
|
|
|
|
.rev()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn drafted_for_profile(&self, profile_id: Uuid) -> impl DoubleEndedIterator<Item = Uuid> {
|
|
|
|
self.profile_drafted_tree
|
|
|
|
.scan_prefix(profile_id_drafted_prefix(profile_id))
|
|
|
|
.values()
|
|
|
|
.filter_map(|res| res.ok())
|
|
|
|
.filter_map(uuid_from_ivec)
|
|
|
|
.rev()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn drafted_date_range_for_profile<K>(
|
|
|
|
&self,
|
|
|
|
range: impl std::ops::RangeBounds<K>,
|
|
|
|
) -> impl DoubleEndedIterator<Item = Uuid>
|
|
|
|
where
|
|
|
|
K: AsRef<[u8]>,
|
|
|
|
{
|
|
|
|
self.profile_drafted_tree
|
|
|
|
.range(range)
|
|
|
|
.values()
|
|
|
|
.filter_map(|res| res.ok())
|
|
|
|
.filter_map(uuid_from_ivec)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn drafted_newer_than_for_profile(
|
|
|
|
&self,
|
|
|
|
id: Uuid,
|
|
|
|
) -> impl DoubleEndedIterator<Item = Uuid> {
|
|
|
|
let this = self.clone();
|
|
|
|
|
|
|
|
self.extract(id, |s| Some((s.profile_id, s.drafted_at)))
|
2021-01-04 17:34:31 +00:00
|
|
|
.into_iter()
|
2021-01-11 04:03:20 +00:00
|
|
|
.flat_map(move |(profile_id, drafted)| {
|
2021-01-25 02:02:03 +00:00
|
|
|
let range_end = profile_id_drafted_submission_range_end(profile_id);
|
|
|
|
let range_entry = profile_id_drafted_submission_range_entry(profile_id, drafted);
|
|
|
|
this.drafted_date_range_for_profile(range_entry..range_end)
|
2021-01-04 17:34:31 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-01-11 04:03:20 +00:00
|
|
|
pub fn drafted_older_than_for_profile(
|
|
|
|
&self,
|
|
|
|
id: Uuid,
|
|
|
|
) -> impl DoubleEndedIterator<Item = Uuid> {
|
2021-01-04 17:34:31 +00:00
|
|
|
let this = self.clone();
|
|
|
|
|
2021-01-11 04:03:20 +00:00
|
|
|
self.extract(id, |s| Some((s.profile_id, s.drafted_at)))
|
|
|
|
.into_iter()
|
|
|
|
.flat_map(move |(profile_id, drafted)| {
|
2021-01-25 02:02:03 +00:00
|
|
|
let range_start = profile_id_drafted_submission_range_beginning(profile_id);
|
|
|
|
let range_entry = profile_id_drafted_submission_range_entry(profile_id, drafted);
|
|
|
|
this.drafted_date_range_for_profile(range_start..range_entry)
|
2021-01-11 04:03:20 +00:00
|
|
|
})
|
|
|
|
.rev()
|
|
|
|
}
|
2021-01-04 17:34:31 +00:00
|
|
|
|
2021-01-11 04:03:20 +00:00
|
|
|
pub fn published_for_profile(&self, profile_id: Uuid) -> impl DoubleEndedIterator<Item = Uuid> {
|
|
|
|
self.profile_published_tree
|
|
|
|
.scan_prefix(profile_id_published_prefix(profile_id))
|
|
|
|
.values()
|
|
|
|
.filter_map(|res| res.ok())
|
|
|
|
.filter_map(uuid_from_ivec)
|
|
|
|
.rev()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn published_date_range_for_profile<K>(
|
|
|
|
&self,
|
|
|
|
range: impl std::ops::RangeBounds<K>,
|
|
|
|
) -> impl DoubleEndedIterator<Item = Uuid>
|
|
|
|
where
|
|
|
|
K: AsRef<[u8]>,
|
|
|
|
{
|
|
|
|
self.profile_published_tree
|
|
|
|
.range(range)
|
|
|
|
.values()
|
|
|
|
.filter_map(|res| res.ok())
|
|
|
|
.filter_map(uuid_from_ivec)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn published_newer_than_for_profile(
|
|
|
|
&self,
|
|
|
|
id: Uuid,
|
|
|
|
) -> impl DoubleEndedIterator<Item = Uuid> {
|
|
|
|
let this = self.clone();
|
|
|
|
|
|
|
|
self.extract(id, |s| s.published.map(|p| (s.profile_id, p)))
|
|
|
|
.into_iter()
|
|
|
|
.flat_map(move |(profile_id, published)| {
|
2021-01-25 02:02:03 +00:00
|
|
|
let range_end = profile_id_publshed_submission_range_end(profile_id);
|
|
|
|
let range_entry = profile_id_publshed_submission_range_entry(profile_id, published);
|
|
|
|
this.published_date_range_for_profile(range_entry..range_end)
|
2021-01-04 17:34:31 +00:00
|
|
|
})
|
2021-01-11 04:03:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn published_older_than_for_profile(
|
|
|
|
&self,
|
|
|
|
id: Uuid,
|
|
|
|
) -> impl DoubleEndedIterator<Item = Uuid> {
|
|
|
|
let this = self.clone();
|
|
|
|
|
|
|
|
self.extract(id, |s| s.published.map(|p| (s.profile_id, p)))
|
2021-01-04 17:34:31 +00:00
|
|
|
.into_iter()
|
2021-01-11 04:03:20 +00:00
|
|
|
.flat_map(move |(profile_id, published)| {
|
2021-01-25 02:02:03 +00:00
|
|
|
let range_start = profile_id_publshed_submission_range_beginning(profile_id);
|
|
|
|
let range_entry = profile_id_publshed_submission_range_entry(profile_id, published);
|
|
|
|
this.published_date_range_for_profile(range_start..range_entry)
|
2021-01-04 17:34:31 +00:00
|
|
|
})
|
2021-01-10 01:48:04 +00:00
|
|
|
.rev()
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn by_id(&self, id: Uuid) -> Result<Option<Submission>, StoreError> {
|
|
|
|
let stored_submission_ivec = match self.submission_tree.get(id_submission_key(id))? {
|
|
|
|
Some(ivec) => ivec,
|
|
|
|
None => return Ok(None),
|
|
|
|
};
|
|
|
|
|
|
|
|
let stored_submission: StoredSubmission = serde_json::from_slice(&stored_submission_ivec)?;
|
|
|
|
|
|
|
|
Ok(Some(stored_submission.into()))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn delete(&self, submission_id: Uuid) -> Result<Option<Undo<Submission>>, StoreError> {
|
|
|
|
let stored_submission_ivec =
|
|
|
|
match self.submission_tree.get(id_submission_key(submission_id))? {
|
|
|
|
Some(ivec) => ivec,
|
|
|
|
None => return Ok(None),
|
|
|
|
};
|
|
|
|
|
|
|
|
let stored_submission: StoredSubmission = serde_json::from_slice(&stored_submission_ivec)?;
|
|
|
|
|
|
|
|
let id = submission_id;
|
|
|
|
let profile_id = stored_submission.profile_id;
|
2021-01-11 04:03:20 +00:00
|
|
|
let drafted_at = stored_submission.drafted_at;
|
|
|
|
let published = stored_submission.published;
|
2021-01-04 17:34:31 +00:00
|
|
|
|
|
|
|
[
|
|
|
|
&self.submission_tree,
|
|
|
|
&self.profile_tree,
|
2021-01-11 04:03:20 +00:00
|
|
|
&self.profile_drafted_tree,
|
|
|
|
&self.published_tree,
|
|
|
|
&self.profile_published_tree,
|
2021-01-04 17:34:31 +00:00
|
|
|
&self.count_tree,
|
|
|
|
]
|
|
|
|
.transaction(move |trees| {
|
|
|
|
let submission_tree = &trees[0];
|
|
|
|
let profile_tree = &trees[1];
|
2021-01-11 04:03:20 +00:00
|
|
|
let profile_drafted_tree = &trees[2];
|
|
|
|
let published_tree = &trees[3];
|
|
|
|
let profile_published_tree = &trees[4];
|
|
|
|
let count_tree = &trees[5];
|
2021-01-04 17:34:31 +00:00
|
|
|
|
|
|
|
submission_tree.remove(id_submission_key(id).as_bytes())?;
|
|
|
|
profile_tree.remove(profile_id_key(profile_id, id).as_bytes())?;
|
2021-01-11 04:03:20 +00:00
|
|
|
profile_drafted_tree
|
|
|
|
.remove(profile_id_drafted_submission_key(profile_id, drafted_at, id).as_bytes())?;
|
|
|
|
|
|
|
|
if let Some(published) = published {
|
|
|
|
published_tree.remove(published_submission_key(published, id).as_bytes())?;
|
|
|
|
profile_published_tree.remove(
|
|
|
|
published_profile_submission_key(profile_id, published, id).as_bytes(),
|
|
|
|
)?;
|
|
|
|
}
|
2021-01-04 17:34:31 +00:00
|
|
|
|
|
|
|
super::count(
|
|
|
|
count_tree,
|
|
|
|
&profile_id_submission_count_key(profile_id),
|
|
|
|
|c| c.saturating_sub(1),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})?;
|
|
|
|
|
|
|
|
Ok(Some(Undo(stored_submission.into())))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-11 04:03:20 +00:00
|
|
|
fn uuid_from_ivec(ivec: sled::IVec) -> Option<Uuid> {
|
|
|
|
String::from_utf8_lossy(&ivec).parse().ok()
|
|
|
|
}
|
|
|
|
|
2021-01-04 17:34:31 +00:00
|
|
|
// Used to map id -> Submission
|
|
|
|
fn id_submission_key(id: Uuid) -> String {
|
|
|
|
format!("/submission/{}/data", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Used to map profile_id -> id
|
|
|
|
fn profile_id_key(profile_id: Uuid, id: Uuid) -> String {
|
|
|
|
format!("/profile/{}/submission/{}", profile_id, id)
|
|
|
|
}
|
|
|
|
|
2021-01-11 04:03:20 +00:00
|
|
|
fn profile_id_published_prefix(profile_id: Uuid) -> String {
|
|
|
|
format!("/profile/{}/published", profile_id)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn profile_id_drafted_prefix(profile_id: Uuid) -> String {
|
|
|
|
format!("/profile/{}/drafted", profile_id)
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Used to fetch submissions for a given profile in a user-recognizalbe order
|
2021-01-11 04:03:20 +00:00
|
|
|
fn profile_id_drafted_submission_key(
|
2021-01-04 17:34:31 +00:00
|
|
|
profile_id: Uuid,
|
2021-01-11 04:03:20 +00:00
|
|
|
drafted_at: DateTime<Utc>,
|
2021-01-04 17:34:31 +00:00
|
|
|
id: Uuid,
|
|
|
|
) -> String {
|
|
|
|
format!(
|
2021-01-11 04:03:20 +00:00
|
|
|
"/profile/{}/drafted/{}/submission/{}",
|
2021-01-04 17:34:31 +00:00
|
|
|
profile_id,
|
2021-01-11 04:03:20 +00:00
|
|
|
drafted_at.to_rfc3339(),
|
2021-01-04 17:34:31 +00:00
|
|
|
id
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-01-25 02:02:03 +00:00
|
|
|
fn profile_id_drafted_submission_range_beginning(profile_id: Uuid) -> String {
|
|
|
|
format!("/profile/{}/drafted/", profile_id)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn profile_id_drafted_submission_range_entry(
|
2021-01-04 17:34:31 +00:00
|
|
|
profile_id: Uuid,
|
2021-01-11 04:03:20 +00:00
|
|
|
drafted_at: DateTime<Utc>,
|
2021-01-04 17:34:31 +00:00
|
|
|
) -> String {
|
|
|
|
format!(
|
2021-01-11 04:03:20 +00:00
|
|
|
"/profile/{}/drafted/{}",
|
2021-01-04 17:34:31 +00:00
|
|
|
profile_id,
|
2021-01-11 04:03:20 +00:00
|
|
|
drafted_at.to_rfc3339()
|
2021-01-04 17:34:31 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-01-25 02:02:03 +00:00
|
|
|
fn profile_id_drafted_submission_range_end(profile_id: Uuid) -> String {
|
|
|
|
format!("/profile/{}/draftee/", profile_id)
|
|
|
|
}
|
|
|
|
|
2021-01-11 04:03:20 +00:00
|
|
|
fn published_profile_submission_key(
|
|
|
|
profile_id: Uuid,
|
|
|
|
published: DateTime<Utc>,
|
|
|
|
id: Uuid,
|
|
|
|
) -> String {
|
|
|
|
format!(
|
|
|
|
"/profile/{}/published/{}/submission/{}",
|
|
|
|
profile_id,
|
|
|
|
published.to_rfc3339(),
|
|
|
|
id
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn published_submission_key(published: DateTime<Utc>, id: Uuid) -> String {
|
|
|
|
format!("/published/{}/submission/{}", published.to_rfc3339(), id)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn published_prefix() -> String {
|
|
|
|
"/published".to_owned()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn published_submission_range_start(published: DateTime<Utc>) -> String {
|
|
|
|
format!("/published/{}", published.to_rfc3339())
|
|
|
|
}
|
|
|
|
|
2021-01-25 02:02:03 +00:00
|
|
|
fn profile_id_publshed_submission_range_beginning(profile_id: Uuid) -> String {
|
|
|
|
format!("/profile/{}/published/", profile_id)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn profile_id_publshed_submission_range_entry(
|
2021-01-11 04:03:20 +00:00
|
|
|
profile_id: Uuid,
|
|
|
|
published: DateTime<Utc>,
|
|
|
|
) -> String {
|
|
|
|
format!(
|
|
|
|
"/profile/{}/published/{}",
|
|
|
|
profile_id,
|
|
|
|
published.to_rfc3339()
|
|
|
|
)
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
2021-01-25 02:02:03 +00:00
|
|
|
fn profile_id_publshed_submission_range_end(profile_id: Uuid) -> String {
|
|
|
|
format!("/profile/{}/publishee/", profile_id)
|
|
|
|
}
|
|
|
|
|
2021-01-04 17:34:31 +00:00
|
|
|
fn profile_id_submission_count_key(profile_id: Uuid) -> String {
|
|
|
|
format!("/profile/{}/count", profile_id)
|
|
|
|
}
|
|
|
|
|
2021-01-16 04:25:06 +00:00
|
|
|
impl From<StoredSubmission> for Submission {
|
|
|
|
fn from(ss: StoredSubmission) -> Self {
|
2021-01-04 17:34:31 +00:00
|
|
|
Submission {
|
|
|
|
id: ss.id,
|
|
|
|
profile_id: ss.profile_id,
|
2021-01-16 04:25:06 +00:00
|
|
|
title: ss.title,
|
2021-01-28 02:58:38 +00:00
|
|
|
title_source: ss.title_source,
|
2021-01-16 04:25:06 +00:00
|
|
|
description: ss.description,
|
2021-01-28 02:58:38 +00:00
|
|
|
description_source: ss.description_source,
|
2021-01-16 04:25:06 +00:00
|
|
|
files: ss.files,
|
2021-01-04 17:34:31 +00:00
|
|
|
published: ss.published,
|
2021-02-02 03:56:41 +00:00
|
|
|
updated: ss.updated,
|
2021-01-04 17:34:31 +00:00
|
|
|
visibility: ss.visibility,
|
2021-02-02 03:56:41 +00:00
|
|
|
local_only: ss.local_only,
|
|
|
|
logged_in_only: ss.logged_in_only,
|
2021-02-03 03:19:29 +00:00
|
|
|
sensitive: ss.sensitive,
|
2021-02-02 03:56:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for Visibility {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
Visibility::Public => write!(f, "Public"),
|
|
|
|
Visibility::Unlisted => write!(f, "Unlisted"),
|
|
|
|
Visibility::Followers => write!(f, "Followers"),
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|