status deletes, profile updates
This commit is contained in:
parent
aeb665df55
commit
54c34feee8
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
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 user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (m *Module) FollowingGETHandler(c *gin.Context) {
|
||||
l := m.log.WithFields(logrus.Fields{
|
||||
"func": "FollowingGETHandler",
|
||||
"url": c.Request.RequestURI,
|
||||
})
|
||||
|
||||
requestedUsername := c.Param(UsernameKey)
|
||||
if requestedUsername == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no username specified in request"})
|
||||
return
|
||||
}
|
||||
|
||||
// make sure this actually an AP request
|
||||
format := c.NegotiateFormat(ActivityPubAcceptHeaders...)
|
||||
if format == "" {
|
||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": "could not negotiate format with given Accept header(s)"})
|
||||
return
|
||||
}
|
||||
l.Tracef("negotiated format: %s", format)
|
||||
|
||||
// make a copy of the context to pass along so we don't break anything
|
||||
cp := c.Copy()
|
||||
user, err := m.processor.GetFediFollowing(requestedUsername, cp.Request) // handles auth as well
|
||||
if err != nil {
|
||||
l.Info(err.Error())
|
||||
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, user)
|
||||
}
|
|
@ -44,6 +44,8 @@ const (
|
|||
UsersInboxPath = UsersBasePathWithUsername + "/" + util.InboxPath
|
||||
// UsersFollowersPath is for serving GET request's to a user's followers list, with the given username key.
|
||||
UsersFollowersPath = UsersBasePathWithUsername + "/" + util.FollowersPath
|
||||
// UsersFollowingPath is for serving GET request's to a user's following list, with the given username key.
|
||||
UsersFollowingPath = UsersBasePathWithUsername + "/" + util.FollowingPath
|
||||
// UsersStatusPath is for serving GET requests to a particular status by a user, with the given username key and status ID
|
||||
UsersStatusPath = UsersBasePathWithUsername + "/" + util.StatusesPath + "/:" + StatusIDKey
|
||||
)
|
||||
|
@ -76,6 +78,7 @@ func (m *Module) Route(s router.Router) error {
|
|||
s.AttachHandler(http.MethodGet, UsersBasePathWithUsername, m.UsersGETHandler)
|
||||
s.AttachHandler(http.MethodPost, UsersInboxPath, m.InboxPOSTHandler)
|
||||
s.AttachHandler(http.MethodGet, UsersFollowersPath, m.FollowersGETHandler)
|
||||
s.AttachHandler(http.MethodGet, UsersFollowingPath, m.FollowingGETHandler)
|
||||
s.AttachHandler(http.MethodGet, UsersStatusPath, m.StatusGETHandler)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ func (suite *UserGetTestSuite) TestGetUser() {
|
|||
|
||||
// convert person to account
|
||||
// since this account is already known, we should get a pretty full model of it from the conversion
|
||||
a, err := suite.tc.ASRepresentationToAccount(person)
|
||||
a, err := suite.tc.ASRepresentationToAccount(person, false)
|
||||
assert.NoError(suite.T(), err)
|
||||
assert.EqualValues(suite.T(), targetAccount.Username, a.Username)
|
||||
}
|
||||
|
|
|
@ -500,6 +500,13 @@ func (ps *postgresService) GetStatusesByTimeDescending(accountID string, statuse
|
|||
return q.Where("? IS NOT NULL", pg.Ident("attachments")).Where("attachments != '{}'"), nil
|
||||
})
|
||||
}
|
||||
if maxID != "" {
|
||||
s := >smodel.Status{}
|
||||
if err := ps.conn.Model(s).Where("id = ?", maxID).Select(); err != nil {
|
||||
return err
|
||||
}
|
||||
q = q.Where("status.created_at < ?", s.CreatedAt)
|
||||
}
|
||||
if err := q.Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return db.ErrNoEntries{}
|
||||
|
@ -1113,6 +1120,14 @@ func (ps *postgresService) GetHomeTimelineForAccount(accountID string, maxID str
|
|||
Limit(limit).
|
||||
Order("status.created_at DESC")
|
||||
|
||||
if maxID != "" {
|
||||
s := >smodel.Status{}
|
||||
if err := ps.conn.Model(s).Where("id = ?", maxID).Select(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q = q.Where("status.created_at < ?", s.CreatedAt)
|
||||
}
|
||||
|
||||
err := q.Select()
|
||||
if err != nil {
|
||||
if err != pg.ErrNoRows {
|
||||
|
|
|
@ -241,6 +241,40 @@ func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) {
|
|||
return true, nil
|
||||
}
|
||||
|
||||
if util.IsFollowersPath(id) {
|
||||
username, err := util.ParseFollowersPath(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err)
|
||||
}
|
||||
if err := f.db.GetLocalAccountByUsername(username, >smodel.Account{}); err != nil {
|
||||
if _, ok := err.(db.ErrNoEntries); ok {
|
||||
// there are no entries for this username
|
||||
return false, nil
|
||||
}
|
||||
// an actual error happened
|
||||
return false, fmt.Errorf("database error fetching account with username %s: %s", username, err)
|
||||
}
|
||||
l.Debug("we DO own this")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if util.IsFollowingPath(id) {
|
||||
username, err := util.ParseFollowingPath(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err)
|
||||
}
|
||||
if err := f.db.GetLocalAccountByUsername(username, >smodel.Account{}); err != nil {
|
||||
if _, ok := err.(db.ErrNoEntries); ok {
|
||||
// there are no entries for this username
|
||||
return false, nil
|
||||
}
|
||||
// an actual error happened
|
||||
return false, fmt.Errorf("database error fetching account with username %s: %s", username, err)
|
||||
}
|
||||
l.Debug("we DO own this")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("could not match activityID: %s", id.String())
|
||||
}
|
||||
|
||||
|
@ -502,6 +536,15 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
|
|||
l.Error("receiving account was set on context but couldn't be parsed")
|
||||
}
|
||||
|
||||
requestingAcctI := ctx.Value(util.APRequestingAccount)
|
||||
if receivingAcctI == nil {
|
||||
l.Error("requesting account wasn't set on context")
|
||||
}
|
||||
requestingAcct, ok := requestingAcctI.(*gtsmodel.Account)
|
||||
if !ok {
|
||||
l.Error("requesting account was set on context but couldn't be parsed")
|
||||
}
|
||||
|
||||
fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey)
|
||||
if fromFederatorChanI == nil {
|
||||
l.Error("from federator channel wasn't set on context")
|
||||
|
@ -511,51 +554,76 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
|
|||
l.Error("from federator channel was set on context but couldn't be parsed")
|
||||
}
|
||||
|
||||
switch asType.GetTypeName() {
|
||||
case gtsmodel.ActivityStreamsUpdate:
|
||||
update, ok := asType.(vocab.ActivityStreamsCreate)
|
||||
if !ok {
|
||||
return errors.New("could not convert type to create")
|
||||
}
|
||||
object := update.GetActivityStreamsObject()
|
||||
for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() {
|
||||
switch objectIter.GetType().GetTypeName() {
|
||||
case string(gtsmodel.ActivityStreamsPerson):
|
||||
person := objectIter.GetActivityStreamsPerson()
|
||||
updatedAcct, err := f.typeConverter.ASRepresentationToAccount(person)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting person to account: %s", err)
|
||||
}
|
||||
if err := f.db.Put(updatedAcct); err != nil {
|
||||
return fmt.Errorf("database error inserting updated account: %s", err)
|
||||
}
|
||||
typeName := asType.GetTypeName()
|
||||
if typeName == gtsmodel.ActivityStreamsApplication ||
|
||||
typeName == gtsmodel.ActivityStreamsGroup ||
|
||||
typeName == gtsmodel.ActivityStreamsOrganization ||
|
||||
typeName == gtsmodel.ActivityStreamsPerson ||
|
||||
typeName == gtsmodel.ActivityStreamsService {
|
||||
// it's an UPDATE to some kind of account
|
||||
var accountable typeutils.Accountable
|
||||
|
||||
fromFederatorChan <- gtsmodel.FromFederator{
|
||||
APObjectType: gtsmodel.ActivityStreamsProfile,
|
||||
APActivityType: gtsmodel.ActivityStreamsUpdate,
|
||||
GTSModel: updatedAcct,
|
||||
ReceivingAccount: receivingAcct,
|
||||
}
|
||||
|
||||
case string(gtsmodel.ActivityStreamsApplication):
|
||||
application := objectIter.GetActivityStreamsApplication()
|
||||
updatedAcct, err := f.typeConverter.ASRepresentationToAccount(application)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting person to account: %s", err)
|
||||
}
|
||||
if err := f.db.Put(updatedAcct); err != nil {
|
||||
return fmt.Errorf("database error inserting updated account: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- gtsmodel.FromFederator{
|
||||
APObjectType: gtsmodel.ActivityStreamsProfile,
|
||||
APActivityType: gtsmodel.ActivityStreamsUpdate,
|
||||
GTSModel: updatedAcct,
|
||||
ReceivingAccount: receivingAcct,
|
||||
}
|
||||
switch asType.GetTypeName() {
|
||||
case gtsmodel.ActivityStreamsApplication:
|
||||
l.Debug("got update for APPLICATION")
|
||||
i, ok := asType.(vocab.ActivityStreamsApplication)
|
||||
if !ok {
|
||||
return errors.New("could not convert type to application")
|
||||
}
|
||||
accountable = i
|
||||
case gtsmodel.ActivityStreamsGroup:
|
||||
l.Debug("got update for GROUP")
|
||||
i, ok := asType.(vocab.ActivityStreamsGroup)
|
||||
if !ok {
|
||||
return errors.New("could not convert type to group")
|
||||
}
|
||||
accountable = i
|
||||
case gtsmodel.ActivityStreamsOrganization:
|
||||
l.Debug("got update for ORGANIZATION")
|
||||
i, ok := asType.(vocab.ActivityStreamsOrganization)
|
||||
if !ok {
|
||||
return errors.New("could not convert type to organization")
|
||||
}
|
||||
accountable = i
|
||||
case gtsmodel.ActivityStreamsPerson:
|
||||
l.Debug("got update for PERSON")
|
||||
i, ok := asType.(vocab.ActivityStreamsPerson)
|
||||
if !ok {
|
||||
return errors.New("could not convert type to person")
|
||||
}
|
||||
accountable = i
|
||||
case gtsmodel.ActivityStreamsService:
|
||||
l.Debug("got update for SERVICE")
|
||||
i, ok := asType.(vocab.ActivityStreamsService)
|
||||
if !ok {
|
||||
return errors.New("could not convert type to service")
|
||||
}
|
||||
accountable = i
|
||||
}
|
||||
|
||||
updatedAcct, err := f.typeConverter.ASRepresentationToAccount(accountable, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting to account: %s", err)
|
||||
}
|
||||
|
||||
if requestingAcct.URI != updatedAcct.URI {
|
||||
return fmt.Errorf("update for account %s was requested by account %s, this is not valid", updatedAcct.URI, requestingAcct.URI)
|
||||
}
|
||||
|
||||
updatedAcct.ID = requestingAcct.ID // set this here so the db will update properly instead of trying to PUT this and getting constraint issues
|
||||
if err := f.db.UpdateByID(requestingAcct.ID, updatedAcct); err != nil {
|
||||
return fmt.Errorf("database error inserting updated account: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- gtsmodel.FromFederator{
|
||||
APObjectType: gtsmodel.ActivityStreamsProfile,
|
||||
APActivityType: gtsmodel.ActivityStreamsUpdate,
|
||||
GTSModel: updatedAcct,
|
||||
ReceivingAccount: receivingAcct,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -565,7 +633,7 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
|
|||
// Protocol instead call Update to create a Tombstone.
|
||||
//
|
||||
// The library makes this call only after acquiring a lock first.
|
||||
func (f *federatingDB) Delete(c context.Context, id *url.URL) error {
|
||||
func (f *federatingDB) Delete(ctx context.Context, id *url.URL) error {
|
||||
l := f.log.WithFields(
|
||||
logrus.Fields{
|
||||
"func": "Delete",
|
||||
|
@ -573,6 +641,63 @@ func (f *federatingDB) Delete(c context.Context, id *url.URL) error {
|
|||
},
|
||||
)
|
||||
l.Debugf("received DELETE id %s", id.String())
|
||||
|
||||
inboxAcctI := ctx.Value(util.APAccount)
|
||||
if inboxAcctI == nil {
|
||||
l.Error("inbox account wasn't set on context")
|
||||
return nil
|
||||
}
|
||||
inboxAcct, ok := inboxAcctI.(*gtsmodel.Account)
|
||||
if !ok {
|
||||
l.Error("inbox account was set on context but couldn't be parsed")
|
||||
return nil
|
||||
}
|
||||
|
||||
fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey)
|
||||
if fromFederatorChanI == nil {
|
||||
l.Error("from federator channel wasn't set on context")
|
||||
return nil
|
||||
}
|
||||
fromFederatorChan, ok := fromFederatorChanI.(chan gtsmodel.FromFederator)
|
||||
if !ok {
|
||||
l.Error("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...
|
||||
where := []db.Where{{Key: "uri", Value: id.String()}}
|
||||
|
||||
s := >smodel.Status{}
|
||||
if err := f.db.GetWhere(where, s); err == nil {
|
||||
// it's a status
|
||||
l.Debug("uri is for status with id: %s", s.ID)
|
||||
if err := f.db.DeleteByID(s.ID, >smodel.Status{}); err != nil {
|
||||
return fmt.Errorf("Delete: err deleting status: %s", err)
|
||||
}
|
||||
fromFederatorChan <- gtsmodel.FromFederator{
|
||||
APObjectType: gtsmodel.ActivityStreamsNote,
|
||||
APActivityType: gtsmodel.ActivityStreamsDelete,
|
||||
GTSModel: s,
|
||||
ReceivingAccount: inboxAcct,
|
||||
}
|
||||
}
|
||||
|
||||
a := >smodel.Account{}
|
||||
if err := f.db.GetWhere(where, a); err == nil {
|
||||
// it's an account
|
||||
l.Debug("uri is for an account with id: %s", s.ID)
|
||||
if err := f.db.DeleteByID(a.ID, >smodel.Account{}); err != nil {
|
||||
return fmt.Errorf("Delete: err deleting account: %s", err)
|
||||
}
|
||||
fromFederatorChan <- gtsmodel.FromFederator{
|
||||
APObjectType: gtsmodel.ActivityStreamsProfile,
|
||||
APActivityType: gtsmodel.ActivityStreamsDelete,
|
||||
GTSModel: a,
|
||||
ReceivingAccount: inboxAcct,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -136,7 +136,7 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr
|
|||
return ctx, false, fmt.Errorf("error dereferencing account with public key id %s: %s", publicKeyOwnerURI.String(), err)
|
||||
}
|
||||
|
||||
a, err := f.typeConverter.ASRepresentationToAccount(person)
|
||||
a, err := f.typeConverter.ASRepresentationToAccount(person, false)
|
||||
if err != nil {
|
||||
return ctx, false, fmt.Errorf("error converting person with public key id %s to account: %s", publicKeyOwnerURI.String(), err)
|
||||
}
|
||||
|
|
|
@ -132,9 +132,11 @@ func (f *federator) AuthenticateFederatedRequest(username string, r *http.Reques
|
|||
|
||||
var publicKey interface{}
|
||||
var pkOwnerURI *url.URL
|
||||
requestingRemoteAccount := >smodel.Account{}
|
||||
requestingLocalAccount := >smodel.Account{}
|
||||
if strings.EqualFold(requestingPublicKeyID.Host, f.config.Host) {
|
||||
// LOCAL ACCOUNT REQUEST
|
||||
// the request is coming from INSIDE THE HOUSE so skip the remote dereferencing
|
||||
requestingLocalAccount := >smodel.Account{}
|
||||
if err := f.db.GetWhere([]db.Where{{Key: "public_key_uri", Value: requestingPublicKeyID.String()}}, requestingLocalAccount); err != nil {
|
||||
return nil, fmt.Errorf("couldn't get local account with public key uri %s from the database: %s", requestingPublicKeyID.String(), err)
|
||||
}
|
||||
|
@ -143,8 +145,18 @@ func (f *federator) AuthenticateFederatedRequest(username string, r *http.Reques
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing url %s: %s", requestingLocalAccount.URI, err)
|
||||
}
|
||||
} else if err := f.db.GetWhere([]db.Where{{Key: "public_key_uri", Value: requestingPublicKeyID.String()}}, requestingRemoteAccount); err == nil {
|
||||
// REMOTE ACCOUNT REQUEST WITH KEY CACHED LOCALLY
|
||||
// this is a remote account and we already have the public key for it so use that
|
||||
publicKey = requestingRemoteAccount.PublicKey
|
||||
pkOwnerURI, err = url.Parse(requestingRemoteAccount.URI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing url %s: %s", requestingRemoteAccount.URI, err)
|
||||
}
|
||||
} else {
|
||||
// the request is remote, so we need to authenticate the request properly by dereferencing the remote key
|
||||
// REMOTE ACCOUNT REQUEST WITHOUT KEY CACHED LOCALLY
|
||||
// the request is remote and we don't have the public key yet,
|
||||
// so we need to authenticate the request properly by dereferencing the remote key
|
||||
transport, err := f.GetTransportForUser(username)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("transport err: %s", err)
|
||||
|
|
|
@ -83,7 +83,7 @@ func (p *processor) AccountGet(authed *oauth.Auth, targetAccountID string) (*api
|
|||
if authed.Account != nil {
|
||||
requestingUsername = authed.Account.Username
|
||||
}
|
||||
if err := p.dereferenceAccountFields(targetAccount, requestingUsername); err != nil {
|
||||
if err := p.dereferenceAccountFields(targetAccount, requestingUsername, false); err != nil {
|
||||
p.log.WithField("func", "AccountGet").Debugf("dereferencing account: %s", err)
|
||||
}
|
||||
|
||||
|
@ -295,7 +295,7 @@ func (p *processor) AccountFollowersGet(authed *oauth.Auth, targetAccountID stri
|
|||
}
|
||||
|
||||
// derefence account fields in case we haven't done it already
|
||||
if err := p.dereferenceAccountFields(a, authed.Account.Username); err != nil {
|
||||
if err := p.dereferenceAccountFields(a, authed.Account.Username, false); 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)
|
||||
}
|
||||
|
@ -346,7 +346,7 @@ func (p *processor) AccountFollowingGet(authed *oauth.Auth, targetAccountID stri
|
|||
}
|
||||
|
||||
// derefence account fields in case we haven't done it already
|
||||
if err := p.dereferenceAccountFields(a, authed.Account.Username); err != nil {
|
||||
if err := p.dereferenceAccountFields(a, authed.Account.Username, false); 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)
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ func (p *processor) authenticateAndDereferenceFediRequest(username string, r *ht
|
|||
}
|
||||
|
||||
// convert it to our internal account representation
|
||||
requestingAccount, err = p.tc.ASRepresentationToAccount(requestingPerson)
|
||||
requestingAccount, err = p.tc.ASRepresentationToAccount(requestingPerson, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't convert dereferenced uri %s to gtsmodel account: %s", requestingAccountURI.String(), err)
|
||||
}
|
||||
|
@ -163,6 +163,46 @@ func (p *processor) GetFediFollowers(requestedUsername string, request *http.Req
|
|||
return data, nil
|
||||
}
|
||||
|
||||
func (p *processor) GetFediFollowing(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode) {
|
||||
// get the account the request is referring to
|
||||
requestedAccount := >smodel.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))
|
||||
}
|
||||
|
||||
requestedFollowing, err := p.federator.FederatingDB().Following(context.Background(), requestedAccountURI)
|
||||
if err != nil {
|
||||
return nil, NewErrorInternalError(fmt.Errorf("error fetching following for uri %s: %s", requestedAccountURI.String(), err))
|
||||
}
|
||||
|
||||
data, err := streams.Serialize(requestedFollowing)
|
||||
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 := >smodel.Account{}
|
||||
|
|
|
@ -68,7 +68,7 @@ func (p *processor) processFromFederator(federatorMsg gtsmodel.FromFederator) er
|
|||
}
|
||||
|
||||
l.Debug("will now derefence incoming account")
|
||||
if err := p.dereferenceAccountFields(incomingAccount, ""); err != nil {
|
||||
if err := p.dereferenceAccountFields(incomingAccount, "", false); err != nil {
|
||||
return fmt.Errorf("error dereferencing account from federator: %s", err)
|
||||
}
|
||||
if err := p.db.UpdateByID(incomingAccount.ID, incomingAccount); err != nil {
|
||||
|
@ -86,13 +86,26 @@ func (p *processor) processFromFederator(federatorMsg gtsmodel.FromFederator) er
|
|||
}
|
||||
|
||||
l.Debug("will now derefence incoming account")
|
||||
if err := p.dereferenceAccountFields(incomingAccount, ""); err != nil {
|
||||
if err := p.dereferenceAccountFields(incomingAccount, federatorMsg.ReceivingAccount.Username, true); 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.ActivityStreamsDelete:
|
||||
// DELETE
|
||||
switch federatorMsg.APObjectType {
|
||||
case gtsmodel.ActivityStreamsNote:
|
||||
// DELETE A STATUS
|
||||
// TODO: handle side effects of status deletion here:
|
||||
// 1. delete all media associated with status
|
||||
// 2. delete boosts of status
|
||||
// 3. etc etc etc
|
||||
case gtsmodel.ActivityStreamsProfile:
|
||||
// DELETE A PROFILE/ACCOUNT
|
||||
// TODO: handle side effects of account deletion here: delete all objects, statuses, media etc associated with account
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -220,7 +233,7 @@ func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
|
|||
continue
|
||||
}
|
||||
|
||||
targetAccount, err = p.tc.ASRepresentationToAccount(accountable)
|
||||
targetAccount, err = p.tc.ASRepresentationToAccount(accountable, false)
|
||||
if err != nil {
|
||||
l.Debugf("error converting remote account with uri %s into gts model: %s", uri.String(), err)
|
||||
continue
|
||||
|
@ -243,7 +256,7 @@ func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *processor) dereferenceAccountFields(account *gtsmodel.Account, requestingUsername string) error {
|
||||
func (p *processor) dereferenceAccountFields(account *gtsmodel.Account, requestingUsername string, refresh bool) error {
|
||||
l := p.log.WithFields(logrus.Fields{
|
||||
"func": "dereferenceAccountFields",
|
||||
"requestingUsername": requestingUsername,
|
||||
|
@ -255,7 +268,7 @@ func (p *processor) dereferenceAccountFields(account *gtsmodel.Account, requesti
|
|||
}
|
||||
|
||||
// fetch the header and avatar
|
||||
if err := p.fetchHeaderAndAviForAccount(account, t); err != nil {
|
||||
if err := p.fetchHeaderAndAviForAccount(account, t, refresh); 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)
|
||||
}
|
||||
|
|
|
@ -140,6 +140,10 @@ type Processor interface {
|
|||
// authentication before returning a JSON serializable interface to the caller.
|
||||
GetFediFollowers(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode)
|
||||
|
||||
// GetFediFollowing handles the getting of a fedi/activitypub representation of a user/account's following, performing appropriate
|
||||
// authentication before returning a JSON serializable interface to the caller.
|
||||
GetFediFollowing(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)
|
||||
|
|
|
@ -130,8 +130,10 @@ func (p *processor) processReplyToID(form *apimodel.AdvancedStatusCreateForm, th
|
|||
return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err)
|
||||
}
|
||||
|
||||
if !repliedStatus.VisibilityAdvanced.Replyable {
|
||||
return fmt.Errorf("status with id %s is marked as not replyable", form.InReplyToID)
|
||||
if repliedStatus.VisibilityAdvanced != nil {
|
||||
if !repliedStatus.VisibilityAdvanced.Replyable {
|
||||
return fmt.Errorf("status with id %s is marked as not replyable", form.InReplyToID)
|
||||
}
|
||||
}
|
||||
|
||||
// check replied account is known to us
|
||||
|
@ -329,8 +331,8 @@ func (p *processor) updateAccountHeader(header *multipart.FileHeader, accountID
|
|||
//
|
||||
// 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 == "" {
|
||||
func (p *processor) fetchHeaderAndAviForAccount(targetAccount *gtsmodel.Account, t transport.Transport, refresh bool) error {
|
||||
if targetAccount.AvatarRemoteURL != "" && (targetAccount.AvatarMediaAttachmentID == "" || refresh) {
|
||||
a, err := p.mediaHandler.ProcessRemoteHeaderOrAvatar(t, >smodel.MediaAttachment{
|
||||
RemoteURL: targetAccount.AvatarRemoteURL,
|
||||
Avatar: true,
|
||||
|
@ -341,7 +343,7 @@ func (p *processor) fetchHeaderAndAviForAccount(targetAccount *gtsmodel.Account,
|
|||
targetAccount.AvatarMediaAttachmentID = a.ID
|
||||
}
|
||||
|
||||
if targetAccount.HeaderRemoteURL != "" && targetAccount.HeaderMediaAttachmentID == "" {
|
||||
if targetAccount.HeaderRemoteURL != "" && (targetAccount.HeaderMediaAttachmentID == "" || refresh) {
|
||||
a, err := p.mediaHandler.ProcessRemoteHeaderOrAvatar(t, >smodel.MediaAttachment{
|
||||
RemoteURL: targetAccount.HeaderRemoteURL,
|
||||
Header: true,
|
||||
|
|
|
@ -29,7 +29,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/go-fed/activity/pub"
|
||||
"github.com/go-fed/activity/streams"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
@ -63,6 +62,9 @@ func extractName(i withName) (string, error) {
|
|||
|
||||
func extractInReplyToURI(i withInReplyTo) (*url.URL, error) {
|
||||
inReplyToProp := i.GetActivityStreamsInReplyTo()
|
||||
if inReplyToProp == nil {
|
||||
return nil, errors.New("in reply to prop was nil")
|
||||
}
|
||||
for iter := inReplyToProp.Begin(); iter != inReplyToProp.End(); iter = iter.Next() {
|
||||
if iter.IsIRI() {
|
||||
if iter.GetIRI() != nil {
|
||||
|
@ -76,6 +78,9 @@ func extractInReplyToURI(i withInReplyTo) (*url.URL, error) {
|
|||
func extractTos(i withTo) ([]*url.URL, error) {
|
||||
to := []*url.URL{}
|
||||
toProp := i.GetActivityStreamsTo()
|
||||
if toProp == nil {
|
||||
return nil, errors.New("toProp was nil")
|
||||
}
|
||||
for iter := toProp.Begin(); iter != toProp.End(); iter = iter.Next() {
|
||||
if iter.IsIRI() {
|
||||
if iter.GetIRI() != nil {
|
||||
|
@ -89,6 +94,9 @@ func extractTos(i withTo) ([]*url.URL, error) {
|
|||
func extractCCs(i withCC) ([]*url.URL, error) {
|
||||
cc := []*url.URL{}
|
||||
ccProp := i.GetActivityStreamsCc()
|
||||
if ccProp == nil {
|
||||
return cc, nil
|
||||
}
|
||||
for iter := ccProp.Begin(); iter != ccProp.End(); iter = iter.Next() {
|
||||
if iter.IsIRI() {
|
||||
if iter.GetIRI() != nil {
|
||||
|
@ -101,6 +109,9 @@ func extractCCs(i withCC) ([]*url.URL, error) {
|
|||
|
||||
func extractAttributedTo(i withAttributedTo) (*url.URL, error) {
|
||||
attributedToProp := i.GetActivityStreamsAttributedTo()
|
||||
if attributedToProp == nil {
|
||||
return nil, errors.New("attributedToProp was nil")
|
||||
}
|
||||
for iter := attributedToProp.Begin(); iter != attributedToProp.End(); iter = iter.Next() {
|
||||
if iter.IsIRI() {
|
||||
if iter.GetIRI() != nil {
|
||||
|
@ -302,27 +313,21 @@ func extractContent(i withContent) (string, error) {
|
|||
|
||||
func extractAttachments(i withAttachment) ([]*gtsmodel.MediaAttachment, error) {
|
||||
attachments := []*gtsmodel.MediaAttachment{}
|
||||
|
||||
attachmentProp := i.GetActivityStreamsAttachment()
|
||||
if attachmentProp == nil {
|
||||
return attachments, nil
|
||||
}
|
||||
for iter := attachmentProp.Begin(); iter != attachmentProp.End(); iter = iter.Next() {
|
||||
|
||||
t := iter.GetType()
|
||||
if t == nil {
|
||||
fmt.Printf("\n\n\nGetType() nil\n\n\n")
|
||||
continue
|
||||
}
|
||||
|
||||
m, _ := streams.Serialize(t)
|
||||
fmt.Printf("\n\n\n%s\n\n\n", m)
|
||||
|
||||
attachmentable, ok := t.(Attachmentable)
|
||||
if !ok {
|
||||
fmt.Printf("\n\n\nnot attachmentable\n\n\n")
|
||||
continue
|
||||
}
|
||||
attachment, err := extractAttachment(attachmentable)
|
||||
if err != nil {
|
||||
fmt.Printf("\n\n\n%s\n\n\n", err)
|
||||
continue
|
||||
}
|
||||
attachments = append(attachments, attachment)
|
||||
|
@ -373,8 +378,10 @@ func extractAttachment(i Attachmentable) (*gtsmodel.MediaAttachment, error) {
|
|||
|
||||
func extractHashtags(i withTag) ([]*gtsmodel.Tag, error) {
|
||||
tags := []*gtsmodel.Tag{}
|
||||
|
||||
tagsProp := i.GetActivityStreamsTag()
|
||||
if tagsProp == nil {
|
||||
return tags, nil
|
||||
}
|
||||
for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() {
|
||||
t := iter.GetType()
|
||||
if t == nil {
|
||||
|
@ -421,6 +428,9 @@ func extractHashtag(i Hashtaggable) (*gtsmodel.Tag, error) {
|
|||
func extractEmojis(i withTag) ([]*gtsmodel.Emoji, error) {
|
||||
emojis := []*gtsmodel.Emoji{}
|
||||
tagsProp := i.GetActivityStreamsTag()
|
||||
if tagsProp == nil {
|
||||
return emojis, nil
|
||||
}
|
||||
for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() {
|
||||
t := iter.GetType()
|
||||
if t == nil {
|
||||
|
@ -478,6 +488,9 @@ func extractEmoji(i Emojiable) (*gtsmodel.Emoji, error) {
|
|||
func extractMentions(i withTag) ([]*gtsmodel.Mention, error) {
|
||||
mentions := []*gtsmodel.Mention{}
|
||||
tagsProp := i.GetActivityStreamsTag()
|
||||
if tagsProp == nil {
|
||||
return mentions, nil
|
||||
}
|
||||
for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() {
|
||||
t := iter.GetType()
|
||||
if t == nil {
|
||||
|
|
|
@ -28,7 +28,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
func (c *converter) ASRepresentationToAccount(accountable Accountable) (*gtsmodel.Account, error) {
|
||||
func (c *converter) ASRepresentationToAccount(accountable Accountable, update bool) (*gtsmodel.Account, error) {
|
||||
// first check if we actually already know this account
|
||||
uriProp := accountable.GetJSONLDId()
|
||||
if uriProp == nil || !uriProp.IsIRI() {
|
||||
|
@ -37,17 +37,19 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable) (*gtsmode
|
|||
uri := uriProp.GetIRI()
|
||||
|
||||
acct := >smodel.Account{}
|
||||
err := c.db.GetWhere([]db.Where{{Key: "uri", Value: uri.String()}}, acct)
|
||||
if err == nil {
|
||||
// we already know this account so we can skip generating it
|
||||
return acct, nil
|
||||
}
|
||||
if _, ok := err.(db.ErrNoEntries); !ok {
|
||||
// we don't know the account and there's been a real error
|
||||
return nil, fmt.Errorf("error getting account with uri %s from the database: %s", uri.String(), err)
|
||||
if !update {
|
||||
err := c.db.GetWhere([]db.Where{{Key: "uri", Value: uri.String()}}, acct)
|
||||
if err == nil {
|
||||
// we already know this account so we can skip generating it
|
||||
return acct, nil
|
||||
}
|
||||
if _, ok := err.(db.ErrNoEntries); !ok {
|
||||
// we don't know the account and there's been a real error
|
||||
return nil, fmt.Errorf("error getting account with uri %s from the database: %s", uri.String(), err)
|
||||
}
|
||||
}
|
||||
|
||||
// we don't know the account so we need to generate it from the person -- at least we already have the URI!
|
||||
// we don't know the account, or we're being told to update it, so we need to generate it from the person -- at least we already have the URI!
|
||||
acct = >smodel.Account{}
|
||||
acct.URI = uri.String()
|
||||
|
||||
|
|
|
@ -349,7 +349,7 @@ func (suite *ASToInternalTestSuite) TestParsePerson() {
|
|||
|
||||
testPerson := suite.people["new_person_1"]
|
||||
|
||||
acct, err := suite.typeconverter.ASRepresentationToAccount(testPerson)
|
||||
acct, err := suite.typeconverter.ASRepresentationToAccount(testPerson, false)
|
||||
assert.NoError(suite.T(), err)
|
||||
|
||||
fmt.Printf("%+v", acct)
|
||||
|
@ -367,7 +367,7 @@ func (suite *ASToInternalTestSuite) TestParseGargron() {
|
|||
rep, ok := t.(typeutils.Accountable)
|
||||
assert.True(suite.T(), ok)
|
||||
|
||||
acct, err := suite.typeconverter.ASRepresentationToAccount(rep)
|
||||
acct, err := suite.typeconverter.ASRepresentationToAccount(rep, false)
|
||||
assert.NoError(suite.T(), err)
|
||||
|
||||
fmt.Printf("%+v", acct)
|
||||
|
|
|
@ -95,8 +95,12 @@ type TypeConverter interface {
|
|||
ACTIVITYSTREAMS MODEL TO INTERNAL (gts) MODEL
|
||||
*/
|
||||
|
||||
// ASPersonToAccount converts a remote account/person/application representation into a gts model account
|
||||
ASRepresentationToAccount(accountable Accountable) (*gtsmodel.Account, error)
|
||||
// ASPersonToAccount converts a remote account/person/application representation into a gts model account.
|
||||
//
|
||||
// If update is false, and the account is already known in the database, then the existing account entry will be returned.
|
||||
// If update is true, then even if the account is already known, all fields in the accountable will be parsed and a new *gtsmodel.Account
|
||||
// will be generated. This is useful when one needs to force refresh of an account, eg., during an Update of a Profile.
|
||||
ASRepresentationToAccount(accountable Accountable, update bool) (*gtsmodel.Account, error)
|
||||
// ASStatus converts a remote activitystreams 'status' representation into a gts model status.
|
||||
ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, error)
|
||||
// ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow request.
|
||||
|
|
|
@ -233,9 +233,9 @@ func (c *converter) MentionToMasto(m *gtsmodel.Mention) (model.Mention, error) {
|
|||
|
||||
var acct string
|
||||
if local {
|
||||
acct = fmt.Sprintf("@%s", target.Username)
|
||||
acct = target.Username
|
||||
} else {
|
||||
acct = fmt.Sprintf("@%s@%s", target.Username, target.Domain)
|
||||
acct = fmt.Sprintf("%s@%s", target.Username, target.Domain)
|
||||
}
|
||||
|
||||
return model.Mention{
|
||||
|
@ -567,7 +567,7 @@ func (c *converter) InstanceToMasto(i *gtsmodel.Instance) (*model.Instance, erro
|
|||
if i.ContactAccountID != "" {
|
||||
ia := >smodel.Account{}
|
||||
if err := c.db.GetByID(i.ContactAccountID, ia); err == nil {
|
||||
ma, err := c.AccountToMastoPublic(ia)
|
||||
ma, err := c.AccountToMastoPublic(ia)
|
||||
if err == nil {
|
||||
mi.ContactAccount = ma
|
||||
}
|
||||
|
|
|
@ -232,3 +232,25 @@ func ParseOutboxPath(id *url.URL) (username string, err error) {
|
|||
username = matches[1]
|
||||
return
|
||||
}
|
||||
|
||||
// ParseFollowersPath returns the username from a path such as /users/example_username/followers
|
||||
func ParseFollowersPath(id *url.URL) (username string, err error) {
|
||||
matches := followersPathRegex.FindStringSubmatch(id.Path)
|
||||
if len(matches) != 2 {
|
||||
err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches))
|
||||
return
|
||||
}
|
||||
username = matches[1]
|
||||
return
|
||||
}
|
||||
|
||||
// ParseFollowingPath returns the username from a path such as /users/example_username/following
|
||||
func ParseFollowingPath(id *url.URL) (username string, err error) {
|
||||
matches := followingPathRegex.FindStringSubmatch(id.Path)
|
||||
if len(matches) != 2 {
|
||||
err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches))
|
||||
return
|
||||
}
|
||||
username = matches[1]
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue