mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[bugfix] self-referencing collection pages for status replies (#2364)
This commit is contained in:
@@ -47,8 +47,15 @@ func (p *Processor) InboxPost(ctx context.Context, w http.ResponseWriter, r *htt
|
||||
|
||||
// OutboxGet returns the activitypub representation of a local user's outbox.
|
||||
// This contains links to PUBLIC posts made by this user.
|
||||
func (p *Processor) OutboxGet(ctx context.Context, requestedUsername string, page bool, maxID string, minID string) (interface{}, gtserror.WithCode) {
|
||||
requestedAccount, _, errWithCode := p.authenticate(ctx, requestedUsername)
|
||||
func (p *Processor) OutboxGet(
|
||||
ctx context.Context,
|
||||
requestedUser string,
|
||||
page bool,
|
||||
maxID string,
|
||||
minID string,
|
||||
) (interface{}, gtserror.WithCode) {
|
||||
// Authenticate the incoming request, getting related user accounts.
|
||||
_, receiver, errWithCode := p.authenticate(ctx, requestedUser)
|
||||
if errWithCode != nil {
|
||||
return nil, errWithCode
|
||||
}
|
||||
@@ -70,7 +77,7 @@ func (p *Processor) OutboxGet(ctx context.Context, requestedUsername string, pag
|
||||
"last": "https://example.org/users/whatever/outbox?min_id=0&page=true"
|
||||
}
|
||||
*/
|
||||
collection, err := p.converter.OutboxToASCollection(ctx, requestedAccount.OutboxURI)
|
||||
collection, err := p.converter.OutboxToASCollection(ctx, receiver.OutboxURI)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
@@ -85,15 +92,16 @@ func (p *Processor) OutboxGet(ctx context.Context, requestedUsername string, pag
|
||||
|
||||
// scenario 2 -- get the requested page
|
||||
// limit pages to 30 entries per page
|
||||
publicStatuses, err := p.state.DB.GetAccountStatuses(ctx, requestedAccount.ID, 30, true, true, maxID, minID, false, true)
|
||||
publicStatuses, err := p.state.DB.GetAccountStatuses(ctx, receiver.ID, 30, true, true, maxID, minID, false, true)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
outboxPage, err := p.converter.StatusesToASOutboxPage(ctx, requestedAccount.OutboxURI, maxID, minID, publicStatuses)
|
||||
outboxPage, err := p.converter.StatusesToASOutboxPage(ctx, receiver.OutboxURI, maxID, minID, publicStatuses)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
data, err = ap.Serialize(outboxPage)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
@@ -104,21 +112,22 @@ func (p *Processor) OutboxGet(ctx context.Context, requestedUsername string, pag
|
||||
|
||||
// FollowersGet handles the getting of a fedi/activitypub representation of a user/account's followers, performing appropriate
|
||||
// authentication before returning a JSON serializable interface to the caller.
|
||||
func (p *Processor) FollowersGet(ctx context.Context, requestedUsername string, page *paging.Page) (interface{}, gtserror.WithCode) {
|
||||
requestedAccount, _, errWithCode := p.authenticate(ctx, requestedUsername)
|
||||
func (p *Processor) FollowersGet(ctx context.Context, requestedUser string, page *paging.Page) (interface{}, gtserror.WithCode) {
|
||||
// Authenticate the incoming request, getting related user accounts.
|
||||
_, receiver, errWithCode := p.authenticate(ctx, requestedUser)
|
||||
if errWithCode != nil {
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// Parse the collection ID object from account's followers URI.
|
||||
collectionID, err := url.Parse(requestedAccount.FollowersURI)
|
||||
collectionID, err := url.Parse(receiver.FollowersURI)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error parsing account followers uri %s: %w", requestedAccount.FollowersURI, err)
|
||||
err := gtserror.Newf("error parsing account followers uri %s: %w", receiver.FollowersURI, err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Calculate total number of followers available for account.
|
||||
total, err := p.state.DB.CountAccountFollowers(ctx, requestedAccount.ID)
|
||||
total, err := p.state.DB.CountAccountFollowers(ctx, receiver.ID)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error counting followers: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
@@ -126,30 +135,36 @@ func (p *Processor) FollowersGet(ctx context.Context, requestedUsername string,
|
||||
|
||||
var obj vocab.Type
|
||||
|
||||
// Start building AS collection params.
|
||||
// Start the AS collection params.
|
||||
var params ap.CollectionParams
|
||||
params.ID = collectionID
|
||||
params.Total = total
|
||||
|
||||
if page == nil {
|
||||
// i.e. paging disabled, the simplest case.
|
||||
//
|
||||
// Just build collection object from params.
|
||||
// i.e. paging disabled, return collection
|
||||
// that links to first page (i.e. path below).
|
||||
params.Query = make(url.Values, 1)
|
||||
params.Query.Set("limit", "40") // enables paging
|
||||
obj = ap.NewASOrderedCollection(params)
|
||||
} else {
|
||||
// i.e. paging enabled
|
||||
|
||||
// Get the request page of full follower objects with attached accounts.
|
||||
followers, err := p.state.DB.GetAccountFollowers(ctx, requestedAccount.ID, page)
|
||||
followers, err := p.state.DB.GetAccountFollowers(ctx, receiver.ID, page)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error getting followers: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Get the lowest and highest
|
||||
// ID values, used for paging.
|
||||
lo := followers[len(followers)-1].ID
|
||||
hi := followers[0].ID
|
||||
// page ID values.
|
||||
var lo, hi string
|
||||
|
||||
if len(followers) > 0 {
|
||||
// Get the lowest and highest
|
||||
// ID values, used for paging.
|
||||
lo = followers[len(followers)-1].ID
|
||||
hi = followers[0].ID
|
||||
}
|
||||
|
||||
// Start building AS collection page params.
|
||||
var pageParams ap.CollectionPageParams
|
||||
@@ -196,21 +211,22 @@ func (p *Processor) FollowersGet(ctx context.Context, requestedUsername string,
|
||||
|
||||
// FollowingGet handles the getting of a fedi/activitypub representation of a user/account's following, performing appropriate
|
||||
// authentication before returning a JSON serializable interface to the caller.
|
||||
func (p *Processor) FollowingGet(ctx context.Context, requestedUsername string, page *paging.Page) (interface{}, gtserror.WithCode) {
|
||||
requestedAccount, _, errWithCode := p.authenticate(ctx, requestedUsername)
|
||||
func (p *Processor) FollowingGet(ctx context.Context, requestedUser string, page *paging.Page) (interface{}, gtserror.WithCode) {
|
||||
// Authenticate the incoming request, getting related user accounts.
|
||||
_, receiver, errWithCode := p.authenticate(ctx, requestedUser)
|
||||
if errWithCode != nil {
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// Parse the collection ID object from account's following URI.
|
||||
collectionID, err := url.Parse(requestedAccount.FollowingURI)
|
||||
// Parse collection ID from account's following URI.
|
||||
collectionID, err := url.Parse(receiver.FollowingURI)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error parsing account following uri %s: %w", requestedAccount.FollowingURI, err)
|
||||
err := gtserror.Newf("error parsing account following uri %s: %w", receiver.FollowingURI, err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Calculate total number of following available for account.
|
||||
total, err := p.state.DB.CountAccountFollows(ctx, requestedAccount.ID)
|
||||
total, err := p.state.DB.CountAccountFollows(ctx, receiver.ID)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error counting follows: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
@@ -218,32 +234,38 @@ func (p *Processor) FollowingGet(ctx context.Context, requestedUsername string,
|
||||
|
||||
var obj vocab.Type
|
||||
|
||||
// Start building AS collection params.
|
||||
// Start AS collection params.
|
||||
var params ap.CollectionParams
|
||||
params.ID = collectionID
|
||||
params.Total = total
|
||||
|
||||
if page == nil {
|
||||
// i.e. paging disabled, the simplest case.
|
||||
//
|
||||
// Just build collection object from params.
|
||||
// i.e. paging disabled, return collection
|
||||
// that links to first page (i.e. path below).
|
||||
params.Query = make(url.Values, 1)
|
||||
params.Query.Set("limit", "40") // enables paging
|
||||
obj = ap.NewASOrderedCollection(params)
|
||||
} else {
|
||||
// i.e. paging enabled
|
||||
|
||||
// Get the request page of full follower objects with attached accounts.
|
||||
follows, err := p.state.DB.GetAccountFollows(ctx, requestedAccount.ID, page)
|
||||
follows, err := p.state.DB.GetAccountFollows(ctx, receiver.ID, page)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error getting follows: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Get the lowest and highest
|
||||
// ID values, used for paging.
|
||||
lo := follows[len(follows)-1].ID
|
||||
hi := follows[0].ID
|
||||
// page ID values.
|
||||
var lo, hi string
|
||||
|
||||
// Start building AS collection page params.
|
||||
if len(follows) > 0 {
|
||||
// Get the lowest and highest
|
||||
// ID values, used for paging.
|
||||
lo = follows[len(follows)-1].ID
|
||||
hi = follows[0].ID
|
||||
}
|
||||
|
||||
// Start AS collection page params.
|
||||
var pageParams ap.CollectionPageParams
|
||||
pageParams.CollectionParams = params
|
||||
|
||||
@@ -288,20 +310,21 @@ func (p *Processor) FollowingGet(ctx context.Context, requestedUsername string,
|
||||
|
||||
// FeaturedCollectionGet returns an ordered collection of the requested username's Pinned posts.
|
||||
// The returned collection have an `items` property which contains an ordered list of status URIs.
|
||||
func (p *Processor) FeaturedCollectionGet(ctx context.Context, requestedUsername string) (interface{}, gtserror.WithCode) {
|
||||
requestedAccount, _, errWithCode := p.authenticate(ctx, requestedUsername)
|
||||
func (p *Processor) FeaturedCollectionGet(ctx context.Context, requestedUser string) (interface{}, gtserror.WithCode) {
|
||||
// Authenticate the incoming request, getting related user accounts.
|
||||
_, receiver, errWithCode := p.authenticate(ctx, requestedUser)
|
||||
if errWithCode != nil {
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
statuses, err := p.state.DB.GetAccountPinnedStatuses(ctx, requestedAccount.ID)
|
||||
statuses, err := p.state.DB.GetAccountPinnedStatuses(ctx, receiver.ID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, db.ErrNoEntries) {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
collection, err := p.converter.StatusesToASFeaturedCollection(ctx, requestedAccount.FeaturedCollectionURI, statuses)
|
||||
collection, err := p.converter.StatusesToASFeaturedCollection(ctx, receiver.FeaturedCollectionURI, statuses)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
@@ -20,7 +20,6 @@ package fedi
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
@@ -28,17 +27,17 @@ import (
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
func (p *Processor) authenticate(ctx context.Context, requestedUsername string) (
|
||||
*gtsmodel.Account, // requestedAccount
|
||||
*gtsmodel.Account, // requestingAccount
|
||||
func (p *Processor) authenticate(ctx context.Context, requestedUser string) (
|
||||
*gtsmodel.Account, // requester: i.e. user making the request
|
||||
*gtsmodel.Account, // receiver: i.e. the receiving inbox user
|
||||
gtserror.WithCode,
|
||||
) {
|
||||
// Get LOCAL account with the requested username.
|
||||
requestedAccount, err := p.state.DB.GetAccountByUsernameDomain(ctx, requestedUsername, "")
|
||||
// First get the requested (receiving) LOCAL account with username from database.
|
||||
receiver, err := p.state.DB.GetAccountByUsernameDomain(ctx, requestedUser, "")
|
||||
if err != nil {
|
||||
if !errors.Is(err, db.ErrNoEntries) {
|
||||
// Real db error.
|
||||
err = gtserror.Newf("db error getting account %s: %w", requestedUsername, err)
|
||||
err = gtserror.Newf("db error getting account %s: %w", requestedUser, err)
|
||||
return nil, nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
@@ -46,41 +45,43 @@ func (p *Processor) authenticate(ctx context.Context, requestedUsername string)
|
||||
return nil, nil, gtserror.NewErrorNotFound(err)
|
||||
}
|
||||
|
||||
var requester *gtsmodel.Account
|
||||
|
||||
// Ensure request signed, and use signature URI to
|
||||
// get requesting account, dereferencing if necessary.
|
||||
pubKeyAuth, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername)
|
||||
pubKeyAuth, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, requestedUser)
|
||||
if errWithCode != nil {
|
||||
return nil, nil, errWithCode
|
||||
}
|
||||
|
||||
requestingAccount, _, err := p.federator.GetAccountByURI(
|
||||
gtscontext.SetFastFail(ctx),
|
||||
requestedUsername,
|
||||
pubKeyAuth.OwnerURI,
|
||||
)
|
||||
if err != nil {
|
||||
err = gtserror.Newf("error getting account %s: %w", pubKeyAuth.OwnerURI, err)
|
||||
return nil, nil, gtserror.NewErrorUnauthorized(err)
|
||||
if requester = pubKeyAuth.Owner; requester == nil {
|
||||
requester, _, err = p.federator.GetAccountByURI(
|
||||
gtscontext.SetFastFail(ctx),
|
||||
requestedUser,
|
||||
pubKeyAuth.OwnerURI,
|
||||
)
|
||||
if err != nil {
|
||||
err = gtserror.Newf("error getting account %s: %w", pubKeyAuth.OwnerURI, err)
|
||||
return nil, nil, gtserror.NewErrorUnauthorized(err)
|
||||
}
|
||||
}
|
||||
|
||||
if !requestingAccount.SuspendedAt.IsZero() {
|
||||
if !requester.SuspendedAt.IsZero() {
|
||||
// Account was marked as suspended by a
|
||||
// local admin action. Stop request early.
|
||||
err = fmt.Errorf("account %s marked as suspended", requestingAccount.ID)
|
||||
return nil, nil, gtserror.NewErrorForbidden(err)
|
||||
const text = "requesting account is suspended"
|
||||
return nil, nil, gtserror.NewErrorForbidden(errors.New(text))
|
||||
}
|
||||
|
||||
// Ensure no block exists between requester + requested.
|
||||
blocked, err := p.state.DB.IsEitherBlocked(ctx, requestedAccount.ID, requestingAccount.ID)
|
||||
blocked, err := p.state.DB.IsEitherBlocked(ctx, receiver.ID, requester.ID)
|
||||
if err != nil {
|
||||
err = gtserror.Newf("db error getting checking block: %w", err)
|
||||
return nil, nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if blocked {
|
||||
err = fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)
|
||||
} else if blocked {
|
||||
err = gtserror.Newf("block exists between accounts %s and %s", requester.ID, receiver.ID)
|
||||
return nil, nil, gtserror.NewErrorForbidden(err)
|
||||
}
|
||||
|
||||
return requestedAccount, requestingAccount, nil
|
||||
return requester, receiver, nil
|
||||
}
|
||||
|
@@ -19,21 +19,33 @@ package fedi
|
||||
|
||||
import (
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing/common"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/visibility"
|
||||
)
|
||||
|
||||
type Processor struct {
|
||||
// embed common logic
|
||||
c *common.Processor
|
||||
|
||||
state *state.State
|
||||
federator *federation.Federator
|
||||
converter *typeutils.Converter
|
||||
filter *visibility.Filter
|
||||
}
|
||||
|
||||
// New returns a new fedi processor.
|
||||
func New(state *state.State, converter *typeutils.Converter, federator *federation.Federator, filter *visibility.Filter) Processor {
|
||||
// New returns a
|
||||
// new fedi processor.
|
||||
func New(
|
||||
state *state.State,
|
||||
common *common.Processor,
|
||||
converter *typeutils.Converter,
|
||||
federator *federation.Federator,
|
||||
filter *visibility.Filter,
|
||||
) Processor {
|
||||
return Processor{
|
||||
c: common,
|
||||
state: state,
|
||||
federator: federator,
|
||||
converter: converter,
|
||||
|
@@ -19,161 +19,192 @@ package fedi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"errors"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
)
|
||||
|
||||
// StatusGet handles the getting of a fedi/activitypub representation of a local status.
|
||||
// It performs appropriate authentication before returning a JSON serializable interface.
|
||||
func (p *Processor) StatusGet(ctx context.Context, requestedUsername string, requestedStatusID string) (interface{}, gtserror.WithCode) {
|
||||
func (p *Processor) StatusGet(ctx context.Context, requestedUser string, statusID string) (interface{}, gtserror.WithCode) {
|
||||
// Authenticate using http signature.
|
||||
requestedAccount, requestingAccount, errWithCode := p.authenticate(ctx, requestedUsername)
|
||||
// Authenticate the incoming request, getting related user accounts.
|
||||
requester, receiver, errWithCode := p.authenticate(ctx, requestedUser)
|
||||
if errWithCode != nil {
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
status, err := p.state.DB.GetStatusByID(ctx, requestedStatusID)
|
||||
status, err := p.state.DB.GetStatusByID(ctx, statusID)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorNotFound(err)
|
||||
}
|
||||
|
||||
if status.AccountID != requestedAccount.ID {
|
||||
err := fmt.Errorf("status with id %s does not belong to account with id %s", status.ID, requestedAccount.ID)
|
||||
return nil, gtserror.NewErrorNotFound(err)
|
||||
if status.AccountID != receiver.ID {
|
||||
const text = "status does not belong to receiving account"
|
||||
return nil, gtserror.NewErrorNotFound(errors.New(text))
|
||||
}
|
||||
|
||||
visible, err := p.filter.StatusVisible(ctx, requestingAccount, status)
|
||||
visible, err := p.filter.StatusVisible(ctx, requester, status)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if !visible {
|
||||
err := fmt.Errorf("status with id %s not visible to user with id %s", status.ID, requestingAccount.ID)
|
||||
return nil, gtserror.NewErrorNotFound(err)
|
||||
const text = "status not vising to requesting account"
|
||||
return nil, gtserror.NewErrorNotFound(errors.New(text))
|
||||
}
|
||||
|
||||
statusable, err := p.converter.StatusToAS(ctx, status)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error converting status: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
data, err := ap.Serialize(statusable)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error serializing status: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// GetStatus handles the getting of a fedi/activitypub representation of replies to a status, performing appropriate
|
||||
// authentication before returning a JSON serializable interface to the caller.
|
||||
func (p *Processor) StatusRepliesGet(ctx context.Context, requestedUsername string, requestedStatusID string, page bool, onlyOtherAccounts bool, onlyOtherAccountsSet bool, minID string) (interface{}, gtserror.WithCode) {
|
||||
requestedAccount, requestingAccount, errWithCode := p.authenticate(ctx, requestedUsername)
|
||||
// GetStatus handles the getting of a fedi/activitypub representation of replies to a status,
|
||||
// performing appropriate authentication before returning a JSON serializable interface to the caller.
|
||||
func (p *Processor) StatusRepliesGet(
|
||||
ctx context.Context,
|
||||
requestedUser string,
|
||||
statusID string,
|
||||
page *paging.Page,
|
||||
onlyOtherAccounts bool,
|
||||
) (interface{}, gtserror.WithCode) {
|
||||
// Authenticate the incoming request, getting related user accounts.
|
||||
requester, receiver, errWithCode := p.authenticate(ctx, requestedUser)
|
||||
if errWithCode != nil {
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
status, err := p.state.DB.GetStatusByID(ctx, requestedStatusID)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorNotFound(err)
|
||||
// Get target status and ensure visible to requester.
|
||||
status, errWithCode := p.c.GetVisibleTargetStatus(ctx,
|
||||
requester,
|
||||
statusID,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
if status.AccountID != requestedAccount.ID {
|
||||
return nil, gtserror.NewErrorNotFound(fmt.Errorf("status with id %s does not belong to account with id %s", status.ID, requestedAccount.ID))
|
||||
// Ensure status is by receiving account.
|
||||
if status.AccountID != receiver.ID {
|
||||
const text = "status does not belong to receiving account"
|
||||
return nil, gtserror.NewErrorNotFound(errors.New(text))
|
||||
}
|
||||
|
||||
visible, err := p.filter.StatusVisible(ctx, requestedAccount, status)
|
||||
// Parse replies collection ID from status' URI with onlyOtherAccounts param.
|
||||
onlyOtherAccStr := "only_other_accounts=" + strconv.FormatBool(onlyOtherAccounts)
|
||||
collectionID, err := url.Parse(status.URI + "/replies?" + onlyOtherAccStr)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error parsing status uri %s: %w", status.URI, err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
if !visible {
|
||||
return nil, gtserror.NewErrorNotFound(fmt.Errorf("status with id %s not visible to user with id %s", status.ID, requestingAccount.ID))
|
||||
|
||||
// Get *all* available replies for status (i.e. without paging).
|
||||
replies, err := p.state.DB.GetStatusReplies(ctx, status.ID)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error getting status replies: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
var data map[string]interface{}
|
||||
if onlyOtherAccounts {
|
||||
// If 'onlyOtherAccounts' is set, drop all by original status author.
|
||||
replies = slices.DeleteFunc(replies, func(reply *gtsmodel.Status) bool {
|
||||
return reply.AccountID == status.AccountID
|
||||
})
|
||||
}
|
||||
|
||||
// now there are three scenarios:
|
||||
// 1. we're asked for the whole collection and not a page -- we can just return the collection, with no items, but a link to 'first' page.
|
||||
// 2. we're asked for a page but only_other_accounts has not been set in the query -- so we should just return the first page of the collection, with no items.
|
||||
// 3. we're asked for a page, and only_other_accounts has been set, and min_id has optionally been set -- so we need to return some actual items!
|
||||
switch {
|
||||
case !page:
|
||||
// scenario 1
|
||||
// get the collection
|
||||
collection, err := p.converter.StatusToASRepliesCollection(ctx, status, onlyOtherAccounts)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
// Reslice replies dropping all those invisible to requester.
|
||||
replies, err = p.filter.StatusesVisible(ctx, requester, replies)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error filtering status replies: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
var obj vocab.Type
|
||||
|
||||
// Start AS collection params.
|
||||
var params ap.CollectionParams
|
||||
params.ID = collectionID
|
||||
params.Total = len(replies)
|
||||
|
||||
if page == nil {
|
||||
// i.e. paging disabled, return collection
|
||||
// that links to first page (i.e. path below).
|
||||
params.Query = make(url.Values, 1)
|
||||
params.Query.Set("limit", "20") // enables paging
|
||||
obj = ap.NewASOrderedCollection(params)
|
||||
} else {
|
||||
// i.e. paging enabled
|
||||
|
||||
// Page and reslice the replies according to given parameters.
|
||||
replies = paging.Page_PageFunc(page, replies, func(reply *gtsmodel.Status) string {
|
||||
return reply.ID
|
||||
})
|
||||
|
||||
// page ID values.
|
||||
var lo, hi string
|
||||
|
||||
if len(replies) > 0 {
|
||||
// Get the lowest and highest
|
||||
// ID values, used for paging.
|
||||
lo = replies[len(replies)-1].ID
|
||||
hi = replies[0].ID
|
||||
}
|
||||
|
||||
data, err = ap.Serialize(collection)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
case page && !onlyOtherAccountsSet:
|
||||
// scenario 2
|
||||
// get the collection
|
||||
collection, err := p.converter.StatusToASRepliesCollection(ctx, status, onlyOtherAccounts)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
// but only return the first page
|
||||
data, err = ap.Serialize(collection.GetActivityStreamsFirst().GetActivityStreamsCollectionPage())
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
default:
|
||||
// scenario 3
|
||||
// get immediate children
|
||||
replies, err := p.state.DB.GetStatusChildren(ctx, status, true, minID)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
// Start AS collection page params.
|
||||
var pageParams ap.CollectionPageParams
|
||||
pageParams.CollectionParams = params
|
||||
|
||||
// filter children and extract URIs
|
||||
replyURIs := map[string]*url.URL{}
|
||||
for _, r := range replies {
|
||||
// only show public or unlocked statuses as replies
|
||||
if r.Visibility != gtsmodel.VisibilityPublic && r.Visibility != gtsmodel.VisibilityUnlocked {
|
||||
continue
|
||||
}
|
||||
// Current page details.
|
||||
pageParams.Current = page
|
||||
pageParams.Count = len(replies)
|
||||
|
||||
// respect onlyOtherAccounts parameter
|
||||
if onlyOtherAccounts && r.AccountID == requestedAccount.ID {
|
||||
continue
|
||||
}
|
||||
// Set linked next/prev parameters.
|
||||
pageParams.Next = page.Next(lo, hi)
|
||||
pageParams.Prev = page.Prev(lo, hi)
|
||||
|
||||
// only show replies that the status owner can see
|
||||
visibleToStatusOwner, err := p.filter.StatusVisible(ctx, requestedAccount, r)
|
||||
if err != nil || !visibleToStatusOwner {
|
||||
continue
|
||||
}
|
||||
// Set the collection item property builder function.
|
||||
pageParams.Append = func(i int, itemsProp ap.ItemsPropertyBuilder) {
|
||||
// Get follower URI at index.
|
||||
status := replies[i]
|
||||
uri := status.URI
|
||||
|
||||
// only show replies that the requester can see
|
||||
visibleToRequester, err := p.filter.StatusVisible(ctx, requestingAccount, r)
|
||||
if err != nil || !visibleToRequester {
|
||||
continue
|
||||
}
|
||||
|
||||
rURI, err := url.Parse(r.URI)
|
||||
// Parse URL object from URI.
|
||||
iri, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
continue
|
||||
log.Errorf(ctx, "error parsing status uri %s: %v", uri, err)
|
||||
return
|
||||
}
|
||||
|
||||
replyURIs[r.ID] = rURI
|
||||
// Add to item property.
|
||||
itemsProp.AppendIRI(iri)
|
||||
}
|
||||
|
||||
repliesPage, err := p.converter.StatusURIsToASRepliesPage(ctx, status, onlyOtherAccounts, minID, replyURIs)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
data, err = ap.Serialize(repliesPage)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
// Build AS collection page object from params.
|
||||
obj = ap.NewASOrderedCollectionPage(pageParams)
|
||||
}
|
||||
|
||||
// Serialized the prepared object.
|
||||
data, err := ap.Serialize(obj)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error serializing: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
|
@@ -156,23 +156,23 @@ func NewProcessor(
|
||||
//
|
||||
// Start with sub processors that will
|
||||
// be required by the workers processor.
|
||||
commonProcessor := common.New(state, converter, federator, filter)
|
||||
processor.account = account.New(&commonProcessor, state, converter, mediaManager, oauthServer, federator, filter, parseMentionFunc)
|
||||
common := common.New(state, converter, federator, filter)
|
||||
processor.account = account.New(&common, state, converter, mediaManager, oauthServer, federator, filter, parseMentionFunc)
|
||||
processor.media = media.New(state, converter, mediaManager, federator.TransportController())
|
||||
processor.stream = stream.New(state, oauthServer)
|
||||
|
||||
// Instantiate the rest of the sub
|
||||
// processors + pin them to this struct.
|
||||
processor.account = account.New(&commonProcessor, state, converter, mediaManager, oauthServer, federator, filter, parseMentionFunc)
|
||||
processor.account = account.New(&common, state, converter, mediaManager, oauthServer, federator, filter, parseMentionFunc)
|
||||
processor.admin = admin.New(state, cleaner, converter, mediaManager, federator.TransportController(), emailSender)
|
||||
processor.fedi = fedi.New(state, converter, federator, filter)
|
||||
processor.fedi = fedi.New(state, &common, converter, federator, filter)
|
||||
processor.list = list.New(state, converter)
|
||||
processor.markers = markers.New(state, converter)
|
||||
processor.polls = polls.New(&commonProcessor, state, converter)
|
||||
processor.polls = polls.New(&common, state, converter)
|
||||
processor.report = report.New(state, converter)
|
||||
processor.timeline = timeline.New(state, converter, filter)
|
||||
processor.search = search.New(state, federator, converter, filter)
|
||||
processor.status = status.New(state, &commonProcessor, &processor.polls, federator, converter, filter, parseMentionFunc)
|
||||
processor.status = status.New(state, &common, &processor.polls, federator, converter, filter, parseMentionFunc)
|
||||
processor.user = user.New(state, emailSender)
|
||||
|
||||
// Workers processor handles asynchronous
|
||||
|
@@ -67,7 +67,7 @@ func (p *Processor) contextGet(
|
||||
Descendants: []apimodel.Status{},
|
||||
}
|
||||
|
||||
parents, err := p.state.DB.GetStatusParents(ctx, targetStatus, false)
|
||||
parents, err := p.state.DB.GetStatusParents(ctx, targetStatus)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
@@ -85,7 +85,7 @@ func (p *Processor) contextGet(
|
||||
return context.Ancestors[i].ID < context.Ancestors[j].ID
|
||||
})
|
||||
|
||||
children, err := p.state.DB.GetStatusChildren(ctx, targetStatus, false, "")
|
||||
children, err := p.state.DB.GetStatusChildren(ctx, targetStatus.ID)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
Reference in New Issue
Block a user