federate status deletes properly

This commit is contained in:
tsmethurst 2021-06-04 16:35:58 +02:00
parent 5d2b69c256
commit 197ef03ead
19 changed files with 154 additions and 143 deletions

View File

@ -56,5 +56,11 @@ func (m *Module) StatusDELETEHandler(c *gin.Context) {
return 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) c.JSON(http.StatusOK, mastoStatus)
} }

View File

@ -212,7 +212,7 @@ type DB interface {
// 3. Accounts boosted by the target status // 3. Accounts boosted by the target status
// //
// Will return an error if something goes wrong while pulling stuff out of the database. // 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 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) 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 checks if a given status has been bookmarked by a given account ID
StatusBookmarkedBy(status *gtsmodel.Status, accountID string) (bool, error) 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. // 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. // 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) WhoFavedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error)
@ -261,10 +257,6 @@ type DB interface {
GetStatusesWhereFollowing(accountID string, limit int, offsetStatusID string) ([]*gtsmodel.Status, error) 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. // 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. // 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) GetPublicTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error)

View File

@ -785,9 +785,11 @@ func (ps *postgresService) GetRelationship(requestingAccount string, targetAccou
return r, nil 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") l := ps.log.WithField("func", "StatusVisible")
targetAccount := relevantAccounts.StatusAuthor
// if target account is suspended then don't show the status // if target account is suspended then don't show the status
if !targetAccount.SuspendedAt.IsZero() { if !targetAccount.SuspendedAt.IsZero() {
l.Debug("target account suspended at is not zero") 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 // check other accounts mentioned/boosted by/replied to by the status, if they exist
if relevantAccounts != nil { if relevantAccounts != nil {
// status replies to account id // 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 { if blocked, err := ps.Blocked(relevantAccounts.ReplyToAccount.ID, requestingAccount.ID); err != nil {
return false, err return false, err
} else if blocked { } else if blocked {
@ -1087,55 +1089,6 @@ func (ps *postgresService) StatusBookmarkedBy(status *gtsmodel.Status, accountID
return ps.conn.Model(&gtsmodel.StatusBookmark{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists() return ps.conn.Model(&gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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(&gtsmodel.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) { func (ps *postgresService) WhoFavedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error) {
accounts := []*gtsmodel.Account{} accounts := []*gtsmodel.Account{}
@ -1216,56 +1169,13 @@ func (ps *postgresService) GetStatusesWhereFollowing(accountID string, limit int
return statuses, nil 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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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) { func (ps *postgresService) GetPublicTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) {
statuses := []*gtsmodel.Status{} statuses := []*gtsmodel.Status{}
q := ps.conn.Model(&statuses). q := ps.conn.Model(&statuses).
Where("visibility = ?", gtsmodel.VisibilityPublic). Where("visibility = ?", gtsmodel.VisibilityPublic).
Where("? IS NULL", pg.Ident("in_reply_to_id")).
Where("? IS NULL", pg.Ident("boost_of_id")).
Limit(limit). Limit(limit).
Order("created_at DESC") Order("created_at DESC")

View File

@ -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)) 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 { if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking status visibility: %s", err)) 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)) 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 { if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking boosted status visibility: %s", err)) return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking boosted status visibility: %s", err))
} }

View File

@ -135,6 +135,22 @@ func (p *processor) processFromClientAPI(clientMsg gtsmodel.FromClientAPI) error
} }
return p.federateUnfave(fave, clientMsg.OriginAccount, clientMsg.TargetAccount) 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 return nil
} }
@ -154,6 +170,43 @@ func (p *processor) federateStatus(status *gtsmodel.Status) error {
return err 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 { 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 both accounts are local there's nothing to do here
if originAccount.Domain == "" && targetAccount.Domain == "" { if originAccount.Domain == "" && targetAccount.Domain == "" {

View File

@ -286,7 +286,7 @@ func (p *processor) timelineStatusForAccount(status *gtsmodel.Status, accountID
} }
// make sure the status is visible // 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 { if err != nil {
errors <- fmt.Errorf("timelineStatus: error getting visibility for status for timeline with id %s: %s", accountID, err) errors <- fmt.Errorf("timelineStatus: error getting visibility for status for timeline with id %s: %s", accountID, err)
return return
@ -301,6 +301,6 @@ func (p *processor) timelineStatusForAccount(status *gtsmodel.Status, accountID
} }
} }
func (p *processor) fullyDeleteStatus(status *gtsmodel.Status, accountID string) error { func (p *processor) deleteStatusFromTimelines(status *gtsmodel.Status) error {
return nil return p.timelineManager.WipeStatusFromAllTimelines(status.ID)
} }

View File

@ -146,6 +146,11 @@ func (p *processor) processFromFederator(federatorMsg gtsmodel.FromFederator) er
// 1. delete all media associated with status // 1. delete all media associated with status
// 2. delete boosts of status // 2. delete boosts of status
// 3. etc etc etc // 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: case gtsmodel.ActivityStreamsProfile:
// DELETE A PROFILE/ACCOUNT // DELETE A PROFILE/ACCOUNT
// TODO: handle side effects of account deletion here: delete all objects, statuses, media etc associated with account // TODO: handle side effects of account deletion here: delete all objects, statuses, media etc associated with account

View File

@ -109,7 +109,7 @@ func (p *processor) SearchGet(authed *oauth.Auth, searchQuery *apimodel.SearchQu
if err != nil { if err != nil {
continue 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 continue
} }

View File

@ -31,7 +31,7 @@ func (p *processor) Boost(account *gtsmodel.Account, application *gtsmodel.Appli
} }
l.Trace("going to see if status is visible") 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 { if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err))
} }

View File

@ -31,7 +31,7 @@ func (p *processor) BoostedBy(account *gtsmodel.Account, targetStatusID string)
} }
l.Trace("going to see if status is visible") 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 { if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error seeing if status %s is visible: %s", targetStatus.ID, err)) return nil, gtserror.NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error seeing if status %s is visible: %s", targetStatus.ID, err))
} }

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" 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/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "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) l.Tracef("going to search for target status %s", targetStatusID)
targetStatus := &gtsmodel.Status{} targetStatus := &gtsmodel.Status{}
if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { 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 { 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)) 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, &gtsmodel.Status{}); err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error deleting status from the database: %s", err)) return nil, gtserror.NewErrorInternalError(fmt.Errorf("error deleting status from the database: %s", err))
} }
// send it back to the processor for async processing // send it back to the processor for async processing
p.fromClientAPI <- gtsmodel.FromClientAPI{ p.fromClientAPI <- gtsmodel.FromClientAPI{
APObjectType: gtsmodel.ActivityStreamsNote, APObjectType: gtsmodel.ActivityStreamsNote,

View File

@ -41,7 +41,7 @@ func (p *processor) Fave(account *gtsmodel.Account, targetStatusID string) (*api
} }
l.Trace("going to see if status is visible") 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 { if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err))
} }

View File

@ -31,7 +31,7 @@ func (p *processor) FavedBy(account *gtsmodel.Account, targetStatusID string) ([
} }
l.Trace("going to see if status is visible") 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 { if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err))
} }

View File

@ -31,7 +31,7 @@ func (p *processor) Get(account *gtsmodel.Account, targetStatusID string) (*apim
} }
l.Trace("going to see if status is visible") 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 { if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err))
} }

View File

@ -31,7 +31,7 @@ func (p *processor) Unfave(account *gtsmodel.Account, targetStatusID string) (*a
} }
l.Trace("going to see if status is visible") 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 { if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) 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 { if toUnfave {
// we had a fave, so take some action to get rid of it // we had a fave, so take some action to get rid of it
_, err = p.db.UnfaveStatus(targetStatus, account.ID) if err := p.db.DeleteWhere([]db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: account.ID}}, gtsFave); err != nil {
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error unfaveing status: %s", err)) return nil, gtserror.NewErrorInternalError(fmt.Errorf("error unfaveing status: %s", err))
} }

View File

@ -74,7 +74,7 @@ func (p *processor) filterStatuses(authed *oauth.Auth, statuses []*gtsmodel.Stat
continue continue
} }
visible, err := p.db.StatusVisible(s, targetAccount, authed.Account, relevantAccounts) visible, err := p.db.StatusVisible(s, authed.Account, relevantAccounts)
if err != nil { if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("HomeTimelineGet: error checking status visibility: %s", err)) 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 continue
} }
boostedVisible, err := p.db.StatusVisible(bs, relevantAccounts.BoostedAccount, authed.Account, boostedRelevantAccounts) boostedVisible, err := p.db.StatusVisible(bs, authed.Account, boostedRelevantAccounts)
if err != nil { if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("HomeTimelineGet: error checking boosted status visibility: %s", err)) 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)) l.Error(fmt.Errorf("initTimelineFor: error getting relevant accounts from status %s: %s", s.ID, err))
continue continue
} }
visible, err := p.db.StatusVisible(s, relevantAccounts.StatusAuthor, timelineAccount, relevantAccounts) visible, err := p.db.StatusVisible(s, timelineAccount, relevantAccounts)
if err != nil { if err != nil {
l.Error(fmt.Errorf("initTimelineFor: error checking visibility of status %s: %s", s.ID, err)) l.Error(fmt.Errorf("initTimelineFor: error checking visibility of status %s: %s", s.ID, err))
continue continue

View File

@ -19,6 +19,8 @@
package timeline package timeline
import ( import (
"fmt"
"strings"
"sync" "sync"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -66,6 +68,10 @@ type Manager interface {
GetOldestIndexedID(timelineAccountID string) (string, error) GetOldestIndexedID(timelineAccountID string) (string, error)
// PrepareXFromTop prepares limit n amount of posts, based on their indexed representations, from the top of the index. // PrepareXFromTop prepares limit n amount of posts, based on their indexed representations, from the top of the index.
PrepareXFromTop(timelineAccountID string, limit int) error 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. // 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) 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 { func (m *manager) getOrCreateTimeline(timelineAccountID string) Timeline {
var t Timeline var t Timeline
i, ok := m.accountTimelines.Load(timelineAccountID) i, ok := m.accountTimelines.Load(timelineAccountID)

View File

@ -334,27 +334,31 @@ func (t *timeline) Remove(statusID string) error {
t.Lock() t.Lock()
defer t.Unlock() defer t.Unlock()
// remove the entry from the post index if t.postIndex != nil && t.postIndex.data != nil {
for e := t.postIndex.data.Front(); e != nil; e = e.Next() { // remove the entry from the post index
entry, ok := e.Value.(*postIndexEntry) for e := t.postIndex.data.Front(); e != nil; e = e.Next() {
if !ok { entry, ok := e.Value.(*postIndexEntry)
return errors.New("Remove: could not parse e as a postIndexEntry") if !ok {
} return errors.New("Remove: could not parse e as a postIndexEntry")
if entry.statusID == statusID { }
t.postIndex.data.Remove(e) if entry.statusID == statusID {
break // bail once we found and removed it t.postIndex.data.Remove(e)
break // bail once we found and removed it
}
} }
} }
// remove the entry from prepared posts // remove the entry from prepared posts
for e := t.preparedPosts.data.Front(); e != nil; e = e.Next() { if t.preparedPosts != nil && t.preparedPosts.data != nil {
entry, ok := e.Value.(*preparedPostsEntry) for e := t.preparedPosts.data.Front(); e != nil; e = e.Next() {
if !ok { entry, ok := e.Value.(*preparedPostsEntry)
return errors.New("Remove: could not parse e as a preparedPostsEntry") if !ok {
} return errors.New("Remove: could not parse e as a preparedPostsEntry")
if entry.statusID == statusID { }
t.preparedPosts.data.Remove(e) if entry.statusID == statusID {
break // bail once we found and removed it t.preparedPosts.data.Remove(e)
break // bail once we found and removed it
}
} }
} }

View File

@ -222,7 +222,7 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e
status.APStatusOwnerURI = attributedTo.String() status.APStatusOwnerURI = attributedTo.String()
statusOwner := &gtsmodel.Account{} statusOwner := &gtsmodel.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) return nil, fmt.Errorf("couldn't get status owner from db: %s", err)
} }
status.AccountID = statusOwner.ID status.AccountID = statusOwner.ID