hyaenidae/profiles/src/apub/actions/submission.rs

270 lines
9 KiB
Rust

use crate::{
apub::actions::{CreateSubmission, DeleteComment, DeleteSubmission, UpdateSubmission},
Action, Context, Error, Outbound, Required,
};
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 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);
if self.published.is_some() {
changes.published(self.published);
}
if let Some(description) = &self.description {
changes.description(&hyaenidae_content::html(description));
}
if let Some(updated) = self.updated {
changes.updated(updated);
}
for file in &self.files {
changes.add_file(*file);
}
let submission = if changes.any_changes() {
ctx.store.submissions.update(&changes)?
} else {
submission
};
if let Some(apub_id) = &self.note_apub_id {
ctx.apub.submission(apub_id, submission_id)?;
}
if let Some(published) = submission.published() {
let profile_id = self.profile_id;
let ctx_clone = ctx.clone();
ctx.spawn_blocking(move || {
for follower_id in ctx_clone.store.view.follows.forward_iter(profile_id) {
if let Ok(Some(true)) = ctx_clone.store.profiles.is_local(follower_id) {
ctx_clone
.store
.view
.submissions
.new(follower_id, submission_id, published);
}
}
});
}
if ctx.is_local(submission.profile_id())? && submission.published().is_some() {
if submission.is_local_only() {
return Ok(Some(Box::new(
crate::apub::results::LocalSubmissionCreated { submission_id },
)));
} else {
return Ok(Some(Box::new(crate::apub::results::SubmissionCreated {
submission_id,
})));
}
}
if submission.published().is_none() {
return Ok(Some(Box::new(
crate::apub::results::UnpublishedSubmissionCreated { submission_id },
)));
}
Ok(None)
}
}
impl Action for UpdateSubmission {
fn perform(&self, ctx: &Context) -> Result<Option<Box<dyn Outbound + Send>>, Error> {
log::debug!("UpdateSubmission");
let submission_id = self.submission_id;
let submission = ctx
.store
.submissions
.by_id(submission_id)?
.req("submission by id")?;
let initial_published = submission.published().is_some();
let mut changes = submission.update();
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));
}
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));
}
if let Some(description_source) = &self.description_source {
changes.description_source(description_source);
}
if let Some(visibility) = self.visibility {
changes.visibility(visibility);
}
if let Some(local_only) = self.local_only {
changes.local_only(local_only);
}
if let Some(logged_in_only) = self.logged_in_only {
changes.logged_in_only(logged_in_only);
}
if let Some(sensitive) = self.sensitive {
changes.sensitive(sensitive);
}
let mut removed_file_ids = HashSet::new();
if let Some(files) = &self.only_files {
for file in submission.files() {
changes.delete_file(*file);
removed_file_ids.insert(*file);
}
for file in files {
changes.add_file(*file);
removed_file_ids.remove(file);
}
} else {
if let Some(removed) = &self.removed_files {
for file in removed {
changes.delete_file(*file);
removed_file_ids.insert(*file);
}
}
if let Some(new) = &self.new_files {
for file in new {
changes.add_file(*file);
removed_file_ids.remove(file);
}
}
}
for file_id in removed_file_ids {
ctx.spawner.purge_file(file_id);
}
let submission = if changes.any_changes() {
ctx.store.submissions.update(&changes)?
} else {
submission
};
let newly_published = !initial_published && submission.published().is_some();
let profile_id = submission.profile_id();
if newly_published {
if let Some(published) = submission.published() {
let ctx_clone = ctx.clone();
ctx.spawn_blocking(move || {
for follower_id in ctx_clone.store.view.follows.forward_iter(profile_id) {
if let Ok(Some(true)) = ctx_clone.store.profiles.is_local(follower_id) {
ctx_clone.store.view.submissions.new(
follower_id,
submission_id,
published,
);
}
}
});
}
}
if !submission.is_local_only() && ctx.is_local(profile_id)? {
if newly_published {
return Ok(Some(Box::new(crate::apub::results::SubmissionCreated {
submission_id,
})));
} else if submission.published().is_some() {
return Ok(Some(Box::new(crate::apub::results::SubmissionUpdated {
submission_id,
})));
}
} else {
if newly_published {
return Ok(Some(Box::new(
crate::apub::results::LocalSubmissionCreated { submission_id },
)));
} else {
return Ok(Some(Box::new(
crate::apub::results::LocalSubmissionUpdated { submission_id },
)));
}
}
Ok(None)
}
}
fn delete_comment(comment_id: uuid::Uuid, ctx: &Context) -> Result<(), Error> {
Action::perform(
&DeleteComment {
delete_apub_id: None,
comment_id,
},
ctx,
)?;
Ok(())
}
impl Action for DeleteSubmission {
fn perform(&self, ctx: &Context) -> Result<Option<Box<dyn Outbound + Send>>, Error> {
let submission_id = self.submission_id;
let opt = ctx.store.submissions.delete(submission_id)?;
if let Some(undo_submission) = opt {
if undo_submission.0.published().is_some() {
let note_apub_id = ctx
.apub
.apub_for_submission(submission_id)?
.req("apub for submission")?;
ctx.apub.delete_object(&note_apub_id)?;
}
let ctx_clone = ctx.clone();
ctx.spawn_blocking(move || {
for comment_id in ctx_clone.store.comments.for_submission(submission_id) {
if let Err(e) = delete_comment(comment_id, &ctx_clone) {
log::error!("Failed to delete comment {}: {}", comment_id, e);
}
}
});
for file_id in undo_submission.0.files() {
ctx.spawner.purge_file(*file_id);
}
let ctx_clone = ctx.clone();
ctx.spawn_blocking(move || {
ctx_clone.store.view.submissions.remove(submission_id);
});
let profile_id = undo_submission.0.profile_id();
if !undo_submission.0.is_local_only()
&& ctx.is_local(profile_id)?
&& undo_submission.0.published().is_some()
{
return Ok(Some(Box::new(crate::apub::results::SubmissionDeleted {
profile_id,
submission_id,
})));
}
}
Ok(None)
}
}