Refactor/tidy (#261)

* tidy up streaming

* cut down code duplication

* test get followers/following

* test streaming processor

* fix some test models

* add TimeMustParse

* fix uri / url typo

* make trace logging less verbose

* make logging more consistent

* disable quote on logging

* remove context.Background

* remove many extraneous mastodon references

* regenerate swagger

* don't log query on no rows result

* log latency first for easier reading
This commit is contained in:
tobi 2021-10-04 15:24:19 +02:00 committed by GitHub
commit e04b187702
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
126 changed files with 1192 additions and 955 deletions

View file

@ -20,11 +20,9 @@ package federatingdb
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/ap"
@ -37,43 +35,29 @@ import (
func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsAccept) error {
l := f.log.WithFields(
logrus.Fields{
"func": "Accept",
"asType": accept.GetTypeName(),
"func": "Accept",
},
)
m, err := streams.Serialize(accept)
if err != nil {
return err
}
b, err := json.Marshal(m)
if err != nil {
return err
}
l.Debugf("received ACCEPT asType %s", string(b))
targetAcctI := ctx.Value(util.APAccount)
if targetAcctI == nil {
// If the target account wasn't set on the context, that means this request didn't pass through the
// API, but came from inside GtS as the result of another activity on this instance. That being so,
if l.Level >= logrus.DebugLevel {
i, err := marshalItem(accept)
if err != nil {
return err
}
l = l.WithField("accept", i)
l.Debug("entering Accept")
}
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
if err != nil {
return err
}
if targetAcct == nil || fromFederatorChan == nil {
// If the target account or federator channel wasn't set on the context, that means this request didn't pass
// through the API, but came from inside GtS as the result of another activity on this instance. That being so,
// we can safely just ignore this activity, since we know we've already processed it elsewhere.
return nil
}
targetAcct, ok := targetAcctI.(*gtsmodel.Account)
if !ok {
l.Error("ACCEPT: target account was set on context but couldn't be parsed")
return nil
}
fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey)
if fromFederatorChanI == nil {
l.Error("ACCEPT: from federator channel wasn't set on context")
return nil
}
fromFederatorChan, ok := fromFederatorChanI.(chan messages.FromFederator)
if !ok {
l.Error("ACCEPT: from federator channel was set on context but couldn't be parsed")
return nil
}
acceptObject := accept.GetActivityStreamsObject()
if acceptObject == nil {

View file

@ -20,16 +20,12 @@ package federatingdb
import (
"context"
"encoding/json"
"fmt"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/messages"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
func (f *federatingDB) Announce(ctx context.Context, announce vocab.ActivityStreamsAnnounce) error {
@ -38,40 +34,26 @@ func (f *federatingDB) Announce(ctx context.Context, announce vocab.ActivityStre
"func": "Announce",
},
)
m, err := streams.Serialize(announce)
if l.Level >= logrus.DebugLevel {
i, err := marshalItem(announce)
if err != nil {
return err
}
l = l.WithField("announce", i)
l.Debug("entering Announce")
}
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
if err != nil {
return err
}
b, err := json.Marshal(m)
if err != nil {
return err
}
l.Debugf("received ANNOUNCE %s", string(b))
targetAcctI := ctx.Value(util.APAccount)
if targetAcctI == nil {
// If the target account wasn't set on the context, that means this request didn't pass through the
// API, but came from inside GtS as the result of another activity on this instance. That being so,
if targetAcct == nil || fromFederatorChan == nil {
// If the target account or federator channel wasn't set on the context, that means this request didn't pass
// through the API, but came from inside GtS as the result of another activity on this instance. That being so,
// we can safely just ignore this activity, since we know we've already processed it elsewhere.
return nil
}
targetAcct, ok := targetAcctI.(*gtsmodel.Account)
if !ok {
l.Error("ANNOUNCE: target account was set on context but couldn't be parsed")
return nil
}
fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey)
if fromFederatorChanI == nil {
l.Error("ANNOUNCE: from federator channel wasn't set on context")
return nil
}
fromFederatorChan, ok := fromFederatorChanI.(chan messages.FromFederator)
if !ok {
l.Error("ANNOUNCE: from federator channel was set on context but couldn't be parsed")
return nil
}
boost, isNew, err := f.typeConverter.ASAnnounceToStatus(ctx, announce)
if err != nil {

View file

@ -20,19 +20,15 @@ package federatingdb
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/messages"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
// Create adds a new entry to the database which must be able to be
@ -50,44 +46,29 @@ import (
func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
l := f.log.WithFields(
logrus.Fields{
"func": "Create",
"asType": asType.GetTypeName(),
"func": "Create",
},
)
m, err := streams.Serialize(asType)
if l.Level >= logrus.DebugLevel {
i, err := marshalItem(asType)
if err != nil {
return err
}
l = l.WithField("create", i)
l.Debug("entering Create")
}
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
if err != nil {
return err
}
b, err := json.Marshal(m)
if err != nil {
return err
}
l.Debugf("received CREATE asType %s", string(b))
targetAcctI := ctx.Value(util.APAccount)
if targetAcctI == nil {
// If the target account wasn't set on the context, that means this request didn't pass through the
// API, but came from inside GtS as the result of another activity on this instance. That being so,
if targetAcct == nil || fromFederatorChan == nil {
// If the target account or federator channel wasn't set on the context, that means this request didn't pass
// through the API, but came from inside GtS as the result of another activity on this instance. That being so,
// we can safely just ignore this activity, since we know we've already processed it elsewhere.
return nil
}
targetAcct, ok := targetAcctI.(*gtsmodel.Account)
if !ok {
l.Error("CREATE: target account was set on context but couldn't be parsed")
return nil
}
fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey)
if fromFederatorChanI == nil {
l.Error("CREATE: from federator channel wasn't set on context")
return nil
}
fromFederatorChan, ok := fromFederatorChanI.(chan messages.FromFederator)
if !ok {
l.Error("CREATE: from federator channel was set on context but couldn't be parsed")
return nil
}
switch asType.GetTypeName() {
case ap.ActivityCreate:

View file

@ -27,7 +27,6 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/messages"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
// Delete removes the entry with the given id.
@ -40,34 +39,21 @@ func (f *federatingDB) Delete(ctx context.Context, id *url.URL) error {
l := f.log.WithFields(
logrus.Fields{
"func": "Delete",
"id": id.String(),
"id": id,
},
)
l.Debugf("received DELETE id %s", id.String())
l.Debug("entering Delete")
targetAcctI := ctx.Value(util.APAccount)
if targetAcctI == nil {
// If the target account wasn't set on the context, that means this request didn't pass through the
// API, but came from inside GtS as the result of another activity on this instance. That being so,
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
if err != nil {
return err
}
if targetAcct == nil || fromFederatorChan == nil {
// If the target account or federator channel wasn't set on the context, that means this request didn't pass
// through the API, but came from inside GtS as the result of another activity on this instance. That being so,
// we can safely just ignore this activity, since we know we've already processed it elsewhere.
return nil
}
targetAcct, ok := targetAcctI.(*gtsmodel.Account)
if !ok {
l.Error("DELETE: target account was set on context but couldn't be parsed")
return nil
}
fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey)
if fromFederatorChanI == nil {
l.Error("DELETE: from federator channel wasn't set on context")
return nil
}
fromFederatorChan, ok := fromFederatorChanI.(chan messages.FromFederator)
if !ok {
l.Error("DELETE: from federator channel was set on context but couldn't be parsed")
return nil
}
// in a delete we only get the URI, we can't know if we have a status or a profile or something else,
// so we have to try a few different things...

View file

@ -29,14 +29,15 @@ import (
// id. It may not be owned by this application instance.
//
// The library makes this call only after acquiring a lock first.
//
// Implementation note: this just straight up isn't implemented, and doesn't *really* need to be either.
func (f *federatingDB) Exists(c context.Context, id *url.URL) (exists bool, err error) {
l := f.log.WithFields(
logrus.Fields{
"func": "Exists",
"id": id.String(),
"id": id,
},
)
l.Debugf("entering EXISTS function with id %s", id.String())
l.Debug("entering Exists")
return false, nil
}

View file

@ -18,4 +18,55 @@
package federatingdb_test
// TODO: write tests for pgfed
import (
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
"github.com/superseriousbusiness/gotosocial/testrig"
)
type FederatingDBTestSuite struct {
suite.Suite
config *config.Config
db db.DB
log *logrus.Logger
tc typeutils.TypeConverter
federatingDB federatingdb.DB
testTokens map[string]*gtsmodel.Token
testClients map[string]*gtsmodel.Client
testApplications map[string]*gtsmodel.Application
testUsers map[string]*gtsmodel.User
testAccounts map[string]*gtsmodel.Account
testAttachments map[string]*gtsmodel.MediaAttachment
testStatuses map[string]*gtsmodel.Status
testBlocks map[string]*gtsmodel.Block
}
func (suite *FederatingDBTestSuite) SetupSuite() {
suite.testTokens = testrig.NewTestTokens()
suite.testClients = testrig.NewTestClients()
suite.testApplications = testrig.NewTestApplications()
suite.testUsers = testrig.NewTestUsers()
suite.testAccounts = testrig.NewTestAccounts()
suite.testAttachments = testrig.NewTestAttachments()
suite.testStatuses = testrig.NewTestStatuses()
suite.testBlocks = testrig.NewTestBlocks()
}
func (suite *FederatingDBTestSuite) SetupTest() {
suite.config = testrig.NewTestConfig()
suite.db = testrig.NewTestDB()
suite.tc = testrig.NewTestTypeConverter(suite.db)
suite.log = testrig.NewTestLog()
suite.federatingDB = testrig.NewTestFederatingDB(suite.db)
testrig.StandardDBSetup(suite.db, suite.testAccounts)
}
func (suite *FederatingDBTestSuite) TearDownTest() {
testrig.StandardDBTeardown(suite.db)
}

View file

@ -5,12 +5,9 @@ import (
"fmt"
"net/url"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
// Followers obtains the Followers Collection for an actor with the
@ -22,39 +19,28 @@ import (
func (f *federatingDB) Followers(ctx context.Context, actorIRI *url.URL) (followers vocab.ActivityStreamsCollection, err error) {
l := f.log.WithFields(
logrus.Fields{
"func": "Followers",
"actorIRI": actorIRI.String(),
"func": "Followers",
"id": actorIRI,
},
)
l.Debugf("entering FOLLOWERS function with actorIRI %s", actorIRI.String())
l.Debug("entering Followers")
acct := &gtsmodel.Account{}
if util.IsUserPath(actorIRI) {
acct, err = f.db.GetAccountByURI(ctx, actorIRI.String())
if err != nil {
return nil, fmt.Errorf("FOLLOWERS: db error getting account with uri %s: %s", actorIRI.String(), err)
}
} else if util.IsFollowersPath(actorIRI) {
if err := f.db.GetWhere(ctx, []db.Where{{Key: "followers_uri", Value: actorIRI.String()}}, acct); err != nil {
return nil, fmt.Errorf("FOLLOWERS: db error getting account with followers uri %s: %s", actorIRI.String(), err)
}
} else {
return nil, fmt.Errorf("FOLLOWERS: could not parse actor IRI %s as users or followers path", actorIRI.String())
acct, err := f.getAccountForIRI(ctx, actorIRI)
if err != nil {
return nil, err
}
acctFollowers, err := f.db.GetAccountFollowedBy(ctx, acct.ID, false)
if err != nil {
return nil, fmt.Errorf("FOLLOWERS: db error getting followers for account id %s: %s", acct.ID, err)
return nil, fmt.Errorf("Followers: db error getting followers for account id %s: %s", acct.ID, err)
}
followers = streams.NewActivityStreamsCollection()
items := streams.NewActivityStreamsItemsProperty()
iris := []*url.URL{}
for _, follow := range acctFollowers {
if follow.Account == nil {
followAccount, err := f.db.GetAccountByID(ctx, follow.AccountID)
a, err := f.db.GetAccountByID(ctx, follow.AccountID)
if err != nil {
errWrapped := fmt.Errorf("FOLLOWERS: db error getting account id %s: %s", follow.AccountID, err)
errWrapped := fmt.Errorf("Followers: db error getting account id %s: %s", follow.AccountID, err)
if err == db.ErrNoEntries {
// no entry for this account id so it's probably been deleted and we haven't caught up yet
l.Error(errWrapped)
@ -64,15 +50,14 @@ func (f *federatingDB) Followers(ctx context.Context, actorIRI *url.URL) (follow
return nil, errWrapped
}
}
follow.Account = followAccount
follow.Account = a
}
uri, err := url.Parse(follow.Account.URI)
u, err := url.Parse(follow.Account.URI)
if err != nil {
return nil, fmt.Errorf("FOLLOWERS: error parsing %s as url: %s", follow.Account.URI, err)
return nil, err
}
items.AppendIRI(uri)
iris = append(iris, u)
}
followers.SetActivityStreamsItems(items)
return
return f.collectIRIs(ctx, iris)
}

View file

@ -0,0 +1,53 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
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 federatingdb_test
import (
"context"
"encoding/json"
"testing"
"github.com/go-fed/activity/streams"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/testrig"
)
type FollowersTestSuite struct {
FederatingDBTestSuite
}
func (suite *FollowersTestSuite) TestGetFollowers() {
testAccount := suite.testAccounts["local_account_2"]
f, err := suite.federatingDB.Followers(context.Background(), testrig.URLMustParse(testAccount.URI))
suite.NoError(err)
fi, err := streams.Serialize(f)
suite.NoError(err)
fJson, err := json.Marshal(fi)
suite.NoError(err)
// zork follows local_account_2 so this should be reflected in the response
suite.Equal(`{"@context":"https://www.w3.org/ns/activitystreams","items":"http://localhost:8080/users/the_mighty_zork","type":"Collection"}`, string(fJson))
}
func TestFollowersTestSuite(t *testing.T) {
suite.Run(t, &FollowersTestSuite{})
}

View file

@ -5,12 +5,9 @@ import (
"fmt"
"net/url"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
// Following obtains the Following Collection for an actor with the
@ -22,53 +19,28 @@ import (
func (f *federatingDB) Following(ctx context.Context, actorIRI *url.URL) (following vocab.ActivityStreamsCollection, err error) {
l := f.log.WithFields(
logrus.Fields{
"func": "Following",
"actorIRI": actorIRI.String(),
"func": "Following",
"id": actorIRI,
},
)
l.Debugf("entering FOLLOWING function with actorIRI %s", actorIRI.String())
l.Debug("entering Following")
var acct *gtsmodel.Account
if util.IsUserPath(actorIRI) {
username, err := util.ParseUserPath(actorIRI)
if err != nil {
return nil, fmt.Errorf("FOLLOWING: error parsing user path: %s", err)
}
a, err := f.db.GetLocalAccountByUsername(ctx, username)
if err != nil {
return nil, fmt.Errorf("FOLLOWING: db error getting account with uri %s: %s", actorIRI.String(), err)
}
acct = a
} else if util.IsFollowingPath(actorIRI) {
username, err := util.ParseFollowingPath(actorIRI)
if err != nil {
return nil, fmt.Errorf("FOLLOWING: error parsing following path: %s", err)
}
a, err := f.db.GetLocalAccountByUsername(ctx, username)
if err != nil {
return nil, fmt.Errorf("FOLLOWING: db error getting account with following uri %s: %s", actorIRI.String(), err)
}
acct = a
} else {
return nil, fmt.Errorf("FOLLOWING: could not parse actor IRI %s as users or following path", actorIRI.String())
acct, err := f.getAccountForIRI(ctx, actorIRI)
if err != nil {
return nil, err
}
acctFollowing, err := f.db.GetAccountFollows(ctx, acct.ID)
if err != nil {
return nil, fmt.Errorf("FOLLOWING: db error getting following for account id %s: %s", acct.ID, err)
return nil, fmt.Errorf("Following: db error getting following for account id %s: %s", acct.ID, err)
}
following = streams.NewActivityStreamsCollection()
items := streams.NewActivityStreamsItemsProperty()
iris := []*url.URL{}
for _, follow := range acctFollowing {
if follow.Account == nil {
followAccount, err := f.db.GetAccountByID(ctx, follow.AccountID)
if follow.TargetAccount == nil {
a, err := f.db.GetAccountByID(ctx, follow.TargetAccountID)
if err != nil {
errWrapped := fmt.Errorf("FOLLOWING: db error getting account id %s: %s", follow.AccountID, err)
errWrapped := fmt.Errorf("Following: db error getting account id %s: %s", follow.TargetAccountID, err)
if err == db.ErrNoEntries {
// no entry for this account id so it's probably been deleted and we haven't caught up yet
l.Error(errWrapped)
@ -78,15 +50,14 @@ func (f *federatingDB) Following(ctx context.Context, actorIRI *url.URL) (follow
return nil, errWrapped
}
}
follow.Account = followAccount
follow.TargetAccount = a
}
uri, err := url.Parse(follow.Account.URI)
u, err := url.Parse(follow.TargetAccount.URI)
if err != nil {
return nil, fmt.Errorf("FOLLOWING: error parsing %s as url: %s", follow.Account.URI, err)
return nil, err
}
items.AppendIRI(uri)
iris = append(iris, u)
}
following.SetActivityStreamsItems(items)
return
return f.collectIRIs(ctx, iris)
}

View file

@ -0,0 +1,53 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
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 federatingdb_test
import (
"context"
"encoding/json"
"testing"
"github.com/go-fed/activity/streams"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/testrig"
)
type FollowingTestSuite struct {
FederatingDBTestSuite
}
func (suite *FollowingTestSuite) TestGetFollowing() {
testAccount := suite.testAccounts["local_account_1"]
f, err := suite.federatingDB.Following(context.Background(), testrig.URLMustParse(testAccount.URI))
suite.NoError(err)
fi, err := streams.Serialize(f)
suite.NoError(err)
fJson, err := json.Marshal(fi)
suite.NoError(err)
// zork follows admin account and local_account_1
suite.Equal(`{"@context":"https://www.w3.org/ns/activitystreams","items":["http://localhost:8080/users/admin","http://localhost:8080/users/1happyturtle"],"type":"Collection"}`, string(fJson))
}
func TestFollowingTestSuite(t *testing.T) {
suite.Run(t, &FollowingTestSuite{})
}

View file

@ -25,8 +25,6 @@ import (
"github.com/go-fed/activity/streams/vocab"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
@ -37,46 +35,33 @@ func (f *federatingDB) Get(ctx context.Context, id *url.URL) (value vocab.Type,
l := f.log.WithFields(
logrus.Fields{
"func": "Get",
"id": id.String(),
"id": id,
},
)
l.Debug("entering GET function")
l.Debug("entering Get")
if util.IsUserPath(id) {
acct, err := f.db.GetAccountByURI(ctx, id.String())
if err != nil {
return nil, err
}
l.Debug("is user path! returning account")
return f.typeConverter.AccountToAS(ctx, acct)
}
if util.IsFollowersPath(id) {
acct := &gtsmodel.Account{}
if err := f.db.GetWhere(ctx, []db.Where{{Key: "followers_uri", Value: id.String()}}, acct); err != nil {
return nil, err
}
followersURI, err := url.Parse(acct.FollowersURI)
if util.IsStatusesPath(id) {
status, err := f.db.GetStatusByURI(ctx, id.String())
if err != nil {
return nil, err
}
return f.typeConverter.StatusToAS(ctx, status)
}
return f.Followers(ctx, followersURI)
if util.IsFollowersPath(id) {
return f.Followers(ctx, id)
}
if util.IsFollowingPath(id) {
acct := &gtsmodel.Account{}
if err := f.db.GetWhere(ctx, []db.Where{{Key: "following_uri", Value: id.String()}}, acct); err != nil {
return nil, err
}
followingURI, err := url.Parse(acct.FollowingURI)
if err != nil {
return nil, err
}
return f.Following(ctx, followingURI)
return f.Following(ctx, id)
}
return nil, errors.New("could not get")

View file

@ -20,44 +20,19 @@ package federatingdb
import (
"context"
"fmt"
"net/url"
"github.com/go-fed/activity/pub"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
// InboxContains returns true if the OrderedCollection at 'inbox'
// contains the specified 'id'.
//
// The library makes this call only after acquiring a lock first.
//
// Implementation note: we have our own logic for inboxes so always return false here.
func (f *federatingDB) InboxContains(c context.Context, inbox, id *url.URL) (contains bool, err error) {
l := f.log.WithFields(
logrus.Fields{
"func": "InboxContains",
"id": id.String(),
},
)
l.Debugf("entering INBOXCONTAINS function with for inbox %s and id %s", inbox.String(), id.String())
if !util.IsInboxPath(inbox) {
return false, fmt.Errorf("%s is not an inbox URI", inbox.String())
}
activityI := c.Value(util.APActivity)
if activityI == nil {
return false, fmt.Errorf("no activity was set for id %s", id.String())
}
activity, ok := activityI.(pub.Activity)
if !ok || activity == nil {
return false, fmt.Errorf("could not parse contextual activity for id %s", id.String())
}
l.Debugf("activity type %s for id %s", activity.GetTypeName(), id.String())
return false, nil
}
@ -65,13 +40,9 @@ func (f *federatingDB) InboxContains(c context.Context, inbox, id *url.URL) (con
// the specified IRI, for prepending new items.
//
// The library makes this call only after acquiring a lock first.
//
// Implementation note: we don't (yet) serve inboxes, so just return empty and nil here.
func (f *federatingDB) GetInbox(c context.Context, inboxIRI *url.URL) (inbox vocab.ActivityStreamsOrderedCollectionPage, err error) {
l := f.log.WithFields(
logrus.Fields{
"func": "GetInbox",
},
)
l.Debugf("entering GETINBOX function with inboxIRI %s", inboxIRI.String())
return streams.NewActivityStreamsOrderedCollectionPage(), nil
}
@ -80,12 +51,8 @@ func (f *federatingDB) GetInbox(c context.Context, inboxIRI *url.URL) (inbox voc
// database entries. Separate calls to Create will do that.
//
// The library makes this call only after acquiring a lock first.
//
// Implementation note: we don't allow inbox setting so just return nil here.
func (f *federatingDB) SetInbox(c context.Context, inbox vocab.ActivityStreamsOrderedCollectionPage) error {
l := f.log.WithFields(
logrus.Fields{
"func": "SetInbox",
},
)
l.Debug("entering SETINBOX function")
return nil
}

View file

@ -22,8 +22,8 @@ import (
"context"
"net/url"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab"
"github.com/sirupsen/logrus"
)
// Liked obtains the Liked Collection for an actor with the
@ -32,13 +32,8 @@ import (
// If modified, the library will then call Update.
//
// The library makes this call only after acquiring a lock first.
//
// Implementation note: we don't serve a Liked collection *yet* so just return an empty collection for now.
func (f *federatingDB) Liked(c context.Context, actorIRI *url.URL) (liked vocab.ActivityStreamsCollection, err error) {
l := f.log.WithFields(
logrus.Fields{
"func": "Liked",
"actorIRI": actorIRI.String(),
},
)
l.Debugf("entering LIKED function with actorIRI %s", actorIRI.String())
return nil, nil
return streams.NewActivityStreamsCollection(), nil
}

View file

@ -20,29 +20,19 @@ package federatingdb
import (
"context"
"fmt"
"net/url"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
// GetOutbox returns the first ordered collection page of the outbox
// at the specified IRI, for prepending new items.
//
// The library makes this call only after acquiring a lock first.
//
// Implementation note: we don't (yet) serve outboxes, so just return empty and nil here.
func (f *federatingDB) GetOutbox(ctx context.Context, outboxIRI *url.URL) (inbox vocab.ActivityStreamsOrderedCollectionPage, err error) {
l := f.log.WithFields(
logrus.Fields{
"func": "GetOutbox",
},
)
l.Debug("entering GETOUTBOX function")
return streams.NewActivityStreamsOrderedCollectionPage(), nil
}
@ -51,14 +41,9 @@ func (f *federatingDB) GetOutbox(ctx context.Context, outboxIRI *url.URL) (inbox
// database entries. Separate calls to Create will do that.
//
// The library makes this call only after acquiring a lock first.
//
// Implementation note: we don't allow outbox setting so just return nil here.
func (f *federatingDB) SetOutbox(ctx context.Context, outbox vocab.ActivityStreamsOrderedCollectionPage) error {
l := f.log.WithFields(
logrus.Fields{
"func": "SetOutbox",
},
)
l.Debug("entering SETOUTBOX function")
return nil
}
@ -67,23 +52,9 @@ func (f *federatingDB) SetOutbox(ctx context.Context, outbox vocab.ActivityStrea
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) OutboxForInbox(ctx context.Context, inboxIRI *url.URL) (outboxIRI *url.URL, err error) {
l := f.log.WithFields(
logrus.Fields{
"func": "OutboxForInbox",
"inboxIRI": inboxIRI.String(),
},
)
l.Debugf("entering OUTBOXFORINBOX function with inboxIRI %s", inboxIRI.String())
if !util.IsInboxPath(inboxIRI) {
return nil, fmt.Errorf("%s is not an inbox URI", inboxIRI.String())
}
acct := &gtsmodel.Account{}
if err := f.db.GetWhere(ctx, []db.Where{{Key: "inbox_uri", Value: inboxIRI.String()}}, acct); err != nil {
if err == db.ErrNoEntries {
return nil, fmt.Errorf("no actor found that corresponds to inbox %s", inboxIRI.String())
}
return nil, fmt.Errorf("db error searching for actor with inbox %s", inboxIRI.String())
acct, err := f.getAccountForIRI(ctx, inboxIRI)
if err != nil {
return nil, err
}
return url.Parse(acct.OutboxURI)
}

View file

@ -36,10 +36,10 @@ func (f *federatingDB) Owns(ctx context.Context, id *url.URL) (bool, error) {
l := f.log.WithFields(
logrus.Fields{
"func": "Owns",
"id": id.String(),
"id": id,
},
)
l.Tracef("entering OWNS function with id %s", id.String())
l.Debug("entering Owns")
// if the id host isn't this instance host, we don't own this IRI
if id.Host != f.config.Host {

View file

@ -20,46 +20,40 @@ package federatingdb
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo) error {
l := f.log.WithFields(
logrus.Fields{
"func": "Undo",
"asType": undo.GetTypeName(),
"func": "Undo",
},
)
m, err := streams.Serialize(undo)
if err != nil {
return err
}
b, err := json.Marshal(m)
if err != nil {
return err
}
l.Debugf("received UNDO asType %s", string(b))
targetAcctI := ctx.Value(util.APAccount)
if targetAcctI == nil {
// If the target account wasn't set on the context, that means this request didn't pass through the
// API, but came from inside GtS as the result of another activity on this instance. That being so,
// we can safely just ignore this activity, since we know we've already processed it elsewhere.
return nil
if l.Level >= logrus.DebugLevel {
i, err := marshalItem(undo)
if err != nil {
return err
}
l = l.WithField("undo", i)
l.Debug("entering Undo")
}
targetAcct, ok := targetAcctI.(*gtsmodel.Account)
if !ok {
l.Error("UNDO: target account was set on context but couldn't be parsed")
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
if err != nil {
return err
}
if targetAcct == nil || fromFederatorChan == nil {
// If the target account or federator channel wasn't set on the context, that means this request didn't pass
// through the API, but came from inside GtS as the result of another activity on this instance. That being so,
// we can safely just ignore this activity, since we know we've already processed it elsewhere.
return nil
}

View file

@ -20,11 +20,9 @@ package federatingdb
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/ap"
@ -45,35 +43,32 @@ import (
func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
l := f.log.WithFields(
logrus.Fields{
"func": "Update",
"asType": asType.GetTypeName(),
"func": "Update",
},
)
m, err := streams.Serialize(asType)
if l.Level >= logrus.DebugLevel {
i, err := marshalItem(asType)
if err != nil {
return err
}
l = l.WithField("update", i)
l.Debug("entering Update")
}
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
if err != nil {
return err
}
b, err := json.Marshal(m)
if err != nil {
return err
}
l.Debugf("received UPDATE asType %s", string(b))
targetAcctI := ctx.Value(util.APAccount)
if targetAcctI == nil {
// If the target account wasn't set on the context, that means this request didn't pass through the
// API, but came from inside GtS as the result of another activity on this instance. That being so,
if targetAcct == nil || fromFederatorChan == nil {
// If the target account or federator channel wasn't set on the context, that means this request didn't pass
// through the API, but came from inside GtS as the result of another activity on this instance. That being so,
// we can safely just ignore this activity, since we know we've already processed it elsewhere.
return nil
}
targetAcct, ok := targetAcctI.(*gtsmodel.Account)
if !ok {
l.Error("UPDATE: target account was set on context but couldn't be parsed")
}
requestingAcctI := ctx.Value(util.APRequestingAccount)
if targetAcctI == nil {
if requestingAcctI == nil {
l.Error("UPDATE: requesting account wasn't set on context")
}
requestingAcct, ok := requestingAcctI.(*gtsmodel.Account)
@ -81,15 +76,6 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
l.Error("UPDATE: requesting account was set on context but couldn't be parsed")
}
fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey)
if fromFederatorChanI == nil {
l.Error("UPDATE: from federator channel wasn't set on context")
}
fromFederatorChan, ok := fromFederatorChanI.(chan messages.FromFederator)
if !ok {
l.Error("UPDATE: from federator channel was set on context but couldn't be parsed")
}
typeName := asType.GetTypeName()
if typeName == ap.ActorApplication ||
typeName == ap.ActorGroup ||

View file

@ -32,6 +32,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/messages"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
@ -64,19 +65,18 @@ func sameActor(activityActor vocab.ActivityStreamsActorProperty, followActor voc
func (f *federatingDB) NewID(ctx context.Context, t vocab.Type) (idURL *url.URL, err error) {
l := f.log.WithFields(
logrus.Fields{
"func": "NewID",
"asType": t.GetTypeName(),
"func": "NewID",
},
)
m, err := streams.Serialize(t)
if err != nil {
return nil, err
if l.Level >= logrus.DebugLevel {
i, err := marshalItem(t)
if err != nil {
return nil, err
}
l = l.WithField("newID", i)
l.Debug("entering NewID")
}
b, err := json.Marshal(m)
if err != nil {
return nil, err
}
l.Debugf("received NEWID request for asType %s", string(b))
switch t.GetTypeName() {
case ap.ActivityFollow:
@ -201,23 +201,9 @@ func (f *federatingDB) NewID(ctx context.Context, t vocab.Type) (idURL *url.URL,
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) ActorForOutbox(ctx context.Context, outboxIRI *url.URL) (actorIRI *url.URL, err error) {
l := f.log.WithFields(
logrus.Fields{
"func": "ActorForOutbox",
"inboxIRI": outboxIRI.String(),
},
)
l.Debugf("entering ACTORFOROUTBOX function with outboxIRI %s", outboxIRI.String())
if !util.IsOutboxPath(outboxIRI) {
return nil, fmt.Errorf("%s is not an outbox URI", outboxIRI.String())
}
acct := &gtsmodel.Account{}
if err := f.db.GetWhere(ctx, []db.Where{{Key: "outbox_uri", Value: outboxIRI.String()}}, acct); err != nil {
if err == db.ErrNoEntries {
return nil, fmt.Errorf("no actor found that corresponds to outbox %s", outboxIRI.String())
}
return nil, fmt.Errorf("db error searching for actor with outbox %s", outboxIRI.String())
acct, err := f.getAccountForIRI(ctx, outboxIRI)
if err != nil {
return nil, err
}
return url.Parse(acct.URI)
}
@ -226,23 +212,116 @@ func (f *federatingDB) ActorForOutbox(ctx context.Context, outboxIRI *url.URL) (
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) ActorForInbox(ctx context.Context, inboxIRI *url.URL) (actorIRI *url.URL, err error) {
l := f.log.WithFields(
logrus.Fields{
"func": "ActorForInbox",
"inboxIRI": inboxIRI.String(),
},
)
l.Debugf("entering ACTORFORINBOX function with inboxIRI %s", inboxIRI.String())
if !util.IsInboxPath(inboxIRI) {
return nil, fmt.Errorf("%s is not an inbox URI", inboxIRI.String())
}
acct := &gtsmodel.Account{}
if err := f.db.GetWhere(ctx, []db.Where{{Key: "inbox_uri", Value: inboxIRI.String()}}, acct); err != nil {
if err == db.ErrNoEntries {
return nil, fmt.Errorf("no actor found that corresponds to inbox %s", inboxIRI.String())
}
return nil, fmt.Errorf("db error searching for actor with inbox %s", inboxIRI.String())
acct, err := f.getAccountForIRI(ctx, inboxIRI)
if err != nil {
return nil, err
}
return url.Parse(acct.URI)
}
// getAccountForIRI returns the account that corresponds to or owns the given IRI.
func (f *federatingDB) getAccountForIRI(ctx context.Context, iri *url.URL) (account *gtsmodel.Account, err error) {
acct := &gtsmodel.Account{}
if util.IsInboxPath(iri) {
if err := f.db.GetWhere(ctx, []db.Where{{Key: "inbox_uri", Value: iri.String()}}, acct); err != nil {
if err == db.ErrNoEntries {
return nil, fmt.Errorf("no actor found that corresponds to inbox %s", iri.String())
}
return nil, fmt.Errorf("db error searching for actor with inbox %s", iri.String())
}
return acct, nil
}
if util.IsOutboxPath(iri) {
if err := f.db.GetWhere(ctx, []db.Where{{Key: "outbox_uri", Value: iri.String()}}, acct); err != nil {
if err == db.ErrNoEntries {
return nil, fmt.Errorf("no actor found that corresponds to outbox %s", iri.String())
}
return nil, fmt.Errorf("db error searching for actor with outbox %s", iri.String())
}
return acct, nil
}
if util.IsUserPath(iri) {
if err := f.db.GetWhere(ctx, []db.Where{{Key: "uri", Value: iri.String()}}, acct); err != nil {
if err == db.ErrNoEntries {
return nil, fmt.Errorf("no actor found that corresponds to uri %s", iri.String())
}
return nil, fmt.Errorf("db error searching for actor with uri %s", iri.String())
}
return acct, nil
}
if util.IsFollowersPath(iri) {
if err := f.db.GetWhere(ctx, []db.Where{{Key: "followers_uri", Value: iri.String()}}, acct); err != nil {
if err == db.ErrNoEntries {
return nil, fmt.Errorf("no actor found that corresponds to followers_uri %s", iri.String())
}
return nil, fmt.Errorf("db error searching for actor with followers_uri %s", iri.String())
}
return acct, nil
}
if util.IsFollowingPath(iri) {
if err := f.db.GetWhere(ctx, []db.Where{{Key: "following_uri", Value: iri.String()}}, acct); err != nil {
if err == db.ErrNoEntries {
return nil, fmt.Errorf("no actor found that corresponds to following_uri %s", iri.String())
}
return nil, fmt.Errorf("db error searching for actor with following_uri %s", iri.String())
}
return acct, nil
}
return nil, fmt.Errorf("getActorForIRI: iri %s not recognised", iri)
}
// collectFollows takes a slice of iris and converts them into ActivityStreamsCollection of IRIs.
func (f *federatingDB) collectIRIs(ctx context.Context, iris []*url.URL) (vocab.ActivityStreamsCollection, error) {
collection := streams.NewActivityStreamsCollection()
items := streams.NewActivityStreamsItemsProperty()
for _, i := range iris {
items.AppendIRI(i)
}
collection.SetActivityStreamsItems(items)
return collection, nil
}
// extractFromCtx extracts some useful values from a context passed into the federatingDB via the API:
// - The target account that owns the inbox or URI being interacted with.
// - A channel that messages for the processor can be placed into.
func extractFromCtx(ctx context.Context) (*gtsmodel.Account, chan messages.FromFederator, error) {
var targetAcct *gtsmodel.Account
targetAcctI := ctx.Value(util.APAccount)
if targetAcctI != nil {
var ok bool
targetAcct, ok = targetAcctI.(*gtsmodel.Account)
if !ok {
return nil, nil, errors.New("extractFromCtx: account value in context not parseable")
}
}
var fromFederatorChan chan messages.FromFederator
fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey)
if fromFederatorChanI != nil {
var ok bool
fromFederatorChan, ok = fromFederatorChanI.(chan messages.FromFederator)
if !ok {
return nil, nil, errors.New("extractFromCtx: fromFederatorChan value in context not parseable")
}
}
return targetAcct, fromFederatorChan, nil
}
func marshalItem(item vocab.Type) (string, error) {
m, err := streams.Serialize(item)
if err != nil {
return "", err
}
b, err := json.Marshal(m)
if err != nil {
return "", err
}
return string(b), nil
}