summaryrefslogtreecommitdiff
path: root/internal/processing/workers/util.go
diff options
context:
space:
mode:
authorLibravatar tobi <31960611+tsmethurst@users.noreply.github.com>2024-03-13 13:53:29 +0100
committerLibravatar GitHub <noreply@github.com>2024-03-13 13:53:29 +0100
commitab2d063fcb04f241a3147c843a021491f5fc0a55 (patch)
tree3d2eff864e8b19d4d9a24f4f1fe92feda8ee4dac /internal/processing/workers/util.go
parent[bugfix]: Add missing Link headers in Swagger spec (#2751) (diff)
downloadgotosocial-ab2d063fcb04f241a3147c843a021491f5fc0a55.tar.xz
[feature] Process outgoing Move from clientAPI (#2750)
* prevent moved accounts from taking create-type actions * update move logic * federate move out * indicate on web profile when an account has moved * [docs] Add migration docs section * lock while checking + setting move state * use redirectFollowers func for clientAPI as well * comment typo * linter? i barely know 'er! * Update internal/uris/uri.go Co-authored-by: Daenney <daenney@users.noreply.github.com> * add a couple tests for move * fix little mistake exposed by tests (thanks tests) * ensure Move marked as successful * attach shared util funcs to struct * lock whole account when doing move * move moving check to after error check * replace repeated text with error func * linterrrrrr!!!! * catch self follow case --------- Co-authored-by: Daenney <daenney@users.noreply.github.com>
Diffstat (limited to 'internal/processing/workers/util.go')
-rw-r--r--internal/processing/workers/util.go240
1 files changed, 240 insertions, 0 deletions
diff --git a/internal/processing/workers/util.go b/internal/processing/workers/util.go
new file mode 100644
index 000000000..a38ecd336
--- /dev/null
+++ b/internal/processing/workers/util.go
@@ -0,0 +1,240 @@
+// 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 <http://www.gnu.org/licenses/>.
+
+package workers
+
+import (
+ "context"
+ "errors"
+
+ apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/processing/account"
+ "github.com/superseriousbusiness/gotosocial/internal/processing/media"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
+)
+
+// utilF wraps util functions used by both
+// the fromClientAPI and fromFediAPI functions.
+type utilF struct {
+ state *state.State
+ media *media.Processor
+ account *account.Processor
+ surface *surface
+}
+
+// wipeStatus encapsulates common logic
+// used to totally delete a status + all
+// its attachments, notifications, boosts,
+// and timeline entries.
+func (u *utilF) wipeStatus(
+ 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:u.state.DB.DeleteAttachmentsForStatus
+ for _, id := range statusToDelete.AttachmentIDs {
+ if err := u.media.Delete(ctx, id); err != nil {
+ errs.Appendf("error deleting media: %w", err)
+ }
+ }
+ } else {
+ // todo:u.state.DB.UnattachAttachmentsForStatus
+ for _, id := range statusToDelete.AttachmentIDs {
+ if _, err := u.media.Unattach(ctx, statusToDelete.Account, id); err != nil {
+ errs.Appendf("error unattaching media: %w", err)
+ }
+ }
+ }
+
+ // delete all mention entries generated by this status
+ // todo:u.state.DB.DeleteMentionsForStatus
+ for _, id := range statusToDelete.MentionIDs {
+ if err := u.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 := u.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 := u.state.DB.DeleteStatusBookmarksForStatus(ctx, statusToDelete.ID); err != nil {
+ errs.Appendf("error deleting status bookmarks: %w", err)
+ }
+
+ // delete all faves of this status
+ if err := u.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 := u.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 := u.state.DB.DeletePollVotes(ctx, pollID); err != nil {
+ errs.Appendf("error deleting status poll votes: %w", err)
+ }
+
+ // Cancel any scheduled expiry task for poll.
+ _ = u.state.Workers.Scheduler.Cancel(pollID)
+ }
+
+ // delete all boosts for this status + remove them from timelines
+ boosts, err := u.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 := u.surface.deleteStatusFromTimelines(ctx, boost.ID); err != nil {
+ errs.Appendf("error deleting boost from timelines: %w", err)
+ }
+ if err := u.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 := u.surface.deleteStatusFromTimelines(ctx, statusToDelete.ID); err != nil {
+ errs.Appendf("error deleting status from timelines: %w", err)
+ }
+
+ // finally, delete the status itself
+ if err := u.state.DB.DeleteStatusByID(ctx, statusToDelete.ID); err != nil {
+ errs.Appendf("error deleting status: %w", err)
+ }
+
+ return errs.Combine()
+}
+
+// redirectFollowers redirects all local
+// followers of originAcct to targetAcct.
+//
+// Both accounts must be fully dereferenced
+// already, and the Move must be valid.
+//
+// Return bool will be true if all goes OK.
+func (u *utilF) redirectFollowers(
+ ctx context.Context,
+ originAcct *gtsmodel.Account,
+ targetAcct *gtsmodel.Account,
+) bool {
+ // Any local followers of originAcct should
+ // send follow requests to targetAcct instead,
+ // and have followers of originAcct removed.
+ //
+ // Select local followers with barebones, since
+ // we only need follow.Account and we can get
+ // that ourselves.
+ followers, err := u.state.DB.GetAccountLocalFollowers(
+ gtscontext.SetBarebones(ctx),
+ originAcct.ID,
+ )
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ log.Errorf(ctx,
+ "db error getting follows targeting originAcct: %v",
+ err,
+ )
+ return false
+ }
+
+ for _, follow := range followers {
+ // Fetch the local account that
+ // owns the follow targeting originAcct.
+ if follow.Account, err = u.state.DB.GetAccountByID(
+ gtscontext.SetBarebones(ctx),
+ follow.AccountID,
+ ); err != nil {
+ log.Errorf(ctx,
+ "db error getting follow account %s: %v",
+ follow.AccountID, err,
+ )
+ return false
+ }
+
+ // Use the account processor FollowCreate
+ // function to send off the new follow,
+ // carrying over the Reblogs and Notify
+ // values from the old follow to the new.
+ //
+ // This will also handle cases where our
+ // account has already followed the target
+ // account, by just updating the existing
+ // follow of target account.
+ //
+ // Also, ensure new follow wouldn't be a
+ // self follow, since that will error.
+ if follow.AccountID != targetAcct.ID {
+ if _, err := u.account.FollowCreate(
+ ctx,
+ follow.Account,
+ &apimodel.AccountFollowRequest{
+ ID: targetAcct.ID,
+ Reblogs: follow.ShowReblogs,
+ Notify: follow.Notify,
+ },
+ ); err != nil {
+ log.Errorf(ctx,
+ "error creating new follow for account %s: %v",
+ follow.AccountID, err,
+ )
+ return false
+ }
+ }
+
+ // New follow is in the process of
+ // sending, remove the existing follow.
+ // This will send out an Undo Activity for each Follow.
+ if _, err := u.account.FollowRemove(
+ ctx,
+ follow.Account,
+ follow.TargetAccountID,
+ ); err != nil {
+ log.Errorf(ctx,
+ "error removing old follow for account %s: %v",
+ follow.AccountID, err,
+ )
+ return false
+ }
+ }
+
+ return true
+}