mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-03 23:02:24 -06:00 
			
		
		
		
	get remote follows/accepts working
This commit is contained in:
		
					parent
					
						
							
								c7b4f847d8
							
						
					
				
			
			
				commit
				
					
						d69786ef17
					
				
			
		
					 16 changed files with 459 additions and 104 deletions
				
			
		| 
						 | 
					@ -59,6 +59,8 @@ const (
 | 
				
			||||||
	GetFollowersPath = BasePathWithID + "/followers"
 | 
						GetFollowersPath = BasePathWithID + "/followers"
 | 
				
			||||||
	// GetRelationshipsPath is for showing an account's relationship with other accounts
 | 
						// GetRelationshipsPath is for showing an account's relationship with other accounts
 | 
				
			||||||
	GetRelationshipsPath = BasePath + "/relationships"
 | 
						GetRelationshipsPath = BasePath + "/relationships"
 | 
				
			||||||
 | 
						// FollowPath is for POSTing new follows to, and updating existing follows
 | 
				
			||||||
 | 
						PostFollowPath = BasePathWithID + "/follow"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Module implements the ClientAPIModule interface for account-related actions
 | 
					// Module implements the ClientAPIModule interface for account-related actions
 | 
				
			||||||
| 
						 | 
					@ -85,6 +87,7 @@ func (m *Module) Route(r router.Router) error {
 | 
				
			||||||
	r.AttachHandler(http.MethodGet, GetStatusesPath, m.AccountStatusesGETHandler)
 | 
						r.AttachHandler(http.MethodGet, GetStatusesPath, m.AccountStatusesGETHandler)
 | 
				
			||||||
	r.AttachHandler(http.MethodGet, GetFollowersPath, m.AccountFollowersGETHandler)
 | 
						r.AttachHandler(http.MethodGet, GetFollowersPath, m.AccountFollowersGETHandler)
 | 
				
			||||||
	r.AttachHandler(http.MethodGet, GetRelationshipsPath, m.AccountRelationshipsGETHandler)
 | 
						r.AttachHandler(http.MethodGet, GetRelationshipsPath, m.AccountRelationshipsGETHandler)
 | 
				
			||||||
 | 
						r.AttachHandler(http.MethodPost, PostFollowPath, m.AccountFollowPOSTHandler)
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										56
									
								
								internal/api/client/account/follow.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								internal/api/client/account/follow.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,56 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					   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 account
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"github.com/superseriousbusiness/gotosocial/internal/api/model"
 | 
				
			||||||
 | 
						"github.com/superseriousbusiness/gotosocial/internal/oauth"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AccountFollowPOSTHandler is the endpoint for creating a new follow request to the target account
 | 
				
			||||||
 | 
					func (m *Module) AccountFollowPOSTHandler(c *gin.Context) {
 | 
				
			||||||
 | 
						authed, err := oauth.Authed(c, true, true, true, true)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						targetAcctID := c.Param(IDKey)
 | 
				
			||||||
 | 
						if targetAcctID == "" {
 | 
				
			||||||
 | 
							c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						form := &model.AccountFollowRequest{}
 | 
				
			||||||
 | 
						if err := c.ShouldBind(form); err != nil {
 | 
				
			||||||
 | 
							c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						form.TargetAccountID = targetAcctID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						relationship, errWithCode := m.processor.AccountFollowCreate(authed, form)
 | 
				
			||||||
 | 
						if errWithCode != nil {
 | 
				
			||||||
 | 
							c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, relationship)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -21,9 +21,14 @@ func (m *Module) AccountRelationshipsGETHandler(c *gin.Context) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	targetAccountIDs := c.QueryArray("id[]")
 | 
						targetAccountIDs := c.QueryArray("id[]")
 | 
				
			||||||
	if len(targetAccountIDs) == 0 {
 | 
						if len(targetAccountIDs) == 0 {
 | 
				
			||||||
		l.Debug("no account id specified in query")
 | 
							// check fallback -- let's be generous and see if maybe it's just set as 'id'?
 | 
				
			||||||
		c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
 | 
							id := c.Query("id")
 | 
				
			||||||
		return
 | 
							if id == "" {
 | 
				
			||||||
 | 
								l.Debug("no account id specified in query")
 | 
				
			||||||
 | 
								c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							targetAccountIDs = append(targetAccountIDs, id)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	relationships := []model.Relationship{}
 | 
						relationships := []model.Relationship{}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -130,7 +130,18 @@ type UpdateSource struct {
 | 
				
			||||||
// By default, max 4 fields and 255 characters per property/value.
 | 
					// By default, max 4 fields and 255 characters per property/value.
 | 
				
			||||||
type UpdateField struct {
 | 
					type UpdateField struct {
 | 
				
			||||||
	// Name of the field
 | 
						// Name of the field
 | 
				
			||||||
	Name *string `form:"name"`
 | 
						Name *string `form:"name" json:"name" xml:"name"`
 | 
				
			||||||
	// Value of the field
 | 
						// Value of the field
 | 
				
			||||||
	Value *string `form:"value"`
 | 
						Value *string `form:"value" json:"value" xml:"value"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AccountFollowRequest is for parsing requests at /api/v1/accounts/:id/follow
 | 
				
			||||||
 | 
					type AccountFollowRequest struct {
 | 
				
			||||||
 | 
						// ID of the account to follow request
 | 
				
			||||||
 | 
						// This should be a URL parameter not a form field
 | 
				
			||||||
 | 
						TargetAccountID string `form:"-"`
 | 
				
			||||||
 | 
						// Show reblogs for this account?
 | 
				
			||||||
 | 
						Reblogs *bool `form:"reblogs" json:"reblogs" xml:"reblogs"`
 | 
				
			||||||
 | 
						// Notify when this account posts?
 | 
				
			||||||
 | 
						Notify *bool `form:"notify" json:"notify" xml:"notify"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,11 +32,17 @@ const (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ErrNoEntries is to be returned from the DB interface when no entries are found for a given query.
 | 
					// ErrNoEntries is to be returned from the DB interface when no entries are found for a given query.
 | 
				
			||||||
type ErrNoEntries struct{}
 | 
					type ErrNoEntries struct{}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func (e ErrNoEntries) Error() string {
 | 
					func (e ErrNoEntries) Error() string {
 | 
				
			||||||
	return "no entries"
 | 
						return "no entries"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrAlreadyExists is to be returned from the DB interface when an entry already exists for a given query or its constraints.
 | 
				
			||||||
 | 
					type ErrAlreadyExists struct{}
 | 
				
			||||||
 | 
					func (e ErrAlreadyExists) Error() string {
 | 
				
			||||||
 | 
						return "already exists"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DB provides methods for interacting with an underlying database or other storage mechanism (for now, just postgres).
 | 
					// DB provides methods for interacting with an underlying database or other storage mechanism (for now, just postgres).
 | 
				
			||||||
// Note that in all of the functions below, the passed interface should be a pointer or a slice, which will then be populated
 | 
					// Note that in all of the functions below, the passed interface should be a pointer or a slice, which will then be populated
 | 
				
			||||||
// by whatever is returned from the database.
 | 
					// by whatever is returned from the database.
 | 
				
			||||||
| 
						 | 
					@ -226,6 +232,9 @@ type DB interface {
 | 
				
			||||||
	// Follows returns true if sourceAccount follows target account, or an error if something goes wrong while finding out.
 | 
						// Follows returns true if sourceAccount follows target account, or an error if something goes wrong while finding out.
 | 
				
			||||||
	Follows(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error)
 | 
						Follows(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// FollowRequested returns true if sourceAccount has requested to follow target account, or an error if something goes wrong while finding out.
 | 
				
			||||||
 | 
						FollowRequested(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Mutuals returns true if account1 and account2 both follow each other, or an error if something goes wrong while finding out.
 | 
						// Mutuals returns true if account1 and account2 both follow each other, or an error if something goes wrong while finding out.
 | 
				
			||||||
	Mutuals(account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, error)
 | 
						Mutuals(account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -242,6 +242,9 @@ func (ps *postgresService) GetAll(i interface{}) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ps *postgresService) Put(i interface{}) error {
 | 
					func (ps *postgresService) Put(i interface{}) error {
 | 
				
			||||||
	_, err := ps.conn.Model(i).Insert(i)
 | 
						_, err := ps.conn.Model(i).Insert(i)
 | 
				
			||||||
 | 
						if err != nil && strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
 | 
				
			||||||
 | 
							return db.ErrAlreadyExists{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -900,6 +903,10 @@ func (ps *postgresService) Follows(sourceAccount *gtsmodel.Account, targetAccoun
 | 
				
			||||||
	return ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", sourceAccount.ID).Where("target_account_id = ?", targetAccount.ID).Exists()
 | 
						return ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", sourceAccount.ID).Where("target_account_id = ?", targetAccount.ID).Exists()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ps *postgresService) FollowRequested(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error) {
 | 
				
			||||||
 | 
						return ps.conn.Model(>smodel.FollowRequest{}).Where("account_id = ?", sourceAccount.ID).Where("target_account_id = ?", targetAccount.ID).Exists()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ps *postgresService) Mutuals(account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, error) {
 | 
					func (ps *postgresService) Mutuals(account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, error) {
 | 
				
			||||||
	// make sure account 1 follows account 2
 | 
						// make sure account 1 follows account 2
 | 
				
			||||||
	f1, err := ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", account1.ID).Where("target_account_id = ?", account2.ID).Exists()
 | 
						f1, err := ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", account1.ID).Where("target_account_id = ?", account2.ID).Exists()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,8 @@ import (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type FederatingDB interface {
 | 
					type FederatingDB interface {
 | 
				
			||||||
	pub.Database
 | 
						pub.Database
 | 
				
			||||||
	Undo(c context.Context, asType vocab.Type) error
 | 
						Undo(ctx context.Context, undo vocab.ActivityStreamsUndo) error
 | 
				
			||||||
 | 
						Accept(ctx context.Context, accept vocab.ActivityStreamsAccept) error
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FederatingDB uses the underlying DB interface to implement the go-fed pub.Database interface.
 | 
					// FederatingDB uses the underlying DB interface to implement the go-fed pub.Database interface.
 | 
				
			||||||
| 
						 | 
					@ -352,6 +353,7 @@ func (f *federatingDB) Get(c context.Context, id *url.URL) (value vocab.Type, er
 | 
				
			||||||
		if err := f.db.GetWhere("uri", id.String(), acct); err != nil {
 | 
							if err := f.db.GetWhere("uri", id.String(), acct); err != nil {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							l.Debug("is user path! returning account")
 | 
				
			||||||
		return f.typeConverter.AccountToAS(acct)
 | 
							return f.typeConverter.AccountToAS(acct)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -426,6 +428,9 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
 | 
				
			||||||
					return fmt.Errorf("error converting note to status: %s", err)
 | 
										return fmt.Errorf("error converting note to status: %s", err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				if err := f.db.Put(status); err != nil {
 | 
									if err := f.db.Put(status); err != nil {
 | 
				
			||||||
 | 
										if _, ok := err.(db.ErrAlreadyExists); ok {
 | 
				
			||||||
 | 
											return nil
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
					return fmt.Errorf("database error inserting status: %s", err)
 | 
										return fmt.Errorf("database error inserting status: %s", err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -461,98 +466,6 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (f *federatingDB) Undo(ctx context.Context, asType vocab.Type) error {
 | 
					 | 
				
			||||||
	l := f.log.WithFields(
 | 
					 | 
				
			||||||
		logrus.Fields{
 | 
					 | 
				
			||||||
			"func":   "Undo",
 | 
					 | 
				
			||||||
			"asType": asType.GetTypeName(),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
	m, err := streams.Serialize(asType)
 | 
					 | 
				
			||||||
	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 {
 | 
					 | 
				
			||||||
		l.Error("UNDO: target account wasn't set on context")
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	targetAcct, ok := targetAcctI.(*gtsmodel.Account)
 | 
					 | 
				
			||||||
	if !ok {
 | 
					 | 
				
			||||||
		l.Error("UNDO: target 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
 | 
					 | 
				
			||||||
	// }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	switch asType.GetTypeName() {
 | 
					 | 
				
			||||||
	// UNDO
 | 
					 | 
				
			||||||
	case gtsmodel.ActivityStreamsUndo:
 | 
					 | 
				
			||||||
		undo, ok := asType.(vocab.ActivityStreamsUndo)
 | 
					 | 
				
			||||||
		if !ok {
 | 
					 | 
				
			||||||
			return errors.New("UNDO: couldn't parse UNDO into vocab.ActivityStreamsUndo")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		undoObject := undo.GetActivityStreamsObject()
 | 
					 | 
				
			||||||
		if undoObject == nil {
 | 
					 | 
				
			||||||
			return errors.New("UNDO: no object set on vocab.ActivityStreamsUndo")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for iter := undoObject.Begin(); iter != undoObject.End(); iter = iter.Next() {
 | 
					 | 
				
			||||||
			switch iter.GetType().GetTypeName() {
 | 
					 | 
				
			||||||
			case string(gtsmodel.ActivityStreamsFollow):
 | 
					 | 
				
			||||||
				// UNDO FOLLOW
 | 
					 | 
				
			||||||
				ASFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow)
 | 
					 | 
				
			||||||
				if !ok {
 | 
					 | 
				
			||||||
					return errors.New("UNDO: couldn't parse follow into vocab.ActivityStreamsFollow")
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				// make sure the actor owns the follow
 | 
					 | 
				
			||||||
				if !sameActor(undo.GetActivityStreamsActor(), ASFollow.GetActivityStreamsActor()) {
 | 
					 | 
				
			||||||
					return errors.New("UNDO: follow actor and activity actor not the same")
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				// convert the follow to something we can understand
 | 
					 | 
				
			||||||
				gtsFollow, err := f.typeConverter.ASFollowToFollow(ASFollow)
 | 
					 | 
				
			||||||
				if err != nil {
 | 
					 | 
				
			||||||
					return fmt.Errorf("UNDO: error converting asfollow to gtsfollow: %s", err)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				// make sure the addressee of the original follow is the same as whatever inbox this landed in
 | 
					 | 
				
			||||||
				if gtsFollow.TargetAccountID != targetAcct.ID {
 | 
					 | 
				
			||||||
					return errors.New("UNDO: follow object account and inbox account were not the same")
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				// delete any existing FOLLOW
 | 
					 | 
				
			||||||
				if err := f.db.DeleteWhere("uri", gtsFollow.URI, >smodel.Follow{}); err != nil {
 | 
					 | 
				
			||||||
					return fmt.Errorf("UNDO: db error removing follow: %s", err)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				// delete any existing FOLLOW REQUEST
 | 
					 | 
				
			||||||
				if err := f.db.DeleteWhere("uri", gtsFollow.URI, >smodel.FollowRequest{}); err != nil {
 | 
					 | 
				
			||||||
					return fmt.Errorf("UNDO: db error removing follow request: %s", err)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				l.Debug("follow undone")
 | 
					 | 
				
			||||||
				return nil
 | 
					 | 
				
			||||||
			case string(gtsmodel.ActivityStreamsLike):
 | 
					 | 
				
			||||||
				// UNDO LIKE
 | 
					 | 
				
			||||||
			case string(gtsmodel.ActivityStreamsAnnounce):
 | 
					 | 
				
			||||||
				// UNDO BOOST/REBLOG/ANNOUNCE
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Update sets an existing entry to the database based on the value's
 | 
					// Update sets an existing entry to the database based on the value's
 | 
				
			||||||
// id.
 | 
					// id.
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
| 
						 | 
					@ -715,9 +628,38 @@ func (f *federatingDB) NewID(c context.Context, t vocab.Type) (id *url.URL, err
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	l.Debugf("received NEWID request for asType %s", string(b))
 | 
						l.Debugf("received NEWID request for asType %s", string(b))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch t.GetTypeName() {
 | 
				
			||||||
 | 
						case gtsmodel.ActivityStreamsFollow:
 | 
				
			||||||
 | 
							// FOLLOW
 | 
				
			||||||
 | 
							// ID might already be set on a follow we've created, so check it here and return it if it is
 | 
				
			||||||
 | 
							follow, ok := t.(vocab.ActivityStreamsFollow)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								return nil, errors.New("newid: follow couldn't be parsed into vocab.ActivityStreamsFollow")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							idProp := follow.GetJSONLDId()
 | 
				
			||||||
 | 
							if idProp != nil {
 | 
				
			||||||
 | 
								if idProp.IsIRI() {
 | 
				
			||||||
 | 
									return idProp.GetIRI(), nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// it's not set so create one based on the actor set on the follow (ie., the followER not the followEE)
 | 
				
			||||||
 | 
							actorProp := follow.GetActivityStreamsActor()
 | 
				
			||||||
 | 
							if actorProp != nil {
 | 
				
			||||||
 | 
								for iter := actorProp.Begin(); iter != actorProp.End(); iter = iter.Next() {
 | 
				
			||||||
 | 
									// take the IRI of the first actor we can find (there should only be one)
 | 
				
			||||||
 | 
									if iter.IsIRI() {
 | 
				
			||||||
 | 
										actorAccount := >smodel.Account{}
 | 
				
			||||||
 | 
										if err := f.db.GetWhere("uri", iter.GetIRI().String(), actorAccount); err == nil { // if there's an error here, just use the fallback behavior -- we don't need to return an error here
 | 
				
			||||||
 | 
											return url.Parse(util.GenerateURIForFollow(actorAccount.Username, f.config.Protocol, f.config.Host))
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// fallback default behavior: just return a random UUID after our protocol and host
 | 
				
			||||||
	return url.Parse(fmt.Sprintf("%s://%s/%s", f.config.Protocol, f.config.Host, uuid.NewString()))
 | 
						return url.Parse(fmt.Sprintf("%s://%s/%s", f.config.Protocol, f.config.Host, uuid.NewString()))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -821,3 +763,139 @@ func (f *federatingDB) Liked(c context.Context, actorIRI *url.URL) (liked vocab.
 | 
				
			||||||
	l.Debugf("entering LIKED function with actorIRI %s", actorIRI.String())
 | 
						l.Debugf("entering LIKED function with actorIRI %s", actorIRI.String())
 | 
				
			||||||
	return nil, nil
 | 
						return nil, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
						CUSTOM FUNCTIONALITY FOR GTS
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo) error {
 | 
				
			||||||
 | 
						l := f.log.WithFields(
 | 
				
			||||||
 | 
							logrus.Fields{
 | 
				
			||||||
 | 
								"func":   "Undo",
 | 
				
			||||||
 | 
								"asType": undo.GetTypeName(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						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 {
 | 
				
			||||||
 | 
							l.Error("UNDO: target account wasn't set on context")
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						targetAcct, ok := targetAcctI.(*gtsmodel.Account)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							l.Error("UNDO: target account was set on context but couldn't be parsed")
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						undoObject := undo.GetActivityStreamsObject()
 | 
				
			||||||
 | 
						if undoObject == nil {
 | 
				
			||||||
 | 
							return errors.New("UNDO: no object set on vocab.ActivityStreamsUndo")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for iter := undoObject.Begin(); iter != undoObject.End(); iter = iter.Next() {
 | 
				
			||||||
 | 
							switch iter.GetType().GetTypeName() {
 | 
				
			||||||
 | 
							case string(gtsmodel.ActivityStreamsFollow):
 | 
				
			||||||
 | 
								// UNDO FOLLOW
 | 
				
			||||||
 | 
								ASFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow)
 | 
				
			||||||
 | 
								if !ok {
 | 
				
			||||||
 | 
									return errors.New("UNDO: couldn't parse follow into vocab.ActivityStreamsFollow")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// make sure the actor owns the follow
 | 
				
			||||||
 | 
								if !sameActor(undo.GetActivityStreamsActor(), ASFollow.GetActivityStreamsActor()) {
 | 
				
			||||||
 | 
									return errors.New("UNDO: follow actor and activity actor not the same")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// convert the follow to something we can understand
 | 
				
			||||||
 | 
								gtsFollow, err := f.typeConverter.ASFollowToFollow(ASFollow)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("UNDO: error converting asfollow to gtsfollow: %s", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// make sure the addressee of the original follow is the same as whatever inbox this landed in
 | 
				
			||||||
 | 
								if gtsFollow.TargetAccountID != targetAcct.ID {
 | 
				
			||||||
 | 
									return errors.New("UNDO: follow object account and inbox account were not the same")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// delete any existing FOLLOW
 | 
				
			||||||
 | 
								if err := f.db.DeleteWhere("uri", gtsFollow.URI, >smodel.Follow{}); err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("UNDO: db error removing follow: %s", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// delete any existing FOLLOW REQUEST
 | 
				
			||||||
 | 
								if err := f.db.DeleteWhere("uri", gtsFollow.URI, >smodel.FollowRequest{}); err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("UNDO: db error removing follow request: %s", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								l.Debug("follow undone")
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							case string(gtsmodel.ActivityStreamsLike):
 | 
				
			||||||
 | 
								// UNDO LIKE
 | 
				
			||||||
 | 
							case string(gtsmodel.ActivityStreamsAnnounce):
 | 
				
			||||||
 | 
								// UNDO BOOST/REBLOG/ANNOUNCE
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsAccept) error {
 | 
				
			||||||
 | 
						l := f.log.WithFields(
 | 
				
			||||||
 | 
							logrus.Fields{
 | 
				
			||||||
 | 
								"func":   "Accept",
 | 
				
			||||||
 | 
								"asType": accept.GetTypeName(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						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))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						inboxAcctI := ctx.Value(util.APAccount)
 | 
				
			||||||
 | 
						if inboxAcctI == nil {
 | 
				
			||||||
 | 
							l.Error("ACCEPT: inbox account wasn't set on context")
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						inboxAcct, ok := inboxAcctI.(*gtsmodel.Account)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							l.Error("ACCEPT: inbox account was set on context but couldn't be parsed")
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						acceptObject := accept.GetActivityStreamsObject()
 | 
				
			||||||
 | 
						if acceptObject == nil {
 | 
				
			||||||
 | 
							return errors.New("ACCEPT: no object set on vocab.ActivityStreamsUndo")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for iter := acceptObject.Begin(); iter != acceptObject.End(); iter = iter.Next() {
 | 
				
			||||||
 | 
							switch iter.GetType().GetTypeName() {
 | 
				
			||||||
 | 
							case string(gtsmodel.ActivityStreamsFollow):
 | 
				
			||||||
 | 
								// ACCEPT FOLLOW
 | 
				
			||||||
 | 
								asFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow)
 | 
				
			||||||
 | 
								if !ok {
 | 
				
			||||||
 | 
									return errors.New("ACCEPT: couldn't parse follow into vocab.ActivityStreamsFollow")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// convert the follow to something we can understand
 | 
				
			||||||
 | 
								gtsFollow, err := f.typeConverter.ASFollowToFollow(asFollow)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("ACCEPT: error converting asfollow to gtsfollow: %s", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// make sure the addressee of the original follow is the same as whatever inbox this landed in
 | 
				
			||||||
 | 
								if gtsFollow.AccountID != inboxAcct.ID {
 | 
				
			||||||
 | 
									return errors.New("ACCEPT: follow object account and inbox account were not the same")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								_, err = f.db.AcceptFollowRequest(gtsFollow.AccountID, gtsFollow.TargetAccountID)
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -266,11 +266,15 @@ func (f *federator) FederatingCallbacks(ctx context.Context) (wrapped pub.Federa
 | 
				
			||||||
		OnFollow: onFollow,
 | 
							OnFollow: onFollow,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// override default undo behavior
 | 
					 | 
				
			||||||
	other = []interface{}{
 | 
						other = []interface{}{
 | 
				
			||||||
 | 
							// override default undo behavior
 | 
				
			||||||
		func(ctx context.Context, undo vocab.ActivityStreamsUndo) error {
 | 
							func(ctx context.Context, undo vocab.ActivityStreamsUndo) error {
 | 
				
			||||||
			return f.FederatingDB().Undo(ctx, undo)
 | 
								return f.FederatingDB().Undo(ctx, undo)
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							// override default accept behavior
 | 
				
			||||||
 | 
							func(ctx context.Context, accept vocab.ActivityStreamsAccept) error {
 | 
				
			||||||
 | 
								return f.FederatingDB().Accept(ctx, accept)
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,8 @@ type FromClientAPI struct {
 | 
				
			||||||
	APObjectType   string
 | 
						APObjectType   string
 | 
				
			||||||
	APActivityType string
 | 
						APActivityType string
 | 
				
			||||||
	GTSModel       interface{}
 | 
						GTSModel       interface{}
 | 
				
			||||||
 | 
						OriginAccount  *Account
 | 
				
			||||||
 | 
						TargetAccount  *Account
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// // ToFederator wraps a message that travels from the processor into the federator
 | 
					// // ToFederator wraps a message that travels from the processor into the federator
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -326,3 +326,87 @@ func (p *processor) AccountRelationshipGet(authed *oauth.Auth, targetAccountID s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return r, nil
 | 
						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 := >smodel.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 := >smodel.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: >smodel.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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,6 +49,18 @@ func (p *processor) processFromClientAPI(clientMsg gtsmodel.FromClientAPI) error
 | 
				
			||||||
				return p.federateStatus(status)
 | 
									return p.federateStatus(status)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			return nil
 | 
								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:
 | 
						case gtsmodel.ActivityStreamsUpdate:
 | 
				
			||||||
		// UPDATE
 | 
							// UPDATE
 | 
				
			||||||
| 
						 | 
					@ -90,8 +102,26 @@ func (p *processor) federateStatus(status *gtsmodel.Status) error {
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *processor) federateFollow(follow *gtsmodel.Follow, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error {
 | 
				
			||||||
 | 
						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) federateAcceptFollowRequest(follow *gtsmodel.Follow) error {
 | 
					func (p *processor) federateAcceptFollowRequest(follow *gtsmodel.Follow) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: tidy up this whole function -- move most of the logic for the conversion to the type converter because this is just a mess! Shame on me!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	followAccepter := >smodel.Account{}
 | 
						followAccepter := >smodel.Account{}
 | 
				
			||||||
	if err := p.db.GetByID(follow.TargetAccountID, followAccepter); err != nil {
 | 
						if err := p.db.GetByID(follow.TargetAccountID, followAccepter); err != nil {
 | 
				
			||||||
		return fmt.Errorf("error federating follow accept: %s", err)
 | 
							return fmt.Errorf("error federating follow accept: %s", err)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,3 +23,7 @@ import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
 | 
				
			||||||
func (p *processor) notifyStatus(status *gtsmodel.Status) error {
 | 
					func (p *processor) notifyStatus(status *gtsmodel.Status) error {
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *processor) notifyFollow(follow *gtsmodel.Follow) error {
 | 
				
			||||||
 | 
					   return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -71,10 +71,12 @@ type Processor interface {
 | 
				
			||||||
	// AccountStatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for
 | 
						// AccountStatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for
 | 
				
			||||||
	// the account given in authed.
 | 
						// the account given in authed.
 | 
				
			||||||
	AccountStatusesGet(authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, maxID string, pinned bool, mediaOnly bool) ([]apimodel.Status, ErrorWithCode)
 | 
						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)
 | 
						AccountFollowersGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, ErrorWithCode)
 | 
				
			||||||
	// AccountRelationshipGet
 | 
						// AccountRelationshipGet returns a relationship model describing the relationship of the targetAccount to the Authed account.
 | 
				
			||||||
	AccountRelationshipGet(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, ErrorWithCode)
 | 
						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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// AdminEmojiCreate handles the creation of a new instance emoji by an admin, using the given form.
 | 
						// 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)
 | 
						AdminEmojiCreate(authed *oauth.Auth, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, error)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -109,6 +109,9 @@ type TypeConverter interface {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// StatusToAS converts a gts model status into an activity streams note, suitable for federation
 | 
						// StatusToAS converts a gts model status into an activity streams note, suitable for federation
 | 
				
			||||||
	StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, error)
 | 
						StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// FollowToASFollow converts a gts model Follow into an activity streams Follow, suitable for federation
 | 
				
			||||||
 | 
						FollowToAS(f *gtsmodel.Follow, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (vocab.ActivityStreamsFollow, error)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type converter struct {
 | 
					type converter struct {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,6 +21,7 @@ package typeutils
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"crypto/x509"
 | 
						"crypto/x509"
 | 
				
			||||||
	"encoding/pem"
 | 
						"encoding/pem"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/go-fed/activity/streams"
 | 
						"github.com/go-fed/activity/streams"
 | 
				
			||||||
| 
						 | 
					@ -258,3 +259,49 @@ func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerso
 | 
				
			||||||
func (c *converter) StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, error) {
 | 
					func (c *converter) StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, error) {
 | 
				
			||||||
	return nil, nil
 | 
						return nil, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *converter) FollowToAS(f *gtsmodel.Follow, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (vocab.ActivityStreamsFollow, error) {
 | 
				
			||||||
 | 
						// parse out the various URIs we need for this
 | 
				
			||||||
 | 
						// origin account (who's doing the follow)
 | 
				
			||||||
 | 
						originAccountURI, err := url.Parse(originAccount.URI)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("followtoasfollow: error parsing origin account uri: %s", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						originActor := streams.NewActivityStreamsActorProperty()
 | 
				
			||||||
 | 
						originActor.AppendIRI(originAccountURI)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// target account (who's being followed)
 | 
				
			||||||
 | 
						targetAccountURI, err := url.Parse(targetAccount.URI)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("followtoasfollow: error parsing target account uri: %s", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// uri of the folow activity itself
 | 
				
			||||||
 | 
						followURI, err := url.Parse(f.URI)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("followtoasfollow: error parsing follow uri: %s", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// start preparing the follow activity
 | 
				
			||||||
 | 
						follow := streams.NewActivityStreamsFollow()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// set the actor
 | 
				
			||||||
 | 
						follow.SetActivityStreamsActor(originActor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// set the id
 | 
				
			||||||
 | 
						followIDProp := streams.NewJSONLDIdProperty()
 | 
				
			||||||
 | 
						followIDProp.SetIRI(followURI)
 | 
				
			||||||
 | 
						follow.SetJSONLDId(followIDProp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// set the object
 | 
				
			||||||
 | 
						followObjectProp := streams.NewActivityStreamsObjectProperty()
 | 
				
			||||||
 | 
						followObjectProp.AppendIRI(targetAccountURI)
 | 
				
			||||||
 | 
						follow.SetActivityStreamsObject(followObjectProp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// set the To property
 | 
				
			||||||
 | 
						followToProp := streams.NewActivityStreamsToProperty()
 | 
				
			||||||
 | 
						followToProp.AppendIRI(targetAccountURI)
 | 
				
			||||||
 | 
						follow.SetActivityStreamsTo(followToProp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return follow, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,6 +22,8 @@ import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/google/uuid"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
| 
						 | 
					@ -47,6 +49,8 @@ const (
 | 
				
			||||||
	FeaturedPath = "featured"
 | 
						FeaturedPath = "featured"
 | 
				
			||||||
	// PublicKeyPath is for serving an account's public key
 | 
						// PublicKeyPath is for serving an account's public key
 | 
				
			||||||
	PublicKeyPath = "main-key"
 | 
						PublicKeyPath = "main-key"
 | 
				
			||||||
 | 
						// FollowPath used to generate the URI for an individual follow or follow request
 | 
				
			||||||
 | 
						FollowPath = "follow"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// APContextKey is a type used specifically for settings values on contexts within go-fed AP request chains
 | 
					// APContextKey is a type used specifically for settings values on contexts within go-fed AP request chains
 | 
				
			||||||
| 
						 | 
					@ -103,6 +107,12 @@ type UserURIs struct {
 | 
				
			||||||
	PublicKeyURI string
 | 
						PublicKeyURI string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GenerateURIForFollow returns the AP URI for a new follow -- something like:
 | 
				
			||||||
 | 
					// https://example.org/users/whatever_user/follow/41c7f33f-1060-48d9-84df-38dcb13cf0d8
 | 
				
			||||||
 | 
					func GenerateURIForFollow(username string, protocol string, host string) string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("%s://%s/%s/%s/%s", protocol, host, UsersPath, FollowPath, uuid.NewString())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GenerateURIsForAccount throws together a bunch of URIs for the given username, with the given protocol and host.
 | 
					// GenerateURIsForAccount throws together a bunch of URIs for the given username, with the given protocol and host.
 | 
				
			||||||
func GenerateURIsForAccount(username string, protocol string, host string) *UserURIs {
 | 
					func GenerateURIsForAccount(username string, protocol string, host string) *UserURIs {
 | 
				
			||||||
	// The below URLs are used for serving web requests
 | 
						// The below URLs are used for serving web requests
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue