diff options
Diffstat (limited to 'internal/processing/status')
-rw-r--r-- | internal/processing/status/bookmark.go | 108 | ||||
-rw-r--r-- | internal/processing/status/boost.go | 15 | ||||
-rw-r--r-- | internal/processing/status/common.go | 63 | ||||
-rw-r--r-- | internal/processing/status/create.go | 8 | ||||
-rw-r--r-- | internal/processing/status/delete.go | 10 | ||||
-rw-r--r-- | internal/processing/status/fave.go | 227 | ||||
-rw-r--r-- | internal/processing/status/get.go | 43 | ||||
-rw-r--r-- | internal/processing/status/pin.go | 50 |
8 files changed, 226 insertions, 298 deletions
diff --git a/internal/processing/status/bookmark.go b/internal/processing/status/bookmark.go index 3324273d7..ea386b183 100644 --- a/internal/processing/status/bookmark.go +++ b/internal/processing/status/bookmark.go @@ -31,91 +31,67 @@ import ( // BookmarkCreate adds a bookmark for the requestingAccount, targeting the given status (no-op if bookmark already exists). func (p *Processor) BookmarkCreate(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - targetStatus, err := p.state.DB.GetStatusByID(ctx, targetStatusID) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) - } - if targetStatus.Account == nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) - } - visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) - } - if !visible { - return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) + targetStatus, existingBookmarkID, errWithCode := p.getBookmarkTarget(ctx, requestingAccount, targetStatusID) + if errWithCode != nil { + return nil, errWithCode } - // first check if the status is already bookmarked, if so we don't need to do anything - newBookmark := true - gtsBookmark := >smodel.StatusBookmark{} - if err := p.state.DB.GetWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsBookmark); err == nil { - // we already have a bookmark for this status - newBookmark = false + if existingBookmarkID != "" { + // Status is already bookmarked. + return p.apiStatus(ctx, targetStatus, requestingAccount) } - if newBookmark { - // we need to create a new bookmark in the database - gtsBookmark := >smodel.StatusBookmark{ - ID: id.NewULID(), - AccountID: requestingAccount.ID, - Account: requestingAccount, - TargetAccountID: targetStatus.AccountID, - TargetAccount: targetStatus.Account, - StatusID: targetStatus.ID, - Status: targetStatus, - } - - if err := p.state.DB.Put(ctx, gtsBookmark); err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error putting bookmark in database: %s", err)) - } + // Create and store a new bookmark. + gtsBookmark := >smodel.StatusBookmark{ + ID: id.NewULID(), + AccountID: requestingAccount.ID, + Account: requestingAccount, + TargetAccountID: targetStatus.AccountID, + TargetAccount: targetStatus.Account, + StatusID: targetStatus.ID, + Status: targetStatus, } - // return the apidon representation of the target status - apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) + if err := p.state.DB.PutStatusBookmark(ctx, gtsBookmark); err != nil { + err = fmt.Errorf("BookmarkCreate: error putting bookmark in database: %w", err) + return nil, gtserror.NewErrorInternalError(err) } - return apiStatus, nil + return p.apiStatus(ctx, targetStatus, requestingAccount) } // BookmarkRemove removes a bookmark for the requesting account, targeting the given status (no-op if bookmark doesn't exist). func (p *Processor) BookmarkRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - targetStatus, err := p.state.DB.GetStatusByID(ctx, targetStatusID) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) - } - if targetStatus.Account == nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) + targetStatus, existingBookmarkID, errWithCode := p.getBookmarkTarget(ctx, requestingAccount, targetStatusID) + if errWithCode != nil { + return nil, errWithCode } - visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) - } - if !visible { - return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) + + if existingBookmarkID == "" { + // Status isn't bookmarked. + return p.apiStatus(ctx, targetStatus, requestingAccount) } - // first check if the status is actually bookmarked - toUnbookmark := false - gtsBookmark := >smodel.StatusBookmark{} - if err := p.state.DB.GetWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsBookmark); err == nil { - // we have a bookmark for this status - toUnbookmark = true + // We have a bookmark to remove. + if err := p.state.DB.DeleteStatusBookmark(ctx, existingBookmarkID); err != nil { + err = fmt.Errorf("BookmarkRemove: error removing status bookmark: %w", err) + return nil, gtserror.NewErrorInternalError(err) } - if toUnbookmark { - if err := p.state.DB.DeleteWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsBookmark); err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error unfaveing status: %s", err)) - } + return p.apiStatus(ctx, targetStatus, requestingAccount) +} + +func (p *Processor) getBookmarkTarget(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*gtsmodel.Status, string, gtserror.WithCode) { + targetStatus, errWithCode := p.getVisibleStatus(ctx, requestingAccount, targetStatusID) + if errWithCode != nil { + return nil, "", errWithCode } - // return the api representation of the target status - apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) + bookmarkID, err := p.state.DB.GetStatusBookmarkID(ctx, requestingAccount.ID, targetStatus.ID) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + err = fmt.Errorf("getBookmarkTarget: error checking existing bookmark: %w", err) + return nil, "", gtserror.NewErrorInternalError(err) } - return apiStatus, nil + return targetStatus, bookmarkID, nil } diff --git a/internal/processing/status/boost.go b/internal/processing/status/boost.go index 510e99a41..f5b5a4052 100644 --- a/internal/processing/status/boost.go +++ b/internal/processing/status/boost.go @@ -86,13 +86,7 @@ func (p *Processor) BoostCreate(ctx context.Context, requestingAccount *gtsmodel TargetAccount: targetStatus.Account, }) - // return the frontend representation of the new status to the submitter - apiStatus, err := p.tc.StatusToAPIStatus(ctx, boostWrapperStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) - } - - return apiStatus, nil + return p.apiStatus(ctx, boostWrapperStatus, requestingAccount) } // BoostRemove processes the unboost/unreblog of a given status, returning the status if all is well. @@ -159,12 +153,7 @@ func (p *Processor) BoostRemove(ctx context.Context, requestingAccount *gtsmodel }) } - apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) - } - - return apiStatus, nil + return p.apiStatus(ctx, targetStatus, requestingAccount) } // StatusBoostedBy returns a slice of accounts that have boosted the given status, filtered according to privacy settings. diff --git a/internal/processing/status/common.go b/internal/processing/status/common.go new file mode 100644 index 000000000..5b73e9c94 --- /dev/null +++ b/internal/processing/status/common.go @@ -0,0 +1,63 @@ +// 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 status + +import ( + "context" + "fmt" + + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +func (p *Processor) apiStatus(ctx context.Context, targetStatus *gtsmodel.Status, requestingAccount *gtsmodel.Account) (*apimodel.Status, gtserror.WithCode) { + apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) + if err != nil { + err = fmt.Errorf("error converting status %s to frontend representation: %w", targetStatus.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + return apiStatus, nil +} + +func (p *Processor) getVisibleStatus(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*gtsmodel.Status, gtserror.WithCode) { + targetStatus, err := p.state.DB.GetStatusByID(ctx, targetStatusID) + if err != nil { + err = fmt.Errorf("getVisibleStatus: db error fetching status %s: %w", targetStatusID, err) + return nil, gtserror.NewErrorNotFound(err) + } + + if targetStatus.Account == nil { + err = fmt.Errorf("getVisibleStatus: no status owner for status %s", targetStatusID) + return nil, gtserror.NewErrorNotFound(err) + } + + visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) + if err != nil { + err = fmt.Errorf("getVisibleStatus: error seeing if status %s is visible: %w", targetStatus.ID, err) + return nil, gtserror.NewErrorNotFound(err) + } + + if !visible { + err = fmt.Errorf("getVisibleStatus: status %s is not visible to requesting account", targetStatusID) + return nil, gtserror.NewErrorNotFound(err) + } + + return targetStatus, nil +} diff --git a/internal/processing/status/create.go b/internal/processing/status/create.go index ad439de30..71db8c18e 100644 --- a/internal/processing/status/create.go +++ b/internal/processing/status/create.go @@ -93,13 +93,7 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, appli OriginAccount: account, }) - // return the frontend representation of the new status to the submitter - apiStatus, err := p.tc.StatusToAPIStatus(ctx, newStatus, account) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", newStatus.ID, err)) - } - - return apiStatus, nil + return p.apiStatus(ctx, newStatus, account) } func processReplyToID(ctx context.Context, dbService db.DB, form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) gtserror.WithCode { diff --git a/internal/processing/status/delete.go b/internal/processing/status/delete.go index fc6743ed2..5549e0329 100644 --- a/internal/processing/status/delete.go +++ b/internal/processing/status/delete.go @@ -35,6 +35,7 @@ func (p *Processor) Delete(ctx context.Context, requestingAccount *gtsmodel.Acco if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) } + if targetStatus.Account == nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) } @@ -43,12 +44,13 @@ func (p *Processor) Delete(ctx context.Context, requestingAccount *gtsmodel.Acco return nil, gtserror.NewErrorForbidden(errors.New("status doesn't belong to requesting account")) } - apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) + // Parse the status to API model BEFORE deleting it. + apiStatus, errWithCode := p.apiStatus(ctx, targetStatus, requestingAccount) + if errWithCode != nil { + return nil, errWithCode } - // send the status back to the processor for async processing + // Process delete side effects. p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{ APObjectType: ap.ObjectNote, APActivityType: ap.ActivityDelete, diff --git a/internal/processing/status/fave.go b/internal/processing/status/fave.go index 19d4cdb22..da1bae8a1 100644 --- a/internal/processing/status/fave.go +++ b/internal/processing/status/fave.go @@ -28,181 +28,140 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/id" + "github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/uris" ) -// FaveCreate processes the faving of a given status, returning the updated status if the fave goes through. +// FaveCreate adds a fave for the requestingAccount, targeting the given status (no-op if fave already exists). func (p *Processor) FaveCreate(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - targetStatus, err := p.state.DB.GetStatusByID(ctx, targetStatusID) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) - } - if targetStatus.Account == nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) + targetStatus, existingFave, errWithCode := p.getFaveTarget(ctx, requestingAccount, targetStatusID) + if errWithCode != nil { + return nil, errWithCode } - visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) - } - if !visible { - return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) - } - if !*targetStatus.Likeable { - return nil, gtserror.NewErrorForbidden(errors.New("status is not faveable")) + if existingFave != nil { + // Status is already faveed. + return p.apiStatus(ctx, targetStatus, requestingAccount) } - // first check if the status is already faved, if so we don't need to do anything - newFave := true - gtsFave := >smodel.StatusFave{} - if err := p.state.DB.GetWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsFave); err == nil { - // we already have a fave for this status - newFave = false + // Create and store a new fave + faveID := id.NewULID() + gtsFave := >smodel.StatusFave{ + ID: faveID, + AccountID: requestingAccount.ID, + Account: requestingAccount, + TargetAccountID: targetStatus.AccountID, + TargetAccount: targetStatus.Account, + StatusID: targetStatus.ID, + Status: targetStatus, + URI: uris.GenerateURIForLike(requestingAccount.Username, faveID), } - if newFave { - thisFaveID := id.NewULID() - - // we need to create a new fave in the database - gtsFave := >smodel.StatusFave{ - ID: thisFaveID, - AccountID: requestingAccount.ID, - Account: requestingAccount, - TargetAccountID: targetStatus.AccountID, - TargetAccount: targetStatus.Account, - StatusID: targetStatus.ID, - Status: targetStatus, - URI: uris.GenerateURIForLike(requestingAccount.Username, thisFaveID), - } - - if err := p.state.DB.Put(ctx, gtsFave); err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error putting fave in database: %s", err)) - } - - // send it back to the processor for async processing - p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{ - APObjectType: ap.ActivityLike, - APActivityType: ap.ActivityCreate, - GTSModel: gtsFave, - OriginAccount: requestingAccount, - TargetAccount: targetStatus.Account, - }) + if err := p.state.DB.PutStatusFave(ctx, gtsFave); err != nil { + err = fmt.Errorf("FaveCreate: error putting fave in database: %w", err) + return nil, gtserror.NewErrorInternalError(err) } - // return the apidon representation of the target status - apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) - } + // Process new status fave side effects. + p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{ + APObjectType: ap.ActivityLike, + APActivityType: ap.ActivityCreate, + GTSModel: gtsFave, + OriginAccount: requestingAccount, + TargetAccount: targetStatus.Account, + }) - return apiStatus, nil + return p.apiStatus(ctx, targetStatus, requestingAccount) } -// FaveRemove processes the unfaving of a given status, returning the updated status if the fave goes through. +// FaveRemove removes a fave for the requesting account, targeting the given status (no-op if fave doesn't exist). func (p *Processor) FaveRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - targetStatus, err := p.state.DB.GetStatusByID(ctx, targetStatusID) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) - } - if targetStatus.Account == nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) + targetStatus, existingFave, errWithCode := p.getFaveTarget(ctx, requestingAccount, targetStatusID) + if errWithCode != nil { + return nil, errWithCode } - visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) - } - if !visible { - return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) - } - - // check if we actually have a fave for this status - var toUnfave bool - - gtsFave := >smodel.StatusFave{} - err = p.state.DB.GetWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsFave) - if err == nil { - // we have a fave - toUnfave = true - } - if err != nil { - // something went wrong in the db finding the fave - if err != db.ErrNoEntries { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error fetching existing fave from database: %s", err)) - } - // we just don't have a fave - toUnfave = false + if existingFave == nil { + // Status isn't faveed. + return p.apiStatus(ctx, targetStatus, requestingAccount) } - if toUnfave { - // we had a fave, so take some action to get rid of it - if err := p.state.DB.DeleteWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsFave); err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error unfaveing status: %s", err)) - } - - // send it back to the processor for async processing - p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{ - APObjectType: ap.ActivityLike, - APActivityType: ap.ActivityUndo, - GTSModel: gtsFave, - OriginAccount: requestingAccount, - TargetAccount: targetStatus.Account, - }) + // We have a fave to remove. + if err := p.state.DB.DeleteStatusFave(ctx, existingFave.ID); err != nil { + err = fmt.Errorf("FaveRemove: error removing status fave: %w", err) + return nil, gtserror.NewErrorInternalError(err) } - apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) - } + // Process remove status fave side effects. + p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{ + APObjectType: ap.ActivityLike, + APActivityType: ap.ActivityUndo, + GTSModel: existingFave, + OriginAccount: requestingAccount, + TargetAccount: targetStatus.Account, + }) - return apiStatus, nil + return p.apiStatus(ctx, targetStatus, requestingAccount) } // FavedBy returns a slice of accounts that have liked the given status, filtered according to privacy settings. func (p *Processor) FavedBy(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) { - targetStatus, err := p.state.DB.GetStatusByID(ctx, targetStatusID) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) - } - if targetStatus.Account == nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) + targetStatus, errWithCode := p.getVisibleStatus(ctx, requestingAccount, targetStatusID) + if errWithCode != nil { + return nil, errWithCode } - visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) + statusFaves, err := p.state.DB.GetStatusFaves(ctx, targetStatus.ID) if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) - } - if !visible { - return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) + return nil, gtserror.NewErrorNotFound(fmt.Errorf("FavedBy: error seeing who faved status: %s", err)) } - statusFaves, err := p.state.DB.GetStatusFaves(ctx, targetStatus) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing who faved status: %s", err)) - } - - // filter the list so the user doesn't see accounts they blocked or which blocked them - filteredAccounts := []*gtsmodel.Account{} + // For each fave, ensure that we're only showing + // the requester accounts that they don't block, + // and which don't block them. + apiAccounts := make([]*apimodel.Account, 0, len(statusFaves)) for _, fave := range statusFaves { - blocked, err := p.state.DB.IsBlocked(ctx, requestingAccount.ID, fave.AccountID, true) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking blocks: %s", err)) + if blocked, err := p.state.DB.IsBlocked(ctx, requestingAccount.ID, fave.AccountID, true); err != nil { + err = fmt.Errorf("FavedBy: error checking blocks: %w", err) + return nil, gtserror.NewErrorInternalError(err) + } else if blocked { + continue } - if !blocked { - filteredAccounts = append(filteredAccounts, fave.Account) + + if fave.Account == nil { + // Account isn't set for some reason, just skip. + log.WithContext(ctx).WithField("fave", fave).Warn("fave had no associated account") + continue } - } - // now we can return the api representation of those accounts - apiAccounts := []*apimodel.Account{} - for _, acc := range filteredAccounts { - apiAccount, err := p.tc.AccountToAPIAccountPublic(ctx, acc) + apiAccount, err := p.tc.AccountToAPIAccountPublic(ctx, fave.Account) if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) + err = fmt.Errorf("FavedBy: error converting account %s to frontend representation: %w", fave.AccountID, err) + return nil, gtserror.NewErrorInternalError(err) } apiAccounts = append(apiAccounts, apiAccount) } return apiAccounts, nil } + +func (p *Processor) getFaveTarget(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*gtsmodel.Status, *gtsmodel.StatusFave, gtserror.WithCode) { + targetStatus, errWithCode := p.getVisibleStatus(ctx, requestingAccount, targetStatusID) + if errWithCode != nil { + return nil, nil, errWithCode + } + + if !*targetStatus.Likeable { + err := errors.New("status is not faveable") + return nil, nil, gtserror.NewErrorForbidden(err, err.Error()) + } + + fave, err := p.state.DB.GetStatusFaveByAccountID(ctx, requestingAccount.ID, targetStatusID) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + err = fmt.Errorf("getFaveTarget: error checking existing fave: %w", err) + return nil, nil, gtserror.NewErrorInternalError(err) + } + + return targetStatus, fave, nil +} diff --git a/internal/processing/status/get.go b/internal/processing/status/get.go index 393c6001e..251a095de 100644 --- a/internal/processing/status/get.go +++ b/internal/processing/status/get.go @@ -19,8 +19,6 @@ package status import ( "context" - "errors" - "fmt" "sort" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" @@ -30,46 +28,19 @@ import ( // Get gets the given status, taking account of privacy settings and blocks etc. func (p *Processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - targetStatus, err := p.state.DB.GetStatusByID(ctx, targetStatusID) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) - } - if targetStatus.Account == nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) - } - - visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) - } - if !visible { - return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) + targetStatus, errWithCode := p.getVisibleStatus(ctx, requestingAccount, targetStatusID) + if errWithCode != nil { + return nil, errWithCode } - apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) - } - - return apiStatus, nil + return p.apiStatus(ctx, targetStatus, requestingAccount) } // ContextGet returns the context (previous and following posts) from the given status ID. func (p *Processor) ContextGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Context, gtserror.WithCode) { - targetStatus, err := p.state.DB.GetStatusByID(ctx, targetStatusID) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) - } - if targetStatus.Account == nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) - } - - visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) - } - if !visible { - return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) + targetStatus, errWithCode := p.getVisibleStatus(ctx, requestingAccount, targetStatusID) + if errWithCode != nil { + return nil, errWithCode } context := &apimodel.Context{ diff --git a/internal/processing/status/pin.go b/internal/processing/status/pin.go index cea509c11..1e7dc40e8 100644 --- a/internal/processing/status/pin.go +++ b/internal/processing/status/pin.go @@ -38,40 +38,24 @@ const allowedPinnedCount = 10 // - Status belongs to requesting account. // - Status is public, unlisted, or followers-only. // - Status is not a boost. -func (p *Processor) getPinnableStatus(ctx context.Context, targetStatusID string, requestingAccountID string) (*gtsmodel.Status, gtserror.WithCode) { - targetStatus, err := p.state.DB.GetStatusByID(ctx, targetStatusID) - if err != nil { - err = fmt.Errorf("error fetching status %s: %w", targetStatusID, err) - return nil, gtserror.NewErrorNotFound(err) - } - - requestingAccount, err := p.state.DB.GetAccountByID(ctx, requestingAccountID) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - if !visible { - err = fmt.Errorf("status %s not visible to account %s", targetStatusID, requestingAccountID) - return nil, gtserror.NewErrorNotFound(err) +func (p *Processor) getPinnableStatus(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*gtsmodel.Status, gtserror.WithCode) { + targetStatus, errWithCode := p.getVisibleStatus(ctx, requestingAccount, targetStatusID) + if errWithCode != nil { + return nil, errWithCode } - if targetStatus.AccountID != requestingAccountID { - err = fmt.Errorf("status %s does not belong to account %s", targetStatusID, requestingAccountID) + if targetStatus.AccountID != requestingAccount.ID { + err := fmt.Errorf("status %s does not belong to account %s", targetStatusID, requestingAccount.ID) return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error()) } if targetStatus.Visibility == gtsmodel.VisibilityDirect { - err = errors.New("cannot pin direct messages") + err := errors.New("cannot pin direct messages") return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error()) } if targetStatus.BoostOfID != "" { - err = errors.New("cannot pin boosts") + err := errors.New("cannot pin boosts") return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error()) } @@ -89,7 +73,7 @@ func (p *Processor) getPinnableStatus(ctx context.Context, targetStatusID string // // If the conditions can't be met, then code 422 Unprocessable Entity will be returned. func (p *Processor) PinCreate(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - targetStatus, errWithCode := p.getPinnableStatus(ctx, targetStatusID, requestingAccount.ID) + targetStatus, errWithCode := p.getPinnableStatus(ctx, requestingAccount, targetStatusID) if errWithCode != nil { return nil, errWithCode } @@ -114,12 +98,7 @@ func (p *Processor) PinCreate(ctx context.Context, requestingAccount *gtsmodel.A return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error pinning status: %w", err)) } - apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %w", targetStatus.ID, err)) - } - - return apiStatus, nil + return p.apiStatus(ctx, targetStatus, requestingAccount) } // PinRemove unpins the target status from the top of requestingAccount's profile, if possible. @@ -134,7 +113,7 @@ func (p *Processor) PinCreate(ctx context.Context, requestingAccount *gtsmodel.A // Unlike with PinCreate, statuses that are already unpinned will not return 422, but just do // nothing and return the api model representation of the status, to conform to the masto API. func (p *Processor) PinRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - targetStatus, errWithCode := p.getPinnableStatus(ctx, targetStatusID, requestingAccount.ID) + targetStatus, errWithCode := p.getPinnableStatus(ctx, requestingAccount, targetStatusID) if errWithCode != nil { return nil, errWithCode } @@ -146,10 +125,5 @@ func (p *Processor) PinRemove(ctx context.Context, requestingAccount *gtsmodel.A } } - apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %w", targetStatus.ID, err)) - } - - return apiStatus, nil + return p.apiStatus(ctx, targetStatus, requestingAccount) } |