From 197ef03eadfe1ae28456f0554d598fd6226c1164 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Fri, 4 Jun 2021 16:35:58 +0200 Subject: [PATCH] federate status deletes properly --- internal/api/client/status/statusdelete.go | 6 ++ internal/db/db.go | 10 +- internal/db/pg/pg.go | 102 ++---------------- internal/processing/account.go | 4 +- internal/processing/fromclientapi.go | 53 +++++++++ internal/processing/fromcommon.go | 6 +- internal/processing/fromfederator.go | 5 + internal/processing/search.go | 2 +- .../processing/synchronous/status/boost.go | 2 +- .../synchronous/status/boostedby.go | 2 +- .../processing/synchronous/status/delete.go | 11 +- .../processing/synchronous/status/fave.go | 2 +- .../processing/synchronous/status/favedby.go | 2 +- internal/processing/synchronous/status/get.go | 2 +- .../processing/synchronous/status/unfave.go | 5 +- internal/processing/timeline.go | 6 +- internal/timeline/manager.go | 37 +++++++ internal/timeline/timeline.go | 38 ++++--- internal/typeutils/astointernal.go | 2 +- 19 files changed, 154 insertions(+), 143 deletions(-) diff --git a/internal/api/client/status/statusdelete.go b/internal/api/client/status/statusdelete.go index e554165..5c2a1aa 100644 --- a/internal/api/client/status/statusdelete.go +++ b/internal/api/client/status/statusdelete.go @@ -56,5 +56,11 @@ func (m *Module) StatusDELETEHandler(c *gin.Context) { return } + // the status was already gone/never existed + if mastoStatus == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Record not found"}) + return + } + c.JSON(http.StatusOK, mastoStatus) } diff --git a/internal/db/db.go b/internal/db/db.go index af0eb89..b5c3266 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -212,7 +212,7 @@ type DB interface { // 3. Accounts boosted by the target status // // Will return an error if something goes wrong while pulling stuff out of the database. - StatusVisible(targetStatus *gtsmodel.Status, targetAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, relevantAccounts *gtsmodel.RelevantAccounts) (bool, error) + StatusVisible(targetStatus *gtsmodel.Status, requestingAccount *gtsmodel.Account, relevantAccounts *gtsmodel.RelevantAccounts) (bool, error) // Follows returns true if sourceAccount follows target account, or an error if something goes wrong while finding out. Follows(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error) @@ -247,10 +247,6 @@ type DB interface { // StatusBookmarkedBy checks if a given status has been bookmarked by a given account ID StatusBookmarkedBy(status *gtsmodel.Status, accountID string) (bool, error) - // UnfaveStatus unfaves the given status, using accountID as the unfaver (sure, that's a word). - // The returned fave will be nil if the status was already not faved. - UnfaveStatus(status *gtsmodel.Status, accountID string) (*gtsmodel.StatusFave, error) - // WhoFavedStatus returns a slice of accounts who faved the given status. // This slice will be unfiltered, not taking account of blocks and whatnot, so filter it before serving it back to a user. WhoFavedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error) @@ -261,10 +257,6 @@ type DB interface { GetStatusesWhereFollowing(accountID string, limit int, offsetStatusID string) ([]*gtsmodel.Status, error) - // GetHomeTimelineForAccount fetches the account's HOME timeline -- ie., posts and replies from people they *follow*. - // It will use the given filters and try to return as many statuses up to the limit as possible. - GetHomeTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) - // GetPublicTimelineForAccount fetches the account's PUBLIC timline -- ie., posts and replies that are public. // It will use the given filters and try to return as many statuses as possible up to the limit. GetPublicTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) diff --git a/internal/db/pg/pg.go b/internal/db/pg/pg.go index 09eb79b..15360bc 100644 --- a/internal/db/pg/pg.go +++ b/internal/db/pg/pg.go @@ -785,9 +785,11 @@ func (ps *postgresService) GetRelationship(requestingAccount string, targetAccou return r, nil } -func (ps *postgresService) StatusVisible(targetStatus *gtsmodel.Status, targetAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, relevantAccounts *gtsmodel.RelevantAccounts) (bool, error) { +func (ps *postgresService) StatusVisible(targetStatus *gtsmodel.Status, requestingAccount *gtsmodel.Account, relevantAccounts *gtsmodel.RelevantAccounts) (bool, error) { l := ps.log.WithField("func", "StatusVisible") + targetAccount := relevantAccounts.StatusAuthor + // if target account is suspended then don't show the status if !targetAccount.SuspendedAt.IsZero() { l.Debug("target account suspended at is not zero") @@ -869,7 +871,7 @@ func (ps *postgresService) StatusVisible(targetStatus *gtsmodel.Status, targetAc // check other accounts mentioned/boosted by/replied to by the status, if they exist if relevantAccounts != nil { // status replies to account id - if relevantAccounts.ReplyToAccount != nil { + if relevantAccounts.ReplyToAccount != nil && relevantAccounts.ReplyToAccount.ID != requestingAccount.ID { if blocked, err := ps.Blocked(relevantAccounts.ReplyToAccount.ID, requestingAccount.ID); err != nil { return false, err } else if blocked { @@ -1087,55 +1089,6 @@ func (ps *postgresService) StatusBookmarkedBy(status *gtsmodel.Status, accountID return ps.conn.Model(>smodel.StatusBookmark{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists() } -// func (ps *postgresService) FaveStatus(status *gtsmodel.Status, accountID string) (*gtsmodel.StatusFave, error) { -// // first check if a fave already exists, we can just return if so -// existingFave := >smodel.StatusFave{} -// err := ps.conn.Model(existingFave).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Select() -// if err == nil { -// // fave already exists so just return nothing at all -// return nil, nil -// } - -// // an error occurred so it might exist or not, we don't know -// if err != pg.ErrNoRows { -// return nil, err -// } - -// // it doesn't exist so create it -// newFave := >smodel.StatusFave{ -// AccountID: accountID, -// TargetAccountID: status.AccountID, -// StatusID: status.ID, -// } -// if _, err = ps.conn.Model(newFave).Insert(); err != nil { -// return nil, err -// } - -// return newFave, nil -// } - -func (ps *postgresService) UnfaveStatus(status *gtsmodel.Status, accountID string) (*gtsmodel.StatusFave, error) { - // if a fave doesn't exist, we don't need to do anything - existingFave := >smodel.StatusFave{} - err := ps.conn.Model(existingFave).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Select() - // the fave doesn't exist so return nothing at all - if err == pg.ErrNoRows { - return nil, nil - } - - // an error occurred so it might exist or not, we don't know - if err != nil && err != pg.ErrNoRows { - return nil, err - } - - // the fave exists so remove it - if _, err = ps.conn.Model(>smodel.StatusFave{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Delete(); err != nil { - return nil, err - } - - return existingFave, nil -} - func (ps *postgresService) WhoFavedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error) { accounts := []*gtsmodel.Account{} @@ -1216,56 +1169,13 @@ func (ps *postgresService) GetStatusesWhereFollowing(accountID string, limit int return statuses, nil } -func (ps *postgresService) GetHomeTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) { - statuses := []*gtsmodel.Status{} - - q := ps.conn.Model(&statuses) - - q = q.ColumnExpr("status.*"). - Join("JOIN follows AS f ON f.target_account_id = status.account_id"). - Where("f.account_id = ?", accountID). - Limit(limit). - Order("status.created_at DESC") - - if maxID != "" { - s := >smodel.Status{} - if err := ps.conn.Model(s).Where("id = ?", maxID).Select(); err != nil { - return nil, err - } - q = q.Where("status.created_at < ?", s.CreatedAt) - } - - if minID != "" { - s := >smodel.Status{} - if err := ps.conn.Model(s).Where("id = ?", minID).Select(); err != nil { - return nil, err - } - q = q.Where("status.created_at > ?", s.CreatedAt) - } - - if sinceID != "" { - s := >smodel.Status{} - if err := ps.conn.Model(s).Where("id = ?", sinceID).Select(); err != nil { - return nil, err - } - q = q.Where("status.created_at > ?", s.CreatedAt) - } - - err := q.Select() - if err != nil { - if err != pg.ErrNoRows { - return nil, err - } - } - - return statuses, nil -} - func (ps *postgresService) GetPublicTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) { statuses := []*gtsmodel.Status{} q := ps.conn.Model(&statuses). Where("visibility = ?", gtsmodel.VisibilityPublic). + Where("? IS NULL", pg.Ident("in_reply_to_id")). + Where("? IS NULL", pg.Ident("boost_of_id")). Limit(limit). Order("created_at DESC") diff --git a/internal/processing/account.go b/internal/processing/account.go index 28fbb51..c570001 100644 --- a/internal/processing/account.go +++ b/internal/processing/account.go @@ -227,7 +227,7 @@ func (p *processor) AccountStatusesGet(authed *oauth.Auth, targetAccountID strin return nil, gtserror.NewErrorInternalError(fmt.Errorf("error getting relevant statuses: %s", err)) } - visible, err := p.db.StatusVisible(&s, targetAccount, authed.Account, relevantAccounts) + visible, err := p.db.StatusVisible(&s, authed.Account, relevantAccounts) if err != nil { return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking status visibility: %s", err)) } @@ -246,7 +246,7 @@ func (p *processor) AccountStatusesGet(authed *oauth.Auth, targetAccountID strin return nil, gtserror.NewErrorInternalError(fmt.Errorf("error getting relevant accounts from boosted status: %s", err)) } - boostedVisible, err := p.db.StatusVisible(bs, relevantAccounts.BoostedAccount, authed.Account, boostedRelevantAccounts) + boostedVisible, err := p.db.StatusVisible(bs, authed.Account, boostedRelevantAccounts) if err != nil { return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking boosted status visibility: %s", err)) } diff --git a/internal/processing/fromclientapi.go b/internal/processing/fromclientapi.go index 7f75982..d171e59 100644 --- a/internal/processing/fromclientapi.go +++ b/internal/processing/fromclientapi.go @@ -135,6 +135,22 @@ func (p *processor) processFromClientAPI(clientMsg gtsmodel.FromClientAPI) error } return p.federateUnfave(fave, clientMsg.OriginAccount, clientMsg.TargetAccount) } + case gtsmodel.ActivityStreamsDelete: + // DELETE + switch clientMsg.APObjectType { + case gtsmodel.ActivityStreamsNote: + // DELETE STATUS/NOTE + statusToDelete, ok := clientMsg.GTSModel.(*gtsmodel.Status) + if !ok { + return errors.New("note was not parseable as *gtsmodel.Status") + } + + if err := p.deleteStatusFromTimelines(statusToDelete); err != nil { + return err + } + + return p.federateStatusDelete(statusToDelete, clientMsg.OriginAccount) + } } return nil } @@ -154,6 +170,43 @@ func (p *processor) federateStatus(status *gtsmodel.Status) error { return err } +func (p *processor) federateStatusDelete(status *gtsmodel.Status, originAccount *gtsmodel.Account) error { + asStatus, err := p.tc.StatusToAS(status) + if err != nil { + return fmt.Errorf("federateStatusDelete: error converting status to as format: %s", err) + } + + outboxIRI, err := url.Parse(originAccount.OutboxURI) + if err != nil { + return fmt.Errorf("federateStatusDelete: error parsing outboxURI %s: %s", originAccount.OutboxURI, err) + } + + actorIRI, err := url.Parse(originAccount.URI) + if err != nil { + return fmt.Errorf("federateStatusDelete: error parsing actorIRI %s: %s", originAccount.URI, err) + } + + // create a delete and set the appropriate actor on it + delete := streams.NewActivityStreamsDelete() + + // set the actor for the delete + deleteActor := streams.NewActivityStreamsActorProperty() + deleteActor.AppendIRI(actorIRI) + delete.SetActivityStreamsActor(deleteActor) + + // Set the status as the 'object' property. + deleteObject := streams.NewActivityStreamsObjectProperty() + deleteObject.AppendActivityStreamsNote(asStatus) + delete.SetActivityStreamsObject(deleteObject) + + // set the to and cc as the original to/cc of the original status + delete.SetActivityStreamsTo(asStatus.GetActivityStreamsTo()) + delete.SetActivityStreamsCc(asStatus.GetActivityStreamsCc()) + + _, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, delete) + return err +} + func (p *processor) federateFollow(followRequest *gtsmodel.FollowRequest, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error { // if both accounts are local there's nothing to do here if originAccount.Domain == "" && targetAccount.Domain == "" { diff --git a/internal/processing/fromcommon.go b/internal/processing/fromcommon.go index 3294e9a..ce4b17a 100644 --- a/internal/processing/fromcommon.go +++ b/internal/processing/fromcommon.go @@ -286,7 +286,7 @@ func (p *processor) timelineStatusForAccount(status *gtsmodel.Status, accountID } // make sure the status is visible - visible, err := p.db.StatusVisible(status, status.GTSAuthorAccount, timelineAccount, relevantAccounts) + visible, err := p.db.StatusVisible(status, timelineAccount, relevantAccounts) if err != nil { errors <- fmt.Errorf("timelineStatus: error getting visibility for status for timeline with id %s: %s", accountID, err) return @@ -301,6 +301,6 @@ func (p *processor) timelineStatusForAccount(status *gtsmodel.Status, accountID } } -func (p *processor) fullyDeleteStatus(status *gtsmodel.Status, accountID string) error { - return nil +func (p *processor) deleteStatusFromTimelines(status *gtsmodel.Status) error { + return p.timelineManager.WipeStatusFromAllTimelines(status.ID) } diff --git a/internal/processing/fromfederator.go b/internal/processing/fromfederator.go index 480e8fd..0bd141a 100644 --- a/internal/processing/fromfederator.go +++ b/internal/processing/fromfederator.go @@ -146,6 +146,11 @@ func (p *processor) processFromFederator(federatorMsg gtsmodel.FromFederator) er // 1. delete all media associated with status // 2. delete boosts of status // 3. etc etc etc + statusToDelete, ok := federatorMsg.GTSModel.(*gtsmodel.Status) + if !ok { + return errors.New("note was not parseable as *gtsmodel.Status") + } + return p.deleteStatusFromTimelines(statusToDelete) case gtsmodel.ActivityStreamsProfile: // DELETE A PROFILE/ACCOUNT // TODO: handle side effects of account deletion here: delete all objects, statuses, media etc associated with account diff --git a/internal/processing/search.go b/internal/processing/search.go index 9cc60cd..4b9282b 100644 --- a/internal/processing/search.go +++ b/internal/processing/search.go @@ -109,7 +109,7 @@ func (p *processor) SearchGet(authed *oauth.Auth, searchQuery *apimodel.SearchQu if err != nil { continue } - if visible, err := p.db.StatusVisible(foundStatus, statusOwner, authed.Account, relevantAccounts); !visible || err != nil { + if visible, err := p.db.StatusVisible(foundStatus, authed.Account, relevantAccounts); !visible || err != nil { continue } diff --git a/internal/processing/synchronous/status/boost.go b/internal/processing/synchronous/status/boost.go index 84cb02c..a746e9f 100644 --- a/internal/processing/synchronous/status/boost.go +++ b/internal/processing/synchronous/status/boost.go @@ -31,7 +31,7 @@ func (p *processor) Boost(account *gtsmodel.Account, application *gtsmodel.Appli } l.Trace("going to see if status is visible") - visible, err := p.db.StatusVisible(targetStatus, targetAccount, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that + visible, err := p.db.StatusVisible(targetStatus, account, relevantAccounts) if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) } diff --git a/internal/processing/synchronous/status/boostedby.go b/internal/processing/synchronous/status/boostedby.go index 8f6cfb5..8ebfceb 100644 --- a/internal/processing/synchronous/status/boostedby.go +++ b/internal/processing/synchronous/status/boostedby.go @@ -31,7 +31,7 @@ func (p *processor) BoostedBy(account *gtsmodel.Account, targetStatusID string) } l.Trace("going to see if status is visible") - visible, err := p.db.StatusVisible(targetStatus, targetAccount, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that + visible, err := p.db.StatusVisible(targetStatus, account, relevantAccounts) if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error seeing if status %s is visible: %s", targetStatus.ID, err)) } diff --git a/internal/processing/synchronous/status/delete.go b/internal/processing/synchronous/status/delete.go index 5d98722..7e25108 100644 --- a/internal/processing/synchronous/status/delete.go +++ b/internal/processing/synchronous/status/delete.go @@ -5,6 +5,7 @@ import ( "fmt" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) @@ -14,7 +15,11 @@ func (p *processor) Delete(account *gtsmodel.Account, targetStatusID string) (*a l.Tracef("going to search for target status %s", targetStatusID) targetStatus := >smodel.Status{} if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) + if _, ok := err.(db.ErrNoEntries); !ok { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) + } + // status is already gone + return nil, nil } if targetStatus.AccountID != account.ID { @@ -40,10 +45,10 @@ func (p *processor) Delete(account *gtsmodel.Account, targetStatusID string) (*a return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) } - if err := p.db.DeleteByID(targetStatus.ID, targetStatus); err != nil { + if err := p.db.DeleteByID(targetStatus.ID, >smodel.Status{}); err != nil { return nil, gtserror.NewErrorInternalError(fmt.Errorf("error deleting status from the database: %s", err)) } - + // send it back to the processor for async processing p.fromClientAPI <- gtsmodel.FromClientAPI{ APObjectType: gtsmodel.ActivityStreamsNote, diff --git a/internal/processing/synchronous/status/fave.go b/internal/processing/synchronous/status/fave.go index e7bf44a..ea06240 100644 --- a/internal/processing/synchronous/status/fave.go +++ b/internal/processing/synchronous/status/fave.go @@ -41,7 +41,7 @@ func (p *processor) Fave(account *gtsmodel.Account, targetStatusID string) (*api } l.Trace("going to see if status is visible") - visible, err := p.db.StatusVisible(targetStatus, targetAccount, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that + visible, err := p.db.StatusVisible(targetStatus, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) } diff --git a/internal/processing/synchronous/status/favedby.go b/internal/processing/synchronous/status/favedby.go index 9b00ce8..bda47d5 100644 --- a/internal/processing/synchronous/status/favedby.go +++ b/internal/processing/synchronous/status/favedby.go @@ -31,7 +31,7 @@ func (p *processor) FavedBy(account *gtsmodel.Account, targetStatusID string) ([ } l.Trace("going to see if status is visible") - visible, err := p.db.StatusVisible(targetStatus, targetAccount, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that + visible, err := p.db.StatusVisible(targetStatus, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) } diff --git a/internal/processing/synchronous/status/get.go b/internal/processing/synchronous/status/get.go index e36304c..7dbbb4e 100644 --- a/internal/processing/synchronous/status/get.go +++ b/internal/processing/synchronous/status/get.go @@ -31,7 +31,7 @@ func (p *processor) Get(account *gtsmodel.Account, targetStatusID string) (*apim } l.Trace("going to see if status is visible") - visible, err := p.db.StatusVisible(targetStatus, targetAccount, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that + visible, err := p.db.StatusVisible(targetStatus, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) } diff --git a/internal/processing/synchronous/status/unfave.go b/internal/processing/synchronous/status/unfave.go index 237eedc..54cbbf5 100644 --- a/internal/processing/synchronous/status/unfave.go +++ b/internal/processing/synchronous/status/unfave.go @@ -31,7 +31,7 @@ func (p *processor) Unfave(account *gtsmodel.Account, targetStatusID string) (*a } l.Trace("going to see if status is visible") - visible, err := p.db.StatusVisible(targetStatus, targetAccount, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that + visible, err := p.db.StatusVisible(targetStatus, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) } @@ -60,8 +60,7 @@ func (p *processor) Unfave(account *gtsmodel.Account, targetStatusID string) (*a if toUnfave { // we had a fave, so take some action to get rid of it - _, err = p.db.UnfaveStatus(targetStatus, account.ID) - if err != nil { + if err := p.db.DeleteWhere([]db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: account.ID}}, gtsFave); err != nil { return nil, gtserror.NewErrorInternalError(fmt.Errorf("error unfaveing status: %s", err)) } diff --git a/internal/processing/timeline.go b/internal/processing/timeline.go index 6db12c1..387972c 100644 --- a/internal/processing/timeline.go +++ b/internal/processing/timeline.go @@ -74,7 +74,7 @@ func (p *processor) filterStatuses(authed *oauth.Auth, statuses []*gtsmodel.Stat continue } - visible, err := p.db.StatusVisible(s, targetAccount, authed.Account, relevantAccounts) + visible, err := p.db.StatusVisible(s, authed.Account, relevantAccounts) if err != nil { return nil, gtserror.NewErrorInternalError(fmt.Errorf("HomeTimelineGet: error checking status visibility: %s", err)) } @@ -98,7 +98,7 @@ func (p *processor) filterStatuses(authed *oauth.Auth, statuses []*gtsmodel.Stat continue } - boostedVisible, err := p.db.StatusVisible(bs, relevantAccounts.BoostedAccount, authed.Account, boostedRelevantAccounts) + boostedVisible, err := p.db.StatusVisible(bs, authed.Account, boostedRelevantAccounts) if err != nil { return nil, gtserror.NewErrorInternalError(fmt.Errorf("HomeTimelineGet: error checking boosted status visibility: %s", err)) } @@ -204,7 +204,7 @@ func (p *processor) indexAndIngest(statuses []*gtsmodel.Status, timelineAccount l.Error(fmt.Errorf("initTimelineFor: error getting relevant accounts from status %s: %s", s.ID, err)) continue } - visible, err := p.db.StatusVisible(s, relevantAccounts.StatusAuthor, timelineAccount, relevantAccounts) + visible, err := p.db.StatusVisible(s, timelineAccount, relevantAccounts) if err != nil { l.Error(fmt.Errorf("initTimelineFor: error checking visibility of status %s: %s", s.ID, err)) continue diff --git a/internal/timeline/manager.go b/internal/timeline/manager.go index 45dd08b..c226922 100644 --- a/internal/timeline/manager.go +++ b/internal/timeline/manager.go @@ -19,6 +19,8 @@ package timeline import ( + "fmt" + "strings" "sync" "github.com/sirupsen/logrus" @@ -66,6 +68,10 @@ type Manager interface { GetOldestIndexedID(timelineAccountID string) (string, error) // PrepareXFromTop prepares limit n amount of posts, based on their indexed representations, from the top of the index. PrepareXFromTop(timelineAccountID string, limit int) error + // WipeStatusFromTimeline completely removes a status and from the index and prepared posts of the given account ID + WipeStatusFromTimeline(timelineAccountID string, statusID string) error + // WipeStatusFromAllTimelines removes the status from the index and prepared posts of all timelines + WipeStatusFromAllTimelines(statusID string) error } // NewManager returns a new timeline manager with the given database, typeconverter, config, and log. @@ -172,6 +178,37 @@ func (m *manager) PrepareXFromTop(timelineAccountID string, limit int) error { return t.PrepareXFromTop(limit) } +func (m *manager) WipeStatusFromTimeline(timelineAccountID string, statusID string) error { + t := m.getOrCreateTimeline(timelineAccountID) + + return t.Remove(statusID) +} + +func (m *manager) WipeStatusFromAllTimelines(statusID string) error { + + errors := []string{} + + m.accountTimelines.Range(func(k interface{}, i interface{}) bool { + t, ok := i.(Timeline) + if !ok { + panic("couldn't parse entry as Timeline, this should never happen so panic") + } + + if err := t.Remove(statusID); err != nil { + errors = append(errors, err.Error()) + } + + return false + }) + + var err error + if len(errors) > 0 { + err = fmt.Errorf("one or more errors removing status %s from all timelines: %s", statusID, strings.Join(errors, ";")) + } + + return err +} + func (m *manager) getOrCreateTimeline(timelineAccountID string) Timeline { var t Timeline i, ok := m.accountTimelines.Load(timelineAccountID) diff --git a/internal/timeline/timeline.go b/internal/timeline/timeline.go index bd4f16f..3898eb6 100644 --- a/internal/timeline/timeline.go +++ b/internal/timeline/timeline.go @@ -334,27 +334,31 @@ func (t *timeline) Remove(statusID string) error { t.Lock() defer t.Unlock() - // remove the entry from the post index - for e := t.postIndex.data.Front(); e != nil; e = e.Next() { - entry, ok := e.Value.(*postIndexEntry) - if !ok { - return errors.New("Remove: could not parse e as a postIndexEntry") - } - if entry.statusID == statusID { - t.postIndex.data.Remove(e) - break // bail once we found and removed it + if t.postIndex != nil && t.postIndex.data != nil { + // remove the entry from the post index + for e := t.postIndex.data.Front(); e != nil; e = e.Next() { + entry, ok := e.Value.(*postIndexEntry) + if !ok { + return errors.New("Remove: could not parse e as a postIndexEntry") + } + if entry.statusID == statusID { + t.postIndex.data.Remove(e) + break // bail once we found and removed it + } } } // remove the entry from prepared posts - for e := t.preparedPosts.data.Front(); e != nil; e = e.Next() { - entry, ok := e.Value.(*preparedPostsEntry) - if !ok { - return errors.New("Remove: could not parse e as a preparedPostsEntry") - } - if entry.statusID == statusID { - t.preparedPosts.data.Remove(e) - break // bail once we found and removed it + if t.preparedPosts != nil && t.preparedPosts.data != nil { + for e := t.preparedPosts.data.Front(); e != nil; e = e.Next() { + entry, ok := e.Value.(*preparedPostsEntry) + if !ok { + return errors.New("Remove: could not parse e as a preparedPostsEntry") + } + if entry.statusID == statusID { + t.preparedPosts.data.Remove(e) + break // bail once we found and removed it + } } } diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go index 936cd9a..3a9cc05 100644 --- a/internal/typeutils/astointernal.go +++ b/internal/typeutils/astointernal.go @@ -222,7 +222,7 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e status.APStatusOwnerURI = attributedTo.String() statusOwner := >smodel.Account{} - if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: attributedTo.String()}}, statusOwner); err != nil { + if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: attributedTo.String(), CaseInsensitive: true}}, statusOwner); err != nil { return nil, fmt.Errorf("couldn't get status owner from db: %s", err) } status.AccountID = statusOwner.ID