// GoToSocial // Copyright (C) GoToSocial Authors admin@gotosocial.org // SPDX-License-Identifier: AGPL-3.0-or-later // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package workers import ( "context" "github.com/superseriousbusiness/gotosocial/internal/gtscontext" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/processing/media" "github.com/superseriousbusiness/gotosocial/internal/state" ) // wipeStatus encapsulates common logic used to totally delete a status // + all its attachments, notifications, boosts, and timeline entries. type wipeStatus func(context.Context, *gtsmodel.Status, bool) error // wipeStatusF returns a wipeStatus util function. func wipeStatusF(state *state.State, media *media.Processor, surface *surface) wipeStatus { return func( ctx context.Context, statusToDelete *gtsmodel.Status, deleteAttachments bool, ) error { var errs gtserror.MultiError // Either delete all attachments for this status, // or simply unattach + clean them separately later. // // Reason to unattach rather than delete is that // the poster might want to reattach them to another // status immediately (in case of delete + redraft) if deleteAttachments { // todo:state.DB.DeleteAttachmentsForStatus for _, id := range statusToDelete.AttachmentIDs { if err := media.Delete(ctx, id); err != nil { errs.Appendf("error deleting media: %w", err) } } } else { // todo:state.DB.UnattachAttachmentsForStatus for _, id := range statusToDelete.AttachmentIDs { if _, err := media.Unattach(ctx, statusToDelete.Account, id); err != nil { errs.Appendf("error unattaching media: %w", err) } } } // delete all mention entries generated by this status // todo:state.DB.DeleteMentionsForStatus for _, id := range statusToDelete.MentionIDs { if err := state.DB.DeleteMentionByID(ctx, id); err != nil { errs.Appendf("error deleting status mention: %w", err) } } // delete all notification entries generated by this status if err := state.DB.DeleteNotificationsForStatus(ctx, statusToDelete.ID); err != nil { errs.Appendf("error deleting status notifications: %w", err) } // delete all bookmarks that point to this status if err := state.DB.DeleteStatusBookmarksForStatus(ctx, statusToDelete.ID); err != nil { errs.Appendf("error deleting status bookmarks: %w", err) } // delete all faves of this status if err := state.DB.DeleteStatusFavesForStatus(ctx, statusToDelete.ID); err != nil { errs.Appendf("error deleting status faves: %w", err) } if pollID := statusToDelete.PollID; pollID != "" { // Delete this poll by ID from the database. if err := state.DB.DeletePollByID(ctx, pollID); err != nil { errs.Appendf("error deleting status poll: %w", err) } // Delete any poll votes pointing to this poll ID. if err := state.DB.DeletePollVotes(ctx, pollID); err != nil { errs.Appendf("error deleting status poll votes: %w", err) } // Cancel any scheduled expiry task for poll. _ = state.Workers.Scheduler.Cancel(pollID) } // delete all boosts for this status + remove them from timelines boosts, err := state.DB.GetStatusBoosts( // we MUST set a barebones context here, // as depending on where it came from the // original BoostOf may already be gone. gtscontext.SetBarebones(ctx), statusToDelete.ID) if err != nil { errs.Appendf("error fetching status boosts: %w", err) } for _, boost := range boosts { if err := surface.deleteStatusFromTimelines(ctx, boost.ID); err != nil { errs.Appendf("error deleting boost from timelines: %w", err) } if err := state.DB.DeleteStatusByID(ctx, boost.ID); err != nil { errs.Appendf("error deleting boost: %w", err) } } // delete this status from any and all timelines if err := surface.deleteStatusFromTimelines(ctx, statusToDelete.ID); err != nil { errs.Appendf("error deleting status from timelines: %w", err) } // finally, delete the status itself if err := state.DB.DeleteStatusByID(ctx, statusToDelete.ID); err != nil { errs.Appendf("error deleting status: %w", err) } return errs.Combine() } }