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>, Error> { log::debug!("CreateSubmission"); let mut changes = ctx.store.submissions.create(ctx, self.profile_id); changes .local_only(self.local_only) .logged_in_only(self.logged_in_only) .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(description); } if let Some(updated) = self.updated { changes.updated(updated); } for file in &self.files { changes.add_file(*file); } 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)?; } 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>, 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(ctx); changes.updated(self.updated); if let Some(published) = self.published { changes.published(Some(published)); } if let Some(title) = &self.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(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() { changes.save()?? } 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>, 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(¬e_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) } }