Profiles: Move bbcode, html sanitizing to Changes types

Handle Updated timestamp for profiles, servers, comments
This commit is contained in:
asonix 2021-02-08 21:14:51 -06:00
parent 0916828a0a
commit 22d8f85fba
15 changed files with 672 additions and 466 deletions

View file

@ -57,6 +57,7 @@ pub(crate) fn application(
.published()
.map(|published| published.into())
.unwrap_or_else(|| Utc::now());
let updated = application.updated().map(|updated| updated.into());
let endpoints = Endpoints {
inbox,
@ -78,6 +79,7 @@ pub(crate) fn application(
title_source: None,
description,
description_source: None,
updated: updated.req("updated")?,
})))
} else {
Ok(Ok(Box::new(CreateServer {
@ -90,6 +92,7 @@ pub(crate) fn application(
title,
description,
published,
updated,
})))
}
}
@ -158,6 +161,7 @@ pub(crate) fn update_application(
.map(|s| s.to_owned());
let public_key_id = application.ext_one.public_key.id.clone();
let public_key = application.ext_one.public_key.public_key_pem.clone();
let updated = application.updated().req("updated")?.into();
let endpoints = Endpoints {
inbox,
@ -178,6 +182,7 @@ pub(crate) fn update_application(
title_source: None,
description,
description_source: None,
updated,
})))
}

View file

@ -91,9 +91,10 @@ pub(super) fn note(
submission_id,
profile_id,
comment_id: None,
body: body.to_owned(),
body: Some(body.to_owned()),
body_source: None,
published: published.into(),
updated: updated.map(|u| u.into()),
})));
}
@ -110,9 +111,10 @@ pub(super) fn note(
submission_id,
profile_id,
comment_id: Some(comment_id),
body: body.to_owned(),
body: Some(body.to_owned()),
body_source: None,
published: published.into(),
updated: updated.map(|u| u.into()),
})));
}
@ -123,9 +125,8 @@ pub(super) fn note(
let title = note
.summary()
.req("summary")?
.as_single_xsd_string()
.req("summary string")?;
.and_then(|s| s.as_single_xsd_string())
.map(|s| s.to_owned());
let description = note
.content()
.and_then(|c| c.as_single_xsd_string())
@ -174,7 +175,7 @@ pub(super) fn note(
return Ok(Ok(Box::new(CreateSubmission {
note_apub_id: Some(note_id.to_owned()),
profile_id,
title: title.to_owned(),
title,
description,
published: Some(published.into()),
updated: updated.map(|u| u.into()),
@ -324,6 +325,7 @@ pub(super) fn update_note(
let note_id = note.id_unchecked().req("note id")?;
let note_id = recover!(note_id, ctx.apub.id_for_apub(note_id)?);
let updated = note.updated().req("updated")?;
let profile_id = recover!(attributed_to, ctx.apub.id_for_apub(attributed_to)?);
let profile_id = profile_id
@ -353,6 +355,7 @@ pub(super) fn update_note(
comment_id,
body,
body_source: None,
updated: updated.into(),
})));
}
@ -376,7 +379,6 @@ pub(super) fn update_note(
.and_then(|c| c.as_single_xsd_string())
.map(|s| s.to_owned());
let published = note.published().req("published")?;
let updated = note.updated().req("updated")?;
let mut missing_files = vec![];
let mut existing_files = vec![];
@ -425,7 +427,7 @@ pub(super) fn update_note(
description_source: None,
visibility: Some(find_visibility(note)),
published: Some(published.into()),
updated: Some(updated.into()),
updated: updated.into(),
removed_files: None,
new_files: None,
only_files: Some(existing_files),

View file

@ -137,7 +137,7 @@ pub(crate) fn person(
login_required: Some(login_required),
icon,
banner,
updated,
updated: updated.req("updated")?,
})))
} else {
Ok(Ok(Box::new(CreateProfile {
@ -223,7 +223,7 @@ pub(crate) fn update_person(
.map(|s| s.to_owned());
let public_key_id = person.ext_one.public_key.id.clone();
let public_key = person.ext_one.public_key.public_key_pem.clone();
let updated = person.updated().map(|u| u.into());
let updated = person.updated().map(|u| u.into()).req("updated")?;
let login_required = person
.to()

View file

@ -30,21 +30,24 @@ impl Action for CreateComment {
None
};
let comment = ctx.store.comments.create(
self.submission_id,
self.profile_id,
self.comment_id,
&hyaenidae_content::html(&self.body),
self.published,
)?;
let comment = if let Some(body_source) = &self.body_source {
let mut changes =
ctx.store
.comments
.update(comment.update().body_source(body_source))?
} else {
comment
};
.create(ctx, self.submission_id, self.profile_id, self.comment_id);
changes.published(self.published);
if let Some(updated) = self.updated {
changes.updated(updated);
}
if let Some(body) = &self.body {
changes.body(body);
}
if let Some(body_source) = &self.body_source {
changes.body_source(body_source);
}
let comment = changes.save()?;
if let Some(apub_id) = &self.note_apub_id {
ctx.apub.comment(apub_id, comment.id())?;
@ -112,15 +115,16 @@ impl Action for UpdateComment {
return Err(Error::Deleted);
}
let mut changes = comment.update();
let mut changes = comment.update(ctx);
changes.updated(self.updated);
if let Some(body) = &self.body {
changes.body(&hyaenidae_content::html(body));
changes.body(body);
}
if let Some(body_source) = &self.body_source {
changes.body_source(body_source);
}
let comment = if changes.any_changes() {
ctx.store.comments.update(&changes)?
changes.save()?
} else {
comment
};

View file

@ -3,7 +3,6 @@ use crate::{
store::{OwnerSource, Visibility},
};
use chrono::{DateTime, Utc};
use hyaenidae_content::bbcode;
use url::Url;
use uuid::Uuid;
@ -36,6 +35,7 @@ pub struct CreateServer {
title: Option<String>,
description: Option<String>,
published: DateTime<Utc>,
updated: Option<DateTime<Utc>>,
}
impl CreateServer {
@ -46,6 +46,7 @@ impl CreateServer {
title: None,
description: None,
published: Utc::now(),
updated: None,
}
}
}
@ -62,31 +63,23 @@ pub struct UpdateServer {
title_source: Option<String>,
description: Option<String>,
description_source: Option<String>,
updated: DateTime<Utc>,
}
impl UpdateServer {
pub fn from_text(server_id: Uuid, title: Option<String>, description: Option<String>) -> Self {
let title_source = title.and_then(|title| {
if title.trim().is_empty() {
None
} else {
Some(title.trim().to_owned())
}
});
let description_source = description.and_then(|description| {
if description.trim().is_empty() {
None
} else {
Some(description.trim().to_owned())
}
});
pub fn from_text(
server_id: Uuid,
title_source: Option<String>,
description_source: Option<String>,
) -> Self {
UpdateServer {
apub: None,
server_id,
title: title_source.clone(),
title: None,
title_source,
description: description_source.as_deref().map(|s| bbcode(s, |v| v)),
description: None,
description_source,
updated: Utc::now(),
}
}
}
@ -194,9 +187,10 @@ pub struct CreateComment {
submission_id: Uuid,
profile_id: Uuid,
comment_id: Option<Uuid>,
body: String,
body: Option<String>,
body_source: Option<String>,
published: DateTime<Utc>,
updated: Option<DateTime<Utc>>,
}
impl CreateComment {
@ -204,19 +198,17 @@ impl CreateComment {
submission_id: Uuid,
profile_id: Uuid,
comment_id: Option<Uuid>,
body: String,
body_source: String,
) -> Self {
let body_source = body;
let body = bbcode(&body_source, |v| v);
CreateComment {
note_apub_id: None,
submission_id,
profile_id,
comment_id,
body,
body: None,
body_source: Some(body_source),
published: Utc::now(),
updated: None,
}
}
}
@ -228,21 +220,17 @@ pub struct UpdateComment {
comment_id: Uuid,
body: Option<String>,
body_source: Option<String>,
updated: DateTime<Utc>,
}
impl UpdateComment {
pub fn from_text(comment_id: Uuid, body: String) -> Self {
let body_source = if body.trim().is_empty() {
None
} else {
Some(body.trim().to_owned())
};
pub fn from_text(comment_id: Uuid, body_source: String) -> Self {
UpdateComment {
update_apub_id: None,
comment_id,
body: body_source.as_deref().map(|s| bbcode(s, |v| v)),
body_source,
body: None,
body_source: Some(body_source),
updated: Utc::now(),
}
}
}
@ -264,7 +252,7 @@ impl DeleteComment {
pub struct CreateSubmission {
note_apub_id: Option<Url>,
profile_id: Uuid,
title: String,
title: Option<String>,
description: Option<String>,
files: Vec<Uuid>,
published: Option<DateTime<Utc>>,
@ -280,7 +268,7 @@ impl CreateSubmission {
CreateSubmission {
note_apub_id: None,
profile_id,
title: "".to_owned(),
title: None,
description: None,
files: vec![file_id],
published: None,
@ -303,7 +291,7 @@ pub struct UpdateSubmission {
local_only: Option<bool>,
logged_in_only: Option<bool>,
published: Option<DateTime<Utc>>,
updated: Option<DateTime<Utc>>,
updated: DateTime<Utc>,
removed_files: Option<Vec<Uuid>>,
new_files: Option<Vec<Uuid>>,
only_files: Option<Vec<Uuid>>,
@ -311,32 +299,22 @@ pub struct UpdateSubmission {
}
impl UpdateSubmission {
pub fn from_text(submission_id: Uuid, title: String, description: Option<String>) -> Self {
let title_source = if title.trim().is_empty() {
None
} else {
Some(title.trim().to_owned())
};
let description_source = description.and_then(|s| {
if s.trim().is_empty() {
None
} else {
Some(s.trim().to_owned())
}
});
pub fn from_text(
submission_id: Uuid,
title_source: String,
description_source: Option<String>,
) -> Self {
UpdateSubmission {
submission_id,
title: title_source.clone(),
title_source,
description: description_source.as_deref().map(|s| bbcode(s, |v| v)),
title: None,
title_source: Some(title_source),
description: None,
description_source,
visibility: None,
local_only: None,
logged_in_only: None,
published: None,
updated: Some(Utc::now()),
updated: Utc::now(),
removed_files: None,
new_files: None,
only_files: None,
@ -360,7 +338,7 @@ impl UpdateSubmission {
local_only: Some(local_only),
logged_in_only: Some(logged_in_only),
published: None,
updated: Some(Utc::now()),
updated: Utc::now(),
removed_files: None,
new_files: None,
only_files: None,
@ -379,7 +357,7 @@ impl UpdateSubmission {
local_only: None,
logged_in_only: None,
published: None,
updated: Some(Utc::now()),
updated: Utc::now(),
removed_files: None,
new_files: Some(vec![file_id]),
only_files: None,
@ -396,7 +374,7 @@ impl UpdateSubmission {
description_source: None,
visibility: None,
published: None,
updated: Some(Utc::now()),
updated: Utc::now(),
local_only: None,
logged_in_only: None,
removed_files: Some(vec![file_id]),
@ -417,7 +395,7 @@ impl UpdateSubmission {
local_only: None,
logged_in_only: None,
published: None,
updated: None,
updated: Utc::now(),
removed_files: None,
new_files: None,
only_files: None,
@ -436,7 +414,7 @@ impl UpdateSubmission {
local_only: None,
logged_in_only: None,
published: Some(Utc::now()),
updated: None,
updated: Utc::now(),
removed_files: None,
new_files: None,
only_files: None,
@ -508,34 +486,26 @@ pub struct UpdateProfile {
login_required: Option<bool>,
icon: Option<Uuid>,
banner: Option<Uuid>,
updated: Option<DateTime<Utc>>,
updated: DateTime<Utc>,
}
impl UpdateProfile {
pub fn from_text(profile_id: Uuid, display_name: String, description: String) -> Self {
let display_name_source = if display_name.trim().is_empty() {
None
} else {
Some(display_name.trim().to_owned())
};
let description_source = if description.trim().is_empty() {
None
} else {
Some(description.trim().to_owned())
};
pub fn from_text(
profile_id: Uuid,
display_name_source: String,
description_source: String,
) -> Self {
UpdateProfile {
apub: None,
profile_id,
display_name: display_name_source.clone(),
display_name_source,
description: description_source.as_deref().map(|s| bbcode(s, |v| v)),
description_source,
display_name: None,
display_name_source: Some(display_name_source),
description: None,
description_source: Some(description_source),
login_required: None,
icon: None,
banner: None,
updated: Some(Utc::now()),
updated: Utc::now(),
}
}
@ -550,7 +520,7 @@ impl UpdateProfile {
login_required: None,
icon: Some(icon),
banner: None,
updated: Some(Utc::now()),
updated: Utc::now(),
}
}
@ -565,7 +535,7 @@ impl UpdateProfile {
login_required: None,
icon: None,
banner: Some(banner),
updated: Some(Utc::now()),
updated: Utc::now(),
}
}
@ -580,7 +550,7 @@ impl UpdateProfile {
login_required: Some(login_required),
icon: None,
banner: None,
updated: Some(Utc::now()),
updated: Utc::now(),
}
}
}

View file

@ -9,20 +9,20 @@ use crate::{
impl Action for CreateProfile {
fn perform(&self, ctx: &Context) -> Result<Option<Box<dyn Outbound + Send>>, Error> {
log::debug!("CreateProfile");
let profile = ctx.store.profiles.create(
let mut changes = ctx.store.profiles.create(
ctx,
self.owner_source.clone(),
&self.handle,
&self.domain,
self.handle.clone(),
self.domain.clone(),
self.published,
)?;
);
let mut changes = profile.update();
changes.login_required(self.login_required);
if let Some(display_name) = &self.display_name {
changes.display_name(&hyaenidae_content::html(display_name));
changes.display_name(display_name);
}
if let Some(description) = &self.description {
changes.description(&hyaenidae_content::html(description));
changes.description(description);
}
if let Some(banner) = self.banner {
changes.banner(banner);
@ -34,11 +34,7 @@ impl Action for CreateProfile {
changes.updated(updated);
}
let profile = if changes.any_changes() {
ctx.store.profiles.update(&changes)?
} else {
profile
};
let profile = changes.save()?;
if let Some(apub) = &self.apub {
ctx.apub.profile(&apub.person_apub_id, profile.id())?;
@ -75,25 +71,23 @@ impl Action for UpdateProfile {
.by_id(self.profile_id)?
.req("profile by id")?;
let mut changes = profile.update();
let mut changes = profile.update(ctx);
changes.updated(self.updated);
if let Some(login_required) = self.login_required {
changes.login_required(login_required);
}
if let Some(display_name) = &self.display_name {
changes.display_name(&hyaenidae_content::html(display_name));
changes.display_name(display_name);
}
if let Some(display_name_source) = &self.display_name_source {
changes.display_name_source(display_name_source);
}
if let Some(description) = &self.description {
changes.description(&hyaenidae_content::html(description));
changes.description(description);
}
if let Some(description_source) = &self.description_source {
changes.description_source(description_source);
}
if let Some(updated) = self.updated {
changes.updated(updated);
}
let previous_banner = profile.banner();
let previous_icon = profile.icon();
@ -116,7 +110,7 @@ impl Action for UpdateProfile {
}
let profile = if changes.any_changes() {
ctx.store.profiles.update(&changes)?
changes.save()?
} else {
profile
};

View file

@ -5,20 +5,19 @@ use crate::{
impl Action for CreateServer {
fn perform(&self, ctx: &Context) -> Result<Option<Box<dyn Outbound + Send>>, Error> {
let server = ctx.store.servers.create(&self.domain, self.published)?;
let mut changes = ctx.store.servers.create(ctx, self.domain.clone());
let mut changes = server.changes();
changes.published(self.published);
if let Some(updated) = self.updated {
changes.updated(updated);
}
if let Some(title) = &self.title {
changes.title(&hyaenidae_content::html(title));
changes.title(title);
}
if let Some(description) = &self.description {
changes.description(&hyaenidae_content::html(description));
changes.description(description);
}
let server = if changes.any_changes() {
ctx.store.servers.update(&changes)?
} else {
server
};
let server = changes.save()?;
if let Some(apub) = &self.apub {
ctx.apub.server(&apub.application_apub_id, server.id())?;
@ -52,21 +51,22 @@ impl Action for UpdateServer {
.by_id(self.server_id)?
.req("server by id")?;
let mut changes = server.changes();
let mut changes = server.changes(ctx);
changes.updated(self.updated);
if let Some(title) = &self.title {
changes.title(&hyaenidae_content::html(title));
changes.title(title);
}
if let Some(title_source) = &self.title_source {
changes.title_source(title_source);
}
if let Some(description) = &self.description {
changes.description(&hyaenidae_content::html(description));
changes.description(description);
}
if let Some(description_source) = &self.description_source {
changes.description_source(description_source);
}
let server = if changes.any_changes() {
ctx.store.servers.update(&changes)?
changes.save()?
} else {
server
};

View file

@ -7,25 +7,22 @@ use std::collections::HashSet;
impl Action for CreateSubmission {
fn perform(&self, ctx: &Context) -> Result<Option<Box<dyn Outbound + Send>>, Error> {
log::debug!("CreateSubmission");
let submission = ctx.store.submissions.create(
self.profile_id,
&hyaenidae_content::html(&self.title),
self.visibility,
)?;
let mut changes = ctx.store.submissions.create(ctx, self.profile_id);
let submission_id = submission.id();
let mut changes = submission.update();
changes
.local_only(self.local_only)
.logged_in_only(self.logged_in_only)
.sensitive(self.sensitive);
.sensitive(self.sensitive)
.visibility(self.visibility);
if self.published.is_some() {
changes.published(self.published);
}
if let Some(title) = &self.title {
changes.title(title);
}
if let Some(description) = &self.description {
changes.description(&hyaenidae_content::html(description));
changes.description(description);
}
if let Some(updated) = self.updated {
changes.updated(updated);
@ -35,11 +32,8 @@ impl Action for CreateSubmission {
changes.add_file(*file);
}
let submission = if changes.any_changes() {
ctx.store.submissions.update(&changes)?
} else {
submission
};
let submission = changes.save()?;
let submission_id = submission.id();
if let Some(apub_id) = &self.note_apub_id {
ctx.apub.submission(apub_id, submission_id)?;
@ -95,21 +89,19 @@ impl Action for UpdateSubmission {
let initial_published = submission.published().is_some();
let mut changes = submission.update();
let mut changes = submission.update(ctx);
changes.updated(self.updated);
if let Some(published) = self.published {
changes.published(Some(published));
}
if let Some(updated) = self.updated {
changes.updated(updated);
}
if let Some(title) = &self.title {
changes.title(&hyaenidae_content::html(title));
changes.title(title);
}
if let Some(title_source) = &self.title_source {
changes.title_source(title_source);
}
if let Some(description) = &self.description {
changes.description(&hyaenidae_content::html(description));
changes.description(description);
}
if let Some(description_source) = &self.description_source {
changes.description_source(description_source);
@ -158,7 +150,7 @@ impl Action for UpdateSubmission {
}
let submission = if changes.any_changes() {
ctx.store.submissions.update(&changes)?
changes.save()?
} else {
submission
};

View file

@ -100,7 +100,7 @@ fn build_comment(
commenter_id: Url,
ctx: &Context,
) -> Result<AnyBase, Error> {
let published = comment.published();
let published = comment.published().req("comment must be published")?;
let endpoints = ctx
.apub
.endpoints_for_profile(comment.profile_id())?
@ -124,6 +124,9 @@ fn build_comment(
.set_published(published.into())
.set_attributed_to(commenter_id)
.add_cc(public());
if let Some(updated) = comment.updated() {
note.set_updated(updated.into());
}
if let Some(followers) = endpoints.followers {
note.add_to(followers);
}

View file

@ -94,6 +94,9 @@ impl Outbound for ServerCreated {
})
.set_preferred_username(server.domain())
.set_published(server.created().into());
if let Some(updated) = server.updated() {
application.set_updated(updated.into());
}
if let Some(title) = server.title() {
application.set_name(title);
@ -190,6 +193,9 @@ impl Outbound for ServerUpdated {
.set_preferred_username(server.domain())
.set_published(server.created().into());
if let Some(updated) = server.updated() {
application.set_updated(updated.into());
}
if let Some(outbox) = endpoints.outbox {
application.set_outbox(outbox);
}

View file

@ -1,9 +1,46 @@
use super::{Comment, CommentChanges, ReplyComment, StoreError, SubmissionComment, Undo};
use super::{StoreError, Undo};
use crate::State;
use chrono::{DateTime, Utc};
use hyaenidae_content::{bbcode, html};
use sled::{Db, Transactional, Tree};
use std::io::Cursor;
use uuid::Uuid;
#[derive(Clone, Debug)]
pub struct Comment {
id: Uuid,
submission_id: Uuid,
profile_id: Uuid,
comment_id: Option<Uuid>,
body: String,
body_source: Option<String>,
published: Option<DateTime<Utc>>,
updated: Option<DateTime<Utc>>,
deleted: bool,
}
#[derive(Debug)]
pub enum CommentChangesKind {
Create {
profile_id: Uuid,
submission_id: Uuid,
comment_id: Option<Uuid>,
},
Update {
id: Uuid,
},
}
#[derive(Debug)]
pub struct CommentChanges<'a> {
state: &'a State,
kind: CommentChangesKind,
body: Option<String>,
body_source: Option<String>,
published: Option<DateTime<Utc>>,
updated: Option<DateTime<Utc>>,
}
#[derive(Clone, Debug)]
pub struct Store {
comment_tree: Tree,
@ -24,13 +61,123 @@ struct StoredComment {
body: String,
#[serde(skip_serializing_if = "Option::is_none")]
body_source: Option<String>,
published: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
published: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
updated: Option<DateTime<Utc>>,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
deleted_at: Option<DateTime<Utc>>,
}
impl Comment {
pub fn id(&self) -> Uuid {
self.id
}
pub fn submission_id(&self) -> Uuid {
self.submission_id
}
pub fn profile_id(&self) -> Uuid {
self.profile_id
}
pub fn comment_id(&self) -> Option<Uuid> {
self.comment_id
}
pub fn body(&self) -> &str {
if !self.deleted() {
self.body.as_str()
} else {
"Comment Deleted"
}
}
pub fn body_source(&self) -> Option<&str> {
self.body_source.as_deref()
}
pub fn published(&self) -> Option<DateTime<Utc>> {
self.published
}
pub fn updated(&self) -> Option<DateTime<Utc>> {
self.updated
}
pub(crate) fn update<'a>(&self, state: &'a State) -> CommentChanges<'a> {
CommentChanges {
state,
kind: CommentChangesKind::Update { id: self.id() },
body: None,
body_source: None,
published: self.published,
updated: None,
}
}
pub fn deleted(&self) -> bool {
self.deleted
}
}
impl<'a> CommentChanges<'a> {
fn new(
state: &'a State,
profile_id: Uuid,
submission_id: Uuid,
comment_id: Option<Uuid>,
) -> Self {
CommentChanges {
state,
kind: CommentChangesKind::Create {
profile_id,
submission_id,
comment_id,
},
body: None,
body_source: None,
published: None,
updated: None,
}
}
pub(crate) fn body(&mut self, body: &str) -> &mut Self {
self.body = Some(html(body));
self
}
pub(crate) fn body_source(&mut self, body_source: &str) -> &mut Self {
self.body_source = Some(body_source.to_owned());
self.body(&bbcode(body_source, |v| v))
}
pub(crate) fn published(&mut self, published: DateTime<Utc>) -> &mut Self {
if self.published.is_none() {
self.published = Some(published);
}
self
}
pub(crate) fn updated(&mut self, updated: DateTime<Utc>) -> &mut Self {
if self.published.is_some() {
self.updated = Some(updated);
}
self
}
pub(crate) fn any_changes(&self) -> bool {
self.body.is_some() || self.published.is_some() || self.updated.is_some()
}
pub(crate) fn save(self) -> Result<Comment, StoreError> {
self.state.store.comments.save(&self)
}
}
impl Store {
pub(super) fn build(db: &Db) -> Result<Self, sled::Error> {
Ok(Store {
@ -43,14 +190,36 @@ impl Store {
})
}
pub(crate) fn create(
pub(crate) fn create<'a>(
&self,
state: &'a State,
submission_id: Uuid,
profile_id: Uuid,
comment_id: Option<Uuid>,
) -> CommentChanges<'a> {
CommentChanges::new(state, submission_id, profile_id, comment_id)
}
fn save(&self, changes: &CommentChanges) -> Result<Comment, StoreError> {
match &changes.kind {
CommentChangesKind::Create {
profile_id,
submission_id,
comment_id,
} => {
let id = self.do_create(*submission_id, *profile_id, *comment_id)?;
self.do_update(id, changes)
}
CommentChangesKind::Update { id } => self.do_update(*id, changes),
}
}
pub(crate) fn do_create(
&self,
submission_id: Uuid,
profile_id: Uuid,
comment_id: Option<Uuid>,
body: &str,
published: DateTime<Utc>,
) -> Result<Comment, StoreError> {
) -> Result<Uuid, StoreError> {
let mut id;
let mut stored_comment;
@ -66,9 +235,10 @@ impl Store {
submission_id,
profile_id,
comment_id,
body: body.to_owned(),
body: String::new(),
body_source: None,
published,
published: None,
updated: None,
created_at: now,
updated_at: now,
deleted_at: None,
@ -128,7 +298,7 @@ impl Store {
return Err(e.into());
}
Ok(stored_comment.into())
Ok(id)
}
pub fn count(&self, submission_id: Uuid) -> Result<u64, StoreError> {
@ -141,20 +311,36 @@ impl Store {
}
}
pub(crate) fn update(&self, changes: &CommentChanges) -> Result<Comment, StoreError> {
let stored_comment_ivec = match self.comment_tree.get(id_comment_key(changes.id))? {
fn do_update(&self, id: Uuid, changes: &CommentChanges) -> Result<Comment, StoreError> {
let stored_comment_ivec = match self.comment_tree.get(id_comment_key(id))? {
Some(ivec) => ivec,
None => return Err(StoreError::Missing),
};
let mut stored_comment: StoredComment = serde_json::from_slice(&stored_comment_ivec)?;
if let Some(updated) = changes.updated {
if let Some(previously_updated) =
stored_comment.updated.or_else(|| stored_comment.published)
{
if updated < previously_updated {
return Err(StoreError::Outdated);
}
}
}
if let Some(body) = &changes.body {
stored_comment.body = body.clone();
}
if let Some(body_source) = &changes.body_source {
stored_comment.body_source = Some(body_source.clone());
}
if let Some(published) = changes.published {
stored_comment.published = Some(published);
}
if let Some(updated) = changes.updated {
stored_comment.updated = Some(updated);
}
stored_comment.updated_at = Utc::now().into();
let stored_comment_vec = serde_json::to_vec(&stored_comment)?;
@ -162,7 +348,7 @@ impl Store {
if self
.comment_tree
.compare_and_swap(
id_comment_key(changes.id),
id_comment_key(id),
Some(&stored_comment_ivec),
Some(stored_comment_vec.as_slice()),
)?
@ -356,27 +542,16 @@ fn uuid_from_ivec(ivec: sled::IVec) -> Option<Uuid> {
impl From<StoredComment> for Comment {
fn from(sc: StoredComment) -> Self {
if let Some(comment_id) = sc.comment_id {
Comment::Reply(ReplyComment {
id: sc.id,
submission_id: sc.submission_id,
profile_id: sc.profile_id,
comment_id,
body: sc.body,
body_source: sc.body_source,
published: sc.published,
deleted: sc.deleted_at.is_some(),
})
} else {
Comment::Submission(SubmissionComment {
id: sc.id,
submission_id: sc.submission_id,
profile_id: sc.profile_id,
body: sc.body,
body_source: sc.body_source,
published: sc.published,
deleted: sc.deleted_at.is_some(),
})
Comment {
id: sc.id,
submission_id: sc.submission_id,
profile_id: sc.profile_id,
comment_id: sc.comment_id,
body: sc.body,
body_source: sc.body_source,
published: sc.published,
updated: sc.updated,
deleted: sc.deleted_at.is_some(),
}
}
}

View file

@ -13,7 +13,7 @@ mod submission;
mod term_search;
pub mod view;
pub use comment::Store as CommentStore;
pub use comment::{Comment, CommentChanges, Store as CommentStore};
pub use file::Store as FileStore;
pub use profile::{OwnerSource, Profile, ProfileChanges, Store as ProfileStore};
pub use react::Store as ReactStore;
@ -289,124 +289,6 @@ impl File {
}
}
#[derive(Clone, Debug)]
pub enum Comment {
Submission(SubmissionComment),
Reply(ReplyComment),
}
impl Comment {
pub fn id(&self) -> Uuid {
match self {
Comment::Reply(ReplyComment { id, .. }) => *id,
Comment::Submission(SubmissionComment { id, .. }) => *id,
}
}
pub fn submission_id(&self) -> Uuid {
match self {
Comment::Reply(ReplyComment { submission_id, .. }) => *submission_id,
Comment::Submission(SubmissionComment { submission_id, .. }) => *submission_id,
}
}
pub fn profile_id(&self) -> Uuid {
match self {
Comment::Reply(ReplyComment { profile_id, .. }) => *profile_id,
Comment::Submission(SubmissionComment { profile_id, .. }) => *profile_id,
}
}
pub fn comment_id(&self) -> Option<Uuid> {
match self {
Comment::Reply(ReplyComment { comment_id, .. }) => Some(*comment_id),
_ => None,
}
}
pub fn body(&self) -> &str {
if !self.deleted() {
match self {
Comment::Reply(ReplyComment { body, .. }) => &body,
Comment::Submission(SubmissionComment { body, .. }) => &body,
}
} else {
"Comment Deleted"
}
}
pub fn body_source(&self) -> Option<&str> {
match self {
Comment::Reply(ReplyComment { body_source, .. }) => body_source.as_deref(),
Comment::Submission(SubmissionComment { body_source, .. }) => body_source.as_deref(),
}
}
pub fn published(&self) -> DateTime<Utc> {
match self {
Comment::Reply(ReplyComment { published, .. }) => *published,
Comment::Submission(SubmissionComment { published, .. }) => *published,
}
}
pub(crate) fn update(&self) -> CommentChanges {
match self {
Comment::Reply(rc) => rc.update(),
Comment::Submission(sc) => sc.update(),
}
}
pub fn deleted(&self) -> bool {
match self {
Comment::Reply(rc) => rc.deleted,
Comment::Submission(sc) => sc.deleted,
}
}
}
#[derive(Clone, Debug)]
pub struct SubmissionComment {
id: Uuid,
submission_id: Uuid,
profile_id: Uuid,
body: String,
body_source: Option<String>,
published: DateTime<Utc>,
deleted: bool,
}
impl SubmissionComment {
fn update(&self) -> CommentChanges {
CommentChanges {
id: self.id,
body: None,
body_source: None,
}
}
}
#[derive(Clone, Debug)]
pub struct ReplyComment {
id: Uuid,
submission_id: Uuid,
profile_id: Uuid,
comment_id: Uuid,
body: String,
body_source: Option<String>,
published: DateTime<Utc>,
deleted: bool,
}
impl ReplyComment {
fn update(&self) -> CommentChanges {
CommentChanges {
id: self.id,
body: None,
body_source: None,
}
}
}
#[derive(Clone, Debug)]
pub enum React {
Submission(SubmissionReact),
@ -479,29 +361,6 @@ pub struct ReplyReact {
#[derive(Clone, Debug)]
pub struct Undo<T>(pub T);
#[derive(Debug)]
pub struct CommentChanges {
id: Uuid,
body: Option<String>,
body_source: Option<String>,
}
impl CommentChanges {
pub(crate) fn body(&mut self, body: &str) -> &mut CommentChanges {
self.body = Some(body.to_owned());
self
}
pub(crate) fn body_source(&mut self, body_source: &str) -> &mut CommentChanges {
self.body_source = Some(body_source.to_owned());
self
}
pub(crate) fn any_changes(&self) -> bool {
self.body.is_some()
}
}
#[derive(Debug, thiserror::Error)]
pub enum StoreError {
#[error("{0}")]

View file

@ -1,12 +1,28 @@
use super::{StoreError, TermSearch, Undo};
use crate::State;
use chrono::{DateTime, Utc};
use hyaenidae_content::{bbcode, html, strip};
use sled::{Db, Transactional, Tree};
use std::io::Cursor;
use uuid::Uuid;
#[derive(Debug)]
pub struct ProfileChanges {
id: Uuid,
enum ProfileChangesKind {
Create {
source: OwnerSource,
domain: String,
handle: String,
published: DateTime<Utc>,
},
Update {
id: Uuid,
},
}
#[derive(Debug)]
pub struct ProfileChanges<'a> {
state: &'a State,
kind: ProfileChangesKind,
display_name: Option<String>,
display_name_source: Option<String>,
description: Option<String>,
@ -17,7 +33,7 @@ pub struct ProfileChanges {
updated: Option<DateTime<Utc>>,
}
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug)]
pub enum OwnerSource {
Local(Uuid),
Remote(Uuid),
@ -82,25 +98,51 @@ struct StoredProfile {
suspended_at: Option<DateTime<Utc>>,
}
impl ProfileChanges {
impl<'a> ProfileChanges<'a> {
pub(crate) fn new(
state: &'a State,
source: OwnerSource,
domain: String,
handle: String,
published: DateTime<Utc>,
) -> Self {
ProfileChanges {
state,
kind: ProfileChangesKind::Create {
source,
domain,
handle,
published,
},
display_name: None,
display_name_source: None,
description: None,
description_source: None,
login_required: None,
icon: None,
banner: None,
updated: None,
}
}
pub(crate) fn display_name(&mut self, display_name: &str) -> &mut Self {
self.display_name = Some(display_name.to_owned());
self.display_name = Some(strip(display_name));
self
}
pub(crate) fn display_name_source(&mut self, display_name_source: &str) -> &mut Self {
self.display_name_source = Some(display_name_source.to_owned());
self
self.display_name(display_name_source)
}
pub(crate) fn description(&mut self, description: &str) -> &mut Self {
self.description = Some(description.to_owned());
self.description = Some(html(description));
self
}
pub(crate) fn description_source(&mut self, description_source: &str) -> &mut Self {
self.description_source = Some(description_source.to_owned());
self
self.description(&bbcode(description_source, |v| v))
}
pub(crate) fn login_required(&mut self, required: bool) -> &mut Self {
@ -131,6 +173,10 @@ impl ProfileChanges {
|| self.banner.is_some()
|| self.updated.is_some()
}
pub(crate) fn save(self) -> Result<Profile, StoreError> {
self.state.store.profiles.save(&self)
}
}
impl OwnerSource {
@ -140,9 +186,10 @@ impl OwnerSource {
}
impl Profile {
pub fn update(&self) -> ProfileChanges {
pub fn update<'a>(&self, state: &'a State) -> ProfileChanges<'a> {
ProfileChanges {
id: self.id,
state,
kind: ProfileChangesKind::Update { id: self.id },
display_name: None,
display_name_source: None,
description: None,
@ -230,13 +277,39 @@ impl Store {
})
}
pub(crate) fn create(
pub(crate) fn create<'a>(
&self,
state: &'a State,
source: OwnerSource,
handle: String,
domain: String,
published: DateTime<Utc>,
) -> ProfileChanges<'a> {
ProfileChanges::new(state, source, domain, handle, published)
}
fn save<'a>(&self, changes: &ProfileChanges<'a>) -> Result<Profile, StoreError> {
match &changes.kind {
ProfileChangesKind::Create {
source,
handle,
domain,
published,
} => {
let id = self.do_create(*source, handle, domain, *published)?;
self.do_update(id, changes)
}
ProfileChangesKind::Update { id } => self.do_update(*id, changes),
}
}
fn do_create(
&self,
source: OwnerSource,
handle: &str,
domain: &str,
published: DateTime<Utc>,
) -> Result<Profile, StoreError> {
) -> Result<Uuid, StoreError> {
let handle_lower = handle.to_lowercase();
let mut id;
@ -340,38 +413,15 @@ impl Store {
return Err(e.into());
}
Ok(stored_profile.into())
Ok(id)
}
pub fn search<'a>(
&'a self,
handle_term: &'a str,
) -> impl DoubleEndedIterator<Item = Uuid> + 'a {
self.handle_index
.search(handle_term)
.flat_map(move |handle| self.handle_iter(handle))
}
fn handle_iter<'a>(&'a self, handle: String) -> impl DoubleEndedIterator<Item = Uuid> + 'a {
self.handle_tree
.scan_prefix(handle_id_prefix(&handle))
.values()
.filter_map(|res| res.ok())
.filter_map(|ivec| Uuid::from_slice(&ivec).ok())
}
pub fn count(&self, source: OwnerSource) -> Result<u64, StoreError> {
match self.count_tree.get(owner_profile_count_key(&source))? {
Some(ivec) => Ok(String::from_utf8_lossy(&ivec).parse::<u64>().unwrap_or(0)),
None => Ok(0),
}
}
pub(crate) fn update(&self, profile_changes: &ProfileChanges) -> Result<Profile, StoreError> {
let stored_profile_ivec = match self
.profile_tree
.get(id_profile_key(profile_changes.id).as_bytes())?
{
fn do_update<'a>(
&self,
id: Uuid,
profile_changes: &ProfileChanges<'a>,
) -> Result<Profile, StoreError> {
let stored_profile_ivec = match self.profile_tree.get(id_profile_key(id).as_bytes())? {
Some(ivec) => ivec,
None => return Err(StoreError::Missing),
};
@ -415,7 +465,7 @@ impl Store {
if self
.profile_tree
.compare_and_swap(
id_profile_key(profile_changes.id).as_bytes(),
id_profile_key(id).as_bytes(),
Some(&stored_profile_ivec),
Some(stored_profile_vec),
)?
@ -427,6 +477,30 @@ impl Store {
Ok(stored_profile.into())
}
pub fn search<'a>(
&'a self,
handle_term: &'a str,
) -> impl DoubleEndedIterator<Item = Uuid> + 'a {
self.handle_index
.search(handle_term)
.flat_map(move |handle| self.handle_iter(handle))
}
fn handle_iter<'a>(&'a self, handle: String) -> impl DoubleEndedIterator<Item = Uuid> + 'a {
self.handle_tree
.scan_prefix(handle_id_prefix(&handle))
.values()
.filter_map(|res| res.ok())
.filter_map(|ivec| Uuid::from_slice(&ivec).ok())
}
pub fn count(&self, source: OwnerSource) -> Result<u64, StoreError> {
match self.count_tree.get(owner_profile_count_key(&source))? {
Some(ivec) => Ok(String::from_utf8_lossy(&ivec).parse::<u64>().unwrap_or(0)),
None => Ok(0),
}
}
pub fn for_local(&self, id: Uuid) -> impl DoubleEndedIterator<Item = Uuid> {
self.for_owner(OwnerSource::Local(id))
}

View file

@ -1,5 +1,6 @@
use crate::store::StoreError;
use crate::{store::StoreError, State};
use chrono::{DateTime, Utc};
use hyaenidae_content::{bbcode, html, strip};
use sled::{Db, Transactional, Tree};
use uuid::Uuid;
@ -12,6 +13,43 @@ pub struct Server {
title_source: Option<String>,
description: Option<String>,
description_source: Option<String>,
published: Option<DateTime<Utc>>,
updated: Option<DateTime<Utc>>,
}
#[derive(Debug)]
pub enum ServerChangesKind {
Create { domain: String },
Update { id: Uuid },
}
#[derive(Debug)]
pub struct ServerChanges<'a> {
state: &'a State,
kind: ServerChangesKind,
title: Option<String>,
title_source: Option<String>,
description: Option<String>,
description_source: Option<String>,
published: Option<DateTime<Utc>>,
updated: Option<DateTime<Utc>>,
}
#[derive(Clone)]
pub struct Store {
created_id: Tree,
domain_id: Tree,
id_domain: Tree,
id_created: Tree,
id_title: Tree,
id_title_source: Tree,
id_description: Tree,
id_description_source: Tree,
id_published: Tree,
id_updated: Tree,
self_server: Tree,
}
impl Server {
@ -43,64 +81,79 @@ impl Server {
self.created
}
pub(crate) fn changes(&self) -> ServerChanges {
pub(crate) fn updated(&self) -> Option<DateTime<Utc>> {
self.updated
}
pub(crate) fn changes<'a>(&self, state: &'a State) -> ServerChanges<'a> {
ServerChanges {
id: self.id,
state,
kind: ServerChangesKind::Update { id: self.id },
title: None,
title_source: None,
description: None,
description_source: None,
published: self.published,
updated: None,
}
}
}
pub struct ServerChanges {
id: Uuid,
title: Option<String>,
title_source: Option<String>,
description: Option<String>,
description_source: Option<String>,
}
impl<'a> ServerChanges<'a> {
fn new(state: &'a State, domain: String) -> Self {
ServerChanges {
state,
kind: ServerChangesKind::Create { domain },
title: None,
title_source: None,
description: None,
description_source: None,
published: None,
updated: None,
}
}
impl ServerChanges {
pub(crate) fn title(&mut self, title: &str) -> &mut Self {
self.title = Some(title.to_owned());
self.title = Some(strip(title));
self
}
pub(crate) fn title_source(&mut self, title_source: &str) -> &mut Self {
self.title_source = Some(title_source.to_owned());
self
self.title(title_source)
}
pub(crate) fn description(&mut self, description: &str) -> &mut Self {
self.description = Some(description.to_owned());
self.description = Some(html(description));
self
}
pub(crate) fn description_source(&mut self, description_source: &str) -> &mut Self {
self.description_source = Some(description_source.to_owned());
self.description(&bbcode(description_source, |v| v))
}
pub(crate) fn published(&mut self, published: DateTime<Utc>) -> &mut Self {
if self.published.is_none() {
self.published = Some(published);
}
self
}
pub(crate) fn updated(&mut self, updated: DateTime<Utc>) -> &mut Self {
if self.published.is_some() {
self.updated = Some(updated);
}
self
}
pub(crate) fn any_changes(&self) -> bool {
self.title.is_some() || self.description.is_some()
}
}
#[derive(Clone)]
pub struct Store {
created_id: Tree,
domain_id: Tree,
id_domain: Tree,
id_created: Tree,
id_title: Tree,
id_title_source: Tree,
id_description: Tree,
id_description_source: Tree,
self_server: Tree,
pub(crate) fn save(self) -> Result<Server, StoreError> {
self.state.store.servers.save(&self)
}
}
impl Store {
@ -113,6 +166,8 @@ impl Store {
let id_title_source = db.open_tree("/profiles/servers/id_title_source")?;
let id_description = db.open_tree("/profiles/servers/id_description")?;
let id_description_source = db.open_tree("/profiles/servers/id_description_source")?;
let id_published = db.open_tree("/profiles/servers/id_published")?;
let id_updated = db.open_tree("/profiles/servers/id_updated")?;
let self_server = db.open_tree("/profiles/servers/self_server")?;
Ok(Store {
@ -124,6 +179,8 @@ impl Store {
id_title_source,
id_description,
id_description_source,
id_published,
id_updated,
self_server,
})
}
@ -146,11 +203,21 @@ impl Store {
.unwrap_or(false))
}
pub(crate) fn create(
&self,
domain: &str,
created: DateTime<Utc>,
) -> Result<Server, StoreError> {
pub(crate) fn create<'a>(&self, state: &'a State, domain: String) -> ServerChanges<'a> {
ServerChanges::new(state, domain)
}
pub(crate) fn save(&self, changes: &ServerChanges) -> Result<Server, StoreError> {
match &changes.kind {
ServerChangesKind::Create { domain } => {
let id = self.do_create(domain, Utc::now())?;
self.do_update(id, changes)
}
ServerChangesKind::Update { id } => self.do_update(*id, changes),
}
}
fn do_create(&self, domain: &str, created: DateTime<Utc>) -> Result<Uuid, StoreError> {
let mut id;
while {
@ -183,60 +250,67 @@ impl Store {
return Err(e.into());
}
Ok(Server {
id,
domain: domain.to_owned(),
title: None,
title_source: None,
description: None,
description_source: None,
created,
})
Ok(id)
}
pub(crate) fn update(&self, changes: &ServerChanges) -> Result<Server, StoreError> {
let domain = match self
.id_domain
.get(changes.id.as_bytes())?
.map(string_from_ivec)
{
fn do_update(&self, id: Uuid, changes: &ServerChanges) -> Result<Server, StoreError> {
let domain = match self.id_domain.get(id.as_bytes())?.map(string_from_ivec) {
Some(domain) => domain,
None => return Err(StoreError::Missing),
};
let created = match self
.id_created
.get(changes.id.as_bytes())?
.and_then(date_from_ivec)
{
let created = match self.id_created.get(id.as_bytes())?.and_then(date_from_ivec) {
Some(date) => date,
None => return Err(StoreError::Missing),
};
let stored_updated = self.id_updated.get(id.as_bytes())?.and_then(date_from_ivec);
let stored_published = self
.id_published
.get(id.as_bytes())?
.and_then(date_from_ivec);
if let Some(updated) = changes.updated {
if let Some(previously_updated) = stored_updated.or_else(|| stored_published) {
if updated < previously_updated {
return Err(StoreError::Outdated);
}
}
}
if let Some(title) = &changes.title {
self.id_title
.insert(changes.id.as_bytes(), title.as_bytes())?;
self.id_title.insert(id.as_bytes(), title.as_bytes())?;
}
if let Some(title_source) = &changes.title_source {
self.id_title_source
.insert(changes.id.as_bytes(), title_source.as_bytes())?;
.insert(id.as_bytes(), title_source.as_bytes())?;
}
if let Some(description) = &changes.description {
self.id_description
.insert(changes.id.as_bytes(), description.as_bytes())?;
.insert(id.as_bytes(), description.as_bytes())?;
}
if let Some(description_source) = &changes.description_source {
self.id_description_source
.insert(changes.id.as_bytes(), description_source.as_bytes())?;
.insert(id.as_bytes(), description_source.as_bytes())?;
}
if let Some(published) = changes.published {
self.id_published
.insert(id.as_bytes(), published.to_rfc3339().as_bytes())?;
}
if let Some(updated) = changes.updated {
self.id_updated
.insert(id.as_bytes(), updated.to_rfc3339().as_bytes())?;
}
Ok(Server {
id: changes.id,
id,
domain,
created,
title: changes.title.clone(),
title_source: changes.title_source.clone(),
description: changes.description.clone(),
description_source: changes.description_source.clone(),
published: changes.published,
updated: changes.updated,
})
}
@ -260,6 +334,11 @@ impl Store {
.id_description_source
.get(id.as_bytes())?
.map(string_from_ivec);
let updated = self.id_updated.get(id.as_bytes())?.and_then(date_from_ivec);
let published = self
.id_published
.get(id.as_bytes())?
.and_then(date_from_ivec);
Ok(domain.and_then(|domain| {
created.map(|created| Server {
@ -270,6 +349,8 @@ impl Store {
description,
description_source,
created,
updated,
published,
})
}))
}

View file

@ -1,5 +1,7 @@
use super::{StoreError, Undo};
use crate::State;
use chrono::{DateTime, Utc};
use hyaenidae_content::{bbcode, html, strip};
use sled::{Db, Transactional, Tree};
use std::{fmt, io::Cursor};
use uuid::Uuid;
@ -29,8 +31,15 @@ pub struct Submission {
}
#[derive(Debug)]
pub struct SubmissionChanges {
id: Uuid,
pub enum SubmissionChangesKind {
Create { profile_id: Uuid },
Update { id: Uuid },
}
#[derive(Debug)]
pub struct SubmissionChanges<'a> {
state: &'a State,
kind: SubmissionChangesKind,
title: Option<String>,
title_source: Option<String>,
description: Option<String>,
@ -82,9 +91,10 @@ struct StoredSubmission {
}
impl Submission {
pub fn update(&self) -> SubmissionChanges {
pub(crate) fn update<'a>(&self, state: &'a State) -> SubmissionChanges<'a> {
SubmissionChanges {
id: self.id,
state,
kind: SubmissionChangesKind::Update { id: self.id },
title: None,
title_source: None,
description: None,
@ -165,25 +175,44 @@ impl Submission {
}
}
impl SubmissionChanges {
impl<'a> SubmissionChanges<'a> {
fn new(state: &'a State, profile_id: Uuid) -> Self {
SubmissionChanges {
state,
kind: SubmissionChangesKind::Create { profile_id },
title: None,
title_source: None,
description: None,
description_source: None,
visibility: None,
published: None,
updated: None,
local_only: None,
logged_in_only: None,
sensitive: None,
original_files: vec![],
files: vec![],
}
}
pub(crate) fn title(&mut self, title: &str) -> &mut Self {
self.title = Some(title.to_owned());
self.title = Some(strip(title));
self
}
pub(crate) fn title_source(&mut self, title_source: &str) -> &mut Self {
self.title_source = Some(title_source.to_owned());
self
self.title(title_source)
}
pub(crate) fn description(&mut self, description: &str) -> &mut Self {
self.description = Some(description.to_owned());
self.description = Some(html(description));
self
}
pub(crate) fn description_source(&mut self, description_source: &str) -> &mut Self {
self.description_source = Some(description_source.to_owned());
self
self.description(&bbcode(description_source, |v| v))
}
pub(crate) fn visibility(&mut self, visibility: Visibility) -> &mut Self {
@ -247,10 +276,14 @@ impl SubmissionChanges {
|| self.sensitive.is_some()
|| self.original_files != self.files
}
pub(crate) fn save(self) -> Result<Submission, StoreError> {
self.state.store.submissions.save(&self)
}
}
impl Store {
pub fn build(db: &Db) -> Result<Self, sled::Error> {
pub(super) 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")?,
@ -261,12 +294,21 @@ impl Store {
})
}
pub fn create(
&self,
profile_id: Uuid,
title: &str,
visibility: Visibility,
) -> Result<Submission, StoreError> {
pub(crate) fn create<'a>(&self, state: &'a State, profile_id: Uuid) -> SubmissionChanges<'a> {
SubmissionChanges::new(state, profile_id)
}
fn save(&self, changes: &SubmissionChanges) -> Result<Submission, StoreError> {
match &changes.kind {
SubmissionChangesKind::Create { profile_id } => {
let id = self.do_create(*profile_id)?;
self.do_update(id, changes)
}
SubmissionChangesKind::Update { id } => self.do_update(*id, changes),
}
}
fn do_create(&self, profile_id: Uuid) -> Result<Uuid, StoreError> {
let mut id;
let mut stored_submission;
@ -280,14 +322,14 @@ impl Store {
stored_submission = StoredSubmission {
id,
profile_id,
title: title.to_owned(),
title: String::new(),
title_source: None,
description: None,
description_source: None,
files: vec![],
published: None,
updated: None,
visibility,
visibility: Visibility::Followers,
local_only: false,
logged_in_only: false,
drafted_at: now,
@ -337,7 +379,7 @@ impl Store {
return Err(e.into());
}
Ok(stored_submission.into())
Ok(id)
}
pub fn count(&self, profile_id: Uuid) -> Result<u64, StoreError> {
@ -352,12 +394,11 @@ impl Store {
}
}
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),
};
fn do_update(&self, id: Uuid, changes: &SubmissionChanges) -> Result<Submission, StoreError> {
let stored_submission_ivec = match self.submission_tree.get(id_submission_key(id))? {
Some(ivec) => ivec,
None => return Err(StoreError::Missing),
};
let mut stored_submission: StoredSubmission =
serde_json::from_slice(&stored_submission_ivec)?;
@ -412,7 +453,7 @@ impl Store {
if self
.submission_tree
.compare_and_swap(
id_submission_key(changes.id),
id_submission_key(id),
Some(&stored_submission_ivec),
Some(stored_submission_vec.as_slice()),
)?