diff options
Diffstat (limited to 'internal/federation')
-rw-r--r-- | internal/federation/dereferencing/status_permitted.go | 262 | ||||
-rw-r--r-- | internal/federation/federatingdb/accept.go | 439 | ||||
-rw-r--r-- | internal/federation/federatingdb/db.go | 7 | ||||
-rw-r--r-- | internal/federation/federatingdb/get.go | 8 |
4 files changed, 639 insertions, 77 deletions
diff --git a/internal/federation/dereferencing/status_permitted.go b/internal/federation/dereferencing/status_permitted.go index 5c16f9f15..97cd61e93 100644 --- a/internal/federation/dereferencing/status_permitted.go +++ b/internal/federation/dereferencing/status_permitted.go @@ -19,10 +19,13 @@ package dereferencing import ( "context" + "net/url" + "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/util" ) // isPermittedStatus returns whether the given status @@ -147,12 +150,67 @@ func (d *Dereferencer) isPermittedReply( return onFalse() } - // TODO in next PR: check conditional / - // with approval and deref Accept. - if !replyable.Permitted() { + if replyable.Permitted() && + !replyable.MatchedOnCollection() { + // Replier is permitted to do this + // interaction, and didn't match on + // a collection so we don't need to + // do further checking. + return true, nil + } + + // Replier is permitted to do this + // interaction pending approval, or + // permitted but matched on a collection. + // + // Check if we can dereference + // an Accept that grants approval. + + if status.ApprovedByURI == "" { + // Status doesn't claim to be approved. + // + // For replies to local statuses that's + // fine, we can put it in the DB pending + // approval, and continue processing it. + // + // If permission was granted based on a match + // with a followers or following collection, + // we can mark it as PreApproved so the processor + // sends an accept out for it immediately. + // + // For replies to remote statuses, though + // we should be polite and just drop it. + if inReplyTo.IsLocal() { + status.PendingApproval = util.Ptr(true) + status.PreApproved = replyable.MatchedOnCollection() + return true, nil + } + + return onFalse() + } + + // Status claims to be approved, check + // this by dereferencing the Accept and + // inspecting the return value. + if err := d.validateApprovedBy( + ctx, + requestUser, + status.ApprovedByURI, + status.URI, + inReplyTo.AccountURI, + ); err != nil { + // Error dereferencing means we couldn't + // get the Accept right now or it wasn't + // valid, so we shouldn't store this status. + // + // Do log the error though as it may be + // interesting for admins to see. + log.Info(ctx, "rejecting reply with undereferenceable ApprovedByURI: %v", err) return onFalse() } + // Status has been approved. + status.PendingApproval = util.Ptr(false) return true, nil } @@ -206,11 +264,203 @@ func (d *Dereferencer) isPermittedBoost( return onFalse() } - // TODO in next PR: check conditional / - // with approval and deref Accept. - if !boostable.Permitted() { + if boostable.Permitted() && + !boostable.MatchedOnCollection() { + // Booster is permitted to do this + // interaction, and didn't match on + // a collection so we don't need to + // do further checking. + return true, nil + } + + // Booster is permitted to do this + // interaction pending approval, or + // permitted but matched on a collection. + // + // Check if we can dereference + // an Accept that grants approval. + + if status.ApprovedByURI == "" { + // Status doesn't claim to be approved. + // + // For boosts of local statuses that's + // fine, we can put it in the DB pending + // approval, and continue processing it. + // + // If permission was granted based on a match + // with a followers or following collection, + // we can mark it as PreApproved so the processor + // sends an accept out for it immediately. + // + // For boosts of remote statuses, though + // we should be polite and just drop it. + if boostOf.IsLocal() { + status.PendingApproval = util.Ptr(true) + status.PreApproved = boostable.MatchedOnCollection() + return true, nil + } + return onFalse() } + // Boost claims to be approved, check + // this by dereferencing the Accept and + // inspecting the return value. + if err := d.validateApprovedBy( + ctx, + requestUser, + status.ApprovedByURI, + status.URI, + boostOf.AccountURI, + ); err != nil { + // Error dereferencing means we couldn't + // get the Accept right now or it wasn't + // valid, so we shouldn't store this status. + // + // Do log the error though as it may be + // interesting for admins to see. + log.Info(ctx, "rejecting boost with undereferenceable ApprovedByURI: %v", err) + return onFalse() + } + + // Status has been approved. + status.PendingApproval = util.Ptr(false) return true, nil } + +// validateApprovedBy dereferences the activitystreams Accept at +// the specified IRI, and checks the Accept for validity against +// the provided expectedObject and expectedActor. +// +// Will return either nil if everything looked OK, or an error if +// something went wrong during deref, or if the dereffed Accept +// did not meet expectations. +func (d *Dereferencer) validateApprovedBy( + ctx context.Context, + requestUser string, + approvedByURIStr string, // Eg., "https://example.org/users/someone/accepts/01J2736AWWJ3411CPR833F6D03" + expectedObject string, // Eg., "https://some.instance.example.org/users/someone_else/statuses/01J27414TWV9F7DC39FN8ABB5R" + expectedActor string, // Eg., "https://example.org/users/someone" +) error { + approvedByURI, err := url.Parse(approvedByURIStr) + if err != nil { + err := gtserror.Newf("error parsing approvedByURI: %w", err) + return err + } + + // Don't make calls to the remote if it's blocked. + if blocked, err := d.state.DB.IsDomainBlocked(ctx, approvedByURI.Host); blocked || err != nil { + err := gtserror.Newf("domain %s is blocked", approvedByURI.Host) + return err + } + + transport, err := d.transportController.NewTransportForUsername(ctx, requestUser) + if err != nil { + err := gtserror.Newf("error creating transport: %w", err) + return err + } + + // Make the call to resolve into an Acceptable. + rsp, err := transport.Dereference(ctx, approvedByURI) + if err != nil { + err := gtserror.Newf("error dereferencing %s: %w", approvedByURIStr, err) + return err + } + + acceptable, err := ap.ResolveAcceptable(ctx, rsp.Body) + + // Tidy up rsp body. + _ = rsp.Body.Close() + + if err != nil { + err := gtserror.Newf("error resolving Accept %s: %w", approvedByURIStr, err) + return err + } + + // Extract the URI/ID of the Accept. + acceptURI := ap.GetJSONLDId(acceptable) + acceptURIStr := acceptURI.String() + + // Check whether input URI and final returned URI + // have changed (i.e. we followed some redirects). + rspURL := rsp.Request.URL + rspURLStr := rspURL.String() + if rspURLStr != approvedByURIStr { + // Final URI was different from approvedByURIStr. + // + // Make sure it's at least on the same host as + // what we expected (ie., we weren't redirected + // across domains), and make sure it's the same + // as the ID of the Accept we were returned. + if rspURL.Host != approvedByURI.Host { + err := gtserror.Newf( + "final dereference host %s did not match approvedByURI host %s", + rspURL.Host, approvedByURI.Host, + ) + return err + } + + if acceptURIStr != rspURLStr { + err := gtserror.Newf( + "final dereference uri %s did not match returned Accept ID/URI %s", + rspURLStr, acceptURIStr, + ) + return err + } + } + + // Ensure the Accept URI has the same host + // as the Accept Actor, so we know we're + // not dealing with someone on a different + // domain just pretending to be the Actor. + actorIRIs := ap.GetActorIRIs(acceptable) + if len(actorIRIs) != 1 { + err := gtserror.New("resolved Accept actor(s) length was not 1") + return gtserror.SetMalformed(err) + } + + actorIRI := actorIRIs[0] + actorStr := actorIRI.String() + + if actorIRI.Host != acceptURI.Host { + err := gtserror.Newf( + "Accept Actor %s was not the same host as Accept %s", + actorStr, acceptURIStr, + ) + return err + } + + // Ensure the Accept Actor is who we expect + // it to be, and not someone else trying to + // do an Accept for an interaction with a + // statusable they don't own. + if actorStr != expectedActor { + err := gtserror.Newf( + "Accept Actor %s was not the same as expected actor %s", + actorStr, expectedActor, + ) + return err + } + + // Ensure the Accept Object is what we expect + // it to be, ie., it's Accepting the interaction + // we need it to Accept, and not something else. + objectIRIs := ap.GetObjectIRIs(acceptable) + if len(objectIRIs) != 1 { + err := gtserror.New("resolved Accept object(s) length was not 1") + return err + } + + objectIRI := objectIRIs[0] + objectStr := objectIRI.String() + + if objectStr != expectedObject { + err := gtserror.Newf( + "resolved Accept Object uri %s was not the same as expected object %s", + objectStr, expectedObject, + ) + return err + } + + return nil +} diff --git a/internal/federation/federatingdb/accept.go b/internal/federation/federatingdb/accept.go index e26e5955b..60b9cfe58 100644 --- a/internal/federation/federatingdb/accept.go +++ b/internal/federation/federatingdb/accept.go @@ -20,16 +20,31 @@ package federatingdb import ( "context" "errors" - "fmt" + "net/url" "codeberg.org/gruf/go-logger/v2/level" "github.com/superseriousbusiness/activity/streams/vocab" "github.com/superseriousbusiness/gotosocial/internal/ap" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/uris" + "github.com/superseriousbusiness/gotosocial/internal/util" ) +func (f *federatingDB) GetAccept( + ctx context.Context, + acceptIRI *url.URL, +) (vocab.ActivityStreamsAccept, error) { + approval, err := f.state.DB.GetInteractionApprovalByURI(ctx, acceptIRI.String()) + if err != nil { + return nil, err + } + return f.converter.InteractionApprovalToASAccept(ctx, approval) +} + func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsAccept) error { if log.Level() >= level.DEBUG { i, err := marshalItem(accept) @@ -55,100 +70,382 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA return nil } - // Iterate all provided objects in the activity. - for _, object := range ap.ExtractObjects(accept) { + activityID := ap.GetJSONLDId(accept) + if activityID == nil { + // We need an ID. + const text = "Accept had no id property" + return gtserror.NewErrorBadRequest(errors.New(text), text) + } - // Check and handle any vocab.Type objects. - if objType := object.GetType(); objType != nil { - switch objType.GetTypeName() { //nolint:gocritic + // Iterate all provided objects in the activity, + // handling the ones we know how to handle. + for _, object := range ap.ExtractObjects(accept) { + if asType := object.GetType(); asType != nil { + // Check and handle any + // vocab.Type objects. + // nolint:gocritic + switch asType.GetTypeName() { + // ACCEPT FOLLOW case ap.ActivityFollow: - // Cast the vocab.Type object to known AS type. - asFollow := objType.(vocab.ActivityStreamsFollow) - - // convert the follow to something we can understand - gtsFollow, err := f.converter.ASFollowToFollow(ctx, asFollow) - if err != nil { - return fmt.Errorf("ACCEPT: error converting asfollow to gtsfollow: %s", err) + if err := f.acceptFollowType( + ctx, + asType, + receivingAcct, + requestingAcct, + ); err != nil { + return err } + } - // Make sure the creator of the original follow - // is the same as whatever inbox this landed in. - if gtsFollow.AccountID != receivingAcct.ID { - return errors.New("ACCEPT: follow account and inbox account were not the same") - } + } else if object.IsIRI() { + // Check and handle any + // IRI type objects. + switch objIRI := object.GetIRI(); { - // Make sure the target of the original follow - // is the same as the account making the request. - if gtsFollow.TargetAccountID != requestingAcct.ID { - return errors.New("ACCEPT: follow target account and requesting account were not the same") + // ACCEPT FOLLOW + case uris.IsFollowPath(objIRI): + if err := f.acceptFollowIRI( + ctx, + objIRI.String(), + receivingAcct, + requestingAcct, + ); err != nil { + return err } - follow, err := f.state.DB.AcceptFollowRequest(ctx, gtsFollow.AccountID, gtsFollow.TargetAccountID) - if err != nil { + // ACCEPT STATUS (reply/boost) + case uris.IsStatusesPath(objIRI): + if err := f.acceptStatusIRI( + ctx, + activityID.String(), + objIRI.String(), + receivingAcct, + requestingAcct, + ); err != nil { return err } - f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{ - APObjectType: ap.ActivityFollow, - APActivityType: ap.ActivityAccept, - GTSModel: follow, - Receiving: receivingAcct, - Requesting: requestingAcct, - }) + // ACCEPT LIKE + case uris.IsLikePath(objIRI): + if err := f.acceptLikeIRI( + ctx, + activityID.String(), + objIRI.String(), + receivingAcct, + requestingAcct, + ); err != nil { + return err + } } - - continue } + } - // Check and handle any - // IRI type objects. - if object.IsIRI() { + return nil +} - // Extract IRI from object. - iri := object.GetIRI() - if !uris.IsFollowPath(iri) { - continue - } +func (f *federatingDB) acceptFollowType( + ctx context.Context, + asType vocab.Type, + receivingAcct *gtsmodel.Account, + requestingAcct *gtsmodel.Account, +) error { + // Cast the vocab.Type object to known AS type. + asFollow := asType.(vocab.ActivityStreamsFollow) - // Serialize IRI. - iriStr := iri.String() + // Reconstruct the follow. + follow, err := f.converter.ASFollowToFollow(ctx, asFollow) + if err != nil { + err := gtserror.Newf("error converting Follow to *gtsmodel.Follow: %w", err) + return gtserror.NewErrorInternalError(err) + } - // ACCEPT FOLLOW - followReq, err := f.state.DB.GetFollowRequestByURI(ctx, iriStr) - if err != nil { - return fmt.Errorf("ACCEPT: couldn't get follow request with id %s from the database: %s", iriStr, err) - } + // Lock on the Follow URI + // as we may be updating it. + unlock := f.state.FedLocks.Lock(follow.URI) + defer unlock() - // Make sure the creator of the original follow - // is the same as whatever inbox this landed in. - if followReq.AccountID != receivingAcct.ID { - return errors.New("ACCEPT: follow account and inbox account were not the same") - } + // Make sure the creator of the original follow + // is the same as whatever inbox this landed in. + if follow.AccountID != receivingAcct.ID { + const text = "Follow account and inbox account were not the same" + return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) + } - // Make sure the target of the original follow - // is the same as the account making the request. - if followReq.TargetAccountID != requestingAcct.ID { - return errors.New("ACCEPT: follow target account and requesting account were not the same") - } + // Make sure the target of the original follow + // is the same as the account making the request. + if follow.TargetAccountID != requestingAcct.ID { + const text = "Follow target account and requesting account were not the same" + return gtserror.NewErrorForbidden(errors.New(text), text) + } - follow, err := f.state.DB.AcceptFollowRequest(ctx, followReq.AccountID, followReq.TargetAccountID) - if err != nil { - return err - } + // Accept and get the populated follow back. + follow, err = f.state.DB.AcceptFollowRequest( + ctx, + follow.AccountID, + follow.TargetAccountID, + ) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + err := gtserror.Newf("db error accepting follow request: %w", err) + return gtserror.NewErrorInternalError(err) + } + + if follow == nil { + // There was no follow request + // to accept, just return 202. + return nil + } - f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{ - APObjectType: ap.ActivityFollow, - APActivityType: ap.ActivityAccept, - GTSModel: follow, - Receiving: receivingAcct, - Requesting: requestingAcct, - }) + // Send the accepted follow through + // the processor to do side effects. + f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{ + APObjectType: ap.ActivityFollow, + APActivityType: ap.ActivityAccept, + GTSModel: follow, + Receiving: receivingAcct, + Requesting: requestingAcct, + }) - continue - } + return nil +} + +func (f *federatingDB) acceptFollowIRI( + ctx context.Context, + objectIRI string, + receivingAcct *gtsmodel.Account, + requestingAcct *gtsmodel.Account, +) error { + // Lock on this potential Follow + // URI as we may be updating it. + unlock := f.state.FedLocks.Lock(objectIRI) + defer unlock() + // Get the follow req from the db. + followReq, err := f.state.DB.GetFollowRequestByURI(ctx, objectIRI) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + err := gtserror.Newf("db error getting follow request: %w", err) + return gtserror.NewErrorInternalError(err) + } + + if followReq == nil { + // We didn't have a follow request + // with this URI, so nothing to do. + // Just return. + return nil + } + + // Make sure the creator of the original follow + // is the same as whatever inbox this landed in. + if followReq.AccountID != receivingAcct.ID { + const text = "Follow account and inbox account were not the same" + return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) + } + + // Make sure the target of the original follow + // is the same as the account making the request. + if followReq.TargetAccountID != requestingAcct.ID { + const text = "Follow target account and requesting account were not the same" + return gtserror.NewErrorForbidden(errors.New(text), text) + } + + // Accept and get the populated follow back. + follow, err := f.state.DB.AcceptFollowRequest( + ctx, + followReq.AccountID, + followReq.TargetAccountID, + ) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + err := gtserror.Newf("db error accepting follow request: %w", err) + return gtserror.NewErrorInternalError(err) + } + + if follow == nil { + // There was no follow request + // to accept, just return 202. + return nil } + // Send the accepted follow through + // the processor to do side effects. + f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{ + APObjectType: ap.ActivityFollow, + APActivityType: ap.ActivityAccept, + GTSModel: follow, + Receiving: receivingAcct, + Requesting: requestingAcct, + }) + + return nil +} + +func (f *federatingDB) acceptStatusIRI( + ctx context.Context, + activityID string, + objectIRI string, + receivingAcct *gtsmodel.Account, + requestingAcct *gtsmodel.Account, +) error { + // Lock on this potential status + // URI as we may be updating it. + unlock := f.state.FedLocks.Lock(objectIRI) + defer unlock() + + // Get the status from the db. + status, err := f.state.DB.GetStatusByURI(ctx, objectIRI) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + err := gtserror.Newf("db error getting status: %w", err) + return gtserror.NewErrorInternalError(err) + } + + if status == nil { + // We didn't have a status with + // this URI, so nothing to do. + // Just return. + return nil + } + + if !status.IsLocal() { + // We don't process Accepts of statuses + // that weren't created on our instance. + // Just return. + return nil + } + + if util.PtrOrValue(status.PendingApproval, false) { + // Status doesn't need approval or it's + // already been approved by an Accept. + // Just return. + return nil + } + + // Make sure the creator of the original status + // is the same as the inbox processing the Accept; + // this also ensures the status is local. + if status.AccountID != receivingAcct.ID { + const text = "status author account and inbox account were not the same" + return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) + } + + // Make sure the target of the interaction (reply/boost) + // is the same as the account doing the Accept. + if status.BoostOfAccountID != requestingAcct.ID && + status.InReplyToAccountID != requestingAcct.ID { + const text = "status reply to or boost of account and requesting account were not the same" + return gtserror.NewErrorForbidden(errors.New(text), text) + } + + // Mark the status as approved by this Accept URI. + status.PendingApproval = util.Ptr(false) + status.ApprovedByURI = activityID + if err := f.state.DB.UpdateStatus( + ctx, + status, + "pending_approval", + "approved_by_uri", + ); err != nil { + err := gtserror.Newf("db error accepting status: %w", err) + return gtserror.NewErrorInternalError(err) + } + + var apObjectType string + if status.InReplyToID != "" { + // Accepting a Reply. + apObjectType = ap.ObjectNote + } else { + // Accepting an Announce. + apObjectType = ap.ActivityAnnounce + } + + // Send the now-approved status through to the + // fedi worker again to process side effects. + f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{ + APObjectType: apObjectType, + APActivityType: ap.ActivityAccept, + GTSModel: status, + Receiving: receivingAcct, + Requesting: requestingAcct, + }) + + return nil +} + +func (f *federatingDB) acceptLikeIRI( + ctx context.Context, + activityID string, + objectIRI string, + receivingAcct *gtsmodel.Account, + requestingAcct *gtsmodel.Account, +) error { + // Lock on this potential Like + // URI as we may be updating it. + unlock := f.state.FedLocks.Lock(objectIRI) + defer unlock() + + // Get the fave from the db. + fave, err := f.state.DB.GetStatusFaveByURI(ctx, objectIRI) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + err := gtserror.Newf("db error getting fave: %w", err) + return gtserror.NewErrorInternalError(err) + } + + if fave == nil { + // We didn't have a fave with + // this URI, so nothing to do. + // Just return. + return nil + } + + if !fave.Account.IsLocal() { + // We don't process Accepts of Likes + // that weren't created on our instance. + // Just return. + return nil + } + + if !util.PtrOrValue(fave.PendingApproval, false) { + // Like doesn't need approval or it's + // already been approved by an Accept. + // Just return. + return nil + } + + // Make sure the creator of the original Like + // is the same as the inbox processing the Accept; + // this also ensures the Like is local. + if fave.AccountID != receivingAcct.ID { + const text = "fave creator account and inbox account were not the same" + return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) + } + + // Make sure the target of the Like is the + // same as the account doing the Accept. + if fave.TargetAccountID != requestingAcct.ID { + const text = "status fave target account and requesting account were not the same" + return gtserror.NewErrorForbidden(errors.New(text), text) + } + + // Mark the fave as approved by this Accept URI. + fave.PendingApproval = util.Ptr(false) + fave.ApprovedByURI = activityID + if err := f.state.DB.UpdateStatusFave( + ctx, + fave, + "pending_approval", + "approved_by_uri", + ); err != nil { + err := gtserror.Newf("db error accepting status: %w", err) + return gtserror.NewErrorInternalError(err) + } + + // Send the now-approved fave through to the + // fedi worker again to process side effects. + f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{ + APObjectType: ap.ActivityLike, + APActivityType: ap.ActivityAccept, + GTSModel: fave, + Receiving: receivingAcct, + Requesting: requestingAcct, + }) + return nil } diff --git a/internal/federation/federatingdb/db.go b/internal/federation/federatingdb/db.go index 12bd5a376..3388d7a03 100644 --- a/internal/federation/federatingdb/db.go +++ b/internal/federation/federatingdb/db.go @@ -19,6 +19,7 @@ package federatingdb import ( "context" + "net/url" "github.com/superseriousbusiness/activity/pub" "github.com/superseriousbusiness/activity/streams/vocab" @@ -43,6 +44,12 @@ type DB interface { Reject(ctx context.Context, reject vocab.ActivityStreamsReject) error Announce(ctx context.Context, announce vocab.ActivityStreamsAnnounce) error Move(ctx context.Context, move vocab.ActivityStreamsMove) error + + /* + Extra/convenience functionality. + */ + + GetAccept(ctx context.Context, acceptIRI *url.URL) (vocab.ActivityStreamsAccept, error) } // FederatingDB uses the given state interface diff --git a/internal/federation/federatingdb/get.go b/internal/federation/federatingdb/get.go index eba58853f..5dcebb877 100644 --- a/internal/federation/federatingdb/get.go +++ b/internal/federation/federatingdb/get.go @@ -37,22 +37,30 @@ func (f *federatingDB) Get(ctx context.Context, id *url.URL) (value vocab.Type, l.Debug("entering Get") switch { + case uris.IsUserPath(id): acct, err := f.state.DB.GetAccountByURI(ctx, id.String()) if err != nil { return nil, err } return f.converter.AccountToAS(ctx, acct) + case uris.IsStatusesPath(id): status, err := f.state.DB.GetStatusByURI(ctx, id.String()) if err != nil { return nil, err } return f.converter.StatusToAS(ctx, status) + case uris.IsFollowersPath(id): return f.Followers(ctx, id) + case uris.IsFollowingPath(id): return f.Following(ctx, id) + + case uris.IsAcceptsPath(id): + return f.GetAccept(ctx, id) + default: return nil, fmt.Errorf("federatingDB: could not Get %s", id.String()) } |