Follows and relationships (#27)

* Follows -- create and undo, both remote and local
* Statuses -- federate new posts, including media, attachments, CWs and image descriptions.
This commit is contained in:
Tobi Smethurst
2021-05-21 15:48:26 +02:00
committed by GitHub
parent dc06e71b76
commit d839f27c30
54 changed files with 2260 additions and 299 deletions

View File

@ -78,6 +78,15 @@ func (p *processor) AccountGet(authed *oauth.Auth, targetAccountID string) (*api
return nil, fmt.Errorf("db error: %s", err)
}
// lazily dereference things on the account if it hasn't been done yet
var requestingUsername string
if authed.Account != nil {
requestingUsername = authed.Account.Username
}
if err := p.dereferenceAccountFields(targetAccount, requestingUsername); err != nil {
p.log.WithField("func", "AccountGet").Debugf("dereferencing account: %s", err)
}
var mastoAccount *apimodel.Account
var err error
if authed.Account != nil && targetAccount.ID == authed.Account.ID {
@ -285,6 +294,12 @@ func (p *processor) AccountFollowersGet(authed *oauth.Auth, targetAccountID stri
return nil, NewErrorInternalError(err)
}
// derefence account fields in case we haven't done it already
if err := p.dereferenceAccountFields(a, authed.Account.Username); err != nil {
// don't bail if we can't fetch them, we'll try another time
p.log.WithField("func", "AccountFollowersGet").Debugf("error dereferencing account fields: %s", err)
}
account, err := p.tc.AccountToMastoPublic(a)
if err != nil {
return nil, NewErrorInternalError(err)
@ -293,3 +308,238 @@ func (p *processor) AccountFollowersGet(authed *oauth.Auth, targetAccountID stri
}
return accounts, nil
}
func (p *processor) AccountFollowingGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, ErrorWithCode) {
blocked, err := p.db.Blocked(authed.Account.ID, targetAccountID)
if err != nil {
return nil, NewErrorInternalError(err)
}
if blocked {
return nil, NewErrorNotFound(fmt.Errorf("block exists between accounts"))
}
following := []gtsmodel.Follow{}
accounts := []apimodel.Account{}
if err := p.db.GetFollowingByAccountID(targetAccountID, &following); err != nil {
if _, ok := err.(db.ErrNoEntries); ok {
return accounts, nil
}
return nil, NewErrorInternalError(err)
}
for _, f := range following {
blocked, err := p.db.Blocked(authed.Account.ID, f.AccountID)
if err != nil {
return nil, NewErrorInternalError(err)
}
if blocked {
continue
}
a := &gtsmodel.Account{}
if err := p.db.GetByID(f.TargetAccountID, a); err != nil {
if _, ok := err.(db.ErrNoEntries); ok {
continue
}
return nil, NewErrorInternalError(err)
}
// derefence account fields in case we haven't done it already
if err := p.dereferenceAccountFields(a, authed.Account.Username); err != nil {
// don't bail if we can't fetch them, we'll try another time
p.log.WithField("func", "AccountFollowingGet").Debugf("error dereferencing account fields: %s", err)
}
account, err := p.tc.AccountToMastoPublic(a)
if err != nil {
return nil, NewErrorInternalError(err)
}
accounts = append(accounts, *account)
}
return accounts, nil
}
func (p *processor) AccountRelationshipGet(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, ErrorWithCode) {
if authed == nil || authed.Account == nil {
return nil, NewErrorForbidden(errors.New("not authed"))
}
gtsR, err := p.db.GetRelationship(authed.Account.ID, targetAccountID)
if err != nil {
return nil, NewErrorInternalError(fmt.Errorf("error getting relationship: %s", err))
}
r, err := p.tc.RelationshipToMasto(gtsR)
if err != nil {
return nil, NewErrorInternalError(fmt.Errorf("error converting relationship: %s", err))
}
return r, nil
}
func (p *processor) AccountFollowCreate(authed *oauth.Auth, form *apimodel.AccountFollowRequest) (*apimodel.Relationship, ErrorWithCode) {
// if there's a block between the accounts we shouldn't create the request ofc
blocked, err := p.db.Blocked(authed.Account.ID, form.TargetAccountID)
if err != nil {
return nil, NewErrorInternalError(err)
}
if blocked {
return nil, NewErrorNotFound(fmt.Errorf("accountfollowcreate: block exists between accounts"))
}
// make sure the target account actually exists in our db
targetAcct := &gtsmodel.Account{}
if err := p.db.GetByID(form.TargetAccountID, targetAcct); err != nil {
if _, ok := err.(db.ErrNoEntries); ok {
return nil, NewErrorNotFound(fmt.Errorf("accountfollowcreate: account %s not found in the db: %s", form.TargetAccountID, err))
}
}
// check if a follow exists already
follows, err := p.db.Follows(authed.Account, targetAcct)
if err != nil {
return nil, NewErrorInternalError(fmt.Errorf("accountfollowcreate: error checking follow in db: %s", err))
}
if follows {
// already follows so just return the relationship
return p.AccountRelationshipGet(authed, form.TargetAccountID)
}
// check if a follow exists already
followRequested, err := p.db.FollowRequested(authed.Account, targetAcct)
if err != nil {
return nil, NewErrorInternalError(fmt.Errorf("accountfollowcreate: error checking follow request in db: %s", err))
}
if followRequested {
// already follow requested so just return the relationship
return p.AccountRelationshipGet(authed, form.TargetAccountID)
}
// make the follow request
fr := &gtsmodel.FollowRequest{
AccountID: authed.Account.ID,
TargetAccountID: form.TargetAccountID,
ShowReblogs: true,
URI: util.GenerateURIForFollow(authed.Account.Username, p.config.Protocol, p.config.Host),
Notify: false,
}
if form.Reblogs != nil {
fr.ShowReblogs = *form.Reblogs
}
if form.Notify != nil {
fr.Notify = *form.Notify
}
// whack it in the database
if err := p.db.Put(fr); err != nil {
return nil, NewErrorInternalError(fmt.Errorf("accountfollowcreate: error creating follow request in db: %s", err))
}
// if it's a local account that's not locked we can just straight up accept the follow request
if !targetAcct.Locked && targetAcct.Domain == "" {
if _, err := p.db.AcceptFollowRequest(authed.Account.ID, form.TargetAccountID); err != nil {
return nil, NewErrorInternalError(fmt.Errorf("accountfollowcreate: error accepting folow request for local unlocked account: %s", err))
}
// return the new relationship
return p.AccountRelationshipGet(authed, form.TargetAccountID)
}
// otherwise we leave the follow request as it is and we handle the rest of the process asynchronously
p.fromClientAPI <- gtsmodel.FromClientAPI{
APObjectType: gtsmodel.ActivityStreamsFollow,
APActivityType: gtsmodel.ActivityStreamsCreate,
GTSModel: &gtsmodel.Follow{
AccountID: authed.Account.ID,
TargetAccountID: form.TargetAccountID,
URI: fr.URI,
},
OriginAccount: authed.Account,
TargetAccount: targetAcct,
}
// return whatever relationship results from this
return p.AccountRelationshipGet(authed, form.TargetAccountID)
}
func (p *processor) AccountFollowRemove(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, ErrorWithCode) {
// if there's a block between the accounts we shouldn't do anything
blocked, err := p.db.Blocked(authed.Account.ID, targetAccountID)
if err != nil {
return nil, NewErrorInternalError(err)
}
if blocked {
return nil, NewErrorNotFound(fmt.Errorf("AccountFollowRemove: block exists between accounts"))
}
// make sure the target account actually exists in our db
targetAcct := &gtsmodel.Account{}
if err := p.db.GetByID(targetAccountID, targetAcct); err != nil {
if _, ok := err.(db.ErrNoEntries); ok {
return nil, NewErrorNotFound(fmt.Errorf("AccountFollowRemove: account %s not found in the db: %s", targetAccountID, err))
}
}
// check if a follow request exists, and remove it if it does (storing the URI for later)
var frChanged bool
var frURI string
fr := &gtsmodel.FollowRequest{}
if err := p.db.GetWhere([]db.Where{
{Key: "account_id", Value: authed.Account.ID},
{Key: "target_account_id", Value: targetAccountID},
}, fr); err == nil {
frURI = fr.URI
if err := p.db.DeleteByID(fr.ID, fr); err != nil {
return nil, NewErrorInternalError(fmt.Errorf("AccountFollowRemove: error removing follow request from db: %s", err))
}
frChanged = true
}
// now do the same thing for any existing follow
var fChanged bool
var fURI string
f := &gtsmodel.Follow{}
if err := p.db.GetWhere([]db.Where{
{Key: "account_id", Value: authed.Account.ID},
{Key: "target_account_id", Value: targetAccountID},
}, f); err == nil {
fURI = f.URI
if err := p.db.DeleteByID(f.ID, f); err != nil {
return nil, NewErrorInternalError(fmt.Errorf("AccountFollowRemove: error removing follow from db: %s", err))
}
fChanged = true
}
// follow request status changed so send the UNDO activity to the channel for async processing
if frChanged {
p.fromClientAPI <- gtsmodel.FromClientAPI{
APObjectType: gtsmodel.ActivityStreamsFollow,
APActivityType: gtsmodel.ActivityStreamsUndo,
GTSModel: &gtsmodel.Follow{
AccountID: authed.Account.ID,
TargetAccountID: targetAccountID,
URI: frURI,
},
OriginAccount: authed.Account,
TargetAccount: targetAcct,
}
}
// follow status changed so send the UNDO activity to the channel for async processing
if fChanged {
p.fromClientAPI <- gtsmodel.FromClientAPI{
APObjectType: gtsmodel.ActivityStreamsFollow,
APActivityType: gtsmodel.ActivityStreamsUndo,
GTSModel: &gtsmodel.Follow{
AccountID: authed.Account.ID,
TargetAccountID: targetAccountID,
URI: fURI,
},
OriginAccount: authed.Account,
TargetAccount: targetAcct,
}
}
// return whatever relationship results from all this
return p.AccountRelationshipGet(authed, targetAccountID)
}

View File

@ -22,6 +22,7 @@ import (
"context"
"fmt"
"net/http"
"net/url"
"github.com/go-fed/activity/streams"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
@ -46,7 +47,7 @@ func (p *processor) authenticateAndDereferenceFediRequest(username string, r *ht
// we might already have an entry for this account so check that first
requestingAccount := &gtsmodel.Account{}
err = p.db.GetWhere("uri", requestingAccountURI.String(), requestingAccount)
err = p.db.GetWhere([]db.Where{{Key: "uri", Value: requestingAccountURI.String()}}, requestingAccount)
if err == nil {
// we do have it yay, return it
return requestingAccount, nil
@ -122,6 +123,89 @@ func (p *processor) GetFediUser(requestedUsername string, request *http.Request)
return data, nil
}
func (p *processor) GetFediFollowers(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode) {
// get the account the request is referring to
requestedAccount := &gtsmodel.Account{}
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
return nil, NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err))
}
// authenticate the request
requestingAccount, err := p.authenticateAndDereferenceFediRequest(requestedUsername, request)
if err != nil {
return nil, NewErrorNotAuthorized(err)
}
blocked, err := p.db.Blocked(requestedAccount.ID, requestingAccount.ID)
if err != nil {
return nil, NewErrorInternalError(err)
}
if blocked {
return nil, NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID))
}
requestedAccountURI, err := url.Parse(requestedAccount.URI)
if err != nil {
return nil, NewErrorInternalError(fmt.Errorf("error parsing url %s: %s", requestedAccount.URI, err))
}
requestedFollowers, err := p.federator.FederatingDB().Followers(context.Background(), requestedAccountURI)
if err != nil {
return nil, NewErrorInternalError(fmt.Errorf("error fetching followers for uri %s: %s", requestedAccountURI.String(), err))
}
data, err := streams.Serialize(requestedFollowers)
if err != nil {
return nil, NewErrorInternalError(err)
}
return data, nil
}
func (p *processor) GetFediStatus(requestedUsername string, requestedStatusID string, request *http.Request) (interface{}, ErrorWithCode) {
// get the account the request is referring to
requestedAccount := &gtsmodel.Account{}
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
return nil, NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err))
}
// authenticate the request
requestingAccount, err := p.authenticateAndDereferenceFediRequest(requestedUsername, request)
if err != nil {
return nil, NewErrorNotAuthorized(err)
}
blocked, err := p.db.Blocked(requestedAccount.ID, requestingAccount.ID)
if err != nil {
return nil, NewErrorInternalError(err)
}
if blocked {
return nil, NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID))
}
s := &gtsmodel.Status{}
if err := p.db.GetWhere([]db.Where{
{Key: "id", Value: requestedStatusID},
{Key: "account_id", Value: requestedAccount.ID},
}, s); err != nil {
return nil, NewErrorNotFound(fmt.Errorf("database error getting status with id %s and account id %s: %s", requestedStatusID, requestedAccount.ID, err))
}
asStatus, err := p.tc.StatusToAS(s)
if err != nil {
return nil, NewErrorInternalError(err)
}
data, err := streams.Serialize(asStatus)
if err != nil {
return nil, NewErrorInternalError(err)
}
return data, nil
}
func (p *processor) GetWebfingerAccount(requestedUsername string, request *http.Request) (*apimodel.WebfingerAccountResponse, ErrorWithCode) {
// get the account the request is referring to
requestedAccount := &gtsmodel.Account{}

View File

@ -19,55 +19,198 @@
package message
import (
"context"
"errors"
"fmt"
"net/url"
"github.com/go-fed/activity/streams"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) processFromClientAPI(clientMsg gtsmodel.FromClientAPI) error {
switch clientMsg.APObjectType {
case gtsmodel.ActivityStreamsNote:
status, ok := clientMsg.GTSModel.(*gtsmodel.Status)
if !ok {
return errors.New("note was not parseable as *gtsmodel.Status")
}
switch clientMsg.APActivityType {
case gtsmodel.ActivityStreamsCreate:
// CREATE
switch clientMsg.APObjectType {
case gtsmodel.ActivityStreamsNote:
// CREATE NOTE
status, ok := clientMsg.GTSModel.(*gtsmodel.Status)
if !ok {
return errors.New("note was not parseable as *gtsmodel.Status")
}
if err := p.notifyStatus(status); err != nil {
return err
}
if err := p.notifyStatus(status); err != nil {
return err
}
if status.VisibilityAdvanced.Federated {
return p.federateStatus(status)
if status.VisibilityAdvanced.Federated {
return p.federateStatus(status)
}
return nil
case gtsmodel.ActivityStreamsFollow:
// CREATE FOLLOW (request)
follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow)
if !ok {
return errors.New("follow was not parseable as *gtsmodel.Follow")
}
if err := p.notifyFollow(follow); err != nil {
return err
}
return p.federateFollow(follow, clientMsg.OriginAccount, clientMsg.TargetAccount)
}
case gtsmodel.ActivityStreamsUpdate:
// UPDATE
case gtsmodel.ActivityStreamsAccept:
// ACCEPT
switch clientMsg.APObjectType {
case gtsmodel.ActivityStreamsFollow:
// ACCEPT FOLLOW
follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow)
if !ok {
return errors.New("accept was not parseable as *gtsmodel.Follow")
}
return p.federateAcceptFollowRequest(follow, clientMsg.OriginAccount, clientMsg.TargetAccount)
}
case gtsmodel.ActivityStreamsUndo:
// UNDO
switch clientMsg.APObjectType {
case gtsmodel.ActivityStreamsFollow:
// UNDO FOLLOW
follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow)
if !ok {
return errors.New("undo was not parseable as *gtsmodel.Follow")
}
return p.federateUnfollow(follow, clientMsg.OriginAccount, clientMsg.TargetAccount)
}
return nil
}
return fmt.Errorf("message type unprocessable: %+v", clientMsg)
return nil
}
func (p *processor) federateStatus(status *gtsmodel.Status) error {
// // derive the sending account -- it might be attached to the status already
// sendingAcct := &gtsmodel.Account{}
// if status.GTSAccount != nil {
// sendingAcct = status.GTSAccount
// } else {
// // it wasn't attached so get it from the db instead
// if err := p.db.GetByID(status.AccountID, sendingAcct); err != nil {
// return err
// }
// }
asStatus, err := p.tc.StatusToAS(status)
if err != nil {
return fmt.Errorf("federateStatus: error converting status to as format: %s", err)
}
// outboxURI, err := url.Parse(sendingAcct.OutboxURI)
// if err != nil {
// return err
// }
outboxIRI, err := url.Parse(status.GTSAccount.OutboxURI)
if err != nil {
return fmt.Errorf("federateStatus: error parsing outboxURI %s: %s", status.GTSAccount.OutboxURI, err)
}
// // convert the status to AS format Note
// note, err := p.tc.StatusToAS(status)
// if err != nil {
// return err
// }
// _, err = p.federator.FederatingActor().Send(context.Background(), outboxURI, note)
return nil
_, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, asStatus)
return err
}
func (p *processor) federateFollow(follow *gtsmodel.Follow, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error {
// if both accounts are local there's nothing to do here
if originAccount.Domain == "" && targetAccount.Domain == "" {
return nil
}
asFollow, err := p.tc.FollowToAS(follow, originAccount, targetAccount)
if err != nil {
return fmt.Errorf("federateFollow: error converting follow to as format: %s", err)
}
outboxIRI, err := url.Parse(originAccount.OutboxURI)
if err != nil {
return fmt.Errorf("federateFollow: error parsing outboxURI %s: %s", originAccount.OutboxURI, err)
}
_, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, asFollow)
return err
}
func (p *processor) federateUnfollow(follow *gtsmodel.Follow, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error {
// if both accounts are local there's nothing to do here
if originAccount.Domain == "" && targetAccount.Domain == "" {
return nil
}
// recreate the follow
asFollow, err := p.tc.FollowToAS(follow, originAccount, targetAccount)
if err != nil {
return fmt.Errorf("federateUnfollow: error converting follow to as format: %s", err)
}
targetAccountURI, err := url.Parse(targetAccount.URI)
if err != nil {
return fmt.Errorf("error parsing uri %s: %s", targetAccount.URI, err)
}
// create an Undo and set the appropriate actor on it
undo := streams.NewActivityStreamsUndo()
undo.SetActivityStreamsActor(asFollow.GetActivityStreamsActor())
// Set the recreated follow as the 'object' property.
undoObject := streams.NewActivityStreamsObjectProperty()
undoObject.AppendActivityStreamsFollow(asFollow)
undo.SetActivityStreamsObject(undoObject)
// Set the To of the undo as the target of the recreated follow
undoTo := streams.NewActivityStreamsToProperty()
undoTo.AppendIRI(targetAccountURI)
undo.SetActivityStreamsTo(undoTo)
outboxIRI, err := url.Parse(originAccount.OutboxURI)
if err != nil {
return fmt.Errorf("federateUnfollow: error parsing outboxURI %s: %s", originAccount.OutboxURI, err)
}
// send off the Undo
_, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, undo)
return err
}
func (p *processor) federateAcceptFollowRequest(follow *gtsmodel.Follow, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error {
// if both accounts are local there's nothing to do here
if originAccount.Domain == "" && targetAccount.Domain == "" {
return nil
}
// recreate the AS follow
asFollow, err := p.tc.FollowToAS(follow, originAccount, targetAccount)
if err != nil {
return fmt.Errorf("federateUnfollow: error converting follow to as format: %s", err)
}
acceptingAccountURI, err := url.Parse(targetAccount.URI)
if err != nil {
return fmt.Errorf("error parsing uri %s: %s", targetAccount.URI, err)
}
requestingAccountURI, err := url.Parse(originAccount.URI)
if err != nil {
return fmt.Errorf("error parsing uri %s: %s", targetAccount.URI, err)
}
// create an Accept
accept := streams.NewActivityStreamsAccept()
// set the accepting actor on it
acceptActorProp := streams.NewActivityStreamsActorProperty()
acceptActorProp.AppendIRI(acceptingAccountURI)
accept.SetActivityStreamsActor(acceptActorProp)
// Set the recreated follow as the 'object' property.
acceptObject := streams.NewActivityStreamsObjectProperty()
acceptObject.AppendActivityStreamsFollow(asFollow)
accept.SetActivityStreamsObject(acceptObject)
// Set the To of the accept as the originator of the follow
acceptTo := streams.NewActivityStreamsToProperty()
acceptTo.AppendIRI(requestingAccountURI)
accept.SetActivityStreamsTo(acceptTo)
outboxIRI, err := url.Parse(targetAccount.OutboxURI)
if err != nil {
return fmt.Errorf("federateAcceptFollowRequest: error parsing outboxURI %s: %s", originAccount.OutboxURI, err)
}
// send off the accept using the accepter's outbox
_, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, accept)
return err
}

View File

@ -23,3 +23,7 @@ import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
func (p *processor) notifyStatus(status *gtsmodel.Status) error {
return nil
}
func (p *processor) notifyFollow(follow *gtsmodel.Follow) error {
return nil
}

View File

@ -38,24 +38,60 @@ func (p *processor) processFromFederator(federatorMsg gtsmodel.FromFederator) er
l.Debug("entering function PROCESS FROM FEDERATOR")
switch federatorMsg.APObjectType {
case gtsmodel.ActivityStreamsNote:
switch federatorMsg.APActivityType {
case gtsmodel.ActivityStreamsCreate:
// CREATE
switch federatorMsg.APObjectType {
case gtsmodel.ActivityStreamsNote:
// CREATE A STATUS
incomingStatus, ok := federatorMsg.GTSModel.(*gtsmodel.Status)
if !ok {
return errors.New("note was not parseable as *gtsmodel.Status")
}
incomingStatus, ok := federatorMsg.GTSModel.(*gtsmodel.Status)
if !ok {
return errors.New("note was not parseable as *gtsmodel.Status")
}
l.Debug("will now derefence incoming status")
if err := p.dereferenceStatusFields(incomingStatus); err != nil {
return fmt.Errorf("error dereferencing status from federator: %s", err)
}
if err := p.db.UpdateByID(incomingStatus.ID, incomingStatus); err != nil {
return fmt.Errorf("error updating dereferenced status in the db: %s", err)
}
l.Debug("will now derefence incoming status")
if err := p.dereferenceStatusFields(incomingStatus); err != nil {
return fmt.Errorf("error dereferencing status from federator: %s", err)
}
if err := p.db.UpdateByID(incomingStatus.ID, incomingStatus); err != nil {
return fmt.Errorf("error updating dereferenced status in the db: %s", err)
}
if err := p.notifyStatus(incomingStatus); err != nil {
return err
}
case gtsmodel.ActivityStreamsProfile:
// CREATE AN ACCOUNT
incomingAccount, ok := federatorMsg.GTSModel.(*gtsmodel.Account)
if !ok {
return errors.New("profile was not parseable as *gtsmodel.Account")
}
if err := p.notifyStatus(incomingStatus); err != nil {
return err
l.Debug("will now derefence incoming account")
if err := p.dereferenceAccountFields(incomingAccount, ""); err != nil {
return fmt.Errorf("error dereferencing account from federator: %s", err)
}
if err := p.db.UpdateByID(incomingAccount.ID, incomingAccount); err != nil {
return fmt.Errorf("error updating dereferenced account in the db: %s", err)
}
}
case gtsmodel.ActivityStreamsUpdate:
// UPDATE
switch federatorMsg.APObjectType {
case gtsmodel.ActivityStreamsProfile:
// UPDATE AN ACCOUNT
incomingAccount, ok := federatorMsg.GTSModel.(*gtsmodel.Account)
if !ok {
return errors.New("profile was not parseable as *gtsmodel.Account")
}
l.Debug("will now derefence incoming account")
if err := p.dereferenceAccountFields(incomingAccount, ""); err != nil {
return fmt.Errorf("error dereferencing account from federator: %s", err)
}
if err := p.db.UpdateByID(incomingAccount.ID, incomingAccount); err != nil {
return fmt.Errorf("error updating dereferenced account in the db: %s", err)
}
}
}
@ -121,7 +157,7 @@ func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
// it might have been processed elsewhere so check first if it's already in the database or not
maybeAttachment := &gtsmodel.MediaAttachment{}
err := p.db.GetWhere("remote_url", a.RemoteURL, maybeAttachment)
err := p.db.GetWhere([]db.Where{{Key: "remote_url", Value: a.RemoteURL}}, maybeAttachment)
if err == nil {
// we already have it in the db, dereferenced, no need to do it again
l.Debugf("attachment already exists with id %s", maybeAttachment.ID)
@ -170,7 +206,7 @@ func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
m.OriginAccountURI = status.GTSAccount.URI
targetAccount := &gtsmodel.Account{}
if err := p.db.GetWhere("uri", uri.String(), targetAccount); err != nil {
if err := p.db.GetWhere([]db.Where{{Key: "uri", Value: uri.String()}}, targetAccount); err != nil {
// proper error
if _, ok := err.(db.ErrNoEntries); !ok {
return fmt.Errorf("db error checking for account with uri %s", uri.String())
@ -206,3 +242,27 @@ func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
return nil
}
func (p *processor) dereferenceAccountFields(account *gtsmodel.Account, requestingUsername string) error {
l := p.log.WithFields(logrus.Fields{
"func": "dereferenceAccountFields",
"requestingUsername": requestingUsername,
})
t, err := p.federator.GetTransportForUser(requestingUsername)
if err != nil {
return fmt.Errorf("error getting transport for user: %s", err)
}
// fetch the header and avatar
if err := p.fetchHeaderAndAviForAccount(account, t); err != nil {
// if this doesn't work, just skip it -- we can do it later
l.Debugf("error fetching header/avi for account: %s", err)
}
if err := p.db.UpdateByID(account.ID, account); err != nil {
return fmt.Errorf("error updating account in database: %s", err)
}
return nil
}

View File

@ -48,11 +48,28 @@ func (p *processor) FollowRequestsGet(auth *oauth.Auth) ([]apimodel.Account, Err
return accts, nil
}
func (p *processor) FollowRequestAccept(auth *oauth.Auth, accountID string) ErrorWithCode {
if err := p.db.AcceptFollowRequest(accountID, auth.Account.ID); err != nil {
return NewErrorNotFound(err)
func (p *processor) FollowRequestAccept(auth *oauth.Auth, accountID string) (*apimodel.Relationship, ErrorWithCode) {
follow, err := p.db.AcceptFollowRequest(accountID, auth.Account.ID)
if err != nil {
return nil, NewErrorNotFound(err)
}
return nil
p.fromClientAPI <- gtsmodel.FromClientAPI{
APActivityType: gtsmodel.ActivityStreamsAccept,
GTSModel: follow,
}
gtsR, err := p.db.GetRelationship(auth.Account.ID, accountID)
if err != nil {
return nil, NewErrorInternalError(err)
}
r, err := p.tc.RelationshipToMasto(gtsR)
if err != nil {
return nil, NewErrorInternalError(err)
}
return r, nil
}
func (p *processor) FollowRequestDeny(auth *oauth.Auth) ErrorWithCode {

View File

@ -22,12 +22,13 @@ import (
"fmt"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) InstanceGet(domain string) (*apimodel.Instance, ErrorWithCode) {
i := &gtsmodel.Instance{}
if err := p.db.GetWhere("domain", domain, i); err != nil {
if err := p.db.GetWhere([]db.Where{{Key: "domain", Value: domain}}, i); err != nil {
return nil, NewErrorInternalError(fmt.Errorf("db error fetching instance %s: %s", p.config.Host, err))
}

View File

@ -71,8 +71,16 @@ type Processor interface {
// AccountStatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for
// the account given in authed.
AccountStatusesGet(authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, maxID string, pinned bool, mediaOnly bool) ([]apimodel.Status, ErrorWithCode)
// AccountFollowersGet
// AccountFollowersGet fetches a list of the target account's followers.
AccountFollowersGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, ErrorWithCode)
// AccountFollowingGet fetches a list of the accounts that target account is following.
AccountFollowingGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, ErrorWithCode)
// AccountRelationshipGet returns a relationship model describing the relationship of the targetAccount to the Authed account.
AccountRelationshipGet(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, ErrorWithCode)
// AccountFollowCreate handles a follow request to an account, either remote or local.
AccountFollowCreate(authed *oauth.Auth, form *apimodel.AccountFollowRequest) (*apimodel.Relationship, ErrorWithCode)
// AccountFollowRemove handles the removal of a follow/follow request to an account, either remote or local.
AccountFollowRemove(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, ErrorWithCode)
// AdminEmojiCreate handles the creation of a new instance emoji by an admin, using the given form.
AdminEmojiCreate(authed *oauth.Auth, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, error)
@ -86,7 +94,7 @@ type Processor interface {
// FollowRequestsGet handles the getting of the authed account's incoming follow requests
FollowRequestsGet(auth *oauth.Auth) ([]apimodel.Account, ErrorWithCode)
// FollowRequestAccept handles the acceptance of a follow request from the given account ID
FollowRequestAccept(auth *oauth.Auth, accountID string) ErrorWithCode
FollowRequestAccept(auth *oauth.Auth, accountID string) (*apimodel.Relationship, ErrorWithCode)
// InstanceGet retrieves instance information for serving at api/v1/instance
InstanceGet(domain string) (*apimodel.Instance, ErrorWithCode)
@ -125,6 +133,14 @@ type Processor interface {
// before returning a JSON serializable interface to the caller.
GetFediUser(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode)
// GetFediFollowers 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.
GetFediFollowers(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode)
// GetFediStatus handles the getting of a fedi/activitypub representation of a particular status, performing appropriate
// authentication before returning a JSON serializable interface to the caller.
GetFediStatus(requestedUsername string, requestedStatusID string, request *http.Request) (interface{}, ErrorWithCode)
// GetWebfingerAccount handles the GET for a webfinger resource. Most commonly, it will be used for returning account lookups.
GetWebfingerAccount(requestedUsername string, request *http.Request) (*apimodel.WebfingerAccountResponse, ErrorWithCode)
@ -200,15 +216,11 @@ func (p *processor) Start() error {
DistLoop:
for {
select {
// case clientMsg := <-p.toClientAPI:
// p.log.Infof("received message TO client API: %+v", clientMsg)
case clientMsg := <-p.fromClientAPI:
p.log.Infof("received message FROM client API: %+v", clientMsg)
if err := p.processFromClientAPI(clientMsg); err != nil {
p.log.Error(err)
}
// case federatorMsg := <-p.toFederator:
// p.log.Infof("received message TO federator: %+v", federatorMsg)
case federatorMsg := <-p.fromFederator:
p.log.Infof("received message FROM federator: %+v", federatorMsg)
if err := p.processFromFederator(federatorMsg); err != nil {

View File

@ -29,6 +29,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/transport"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
@ -205,7 +206,7 @@ func (p *processor) processMentions(form *apimodel.AdvancedStatusCreateForm, acc
if err := p.db.Put(menchie); err != nil {
return fmt.Errorf("error putting mentions in db: %s", err)
}
menchies = append(menchies, menchie.TargetAccountID)
menchies = append(menchies, menchie.ID)
}
// add full populated gts menchies to the status for passing them around conveniently
status.GTSMentions = gtsMenchies
@ -280,7 +281,7 @@ func (p *processor) updateAccountAvatar(avatar *multipart.FileHeader, accountID
}
// do the setting
avatarInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Avatar)
avatarInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Avatar, "")
if err != nil {
return nil, fmt.Errorf("error processing avatar: %s", err)
}
@ -313,10 +314,42 @@ func (p *processor) updateAccountHeader(header *multipart.FileHeader, accountID
}
// do the setting
headerInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Header)
headerInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Header, "")
if err != nil {
return nil, fmt.Errorf("error processing header: %s", err)
}
return headerInfo, f.Close()
}
// fetchHeaderAndAviForAccount fetches the header and avatar for a remote account, using a transport
// on behalf of requestingUsername.
//
// targetAccount's AvatarMediaAttachmentID and HeaderMediaAttachmentID will be updated as necessary.
//
// SIDE EFFECTS: remote header and avatar will be stored in local storage, and the database will be updated
// to reflect the creation of these new attachments.
func (p *processor) fetchHeaderAndAviForAccount(targetAccount *gtsmodel.Account, t transport.Transport) error {
if targetAccount.AvatarRemoteURL != "" && targetAccount.AvatarMediaAttachmentID == "" {
a, err := p.mediaHandler.ProcessRemoteHeaderOrAvatar(t, &gtsmodel.MediaAttachment{
RemoteURL: targetAccount.AvatarRemoteURL,
Avatar: true,
}, targetAccount.ID)
if err != nil {
return fmt.Errorf("error processing avatar for user: %s", err)
}
targetAccount.AvatarMediaAttachmentID = a.ID
}
if targetAccount.HeaderRemoteURL != "" && targetAccount.HeaderMediaAttachmentID == "" {
a, err := p.mediaHandler.ProcessRemoteHeaderOrAvatar(t, &gtsmodel.MediaAttachment{
RemoteURL: targetAccount.HeaderRemoteURL,
Header: true,
}, targetAccount.ID)
if err != nil {
return fmt.Errorf("error processing header for user: %s", err)
}
targetAccount.HeaderMediaAttachmentID = a.ID
}
return nil
}