mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 12:52:25 -05:00 
			
		
		
		
	restructure a bunch, get unfaves working
This commit is contained in:
		
					parent
					
						
							
								de3b6bc6d9
							
						
					
				
			
			
				commit
				
					
						1f44b06c06
					
				
			
		
					 25 changed files with 1190 additions and 681 deletions
				
			
		|  | @ -23,7 +23,7 @@ import ( | |||
| 
 | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| ) | ||||
| 
 | ||||
| // InboxPOSTHandler deals with incoming POST requests to an actor's inbox. | ||||
|  | @ -42,7 +42,7 @@ func (m *Module) InboxPOSTHandler(c *gin.Context) { | |||
| 
 | ||||
| 	posted, err := m.processor.InboxPost(c.Request.Context(), c.Writer, c.Request) | ||||
| 	if err != nil { | ||||
| 		if withCode, ok := err.(processing.ErrorWithCode); ok { | ||||
| 		if withCode, ok := err.(gtserror.WithCode); ok { | ||||
| 			l.Debug(withCode.Error()) | ||||
| 			c.JSON(withCode.Code(), withCode.Safe()) | ||||
| 			return | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ | |||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| package processing | ||||
| package gtserror | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
|  | @ -24,12 +24,12 @@ import ( | |||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // ErrorWithCode wraps an internal error with an http code, and a 'safe' version of | ||||
| // WithCode wraps an internal error with an http code, and a 'safe' version of | ||||
| // the error that can be served to clients without revealing internal business logic. | ||||
| // | ||||
| // A typical use of this error would be to first log the Original error, then return | ||||
| // the Safe error and the StatusCode to an API caller. | ||||
| type ErrorWithCode interface { | ||||
| type WithCode interface { | ||||
| 	// Error returns the original internal error for debugging within the GoToSocial logs. | ||||
| 	// This should *NEVER* be returned to a client as it may contain sensitive information. | ||||
| 	Error() string | ||||
|  | @ -40,31 +40,31 @@ type ErrorWithCode interface { | |||
| 	Code() int | ||||
| } | ||||
| 
 | ||||
| type errorWithCode struct { | ||||
| type withCode struct { | ||||
| 	original error | ||||
| 	safe     error | ||||
| 	code     int | ||||
| } | ||||
| 
 | ||||
| func (e errorWithCode) Error() string { | ||||
| func (e withCode) Error() string { | ||||
| 	return e.original.Error() | ||||
| } | ||||
| 
 | ||||
| func (e errorWithCode) Safe() string { | ||||
| func (e withCode) Safe() string { | ||||
| 	return e.safe.Error() | ||||
| } | ||||
| 
 | ||||
| func (e errorWithCode) Code() int { | ||||
| func (e withCode) Code() int { | ||||
| 	return e.code | ||||
| } | ||||
| 
 | ||||
| // NewErrorBadRequest returns an ErrorWithCode 400 with the given original error and optional help text. | ||||
| func NewErrorBadRequest(original error, helpText ...string) ErrorWithCode { | ||||
| func NewErrorBadRequest(original error, helpText ...string) WithCode { | ||||
| 	safe := "bad request" | ||||
| 	if helpText != nil { | ||||
| 		safe = safe + ": " + strings.Join(helpText, ": ") | ||||
| 	} | ||||
| 	return errorWithCode{ | ||||
| 	return withCode{ | ||||
| 		original: original, | ||||
| 		safe:     errors.New(safe), | ||||
| 		code:     http.StatusBadRequest, | ||||
|  | @ -72,12 +72,12 @@ func NewErrorBadRequest(original error, helpText ...string) ErrorWithCode { | |||
| } | ||||
| 
 | ||||
| // NewErrorNotAuthorized returns an ErrorWithCode 401 with the given original error and optional help text. | ||||
| func NewErrorNotAuthorized(original error, helpText ...string) ErrorWithCode { | ||||
| func NewErrorNotAuthorized(original error, helpText ...string) WithCode { | ||||
| 	safe := "not authorized" | ||||
| 	if helpText != nil { | ||||
| 		safe = safe + ": " + strings.Join(helpText, ": ") | ||||
| 	} | ||||
| 	return errorWithCode{ | ||||
| 	return withCode{ | ||||
| 		original: original, | ||||
| 		safe:     errors.New(safe), | ||||
| 		code:     http.StatusUnauthorized, | ||||
|  | @ -85,12 +85,12 @@ func NewErrorNotAuthorized(original error, helpText ...string) ErrorWithCode { | |||
| } | ||||
| 
 | ||||
| // NewErrorForbidden returns an ErrorWithCode 403 with the given original error and optional help text. | ||||
| func NewErrorForbidden(original error, helpText ...string) ErrorWithCode { | ||||
| func NewErrorForbidden(original error, helpText ...string) WithCode { | ||||
| 	safe := "forbidden" | ||||
| 	if helpText != nil { | ||||
| 		safe = safe + ": " + strings.Join(helpText, ": ") | ||||
| 	} | ||||
| 	return errorWithCode{ | ||||
| 	return withCode{ | ||||
| 		original: original, | ||||
| 		safe:     errors.New(safe), | ||||
| 		code:     http.StatusForbidden, | ||||
|  | @ -98,12 +98,12 @@ func NewErrorForbidden(original error, helpText ...string) ErrorWithCode { | |||
| } | ||||
| 
 | ||||
| // NewErrorNotFound returns an ErrorWithCode 404 with the given original error and optional help text. | ||||
| func NewErrorNotFound(original error, helpText ...string) ErrorWithCode { | ||||
| func NewErrorNotFound(original error, helpText ...string) WithCode { | ||||
| 	safe := "404 not found" | ||||
| 	if helpText != nil { | ||||
| 		safe = safe + ": " + strings.Join(helpText, ": ") | ||||
| 	} | ||||
| 	return errorWithCode{ | ||||
| 	return withCode{ | ||||
| 		original: original, | ||||
| 		safe:     errors.New(safe), | ||||
| 		code:     http.StatusNotFound, | ||||
|  | @ -111,12 +111,12 @@ func NewErrorNotFound(original error, helpText ...string) ErrorWithCode { | |||
| } | ||||
| 
 | ||||
| // NewErrorInternalError returns an ErrorWithCode 500 with the given original error and optional help text. | ||||
| func NewErrorInternalError(original error, helpText ...string) ErrorWithCode { | ||||
| func NewErrorInternalError(original error, helpText ...string) WithCode { | ||||
| 	safe := "internal server error" | ||||
| 	if helpText != nil { | ||||
| 		safe = safe + ": " + strings.Join(helpText, ": ") | ||||
| 	} | ||||
| 	return errorWithCode{ | ||||
| 	return withCode{ | ||||
| 		original: original, | ||||
| 		safe:     errors.New(safe), | ||||
| 		code:     http.StatusInternalServerError, | ||||
|  | @ -25,6 +25,7 @@ import ( | |||
| 	"github.com/google/uuid" | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||
|  | @ -202,13 +203,13 @@ func (p *processor) AccountUpdate(authed *oauth.Auth, form *apimodel.UpdateCrede | |||
| 	return acctSensitive, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) AccountStatusesGet(authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, maxID string, pinned bool, mediaOnly bool) ([]apimodel.Status, ErrorWithCode) { | ||||
| func (p *processor) AccountStatusesGet(authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, maxID string, pinned bool, mediaOnly bool) ([]apimodel.Status, gtserror.WithCode) { | ||||
| 	targetAccount := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(targetAccountID, targetAccount); err != nil { | ||||
| 		if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 			return nil, NewErrorNotFound(fmt.Errorf("no entry found for account id %s", targetAccountID)) | ||||
| 			return nil, gtserror.NewErrorNotFound(fmt.Errorf("no entry found for account id %s", targetAccountID)) | ||||
| 		} | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	statuses := []gtsmodel.Status{} | ||||
|  | @ -217,18 +218,18 @@ func (p *processor) AccountStatusesGet(authed *oauth.Auth, targetAccountID strin | |||
| 		if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 			return apiStatuses, nil | ||||
| 		} | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	for _, s := range statuses { | ||||
| 		relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(&s) | ||||
| 		if err != nil { | ||||
| 			return nil, NewErrorInternalError(fmt.Errorf("error getting relevant statuses: %s", err)) | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error getting relevant statuses: %s", err)) | ||||
| 		} | ||||
| 
 | ||||
| 		visible, err := p.db.StatusVisible(&s, targetAccount, authed.Account, relevantAccounts) | ||||
| 		if err != nil { | ||||
| 			return nil, NewErrorInternalError(fmt.Errorf("error checking status visibility: %s", err)) | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking status visibility: %s", err)) | ||||
| 		} | ||||
| 		if !visible { | ||||
| 			continue | ||||
|  | @ -238,16 +239,16 @@ func (p *processor) AccountStatusesGet(authed *oauth.Auth, targetAccountID strin | |||
| 		if s.BoostOfID != "" { | ||||
| 			bs := >smodel.Status{} | ||||
| 			if err := p.db.GetByID(s.BoostOfID, bs); err != nil { | ||||
| 				return nil, NewErrorInternalError(fmt.Errorf("error getting boosted status: %s", err)) | ||||
| 				return nil, gtserror.NewErrorInternalError(fmt.Errorf("error getting boosted status: %s", err)) | ||||
| 			} | ||||
| 			boostedRelevantAccounts, err := p.db.PullRelevantAccountsFromStatus(bs) | ||||
| 			if err != nil { | ||||
| 				return nil, NewErrorInternalError(fmt.Errorf("error getting relevant accounts from boosted status: %s", err)) | ||||
| 				return nil, gtserror.NewErrorInternalError(fmt.Errorf("error getting relevant accounts from boosted status: %s", err)) | ||||
| 			} | ||||
| 
 | ||||
| 			boostedVisible, err := p.db.StatusVisible(bs, relevantAccounts.BoostedAccount, authed.Account, boostedRelevantAccounts) | ||||
| 			if err != nil { | ||||
| 				return nil, NewErrorInternalError(fmt.Errorf("error checking boosted status visibility: %s", err)) | ||||
| 				return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking boosted status visibility: %s", err)) | ||||
| 			} | ||||
| 
 | ||||
| 			if boostedVisible { | ||||
|  | @ -257,7 +258,7 @@ func (p *processor) AccountStatusesGet(authed *oauth.Auth, targetAccountID strin | |||
| 
 | ||||
| 		apiStatus, err := p.tc.StatusToMasto(&s, targetAccount, authed.Account, relevantAccounts.BoostedAccount, relevantAccounts.ReplyToAccount, boostedStatus) | ||||
| 		if err != nil { | ||||
| 			return nil, NewErrorInternalError(fmt.Errorf("error converting status to masto: %s", err)) | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status to masto: %s", err)) | ||||
| 		} | ||||
| 
 | ||||
| 		apiStatuses = append(apiStatuses, *apiStatus) | ||||
|  | @ -266,14 +267,14 @@ func (p *processor) AccountStatusesGet(authed *oauth.Auth, targetAccountID strin | |||
| 	return apiStatuses, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) AccountFollowersGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, ErrorWithCode) { | ||||
| func (p *processor) AccountFollowersGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) { | ||||
| 	blocked, err := p.db.Blocked(authed.Account.ID, targetAccountID) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if blocked { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("block exists between accounts")) | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("block exists between accounts")) | ||||
| 	} | ||||
| 
 | ||||
| 	followers := []gtsmodel.Follow{} | ||||
|  | @ -282,13 +283,13 @@ func (p *processor) AccountFollowersGet(authed *oauth.Auth, targetAccountID stri | |||
| 		if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 			return accounts, nil | ||||
| 		} | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	for _, f := range followers { | ||||
| 		blocked, err := p.db.Blocked(authed.Account.ID, f.AccountID) | ||||
| 		if err != nil { | ||||
| 			return nil, NewErrorInternalError(err) | ||||
| 			return nil, gtserror.NewErrorInternalError(err) | ||||
| 		} | ||||
| 		if blocked { | ||||
| 			continue | ||||
|  | @ -299,7 +300,7 @@ func (p *processor) AccountFollowersGet(authed *oauth.Auth, targetAccountID stri | |||
| 			if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 				continue | ||||
| 			} | ||||
| 			return nil, NewErrorInternalError(err) | ||||
| 			return nil, gtserror.NewErrorInternalError(err) | ||||
| 		} | ||||
| 
 | ||||
| 		// derefence account fields in case we haven't done it already | ||||
|  | @ -310,21 +311,21 @@ func (p *processor) AccountFollowersGet(authed *oauth.Auth, targetAccountID stri | |||
| 
 | ||||
| 		account, err := p.tc.AccountToMastoPublic(a) | ||||
| 		if err != nil { | ||||
| 			return nil, NewErrorInternalError(err) | ||||
| 			return nil, gtserror.NewErrorInternalError(err) | ||||
| 		} | ||||
| 		accounts = append(accounts, *account) | ||||
| 	} | ||||
| 	return accounts, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) AccountFollowingGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, ErrorWithCode) { | ||||
| func (p *processor) AccountFollowingGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) { | ||||
| 	blocked, err := p.db.Blocked(authed.Account.ID, targetAccountID) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if blocked { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("block exists between accounts")) | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("block exists between accounts")) | ||||
| 	} | ||||
| 
 | ||||
| 	following := []gtsmodel.Follow{} | ||||
|  | @ -333,13 +334,13 @@ func (p *processor) AccountFollowingGet(authed *oauth.Auth, targetAccountID stri | |||
| 		if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 			return accounts, nil | ||||
| 		} | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	for _, f := range following { | ||||
| 		blocked, err := p.db.Blocked(authed.Account.ID, f.AccountID) | ||||
| 		if err != nil { | ||||
| 			return nil, NewErrorInternalError(err) | ||||
| 			return nil, gtserror.NewErrorInternalError(err) | ||||
| 		} | ||||
| 		if blocked { | ||||
| 			continue | ||||
|  | @ -350,7 +351,7 @@ func (p *processor) AccountFollowingGet(authed *oauth.Auth, targetAccountID stri | |||
| 			if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 				continue | ||||
| 			} | ||||
| 			return nil, NewErrorInternalError(err) | ||||
| 			return nil, gtserror.NewErrorInternalError(err) | ||||
| 		} | ||||
| 
 | ||||
| 		// derefence account fields in case we haven't done it already | ||||
|  | @ -361,53 +362,53 @@ func (p *processor) AccountFollowingGet(authed *oauth.Auth, targetAccountID stri | |||
| 
 | ||||
| 		account, err := p.tc.AccountToMastoPublic(a) | ||||
| 		if err != nil { | ||||
| 			return nil, NewErrorInternalError(err) | ||||
| 			return nil, gtserror.NewErrorInternalError(err) | ||||
| 		} | ||||
| 		accounts = append(accounts, *account) | ||||
| 	} | ||||
| 	return accounts, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) AccountRelationshipGet(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, ErrorWithCode) { | ||||
| func (p *processor) AccountRelationshipGet(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { | ||||
| 	if authed == nil || authed.Account == nil { | ||||
| 		return nil, NewErrorForbidden(errors.New("not authed")) | ||||
| 		return nil, gtserror.NewErrorForbidden(errors.New("not authed")) | ||||
| 	} | ||||
| 
 | ||||
| 	gtsR, err := p.db.GetRelationship(authed.Account.ID, targetAccountID) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(fmt.Errorf("error getting relationship: %s", err)) | ||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error getting relationship: %s", err)) | ||||
| 	} | ||||
| 
 | ||||
| 	r, err := p.tc.RelationshipToMasto(gtsR) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(fmt.Errorf("error converting relationship: %s", err)) | ||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting relationship: %s", err)) | ||||
| 	} | ||||
| 
 | ||||
| 	return r, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) AccountFollowCreate(authed *oauth.Auth, form *apimodel.AccountFollowRequest) (*apimodel.Relationship, ErrorWithCode) { | ||||
| func (p *processor) AccountFollowCreate(authed *oauth.Auth, form *apimodel.AccountFollowRequest) (*apimodel.Relationship, gtserror.WithCode) { | ||||
| 	// 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) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 	if blocked { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("accountfollowcreate: block exists between accounts")) | ||||
| 		return nil, gtserror.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)) | ||||
| 			return nil, gtserror.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)) | ||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("accountfollowcreate: error checking follow in db: %s", err)) | ||||
| 	} | ||||
| 	if follows { | ||||
| 		// already follows so just return the relationship | ||||
|  | @ -417,7 +418,7 @@ func (p *processor) AccountFollowCreate(authed *oauth.Auth, form *apimodel.Accou | |||
| 	// 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)) | ||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("accountfollowcreate: error checking follow request in db: %s", err)) | ||||
| 	} | ||||
| 	if followRequested { | ||||
| 		// already follow requested so just return the relationship | ||||
|  | @ -445,13 +446,13 @@ func (p *processor) AccountFollowCreate(authed *oauth.Auth, form *apimodel.Accou | |||
| 
 | ||||
| 	// 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)) | ||||
| 		return nil, gtserror.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 nil, gtserror.NewErrorInternalError(fmt.Errorf("accountfollowcreate: error accepting folow request for local unlocked account: %s", err)) | ||||
| 		} | ||||
| 		// return the new relationship | ||||
| 		return p.AccountRelationshipGet(authed, form.TargetAccountID) | ||||
|  | @ -470,21 +471,21 @@ func (p *processor) AccountFollowCreate(authed *oauth.Auth, form *apimodel.Accou | |||
| 	return p.AccountRelationshipGet(authed, form.TargetAccountID) | ||||
| } | ||||
| 
 | ||||
| func (p *processor) AccountFollowRemove(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, ErrorWithCode) { | ||||
| func (p *processor) AccountFollowRemove(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { | ||||
| 	// if there's a block between the accounts we shouldn't do anything | ||||
| 	blocked, err := p.db.Blocked(authed.Account.ID, targetAccountID) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 	if blocked { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("AccountFollowRemove: block exists between accounts")) | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("AccountFollowRemove: block exists between accounts")) | ||||
| 	} | ||||
| 
 | ||||
| 	// make sure the target account actually exists in our db | ||||
| 	targetAcct := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(targetAccountID, targetAcct); err != nil { | ||||
| 		if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 			return nil, NewErrorNotFound(fmt.Errorf("AccountFollowRemove: account %s not found in the db: %s", targetAccountID, err)) | ||||
| 			return nil, gtserror.NewErrorNotFound(fmt.Errorf("AccountFollowRemove: account %s not found in the db: %s", targetAccountID, err)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | @ -498,7 +499,7 @@ func (p *processor) AccountFollowRemove(authed *oauth.Auth, targetAccountID stri | |||
| 	}, fr); err == nil { | ||||
| 		frURI = fr.URI | ||||
| 		if err := p.db.DeleteByID(fr.ID, fr); err != nil { | ||||
| 			return nil, NewErrorInternalError(fmt.Errorf("AccountFollowRemove: error removing follow request from db: %s", err)) | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("AccountFollowRemove: error removing follow request from db: %s", err)) | ||||
| 		} | ||||
| 		frChanged = true | ||||
| 	} | ||||
|  | @ -513,7 +514,7 @@ func (p *processor) AccountFollowRemove(authed *oauth.Auth, targetAccountID stri | |||
| 	}, f); err == nil { | ||||
| 		fURI = f.URI | ||||
| 		if err := p.db.DeleteByID(f.ID, f); err != nil { | ||||
| 			return nil, NewErrorInternalError(fmt.Errorf("AccountFollowRemove: error removing follow from db: %s", err)) | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("AccountFollowRemove: error removing follow from db: %s", err)) | ||||
| 		} | ||||
| 		fChanged = true | ||||
| 	} | ||||
|  |  | |||
|  | @ -27,6 +27,7 @@ import ( | |||
| 	"github.com/go-fed/activity/streams" | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||
| ) | ||||
|  | @ -88,141 +89,141 @@ func (p *processor) authenticateAndDereferenceFediRequest(username string, r *ht | |||
| 	return requestingAccount, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) GetFediUser(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode) { | ||||
| func (p *processor) GetFediUser(requestedUsername string, request *http.Request) (interface{}, gtserror.WithCode) { | ||||
| 	// 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)) | ||||
| 		return nil, gtserror.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) | ||||
| 		return nil, gtserror.NewErrorNotAuthorized(err) | ||||
| 	} | ||||
| 
 | ||||
| 	blocked, err := p.db.Blocked(requestedAccount.ID, requestingAccount.ID) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if blocked { | ||||
| 		return nil, NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)) | ||||
| 		return nil, gtserror.NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)) | ||||
| 	} | ||||
| 
 | ||||
| 	requestedPerson, err := p.tc.AccountToAS(requestedAccount) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	data, err := streams.Serialize(requestedPerson) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	return data, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) GetFediFollowers(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode) { | ||||
| func (p *processor) GetFediFollowers(requestedUsername string, request *http.Request) (interface{}, gtserror.WithCode) { | ||||
| 	// 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)) | ||||
| 		return nil, gtserror.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) | ||||
| 		return nil, gtserror.NewErrorNotAuthorized(err) | ||||
| 	} | ||||
| 
 | ||||
| 	blocked, err := p.db.Blocked(requestedAccount.ID, requestingAccount.ID) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if blocked { | ||||
| 		return nil, NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)) | ||||
| 		return nil, gtserror.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)) | ||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error parsing url %s: %s", requestedAccount.URI, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	requestedFollowers, err := p.federator.FederatingDB().Followers(context.Background(), requestedAccountURI) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(fmt.Errorf("error fetching followers for uri %s: %s", requestedAccountURI.String(), err)) | ||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error fetching followers for uri %s: %s", requestedAccountURI.String(), err)) | ||||
| 	} | ||||
| 
 | ||||
| 	data, err := streams.Serialize(requestedFollowers) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	return data, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) GetFediFollowing(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode) { | ||||
| func (p *processor) GetFediFollowing(requestedUsername string, request *http.Request) (interface{}, gtserror.WithCode) { | ||||
| 	// 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)) | ||||
| 		return nil, gtserror.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) | ||||
| 		return nil, gtserror.NewErrorNotAuthorized(err) | ||||
| 	} | ||||
| 
 | ||||
| 	blocked, err := p.db.Blocked(requestedAccount.ID, requestingAccount.ID) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if blocked { | ||||
| 		return nil, NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)) | ||||
| 		return nil, gtserror.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)) | ||||
| 		return nil, gtserror.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)) | ||||
| 		return nil, gtserror.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 nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	return data, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) GetFediStatus(requestedUsername string, requestedStatusID string, request *http.Request) (interface{}, ErrorWithCode) { | ||||
| func (p *processor) GetFediStatus(requestedUsername string, requestedStatusID string, request *http.Request) (interface{}, gtserror.WithCode) { | ||||
| 	// 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)) | ||||
| 		return nil, gtserror.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) | ||||
| 		return nil, gtserror.NewErrorNotAuthorized(err) | ||||
| 	} | ||||
| 
 | ||||
| 	blocked, err := p.db.Blocked(requestedAccount.ID, requestingAccount.ID) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if blocked { | ||||
| 		return nil, NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)) | ||||
| 		return nil, gtserror.NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)) | ||||
| 	} | ||||
| 
 | ||||
| 	s := >smodel.Status{} | ||||
|  | @ -230,27 +231,27 @@ func (p *processor) GetFediStatus(requestedUsername string, requestedStatusID st | |||
| 		{Key: "id", Value: requestedStatusID}, | ||||
| 		{Key: "account_id", Value: requestedAccount.ID}, | ||||
| 	}, s); err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("database error getting status with id %s and account id %s: %s", requestedStatusID, requestedAccount.ID, err)) | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting status with id %s and account id %s: %s", requestedStatusID, requestedAccount.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	asStatus, err := p.tc.StatusToAS(s) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	data, err := streams.Serialize(asStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	return data, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) GetWebfingerAccount(requestedUsername string, request *http.Request) (*apimodel.WebfingerAccountResponse, ErrorWithCode) { | ||||
| func (p *processor) GetWebfingerAccount(requestedUsername string, request *http.Request) (*apimodel.WebfingerAccountResponse, gtserror.WithCode) { | ||||
| 	// 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)) | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	// return the webfinger representation | ||||
|  |  | |||
|  | @ -21,15 +21,16 @@ package processing | |||
| import ( | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) FollowRequestsGet(auth *oauth.Auth) ([]apimodel.Account, ErrorWithCode) { | ||||
| func (p *processor) FollowRequestsGet(auth *oauth.Auth) ([]apimodel.Account, gtserror.WithCode) { | ||||
| 	frs := []gtsmodel.FollowRequest{} | ||||
| 	if err := p.db.GetFollowRequestsForAccountID(auth.Account.ID, &frs); err != nil { | ||||
| 		if _, ok := err.(db.ErrNoEntries); !ok { | ||||
| 			return nil, NewErrorInternalError(err) | ||||
| 			return nil, gtserror.NewErrorInternalError(err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | @ -37,31 +38,31 @@ func (p *processor) FollowRequestsGet(auth *oauth.Auth) ([]apimodel.Account, Err | |||
| 	for _, fr := range frs { | ||||
| 		acct := >smodel.Account{} | ||||
| 		if err := p.db.GetByID(fr.AccountID, acct); err != nil { | ||||
| 			return nil, NewErrorInternalError(err) | ||||
| 			return nil, gtserror.NewErrorInternalError(err) | ||||
| 		} | ||||
| 		mastoAcct, err := p.tc.AccountToMastoPublic(acct) | ||||
| 		if err != nil { | ||||
| 			return nil, NewErrorInternalError(err) | ||||
| 			return nil, gtserror.NewErrorInternalError(err) | ||||
| 		} | ||||
| 		accts = append(accts, *mastoAcct) | ||||
| 	} | ||||
| 	return accts, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) FollowRequestAccept(auth *oauth.Auth, accountID string) (*apimodel.Relationship, ErrorWithCode) { | ||||
| func (p *processor) FollowRequestAccept(auth *oauth.Auth, accountID string) (*apimodel.Relationship, gtserror.WithCode) { | ||||
| 	follow, err := p.db.AcceptFollowRequest(accountID, auth.Account.ID) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorNotFound(err) | ||||
| 		return nil, gtserror.NewErrorNotFound(err) | ||||
| 	} | ||||
| 
 | ||||
| 	originAccount := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(follow.AccountID, originAccount); err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	targetAccount := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(follow.TargetAccountID, targetAccount); err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	p.fromClientAPI <- gtsmodel.FromClientAPI{ | ||||
|  | @ -74,17 +75,17 @@ func (p *processor) FollowRequestAccept(auth *oauth.Auth, accountID string) (*ap | |||
| 
 | ||||
| 	gtsR, err := p.db.GetRelationship(auth.Account.ID, accountID) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	r, err := p.tc.RelationshipToMasto(gtsR) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	return r, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) FollowRequestDeny(auth *oauth.Auth) ErrorWithCode { | ||||
| func (p *processor) FollowRequestDeny(auth *oauth.Auth) gtserror.WithCode { | ||||
| 	return nil | ||||
| } | ||||
|  |  | |||
|  | @ -127,6 +127,13 @@ func (p *processor) processFromClientAPI(clientMsg gtsmodel.FromClientAPI) error | |||
| 				return errors.New("undo was not parseable as *gtsmodel.Follow") | ||||
| 			} | ||||
| 			return p.federateUnfollow(follow, clientMsg.OriginAccount, clientMsg.TargetAccount) | ||||
| 		case gtsmodel.ActivityStreamsLike: | ||||
| 			// UNDO LIKE/FAVE | ||||
| 			fave, ok := clientMsg.GTSModel.(*gtsmodel.StatusFave) | ||||
| 			if !ok { | ||||
| 				return errors.New("undo was not parseable as *gtsmodel.StatusFave") | ||||
| 			} | ||||
| 			return p.federateUnfave(fave, clientMsg.OriginAccount, clientMsg.TargetAccount) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
|  | @ -210,6 +217,45 @@ func (p *processor) federateUnfollow(follow *gtsmodel.Follow, originAccount *gts | |||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (p *processor) federateUnfave(fave *gtsmodel.StatusFave, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error { | ||||
| 	// if both accounts are local there's nothing to do here | ||||
| 	if originAccount.Domain == "" && targetAccount.Domain == "" { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	// create the AS fave | ||||
| 	asFave, err := p.tc.FaveToAS(fave) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("federateFave: error converting fave to as format: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	targetAccountURI, err := url.Parse(targetAccount.URI) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("error parsing uri %s: %s", targetAccount.URI, err) | ||||
| 	} | ||||
| 
 | ||||
| 	// create an Undo and set the appropriate actor on it | ||||
| 	undo := streams.NewActivityStreamsUndo() | ||||
| 	undo.SetActivityStreamsActor(asFave.GetActivityStreamsActor()) | ||||
| 
 | ||||
| 	// Set the fave as the 'object' property. | ||||
| 	undoObject := streams.NewActivityStreamsObjectProperty() | ||||
| 	undoObject.AppendActivityStreamsLike(asFave) | ||||
| 	undo.SetActivityStreamsObject(undoObject) | ||||
| 
 | ||||
| 	// Set the To of the undo as the target of the fave | ||||
| 	undoTo := streams.NewActivityStreamsToProperty() | ||||
| 	undoTo.AppendIRI(targetAccountURI) | ||||
| 	undo.SetActivityStreamsTo(undoTo) | ||||
| 
 | ||||
| 	outboxIRI, err := url.Parse(originAccount.OutboxURI) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("federateFave: error parsing outboxURI %s: %s", originAccount.OutboxURI, err) | ||||
| 	} | ||||
| 	_, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, undo) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (p *processor) federateAcceptFollowRequest(follow *gtsmodel.Follow, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error { | ||||
| 	// if both accounts are local there's nothing to do here | ||||
| 	if originAccount.Domain == "" && targetAccount.Domain == "" { | ||||
|  |  | |||
|  | @ -300,3 +300,7 @@ func (p *processor) timelineStatusForAccount(status *gtsmodel.Status, accountID | |||
| 		errors <- fmt.Errorf("initTimelineFor: error ingesting status %s: %s", status.ID, err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (p *processor) fullyDeleteStatus(status *gtsmodel.Status, accountID string) error { | ||||
| 	return nil | ||||
| } | ||||
|  |  | |||
|  | @ -23,18 +23,19 @@ import ( | |||
| 
 | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) InstanceGet(domain string) (*apimodel.Instance, ErrorWithCode) { | ||||
| func (p *processor) InstanceGet(domain string) (*apimodel.Instance, gtserror.WithCode) { | ||||
| 	i := >smodel.Instance{} | ||||
| 	if err := p.db.GetWhere([]db.Where{{Key: "domain", Value: domain}}, i); err != nil { | ||||
| 		return nil, NewErrorInternalError(fmt.Errorf("db error fetching instance %s: %s", p.config.Host, err)) | ||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error fetching instance %s: %s", p.config.Host, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	ai, err := p.tc.InstanceToMasto(i) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(fmt.Errorf("error converting instance to api representation: %s", err)) | ||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting instance to api representation: %s", err)) | ||||
| 	} | ||||
| 
 | ||||
| 	return ai, nil | ||||
|  |  | |||
|  | @ -28,6 +28,7 @@ import ( | |||
| 
 | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
|  | @ -92,64 +93,64 @@ func (p *processor) MediaCreate(authed *oauth.Auth, form *apimodel.AttachmentReq | |||
| 	return &mastoAttachment, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) MediaGet(authed *oauth.Auth, mediaAttachmentID string) (*apimodel.Attachment, ErrorWithCode) { | ||||
| func (p *processor) MediaGet(authed *oauth.Auth, mediaAttachmentID string) (*apimodel.Attachment, gtserror.WithCode) { | ||||
| 	attachment := >smodel.MediaAttachment{} | ||||
| 	if err := p.db.GetByID(mediaAttachmentID, attachment); err != nil { | ||||
| 		if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 			// attachment doesn't exist | ||||
| 			return nil, NewErrorNotFound(errors.New("attachment doesn't exist in the db")) | ||||
| 			return nil, gtserror.NewErrorNotFound(errors.New("attachment doesn't exist in the db")) | ||||
| 		} | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("db error getting attachment: %s", err)) | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("db error getting attachment: %s", err)) | ||||
| 	} | ||||
| 
 | ||||
| 	if attachment.AccountID != authed.Account.ID { | ||||
| 		return nil, NewErrorNotFound(errors.New("attachment not owned by requesting account")) | ||||
| 		return nil, gtserror.NewErrorNotFound(errors.New("attachment not owned by requesting account")) | ||||
| 	} | ||||
| 
 | ||||
| 	a, err := p.tc.AttachmentToMasto(attachment) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("error converting attachment: %s", err)) | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error converting attachment: %s", err)) | ||||
| 	} | ||||
| 
 | ||||
| 	return &a, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) MediaUpdate(authed *oauth.Auth, mediaAttachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, ErrorWithCode) { | ||||
| func (p *processor) MediaUpdate(authed *oauth.Auth, mediaAttachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode) { | ||||
| 	attachment := >smodel.MediaAttachment{} | ||||
| 	if err := p.db.GetByID(mediaAttachmentID, attachment); err != nil { | ||||
| 		if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 			// attachment doesn't exist | ||||
| 			return nil, NewErrorNotFound(errors.New("attachment doesn't exist in the db")) | ||||
| 			return nil, gtserror.NewErrorNotFound(errors.New("attachment doesn't exist in the db")) | ||||
| 		} | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("db error getting attachment: %s", err)) | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("db error getting attachment: %s", err)) | ||||
| 	} | ||||
| 
 | ||||
| 	if attachment.AccountID != authed.Account.ID { | ||||
| 		return nil, NewErrorNotFound(errors.New("attachment not owned by requesting account")) | ||||
| 		return nil, gtserror.NewErrorNotFound(errors.New("attachment not owned by requesting account")) | ||||
| 	} | ||||
| 
 | ||||
| 	if form.Description != nil { | ||||
| 		attachment.Description = *form.Description | ||||
| 		if err := p.db.UpdateByID(mediaAttachmentID, attachment); err != nil { | ||||
| 			return nil, NewErrorInternalError(fmt.Errorf("database error updating description: %s", err)) | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("database error updating description: %s", err)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if form.Focus != nil { | ||||
| 		focusx, focusy, err := parseFocus(*form.Focus) | ||||
| 		if err != nil { | ||||
| 			return nil, NewErrorBadRequest(err) | ||||
| 			return nil, gtserror.NewErrorBadRequest(err) | ||||
| 		} | ||||
| 		attachment.FileMeta.Focus.X = focusx | ||||
| 		attachment.FileMeta.Focus.Y = focusy | ||||
| 		if err := p.db.UpdateByID(mediaAttachmentID, attachment); err != nil { | ||||
| 			return nil, NewErrorInternalError(fmt.Errorf("database error updating focus: %s", err)) | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("database error updating focus: %s", err)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	a, err := p.tc.AttachmentToMasto(attachment) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("error converting attachment: %s", err)) | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error converting attachment: %s", err)) | ||||
| 	} | ||||
| 
 | ||||
| 	return &a, nil | ||||
|  | @ -159,37 +160,37 @@ func (p *processor) FileGet(authed *oauth.Auth, form *apimodel.GetContentRequest | |||
| 	// parse the form fields | ||||
| 	mediaSize, err := media.ParseMediaSize(form.MediaSize) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("media size %s not valid", form.MediaSize)) | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("media size %s not valid", form.MediaSize)) | ||||
| 	} | ||||
| 
 | ||||
| 	mediaType, err := media.ParseMediaType(form.MediaType) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("media type %s not valid", form.MediaType)) | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("media type %s not valid", form.MediaType)) | ||||
| 	} | ||||
| 
 | ||||
| 	spl := strings.Split(form.FileName, ".") | ||||
| 	if len(spl) != 2 || spl[0] == "" || spl[1] == "" { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("file name %s not parseable", form.FileName)) | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("file name %s not parseable", form.FileName)) | ||||
| 	} | ||||
| 	wantedMediaID := spl[0] | ||||
| 
 | ||||
| 	// get the account that owns the media and make sure it's not suspended | ||||
| 	acct := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(form.AccountID, acct); err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("account with id %s could not be selected from the db: %s", form.AccountID, err)) | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("account with id %s could not be selected from the db: %s", form.AccountID, err)) | ||||
| 	} | ||||
| 	if !acct.SuspendedAt.IsZero() { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("account with id %s is suspended", form.AccountID)) | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("account with id %s is suspended", form.AccountID)) | ||||
| 	} | ||||
| 
 | ||||
| 	// make sure the requesting account and the media account don't block each other | ||||
| 	if authed.Account != nil { | ||||
| 		blocked, err := p.db.Blocked(authed.Account.ID, form.AccountID) | ||||
| 		if err != nil { | ||||
| 			return nil, NewErrorNotFound(fmt.Errorf("block status could not be established between accounts %s and %s: %s", form.AccountID, authed.Account.ID, err)) | ||||
| 			return nil, gtserror.NewErrorNotFound(fmt.Errorf("block status could not be established between accounts %s and %s: %s", form.AccountID, authed.Account.ID, err)) | ||||
| 		} | ||||
| 		if blocked { | ||||
| 			return nil, NewErrorNotFound(fmt.Errorf("block exists between accounts %s and %s", form.AccountID, authed.Account.ID)) | ||||
| 			return nil, gtserror.NewErrorNotFound(fmt.Errorf("block exists between accounts %s and %s", form.AccountID, authed.Account.ID)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | @ -201,10 +202,10 @@ func (p *processor) FileGet(authed *oauth.Auth, form *apimodel.GetContentRequest | |||
| 	case media.Emoji: | ||||
| 		e := >smodel.Emoji{} | ||||
| 		if err := p.db.GetByID(wantedMediaID, e); err != nil { | ||||
| 			return nil, NewErrorNotFound(fmt.Errorf("emoji %s could not be taken from the db: %s", wantedMediaID, err)) | ||||
| 			return nil, gtserror.NewErrorNotFound(fmt.Errorf("emoji %s could not be taken from the db: %s", wantedMediaID, err)) | ||||
| 		} | ||||
| 		if e.Disabled { | ||||
| 			return nil, NewErrorNotFound(fmt.Errorf("emoji %s has been disabled", wantedMediaID)) | ||||
| 			return nil, gtserror.NewErrorNotFound(fmt.Errorf("emoji %s has been disabled", wantedMediaID)) | ||||
| 		} | ||||
| 		switch mediaSize { | ||||
| 		case media.Original: | ||||
|  | @ -214,15 +215,15 @@ func (p *processor) FileGet(authed *oauth.Auth, form *apimodel.GetContentRequest | |||
| 			content.ContentType = e.ImageStaticContentType | ||||
| 			storagePath = e.ImageStaticPath | ||||
| 		default: | ||||
| 			return nil, NewErrorNotFound(fmt.Errorf("media size %s not recognized for emoji", mediaSize)) | ||||
| 			return nil, gtserror.NewErrorNotFound(fmt.Errorf("media size %s not recognized for emoji", mediaSize)) | ||||
| 		} | ||||
| 	case media.Attachment, media.Header, media.Avatar: | ||||
| 		a := >smodel.MediaAttachment{} | ||||
| 		if err := p.db.GetByID(wantedMediaID, a); err != nil { | ||||
| 			return nil, NewErrorNotFound(fmt.Errorf("attachment %s could not be taken from the db: %s", wantedMediaID, err)) | ||||
| 			return nil, gtserror.NewErrorNotFound(fmt.Errorf("attachment %s could not be taken from the db: %s", wantedMediaID, err)) | ||||
| 		} | ||||
| 		if a.AccountID != form.AccountID { | ||||
| 			return nil, NewErrorNotFound(fmt.Errorf("attachment %s is not owned by %s", wantedMediaID, form.AccountID)) | ||||
| 			return nil, gtserror.NewErrorNotFound(fmt.Errorf("attachment %s is not owned by %s", wantedMediaID, form.AccountID)) | ||||
| 		} | ||||
| 		switch mediaSize { | ||||
| 		case media.Original: | ||||
|  | @ -232,13 +233,13 @@ func (p *processor) FileGet(authed *oauth.Auth, form *apimodel.GetContentRequest | |||
| 			content.ContentType = a.Thumbnail.ContentType | ||||
| 			storagePath = a.Thumbnail.Path | ||||
| 		default: | ||||
| 			return nil, NewErrorNotFound(fmt.Errorf("media size %s not recognized for attachment", mediaSize)) | ||||
| 			return nil, gtserror.NewErrorNotFound(fmt.Errorf("media size %s not recognized for attachment", mediaSize)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	bytes, err := p.storage.RetrieveFileFrom(storagePath) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("error retrieving from storage: %s", err)) | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error retrieving from storage: %s", err)) | ||||
| 	} | ||||
| 
 | ||||
| 	content.ContentLength = int64(len(bytes)) | ||||
|  |  | |||
|  | @ -20,15 +20,16 @@ package processing | |||
| 
 | ||||
| import ( | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) NotificationsGet(authed *oauth.Auth, limit int, maxID string, sinceID string) ([]*apimodel.Notification, ErrorWithCode) { | ||||
| func (p *processor) NotificationsGet(authed *oauth.Auth, limit int, maxID string, sinceID string) ([]*apimodel.Notification, gtserror.WithCode) { | ||||
| 	l := p.log.WithField("func", "NotificationsGet") | ||||
| 
 | ||||
| 	notifs, err := p.db.GetNotificationsForAccount(authed.Account.ID, limit, maxID, sinceID) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	mastoNotifs := []*apimodel.Notification{} | ||||
|  |  | |||
|  | @ -28,9 +28,11 @@ import ( | |||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing/status" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing/timeline" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||
| ) | ||||
|  | @ -71,17 +73,17 @@ type Processor interface { | |||
| 	AccountUpdate(authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error) | ||||
| 	// AccountStatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for | ||||
| 	// the account given in authed. | ||||
| 	AccountStatusesGet(authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, maxID string, pinned bool, mediaOnly bool) ([]apimodel.Status, ErrorWithCode) | ||||
| 	AccountStatusesGet(authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, maxID string, pinned bool, mediaOnly bool) ([]apimodel.Status, gtserror.WithCode) | ||||
| 	// 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, gtserror.WithCode) | ||||
| 	// AccountFollowingGet fetches a list of the accounts that target account is following. | ||||
| 	AccountFollowingGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, ErrorWithCode) | ||||
| 	AccountFollowingGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) | ||||
| 	// 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, gtserror.WithCode) | ||||
| 	// AccountFollowCreate handles a follow request to an account, either remote or local. | ||||
| 	AccountFollowCreate(authed *oauth.Auth, form *apimodel.AccountFollowRequest) (*apimodel.Relationship, ErrorWithCode) | ||||
| 	AccountFollowCreate(authed *oauth.Auth, form *apimodel.AccountFollowRequest) (*apimodel.Relationship, gtserror.WithCode) | ||||
| 	// AccountFollowRemove handles the removal of a follow/follow request to an account, either remote or local. | ||||
| 	AccountFollowRemove(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, ErrorWithCode) | ||||
| 	AccountFollowRemove(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) | ||||
| 
 | ||||
| 	// 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) | ||||
|  | @ -93,25 +95,25 @@ type Processor interface { | |||
| 	FileGet(authed *oauth.Auth, form *apimodel.GetContentRequestForm) (*apimodel.Content, error) | ||||
| 
 | ||||
| 	// FollowRequestsGet handles the getting of the authed account's incoming follow requests | ||||
| 	FollowRequestsGet(auth *oauth.Auth) ([]apimodel.Account, ErrorWithCode) | ||||
| 	FollowRequestsGet(auth *oauth.Auth) ([]apimodel.Account, gtserror.WithCode) | ||||
| 	// FollowRequestAccept handles the acceptance of a follow request from the given account ID | ||||
| 	FollowRequestAccept(auth *oauth.Auth, accountID string) (*apimodel.Relationship, ErrorWithCode) | ||||
| 	FollowRequestAccept(auth *oauth.Auth, accountID string) (*apimodel.Relationship, gtserror.WithCode) | ||||
| 
 | ||||
| 	// InstanceGet retrieves instance information for serving at api/v1/instance | ||||
| 	InstanceGet(domain string) (*apimodel.Instance, ErrorWithCode) | ||||
| 	InstanceGet(domain string) (*apimodel.Instance, gtserror.WithCode) | ||||
| 
 | ||||
| 	// MediaCreate handles the creation of a media attachment, using the given form. | ||||
| 	MediaCreate(authed *oauth.Auth, form *apimodel.AttachmentRequest) (*apimodel.Attachment, error) | ||||
| 	// MediaGet handles the GET of a media attachment with the given ID | ||||
| 	MediaGet(authed *oauth.Auth, attachmentID string) (*apimodel.Attachment, ErrorWithCode) | ||||
| 	MediaGet(authed *oauth.Auth, attachmentID string) (*apimodel.Attachment, gtserror.WithCode) | ||||
| 	// MediaUpdate handles the PUT of a media attachment with the given ID and form | ||||
| 	MediaUpdate(authed *oauth.Auth, attachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, ErrorWithCode) | ||||
| 	MediaUpdate(authed *oauth.Auth, attachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode) | ||||
| 
 | ||||
| 	// NotificationsGet | ||||
| 	NotificationsGet(authed *oauth.Auth, limit int, maxID string, sinceID string) ([]*apimodel.Notification, ErrorWithCode) | ||||
| 	NotificationsGet(authed *oauth.Auth, limit int, maxID string, sinceID string) ([]*apimodel.Notification, gtserror.WithCode) | ||||
| 
 | ||||
| 	// SearchGet performs a search with the given params, resolving/dereferencing remotely as desired | ||||
| 	SearchGet(authed *oauth.Auth, searchQuery *apimodel.SearchQuery) (*apimodel.SearchResult, ErrorWithCode) | ||||
| 	SearchGet(authed *oauth.Auth, searchQuery *apimodel.SearchQuery) (*apimodel.SearchResult, gtserror.WithCode) | ||||
| 
 | ||||
| 	// StatusCreate processes the given form to create a new status, returning the api model representation of that status if it's OK. | ||||
| 	StatusCreate(authed *oauth.Auth, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, error) | ||||
|  | @ -120,9 +122,9 @@ type Processor interface { | |||
| 	// StatusFave processes the faving of a given status, returning the updated status if the fave goes through. | ||||
| 	StatusFave(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) | ||||
| 	// StatusBoost processes the boost/reblog of a given status, returning the newly-created boost if all is well. | ||||
| 	StatusBoost(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, ErrorWithCode) | ||||
| 	StatusBoost(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) | ||||
| 	// StatusBoostedBy returns a slice of accounts that have boosted the given status, filtered according to privacy settings. | ||||
| 	StatusBoostedBy(authed *oauth.Auth, targetStatusID string) ([]*apimodel.Account, ErrorWithCode) | ||||
| 	StatusBoostedBy(authed *oauth.Auth, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) | ||||
| 	// StatusFavedBy returns a slice of accounts that have liked the given status, filtered according to privacy settings. | ||||
| 	StatusFavedBy(authed *oauth.Auth, targetStatusID string) ([]*apimodel.Account, error) | ||||
| 	// StatusGet gets the given status, taking account of privacy settings and blocks etc. | ||||
|  | @ -130,12 +132,12 @@ type Processor interface { | |||
| 	// StatusUnfave processes the unfaving of a given status, returning the updated status if the fave goes through. | ||||
| 	StatusUnfave(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) | ||||
| 	// StatusGetContext returns the context (previous and following posts) from the given status ID | ||||
| 	StatusGetContext(authed *oauth.Auth, targetStatusID string) (*apimodel.Context, ErrorWithCode) | ||||
| 	StatusGetContext(authed *oauth.Auth, targetStatusID string) (*apimodel.Context, gtserror.WithCode) | ||||
| 
 | ||||
| 	// HomeTimelineGet returns statuses from the home timeline, with the given filters/parameters. | ||||
| 	HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) ([]*apimodel.Status, ErrorWithCode) | ||||
| 	HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) ([]*apimodel.Status, gtserror.WithCode) | ||||
| 	// PublicTimelineGet returns statuses from the public/local timeline, with the given filters/parameters. | ||||
| 	PublicTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) ([]*apimodel.Status, ErrorWithCode) | ||||
| 	PublicTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) ([]*apimodel.Status, gtserror.WithCode) | ||||
| 
 | ||||
| 	/* | ||||
| 		FEDERATION API-FACING PROCESSING FUNCTIONS | ||||
|  | @ -147,22 +149,22 @@ type Processor interface { | |||
| 
 | ||||
| 	// GetFediUser handles the getting of a fedi/activitypub representation of a user/account, performing appropriate authentication | ||||
| 	// before returning a JSON serializable interface to the caller. | ||||
| 	GetFediUser(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode) | ||||
| 	GetFediUser(requestedUsername string, request *http.Request) (interface{}, gtserror.WithCode) | ||||
| 
 | ||||
| 	// GetFediFollowers handles the getting of a fedi/activitypub representation of a user/account's followers, performing appropriate | ||||
| 	// authentication before returning a JSON serializable interface to the caller. | ||||
| 	GetFediFollowers(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode) | ||||
| 	GetFediFollowers(requestedUsername string, request *http.Request) (interface{}, gtserror.WithCode) | ||||
| 
 | ||||
| 	// 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) | ||||
| 	GetFediFollowing(requestedUsername string, request *http.Request) (interface{}, gtserror.WithCode) | ||||
| 
 | ||||
| 	// 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) | ||||
| 	GetFediStatus(requestedUsername string, requestedStatusID string, request *http.Request) (interface{}, gtserror.WithCode) | ||||
| 
 | ||||
| 	// GetWebfingerAccount handles the GET for a webfinger resource. Most commonly, it will be used for returning account lookups. | ||||
| 	GetWebfingerAccount(requestedUsername string, request *http.Request) (*apimodel.WebfingerAccountResponse, ErrorWithCode) | ||||
| 	GetWebfingerAccount(requestedUsername string, request *http.Request) (*apimodel.WebfingerAccountResponse, gtserror.WithCode) | ||||
| 
 | ||||
| 	// InboxPost handles POST requests to a user's inbox for new activitypub messages. | ||||
| 	// | ||||
|  | @ -179,10 +181,7 @@ type Processor interface { | |||
| 
 | ||||
| // processor just implements the Processor interface | ||||
| type processor struct { | ||||
| 	// federator     pub.FederatingActor | ||||
| 	// toClientAPI   chan gtsmodel.ToClientAPI | ||||
| 	fromClientAPI chan gtsmodel.FromClientAPI | ||||
| 	// toFederator   chan gtsmodel.ToFederator | ||||
| 	fromClientAPI   chan gtsmodel.FromClientAPI | ||||
| 	fromFederator   chan gtsmodel.FromFederator | ||||
| 	federator       federation.Federator | ||||
| 	stop            chan interface{} | ||||
|  | @ -194,15 +193,25 @@ type processor struct { | |||
| 	storage         blob.Storage | ||||
| 	timelineManager timeline.Manager | ||||
| 	db              db.DB | ||||
| 
 | ||||
| 	/* | ||||
| 		SUB-PROCESSORS | ||||
| 	*/ | ||||
| 
 | ||||
| 	statusProcessor status.Processor | ||||
| } | ||||
| 
 | ||||
| // NewProcessor returns a new Processor that uses the given federator and logger | ||||
| func NewProcessor(config *config.Config, tc typeutils.TypeConverter, federator federation.Federator, oauthServer oauth.Server, mediaHandler media.Handler, storage blob.Storage, timelineManager timeline.Manager, db db.DB, log *logrus.Logger) Processor { | ||||
| 
 | ||||
| 	fromClientAPI := make(chan gtsmodel.FromClientAPI, 1000) | ||||
| 	fromFederator := make(chan gtsmodel.FromFederator, 1000) | ||||
| 
 | ||||
| 	statusProcessor := status.New(db, tc, config, fromClientAPI, log) | ||||
| 
 | ||||
| 	return &processor{ | ||||
| 		// toClientAPI:   make(chan gtsmodel.ToClientAPI, 100), | ||||
| 		fromClientAPI: make(chan gtsmodel.FromClientAPI, 100), | ||||
| 		// toFederator:   make(chan gtsmodel.ToFederator, 100), | ||||
| 		fromFederator:   make(chan gtsmodel.FromFederator, 100), | ||||
| 		fromClientAPI:   fromClientAPI, | ||||
| 		fromFederator:   fromFederator, | ||||
| 		federator:       federator, | ||||
| 		stop:            make(chan interface{}), | ||||
| 		log:             log, | ||||
|  | @ -213,6 +222,8 @@ func NewProcessor(config *config.Config, tc typeutils.TypeConverter, federator f | |||
| 		storage:         storage, | ||||
| 		timelineManager: timelineManager, | ||||
| 		db:              db, | ||||
| 
 | ||||
| 		statusProcessor: statusProcessor, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,12 +27,13 @@ import ( | |||
| 	"github.com/sirupsen/logrus" | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) SearchGet(authed *oauth.Auth, searchQuery *apimodel.SearchQuery) (*apimodel.SearchResult, ErrorWithCode) { | ||||
| func (p *processor) SearchGet(authed *oauth.Auth, searchQuery *apimodel.SearchQuery) (*apimodel.SearchResult, gtserror.WithCode) { | ||||
| 	l := p.log.WithFields(logrus.Fields{ | ||||
| 		"func":  "SearchGet", | ||||
| 		"query": searchQuery.Query, | ||||
|  | @ -164,7 +165,7 @@ func (p *processor) searchStatusByURI(authed *oauth.Auth, uri *url.URL, resolve | |||
| 			// first turn it into a gtsmodel.Status | ||||
| 			status, err := p.tc.ASStatusToStatus(statusable) | ||||
| 			if err != nil { | ||||
| 				return nil, NewErrorInternalError(err) | ||||
| 				return nil, gtserror.NewErrorInternalError(err) | ||||
| 			} | ||||
| 
 | ||||
| 			// put it in the DB so it gets a UUID | ||||
|  |  | |||
|  | @ -19,531 +19,43 @@ | |||
| package processing | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/google/uuid" | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) StatusCreate(auth *oauth.Auth, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, error) { | ||||
| 	uris := util.GenerateURIsForAccount(auth.Account.Username, p.config.Protocol, p.config.Host) | ||||
| 	thisStatusID := uuid.NewString() | ||||
| 	thisStatusURI := fmt.Sprintf("%s/%s", uris.StatusesURI, thisStatusID) | ||||
| 	thisStatusURL := fmt.Sprintf("%s/%s", uris.StatusesURL, thisStatusID) | ||||
| 	newStatus := >smodel.Status{ | ||||
| 		ID:                       thisStatusID, | ||||
| 		URI:                      thisStatusURI, | ||||
| 		URL:                      thisStatusURL, | ||||
| 		Content:                  util.HTMLFormat(form.Status), | ||||
| 		CreatedAt:                time.Now(), | ||||
| 		UpdatedAt:                time.Now(), | ||||
| 		Local:                    true, | ||||
| 		AccountID:                auth.Account.ID, | ||||
| 		ContentWarning:           form.SpoilerText, | ||||
| 		ActivityStreamsType:      gtsmodel.ActivityStreamsNote, | ||||
| 		Sensitive:                form.Sensitive, | ||||
| 		Language:                 form.Language, | ||||
| 		CreatedWithApplicationID: auth.Application.ID, | ||||
| 		Text:                     form.Status, | ||||
| 	} | ||||
| 
 | ||||
| 	// check if replyToID is ok | ||||
| 	if err := p.processReplyToID(form, auth.Account.ID, newStatus); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// check if mediaIDs are ok | ||||
| 	if err := p.processMediaIDs(form, auth.Account.ID, newStatus); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// check if visibility settings are ok | ||||
| 	if err := p.processVisibility(form, auth.Account.Privacy, newStatus); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// handle language settings | ||||
| 	if err := p.processLanguage(form, auth.Account.Language, newStatus); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// handle mentions | ||||
| 	if err := p.processMentions(form, auth.Account.ID, newStatus); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if err := p.processTags(form, auth.Account.ID, newStatus); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if err := p.processEmojis(form, auth.Account.ID, newStatus); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// put the new status in the database, generating an ID for it in the process | ||||
| 	if err := p.db.Put(newStatus); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// change the status ID of the media attachments to the new status | ||||
| 	for _, a := range newStatus.GTSMediaAttachments { | ||||
| 		a.StatusID = newStatus.ID | ||||
| 		a.UpdatedAt = time.Now() | ||||
| 		if err := p.db.UpdateByID(a.ID, a); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// put the new status in the appropriate channel for async processing | ||||
| 	p.fromClientAPI <- gtsmodel.FromClientAPI{ | ||||
| 		APObjectType:   newStatus.ActivityStreamsType, | ||||
| 		APActivityType: gtsmodel.ActivityStreamsCreate, | ||||
| 		GTSModel:       newStatus, | ||||
| 	} | ||||
| 
 | ||||
| 	// return the frontend representation of the new status to the submitter | ||||
| 	return p.tc.StatusToMasto(newStatus, auth.Account, auth.Account, nil, newStatus.GTSReplyToAccount, nil) | ||||
| func (p *processor) StatusCreate(authed *oauth.Auth, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, error) { | ||||
| 	return p.statusProcessor.Create(authed.Account, authed.Application, form) | ||||
| } | ||||
| 
 | ||||
| func (p *processor) StatusDelete(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) { | ||||
| 	l := p.log.WithField("func", "StatusDelete") | ||||
| 	l.Tracef("going to search for target status %s", targetStatusID) | ||||
| 	targetStatus := >smodel.Status{} | ||||
| 	if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { | ||||
| 		return nil, fmt.Errorf("error fetching status %s: %s", targetStatusID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	if targetStatus.AccountID != authed.Account.ID { | ||||
| 		return nil, errors.New("status doesn't belong to requesting account") | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to get relevant accounts") | ||||
| 	relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error fetching related accounts for status %s: %s", targetStatusID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	var boostOfStatus *gtsmodel.Status | ||||
| 	if targetStatus.BoostOfID != "" { | ||||
| 		boostOfStatus = >smodel.Status{} | ||||
| 		if err := p.db.GetByID(targetStatus.BoostOfID, boostOfStatus); err != nil { | ||||
| 			return nil, fmt.Errorf("error fetching boosted status %s: %s", targetStatus.BoostOfID, err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	mastoStatus, err := p.tc.StatusToMasto(targetStatus, authed.Account, authed.Account, relevantAccounts.BoostedAccount, relevantAccounts.ReplyToAccount, boostOfStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := p.db.DeleteByID(targetStatus.ID, targetStatus); err != nil { | ||||
| 		return nil, fmt.Errorf("error deleting status from the database: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return mastoStatus, nil | ||||
| 	return p.statusProcessor.Delete(authed.Account, targetStatusID) | ||||
| } | ||||
| 
 | ||||
| func (p *processor) StatusFave(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) { | ||||
| 	l := p.log.WithField("func", "StatusFave") | ||||
| 	l.Tracef("going to search for target status %s", targetStatusID) | ||||
| 	targetStatus := >smodel.Status{} | ||||
| 	if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { | ||||
| 		return nil, fmt.Errorf("error fetching status %s: %s", targetStatusID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Tracef("going to search for target account %s", targetStatus.AccountID) | ||||
| 	targetAccount := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(targetStatus.AccountID, targetAccount); err != nil { | ||||
| 		return nil, fmt.Errorf("error fetching target account %s: %s", targetStatus.AccountID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to get relevant accounts") | ||||
| 	relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error fetching related accounts for status %s: %s", targetStatusID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	var boostOfStatus *gtsmodel.Status | ||||
| 	if targetStatus.BoostOfID != "" { | ||||
| 		boostOfStatus = >smodel.Status{} | ||||
| 		if err := p.db.GetByID(targetStatus.BoostOfID, boostOfStatus); err != nil { | ||||
| 			return nil, fmt.Errorf("error fetching boosted status %s: %s", targetStatus.BoostOfID, err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to see if status is visible") | ||||
| 	visible, err := p.db.StatusVisible(targetStatus, targetAccount, authed.Account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	if !visible { | ||||
| 		return nil, errors.New("status is not visible") | ||||
| 	} | ||||
| 
 | ||||
| 	// is the status faveable? | ||||
| 	if targetStatus.VisibilityAdvanced != nil { | ||||
| 		if !targetStatus.VisibilityAdvanced.Likeable { | ||||
| 			return nil, errors.New("status is not faveable") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// first check if the status is already faved, if so we don't need to do anything | ||||
| 	newFave := true | ||||
| 	gtsFave := >smodel.Status{} | ||||
| 	if err := p.db.GetWhere([]db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: authed.Account.ID}}, gtsFave); err == nil { | ||||
| 		// we already have a fave for this status | ||||
| 		newFave = false | ||||
| 	} | ||||
| 
 | ||||
| 	if newFave { | ||||
| 		thisFaveID := uuid.NewString() | ||||
| 
 | ||||
| 		// we need to create a new fave in the database | ||||
| 		gtsFave := >smodel.StatusFave{ | ||||
| 			ID:               thisFaveID, | ||||
| 			AccountID:        authed.Account.ID, | ||||
| 			TargetAccountID:  targetAccount.ID, | ||||
| 			StatusID:         targetStatus.ID, | ||||
| 			URI:              util.GenerateURIForLike(authed.Account.Username, p.config.Protocol, p.config.Host, thisFaveID), | ||||
| 			GTSStatus:        targetStatus, | ||||
| 			GTSTargetAccount: targetAccount, | ||||
| 			GTSFavingAccount: authed.Account, | ||||
| 		} | ||||
| 
 | ||||
| 		if err := p.db.Put(gtsFave); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		// send the new fave through the processor channel for federation etc | ||||
| 		p.fromClientAPI <- gtsmodel.FromClientAPI{ | ||||
| 			APObjectType:   gtsmodel.ActivityStreamsLike, | ||||
| 			APActivityType: gtsmodel.ActivityStreamsCreate, | ||||
| 			GTSModel:       gtsFave, | ||||
| 			OriginAccount:  authed.Account, | ||||
| 			TargetAccount:  targetAccount, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// return the mastodon representation of the target status | ||||
| 	mastoStatus, err := p.tc.StatusToMasto(targetStatus, targetAccount, authed.Account, relevantAccounts.BoostedAccount, relevantAccounts.ReplyToAccount, boostOfStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	return mastoStatus, nil | ||||
| 	return p.statusProcessor.Fave(authed.Account, targetStatusID) | ||||
| } | ||||
| 
 | ||||
| func (p *processor) StatusBoost(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, ErrorWithCode) { | ||||
| 	l := p.log.WithField("func", "StatusBoost") | ||||
| 
 | ||||
| 	l.Tracef("going to search for target status %s", targetStatusID) | ||||
| 	targetStatus := >smodel.Status{} | ||||
| 	if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Tracef("going to search for target account %s", targetStatus.AccountID) | ||||
| 	targetAccount := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(targetStatus.AccountID, targetAccount); err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("error fetching target account %s: %s", targetStatus.AccountID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to get relevant accounts") | ||||
| 	relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("error fetching related accounts for status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to see if status is visible") | ||||
| 	visible, err := p.db.StatusVisible(targetStatus, targetAccount, authed.Account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	if !visible { | ||||
| 		return nil, NewErrorNotFound(errors.New("status is not visible")) | ||||
| 	} | ||||
| 
 | ||||
| 	if targetStatus.VisibilityAdvanced != nil { | ||||
| 		if !targetStatus.VisibilityAdvanced.Boostable { | ||||
| 			return nil, NewErrorForbidden(errors.New("status is not boostable")) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// it's visible! it's boostable! so let's boost the FUCK out of it | ||||
| 	boostWrapperStatus, err := p.tc.StatusToBoost(targetStatus, authed.Account) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	boostWrapperStatus.CreatedWithApplicationID = authed.Application.ID | ||||
| 	boostWrapperStatus.GTSBoostedAccount = targetAccount | ||||
| 
 | ||||
| 	// put the boost in the database | ||||
| 	if err := p.db.Put(boostWrapperStatus); err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// send it to the processor for async processing | ||||
| 	p.fromClientAPI <- gtsmodel.FromClientAPI{ | ||||
| 		APObjectType:   gtsmodel.ActivityStreamsAnnounce, | ||||
| 		APActivityType: gtsmodel.ActivityStreamsCreate, | ||||
| 		GTSModel:       boostWrapperStatus, | ||||
| 		OriginAccount:  authed.Account, | ||||
| 		TargetAccount:  targetAccount, | ||||
| 	} | ||||
| 
 | ||||
| 	// return the frontend representation of the new status to the submitter | ||||
| 	mastoStatus, err := p.tc.StatusToMasto(boostWrapperStatus, authed.Account, authed.Account, targetAccount, nil, targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	return mastoStatus, nil | ||||
| func (p *processor) StatusBoost(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { | ||||
| 	return p.statusProcessor.Boost(authed.Account, authed.Application, targetStatusID) | ||||
| } | ||||
| 
 | ||||
| func (p *processor) StatusBoostedBy(authed *oauth.Auth, targetStatusID string) ([]*apimodel.Account, ErrorWithCode) { | ||||
| 	l := p.log.WithField("func", "StatusBoostedBy") | ||||
| 
 | ||||
| 	l.Tracef("going to search for target status %s", targetStatusID) | ||||
| 	targetStatus := >smodel.Status{} | ||||
| 	if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error fetching status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Tracef("going to search for target account %s", targetStatus.AccountID) | ||||
| 	targetAccount := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(targetStatus.AccountID, targetAccount); err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error fetching target account %s: %s", targetStatus.AccountID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to get relevant accounts") | ||||
| 	relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error fetching related accounts for status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to see if status is visible") | ||||
| 	visible, err := p.db.StatusVisible(targetStatus, targetAccount, authed.Account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error seeing if status %s is visible: %s", targetStatus.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	if !visible { | ||||
| 		return nil, NewErrorNotFound(errors.New("StatusBoostedBy: status is not visible")) | ||||
| 	} | ||||
| 
 | ||||
| 	// get ALL accounts that faved a status -- doesn't take account of blocks and mutes and stuff | ||||
| 	favingAccounts, err := p.db.WhoBoostedStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error seeing who boosted status: %s", err)) | ||||
| 	} | ||||
| 
 | ||||
| 	// filter the list so the user doesn't see accounts they blocked or which blocked them | ||||
| 	filteredAccounts := []*gtsmodel.Account{} | ||||
| 	for _, acc := range favingAccounts { | ||||
| 		blocked, err := p.db.Blocked(authed.Account.ID, acc.ID) | ||||
| 		if err != nil { | ||||
| 			return nil, NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error checking blocks: %s", err)) | ||||
| 		} | ||||
| 		if !blocked { | ||||
| 			filteredAccounts = append(filteredAccounts, acc) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO: filter other things here? suspended? muted? silenced? | ||||
| 
 | ||||
| 	// now we can return the masto representation of those accounts | ||||
| 	mastoAccounts := []*apimodel.Account{} | ||||
| 	for _, acc := range filteredAccounts { | ||||
| 		mastoAccount, err := p.tc.AccountToMastoPublic(acc) | ||||
| 		if err != nil { | ||||
| 			return nil, NewErrorNotFound(fmt.Errorf("StatusFavedBy: error converting account to api model: %s", err)) | ||||
| 		} | ||||
| 		mastoAccounts = append(mastoAccounts, mastoAccount) | ||||
| 	} | ||||
| 
 | ||||
| 	return mastoAccounts, nil | ||||
| func (p *processor) StatusBoostedBy(authed *oauth.Auth, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) { | ||||
| 	return p.statusProcessor.BoostedBy(authed.Account, targetStatusID) | ||||
| } | ||||
| 
 | ||||
| func (p *processor) StatusFavedBy(authed *oauth.Auth, targetStatusID string) ([]*apimodel.Account, error) { | ||||
| 	l := p.log.WithField("func", "StatusFavedBy") | ||||
| 
 | ||||
| 	l.Tracef("going to search for target status %s", targetStatusID) | ||||
| 	targetStatus := >smodel.Status{} | ||||
| 	if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { | ||||
| 		return nil, fmt.Errorf("error fetching status %s: %s", targetStatusID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Tracef("going to search for target account %s", targetStatus.AccountID) | ||||
| 	targetAccount := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(targetStatus.AccountID, targetAccount); err != nil { | ||||
| 		return nil, fmt.Errorf("error fetching target account %s: %s", targetStatus.AccountID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to get relevant accounts") | ||||
| 	relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error fetching related accounts for status %s: %s", targetStatusID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to see if status is visible") | ||||
| 	visible, err := p.db.StatusVisible(targetStatus, targetAccount, authed.Account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	if !visible { | ||||
| 		return nil, errors.New("status is not visible") | ||||
| 	} | ||||
| 
 | ||||
| 	// get ALL accounts that faved a status -- doesn't take account of blocks and mutes and stuff | ||||
| 	favingAccounts, err := p.db.WhoFavedStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error seeing who faved status: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// filter the list so the user doesn't see accounts they blocked or which blocked them | ||||
| 	filteredAccounts := []*gtsmodel.Account{} | ||||
| 	for _, acc := range favingAccounts { | ||||
| 		blocked, err := p.db.Blocked(authed.Account.ID, acc.ID) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("error checking blocks: %s", err) | ||||
| 		} | ||||
| 		if !blocked { | ||||
| 			filteredAccounts = append(filteredAccounts, acc) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO: filter other things here? suspended? muted? silenced? | ||||
| 
 | ||||
| 	// now we can return the masto representation of those accounts | ||||
| 	mastoAccounts := []*apimodel.Account{} | ||||
| 	for _, acc := range filteredAccounts { | ||||
| 		mastoAccount, err := p.tc.AccountToMastoPublic(acc) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("error converting account to api model: %s", err) | ||||
| 		} | ||||
| 		mastoAccounts = append(mastoAccounts, mastoAccount) | ||||
| 	} | ||||
| 
 | ||||
| 	return mastoAccounts, nil | ||||
| 	return p.statusProcessor.FavedBy(authed.Account, targetStatusID) | ||||
| } | ||||
| 
 | ||||
| func (p *processor) StatusGet(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) { | ||||
| 	l := p.log.WithField("func", "StatusGet") | ||||
| 
 | ||||
| 	l.Tracef("going to search for target status %s", targetStatusID) | ||||
| 	targetStatus := >smodel.Status{} | ||||
| 	if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { | ||||
| 		return nil, fmt.Errorf("error fetching status %s: %s", targetStatusID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Tracef("going to search for target account %s", targetStatus.AccountID) | ||||
| 	targetAccount := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(targetStatus.AccountID, targetAccount); err != nil { | ||||
| 		return nil, fmt.Errorf("error fetching target account %s: %s", targetStatus.AccountID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to get relevant accounts") | ||||
| 	relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error fetching related accounts for status %s: %s", targetStatusID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to see if status is visible") | ||||
| 	visible, err := p.db.StatusVisible(targetStatus, targetAccount, authed.Account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	if !visible { | ||||
| 		return nil, errors.New("status is not visible") | ||||
| 	} | ||||
| 
 | ||||
| 	var boostOfStatus *gtsmodel.Status | ||||
| 	if targetStatus.BoostOfID != "" { | ||||
| 		boostOfStatus = >smodel.Status{} | ||||
| 		if err := p.db.GetByID(targetStatus.BoostOfID, boostOfStatus); err != nil { | ||||
| 			return nil, fmt.Errorf("error fetching boosted status %s: %s", targetStatus.BoostOfID, err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	mastoStatus, err := p.tc.StatusToMasto(targetStatus, targetAccount, authed.Account, relevantAccounts.BoostedAccount, relevantAccounts.ReplyToAccount, boostOfStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	return mastoStatus, nil | ||||
| 
 | ||||
| 	return p.statusProcessor.Get(authed.Account, targetStatusID) | ||||
| } | ||||
| 
 | ||||
| func (p *processor) StatusUnfave(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) { | ||||
| 	l := p.log.WithField("func", "StatusUnfave") | ||||
| 	l.Tracef("going to search for target status %s", targetStatusID) | ||||
| 	targetStatus := >smodel.Status{} | ||||
| 	if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { | ||||
| 		return nil, fmt.Errorf("error fetching status %s: %s", targetStatusID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Tracef("going to search for target account %s", targetStatus.AccountID) | ||||
| 	targetAccount := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(targetStatus.AccountID, targetAccount); err != nil { | ||||
| 		return nil, fmt.Errorf("error fetching target account %s: %s", targetStatus.AccountID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to get relevant accounts") | ||||
| 	relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error fetching related accounts for status %s: %s", targetStatusID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to see if status is visible") | ||||
| 	visible, err := p.db.StatusVisible(targetStatus, targetAccount, authed.Account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	if !visible { | ||||
| 		return nil, errors.New("status is not visible") | ||||
| 	} | ||||
| 
 | ||||
| 	// is the status faveable? | ||||
| 	if targetStatus.VisibilityAdvanced != nil { | ||||
| 		if !targetStatus.VisibilityAdvanced.Likeable { | ||||
| 			return nil, errors.New("status is not faveable") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// it's visible! it's faveable! so let's unfave the FUCK out of it | ||||
| 	_, err = p.db.UnfaveStatus(targetStatus, authed.Account.ID) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error unfaveing status: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	var boostOfStatus *gtsmodel.Status | ||||
| 	if targetStatus.BoostOfID != "" { | ||||
| 		boostOfStatus = >smodel.Status{} | ||||
| 		if err := p.db.GetByID(targetStatus.BoostOfID, boostOfStatus); err != nil { | ||||
| 			return nil, fmt.Errorf("error fetching boosted status %s: %s", targetStatus.BoostOfID, err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	mastoStatus, err := p.tc.StatusToMasto(targetStatus, targetAccount, authed.Account, relevantAccounts.BoostedAccount, relevantAccounts.ReplyToAccount, boostOfStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	return mastoStatus, nil | ||||
| 	return p.statusProcessor.Unfave(authed.Account, targetStatusID) | ||||
| } | ||||
| 
 | ||||
| func (p *processor) StatusGetContext(authed *oauth.Auth, targetStatusID string) (*apimodel.Context, ErrorWithCode) { | ||||
| 	return &apimodel.Context{}, nil | ||||
| func (p *processor) StatusGetContext(authed *oauth.Auth, targetStatusID string) (*apimodel.Context, gtserror.WithCode) { | ||||
| 	return p.statusProcessor.Context(authed.Account, targetStatusID) | ||||
| } | ||||
|  |  | |||
							
								
								
									
										79
									
								
								internal/processing/status/boost.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								internal/processing/status/boost.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,79 @@ | |||
| package status | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) Boost(account *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { | ||||
| 	l := p.log.WithField("func", "StatusBoost") | ||||
| 
 | ||||
| 	l.Tracef("going to search for target status %s", targetStatusID) | ||||
| 	targetStatus := >smodel.Status{} | ||||
| 	if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Tracef("going to search for target account %s", targetStatus.AccountID) | ||||
| 	targetAccount := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(targetStatus.AccountID, targetAccount); err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching target account %s: %s", targetStatus.AccountID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to get relevant accounts") | ||||
| 	relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching related accounts for status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to see if status is visible") | ||||
| 	visible, err := p.db.StatusVisible(targetStatus, targetAccount, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	if !visible { | ||||
| 		return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) | ||||
| 	} | ||||
| 
 | ||||
| 	if targetStatus.VisibilityAdvanced != nil { | ||||
| 		if !targetStatus.VisibilityAdvanced.Boostable { | ||||
| 			return nil, gtserror.NewErrorForbidden(errors.New("status is not boostable")) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// it's visible! it's boostable! so let's boost the FUCK out of it | ||||
| 	boostWrapperStatus, err := p.tc.StatusToBoost(targetStatus, account) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	boostWrapperStatus.CreatedWithApplicationID = application.ID | ||||
| 	boostWrapperStatus.GTSBoostedAccount = targetAccount | ||||
| 
 | ||||
| 	// put the boost in the database | ||||
| 	if err := p.db.Put(boostWrapperStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// send it to the processor for async processing | ||||
| 	p.fromClientAPI <- gtsmodel.FromClientAPI{ | ||||
| 		APObjectType:   gtsmodel.ActivityStreamsAnnounce, | ||||
| 		APActivityType: gtsmodel.ActivityStreamsCreate, | ||||
| 		GTSModel:       boostWrapperStatus, | ||||
| 		OriginAccount:  account, | ||||
| 		TargetAccount:  targetAccount, | ||||
| 	} | ||||
| 
 | ||||
| 	// return the frontend representation of the new status to the submitter | ||||
| 	mastoStatus, err := p.tc.StatusToMasto(boostWrapperStatus, account, account, targetAccount, nil, targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	return mastoStatus, nil | ||||
| } | ||||
							
								
								
									
										74
									
								
								internal/processing/status/boostedby.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								internal/processing/status/boostedby.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | |||
| package status | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) BoostedBy(account *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) { | ||||
| 	l := p.log.WithField("func", "StatusBoostedBy") | ||||
| 
 | ||||
| 	l.Tracef("going to search for target status %s", targetStatusID) | ||||
| 	targetStatus := >smodel.Status{} | ||||
| 	if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error fetching status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Tracef("going to search for target account %s", targetStatus.AccountID) | ||||
| 	targetAccount := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(targetStatus.AccountID, targetAccount); err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error fetching target account %s: %s", targetStatus.AccountID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to get relevant accounts") | ||||
| 	relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error fetching related accounts for status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to see if status is visible") | ||||
| 	visible, err := p.db.StatusVisible(targetStatus, targetAccount, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error seeing if status %s is visible: %s", targetStatus.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	if !visible { | ||||
| 		return nil, gtserror.NewErrorNotFound(errors.New("StatusBoostedBy: status is not visible")) | ||||
| 	} | ||||
| 
 | ||||
| 	// get ALL accounts that faved a status -- doesn't take account of blocks and mutes and stuff | ||||
| 	favingAccounts, err := p.db.WhoBoostedStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error seeing who boosted status: %s", err)) | ||||
| 	} | ||||
| 
 | ||||
| 	// filter the list so the user doesn't see accounts they blocked or which blocked them | ||||
| 	filteredAccounts := []*gtsmodel.Account{} | ||||
| 	for _, acc := range favingAccounts { | ||||
| 		blocked, err := p.db.Blocked(account.ID, acc.ID) | ||||
| 		if err != nil { | ||||
| 			return nil, gtserror.NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error checking blocks: %s", err)) | ||||
| 		} | ||||
| 		if !blocked { | ||||
| 			filteredAccounts = append(filteredAccounts, acc) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO: filter other things here? suspended? muted? silenced? | ||||
| 
 | ||||
| 	// now we can return the masto representation of those accounts | ||||
| 	mastoAccounts := []*apimodel.Account{} | ||||
| 	for _, acc := range filteredAccounts { | ||||
| 		mastoAccount, err := p.tc.AccountToMastoPublic(acc) | ||||
| 		if err != nil { | ||||
| 			return nil, gtserror.NewErrorNotFound(fmt.Errorf("StatusFavedBy: error converting account to api model: %s", err)) | ||||
| 		} | ||||
| 		mastoAccounts = append(mastoAccounts, mastoAccount) | ||||
| 	} | ||||
| 
 | ||||
| 	return mastoAccounts, nil | ||||
| } | ||||
							
								
								
									
										11
									
								
								internal/processing/status/context.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								internal/processing/status/context.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| package status | ||||
| 
 | ||||
| import ( | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) Context(account *gtsmodel.Account, targetStatusID string) (*apimodel.Context, gtserror.WithCode) { | ||||
| 	return &apimodel.Context{}, nil | ||||
| } | ||||
							
								
								
									
										98
									
								
								internal/processing/status/create.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								internal/processing/status/create.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,98 @@ | |||
| package status | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/google/uuid" | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) Create(account *gtsmodel.Account, application *gtsmodel.Application, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, gtserror.WithCode) { | ||||
| 	uris := util.GenerateURIsForAccount(account.Username, p.config.Protocol, p.config.Host) | ||||
| 	thisStatusID := uuid.NewString() | ||||
| 	thisStatusURI := fmt.Sprintf("%s/%s", uris.StatusesURI, thisStatusID) | ||||
| 	thisStatusURL := fmt.Sprintf("%s/%s", uris.StatusesURL, thisStatusID) | ||||
| 	newStatus := >smodel.Status{ | ||||
| 		ID:                       thisStatusID, | ||||
| 		URI:                      thisStatusURI, | ||||
| 		URL:                      thisStatusURL, | ||||
| 		Content:                  util.HTMLFormat(form.Status), | ||||
| 		CreatedAt:                time.Now(), | ||||
| 		UpdatedAt:                time.Now(), | ||||
| 		Local:                    true, | ||||
| 		AccountID:                account.ID, | ||||
| 		ContentWarning:           form.SpoilerText, | ||||
| 		ActivityStreamsType:      gtsmodel.ActivityStreamsNote, | ||||
| 		Sensitive:                form.Sensitive, | ||||
| 		Language:                 form.Language, | ||||
| 		CreatedWithApplicationID: application.ID, | ||||
| 		Text:                     form.Status, | ||||
| 	} | ||||
| 
 | ||||
| 	// check if replyToID is ok | ||||
| 	if err := p.processReplyToID(form, account.ID, newStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// check if mediaIDs are ok | ||||
| 	if err := p.processMediaIDs(form, account.ID, newStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// check if visibility settings are ok | ||||
| 	if err := p.processVisibility(form, account.Privacy, newStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// handle language settings | ||||
| 	if err := p.processLanguage(form, account.Language, newStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// handle mentions | ||||
| 	if err := p.processMentions(form, account.ID, newStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := p.processTags(form, account.ID, newStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := p.processEmojis(form, account.ID, newStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// put the new status in the database, generating an ID for it in the process | ||||
| 	if err := p.db.Put(newStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// change the status ID of the media attachments to the new status | ||||
| 	for _, a := range newStatus.GTSMediaAttachments { | ||||
| 		a.StatusID = newStatus.ID | ||||
| 		a.UpdatedAt = time.Now() | ||||
| 		if err := p.db.UpdateByID(a.ID, a); err != nil { | ||||
| 			return nil, gtserror.NewErrorInternalError(err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// put the new status in the appropriate channel for async processing | ||||
| 	p.fromClientAPI <- gtsmodel.FromClientAPI{ | ||||
| 		APObjectType:   gtsmodel.ActivityStreamsNote, | ||||
| 		APActivityType: gtsmodel.ActivityStreamsCreate, | ||||
| 		GTSModel:       newStatus, | ||||
| 		OriginAccount:  account, | ||||
| 	} | ||||
| 
 | ||||
| 	// return the frontend representation of the new status to the submitter | ||||
| 	mastoStatus, err := p.tc.StatusToMasto(newStatus, account, account, nil, newStatus.GTSReplyToAccount, nil) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", newStatus.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	return mastoStatus, nil | ||||
| } | ||||
							
								
								
									
										55
									
								
								internal/processing/status/delete.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								internal/processing/status/delete.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | |||
| package status | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) Delete(account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { | ||||
| 	l := p.log.WithField("func", "StatusDelete") | ||||
| 	l.Tracef("going to search for target status %s", targetStatusID) | ||||
| 	targetStatus := >smodel.Status{} | ||||
| 	if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	if targetStatus.AccountID != account.ID { | ||||
| 		return nil, gtserror.NewErrorForbidden(errors.New("status doesn't belong to requesting account")) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to get relevant accounts") | ||||
| 	relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching related accounts for status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	var boostOfStatus *gtsmodel.Status | ||||
| 	if targetStatus.BoostOfID != "" { | ||||
| 		boostOfStatus = >smodel.Status{} | ||||
| 		if err := p.db.GetByID(targetStatus.BoostOfID, boostOfStatus); err != nil { | ||||
| 			return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching boosted status %s: %s", targetStatus.BoostOfID, err)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	mastoStatus, err := p.tc.StatusToMasto(targetStatus, account, account, relevantAccounts.BoostedAccount, relevantAccounts.ReplyToAccount, boostOfStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := p.db.DeleteByID(targetStatus.ID, targetStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error deleting status from the database: %s", err)) | ||||
| 	} | ||||
| 
 | ||||
| 	p.fromClientAPI <- gtsmodel.FromClientAPI{ | ||||
| 		APObjectType:   gtsmodel.ActivityStreamsNote, | ||||
| 		APActivityType: gtsmodel.ActivityStreamsDelete, | ||||
| 		GTSModel:       targetStatus, | ||||
| 		OriginAccount:  account, | ||||
| 	} | ||||
| 
 | ||||
| 	return mastoStatus, nil | ||||
| } | ||||
							
								
								
									
										104
									
								
								internal/processing/status/fave.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								internal/processing/status/fave.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,104 @@ | |||
| package status | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"github.com/google/uuid" | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) Fave(account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { | ||||
| 	l := p.log.WithField("func", "StatusFave") | ||||
| 	l.Tracef("going to search for target status %s", targetStatusID) | ||||
| 	targetStatus := >smodel.Status{} | ||||
| 	if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Tracef("going to search for target account %s", targetStatus.AccountID) | ||||
| 	targetAccount := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(targetStatus.AccountID, targetAccount); err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching target account %s: %s", targetStatus.AccountID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to get relevant accounts") | ||||
| 	relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching related accounts for status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	var boostOfStatus *gtsmodel.Status | ||||
| 	if targetStatus.BoostOfID != "" { | ||||
| 		boostOfStatus = >smodel.Status{} | ||||
| 		if err := p.db.GetByID(targetStatus.BoostOfID, boostOfStatus); err != nil { | ||||
| 			return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching boosted status %s: %s", targetStatus.BoostOfID, err)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to see if status is visible") | ||||
| 	visible, err := p.db.StatusVisible(targetStatus, targetAccount, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	if !visible { | ||||
| 		return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) | ||||
| 	} | ||||
| 
 | ||||
| 	// is the status faveable? | ||||
| 	if targetStatus.VisibilityAdvanced != nil { | ||||
| 		if !targetStatus.VisibilityAdvanced.Likeable { | ||||
| 			return nil, gtserror.NewErrorForbidden(errors.New("status is not faveable")) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// first check if the status is already faved, if so we don't need to do anything | ||||
| 	newFave := true | ||||
| 	gtsFave := >smodel.StatusFave{} | ||||
| 	if err := p.db.GetWhere([]db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: account.ID}}, gtsFave); err == nil { | ||||
| 		// we already have a fave for this status | ||||
| 		newFave = false | ||||
| 	} | ||||
| 
 | ||||
| 	if newFave { | ||||
| 		thisFaveID := uuid.NewString() | ||||
| 
 | ||||
| 		// we need to create a new fave in the database | ||||
| 		gtsFave := >smodel.StatusFave{ | ||||
| 			ID:               thisFaveID, | ||||
| 			AccountID:        account.ID, | ||||
| 			TargetAccountID:  targetAccount.ID, | ||||
| 			StatusID:         targetStatus.ID, | ||||
| 			URI:              util.GenerateURIForLike(account.Username, p.config.Protocol, p.config.Host, thisFaveID), | ||||
| 			GTSStatus:        targetStatus, | ||||
| 			GTSTargetAccount: targetAccount, | ||||
| 			GTSFavingAccount: account, | ||||
| 		} | ||||
| 
 | ||||
| 		if err := p.db.Put(gtsFave); err != nil { | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error putting fave in database: %s", err)) | ||||
| 		} | ||||
| 
 | ||||
| 		// send the new fave through the processor channel for federation etc | ||||
| 		p.fromClientAPI <- gtsmodel.FromClientAPI{ | ||||
| 			APObjectType:   gtsmodel.ActivityStreamsLike, | ||||
| 			APActivityType: gtsmodel.ActivityStreamsCreate, | ||||
| 			GTSModel:       gtsFave, | ||||
| 			OriginAccount:  account, | ||||
| 			TargetAccount:  targetAccount, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// return the mastodon representation of the target status | ||||
| 	mastoStatus, err := p.tc.StatusToMasto(targetStatus, targetAccount, account, relevantAccounts.BoostedAccount, relevantAccounts.ReplyToAccount, boostOfStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	return mastoStatus, nil | ||||
| } | ||||
							
								
								
									
										74
									
								
								internal/processing/status/favedby.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								internal/processing/status/favedby.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | |||
| package status | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) FavedBy(account *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) { | ||||
| 	l := p.log.WithField("func", "StatusFavedBy") | ||||
| 
 | ||||
| 	l.Tracef("going to search for target status %s", targetStatusID) | ||||
| 	targetStatus := >smodel.Status{} | ||||
| 	if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Tracef("going to search for target account %s", targetStatus.AccountID) | ||||
| 	targetAccount := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(targetStatus.AccountID, targetAccount); err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching target account %s: %s", targetStatus.AccountID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to get relevant accounts") | ||||
| 	relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching related accounts for status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to see if status is visible") | ||||
| 	visible, err := p.db.StatusVisible(targetStatus, targetAccount, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	if !visible { | ||||
| 		return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) | ||||
| 	} | ||||
| 
 | ||||
| 	// get ALL accounts that faved a status -- doesn't take account of blocks and mutes and stuff | ||||
| 	favingAccounts, err := p.db.WhoFavedStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing who faved status: %s", err)) | ||||
| 	} | ||||
| 
 | ||||
| 	// filter the list so the user doesn't see accounts they blocked or which blocked them | ||||
| 	filteredAccounts := []*gtsmodel.Account{} | ||||
| 	for _, acc := range favingAccounts { | ||||
| 		blocked, err := p.db.Blocked(account.ID, acc.ID) | ||||
| 		if err != nil { | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking blocks: %s", err)) | ||||
| 		} | ||||
| 		if !blocked { | ||||
| 			filteredAccounts = append(filteredAccounts, acc) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO: filter other things here? suspended? muted? silenced? | ||||
| 
 | ||||
| 	// now we can return the masto representation of those accounts | ||||
| 	mastoAccounts := []*apimodel.Account{} | ||||
| 	for _, acc := range filteredAccounts { | ||||
| 		mastoAccount, err := p.tc.AccountToMastoPublic(acc) | ||||
| 		if err != nil { | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) | ||||
| 		} | ||||
| 		mastoAccounts = append(mastoAccounts, mastoAccount) | ||||
| 	} | ||||
| 
 | ||||
| 	return mastoAccounts, nil | ||||
| } | ||||
							
								
								
									
										58
									
								
								internal/processing/status/get.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								internal/processing/status/get.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,58 @@ | |||
| package status | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) Get(account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { | ||||
| 	l := p.log.WithField("func", "StatusGet") | ||||
| 
 | ||||
| 	l.Tracef("going to search for target status %s", targetStatusID) | ||||
| 	targetStatus := >smodel.Status{} | ||||
| 	if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Tracef("going to search for target account %s", targetStatus.AccountID) | ||||
| 	targetAccount := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(targetStatus.AccountID, targetAccount); err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching target account %s: %s", targetStatus.AccountID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to get relevant accounts") | ||||
| 	relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching related accounts for status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to see if status is visible") | ||||
| 	visible, err := p.db.StatusVisible(targetStatus, targetAccount, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	if !visible { | ||||
| 		return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) | ||||
| 	} | ||||
| 
 | ||||
| 	var boostOfStatus *gtsmodel.Status | ||||
| 	if targetStatus.BoostOfID != "" { | ||||
| 		boostOfStatus = >smodel.Status{} | ||||
| 		if err := p.db.GetByID(targetStatus.BoostOfID, boostOfStatus); err != nil { | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error fetching boosted status %s: %s", targetStatus.BoostOfID, err)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	mastoStatus, err := p.tc.StatusToMasto(targetStatus, targetAccount, account, relevantAccounts.BoostedAccount, relevantAccounts.ReplyToAccount, boostOfStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	return mastoStatus, nil | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										52
									
								
								internal/processing/status/status.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								internal/processing/status/status.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | |||
| package status | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||
| ) | ||||
| 
 | ||||
| // Processor wraps a bunch of functions for processing statuses. | ||||
| type Processor interface { | ||||
| 	// Create processes the given form to create a new status, returning the api model representation of that status if it's OK. | ||||
| 	Create(account *gtsmodel.Account, application *gtsmodel.Application, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, gtserror.WithCode) | ||||
| 	// Delete processes the delete of a given status, returning the deleted status if the delete goes through. | ||||
| 	Delete(account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) | ||||
| 	// Fave processes the faving of a given status, returning the updated status if the fave goes through. | ||||
| 	Fave(account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) | ||||
| 	// Boost processes the boost/reblog of a given status, returning the newly-created boost if all is well. | ||||
| 	Boost(account *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) | ||||
| 	// BoostedBy returns a slice of accounts that have boosted the given status, filtered according to privacy settings. | ||||
| 	BoostedBy(account *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) | ||||
| 	// FavedBy returns a slice of accounts that have liked the given status, filtered according to privacy settings. | ||||
| 	FavedBy(account *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) | ||||
| 	// Get gets the given status, taking account of privacy settings and blocks etc. | ||||
| 	Get(account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) | ||||
| 	// Unfave processes the unfaving of a given status, returning the updated status if the fave goes through. | ||||
| 	Unfave(account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) | ||||
| 	// Context returns the context (previous and following posts) from the given status ID | ||||
| 	Context(account *gtsmodel.Account, targetStatusID string) (*apimodel.Context, gtserror.WithCode) | ||||
| } | ||||
| 
 | ||||
| type processor struct { | ||||
| 	tc            typeutils.TypeConverter | ||||
| 	config        *config.Config | ||||
| 	db            db.DB | ||||
| 	fromClientAPI chan gtsmodel.FromClientAPI | ||||
| 	log           *logrus.Logger | ||||
| } | ||||
| 
 | ||||
| // New returns a new status processor. | ||||
| func New(db db.DB, tc typeutils.TypeConverter, config *config.Config, fromClientAPI chan gtsmodel.FromClientAPI, log *logrus.Logger) Processor { | ||||
| 	return &processor{ | ||||
| 		tc:            tc, | ||||
| 		config:        config, | ||||
| 		db:            db, | ||||
| 		fromClientAPI: fromClientAPI, | ||||
| 		log: log, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										93
									
								
								internal/processing/status/unfave.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								internal/processing/status/unfave.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,93 @@ | |||
| package status | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) Unfave(account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { | ||||
| 	l := p.log.WithField("func", "StatusUnfave") | ||||
| 	l.Tracef("going to search for target status %s", targetStatusID) | ||||
| 	targetStatus := >smodel.Status{} | ||||
| 	if err := p.db.GetByID(targetStatusID, targetStatus); err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Tracef("going to search for target account %s", targetStatus.AccountID) | ||||
| 	targetAccount := >smodel.Account{} | ||||
| 	if err := p.db.GetByID(targetStatus.AccountID, targetAccount); err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching target account %s: %s", targetStatus.AccountID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to get relevant accounts") | ||||
| 	relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(targetStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching related accounts for status %s: %s", targetStatusID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	l.Trace("going to see if status is visible") | ||||
| 	visible, err := p.db.StatusVisible(targetStatus, targetAccount, account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	if !visible { | ||||
| 		return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) | ||||
| 	} | ||||
| 
 | ||||
| 	// check if we actually have a fave for this status | ||||
| 	var toUnfave bool | ||||
| 
 | ||||
| 	gtsFave := >smodel.StatusFave{} | ||||
| 	err = p.db.GetWhere([]db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: account.ID}}, gtsFave) | ||||
| 	if err == nil { | ||||
| 		// we have a fave | ||||
| 		toUnfave = true | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		// something went wrong in the db finding the fave | ||||
| 		if _, ok := err.(db.ErrNoEntries); !ok { | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error fetching existing fave from database: %s", err)) | ||||
| 		} | ||||
| 		// we just don't have a fave | ||||
| 		toUnfave = false | ||||
| 	} | ||||
| 
 | ||||
| 	if toUnfave { | ||||
| 		// we had a fave, so take some action to get rid of it | ||||
| 		_, err = p.db.UnfaveStatus(targetStatus, account.ID) | ||||
| 		if err != nil { | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error unfaveing status: %s", err)) | ||||
| 		} | ||||
| 
 | ||||
| 		// send the unfave through the processor channel for federation etc | ||||
| 		p.fromClientAPI <- gtsmodel.FromClientAPI{ | ||||
| 			APObjectType:   gtsmodel.ActivityStreamsLike, | ||||
| 			APActivityType: gtsmodel.ActivityStreamsUndo, | ||||
| 			GTSModel:       gtsFave, | ||||
| 			OriginAccount:  account, | ||||
| 			TargetAccount:  targetAccount, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// return the status (whatever its state) back to the caller | ||||
| 	var boostOfStatus *gtsmodel.Status | ||||
| 	if targetStatus.BoostOfID != "" { | ||||
| 		boostOfStatus = >smodel.Status{} | ||||
| 		if err := p.db.GetByID(targetStatus.BoostOfID, boostOfStatus); err != nil { | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error fetching boosted status %s: %s", targetStatus.BoostOfID, err)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	mastoStatus, err := p.tc.StatusToMasto(targetStatus, targetAccount, account, relevantAccounts.BoostedAccount, relevantAccounts.ReplyToAccount, boostOfStatus) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) | ||||
| 	} | ||||
| 
 | ||||
| 	return mastoStatus, nil | ||||
| } | ||||
							
								
								
									
										230
									
								
								internal/processing/status/util.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								internal/processing/status/util.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,230 @@ | |||
| package status | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) processVisibility(form *apimodel.AdvancedStatusCreateForm, accountDefaultVis gtsmodel.Visibility, status *gtsmodel.Status) error { | ||||
| 	// by default all flags are set to true | ||||
| 	gtsAdvancedVis := >smodel.VisibilityAdvanced{ | ||||
| 		Federated: true, | ||||
| 		Boostable: true, | ||||
| 		Replyable: true, | ||||
| 		Likeable:  true, | ||||
| 	} | ||||
| 
 | ||||
| 	var gtsBasicVis gtsmodel.Visibility | ||||
| 	// Advanced takes priority if it's set. | ||||
| 	// If it's not set, take whatever masto visibility is set. | ||||
| 	// If *that's* not set either, then just take the account default. | ||||
| 	// If that's also not set, take the default for the whole instance. | ||||
| 	if form.VisibilityAdvanced != nil { | ||||
| 		gtsBasicVis = gtsmodel.Visibility(*form.VisibilityAdvanced) | ||||
| 	} else if form.Visibility != "" { | ||||
| 		gtsBasicVis = p.tc.MastoVisToVis(form.Visibility) | ||||
| 	} else if accountDefaultVis != "" { | ||||
| 		gtsBasicVis = accountDefaultVis | ||||
| 	} else { | ||||
| 		gtsBasicVis = gtsmodel.VisibilityDefault | ||||
| 	} | ||||
| 
 | ||||
| 	switch gtsBasicVis { | ||||
| 	case gtsmodel.VisibilityPublic: | ||||
| 		// for public, there's no need to change any of the advanced flags from true regardless of what the user filled out | ||||
| 		break | ||||
| 	case gtsmodel.VisibilityUnlocked: | ||||
| 		// for unlocked the user can set any combination of flags they like so look at them all to see if they're set and then apply them | ||||
| 		if form.Federated != nil { | ||||
| 			gtsAdvancedVis.Federated = *form.Federated | ||||
| 		} | ||||
| 
 | ||||
| 		if form.Boostable != nil { | ||||
| 			gtsAdvancedVis.Boostable = *form.Boostable | ||||
| 		} | ||||
| 
 | ||||
| 		if form.Replyable != nil { | ||||
| 			gtsAdvancedVis.Replyable = *form.Replyable | ||||
| 		} | ||||
| 
 | ||||
| 		if form.Likeable != nil { | ||||
| 			gtsAdvancedVis.Likeable = *form.Likeable | ||||
| 		} | ||||
| 
 | ||||
| 	case gtsmodel.VisibilityFollowersOnly, gtsmodel.VisibilityMutualsOnly: | ||||
| 		// for followers or mutuals only, boostable will *always* be false, but the other fields can be set so check and apply them | ||||
| 		gtsAdvancedVis.Boostable = false | ||||
| 
 | ||||
| 		if form.Federated != nil { | ||||
| 			gtsAdvancedVis.Federated = *form.Federated | ||||
| 		} | ||||
| 
 | ||||
| 		if form.Replyable != nil { | ||||
| 			gtsAdvancedVis.Replyable = *form.Replyable | ||||
| 		} | ||||
| 
 | ||||
| 		if form.Likeable != nil { | ||||
| 			gtsAdvancedVis.Likeable = *form.Likeable | ||||
| 		} | ||||
| 
 | ||||
| 	case gtsmodel.VisibilityDirect: | ||||
| 		// direct is pretty easy: there's only one possible setting so return it | ||||
| 		gtsAdvancedVis.Federated = true | ||||
| 		gtsAdvancedVis.Boostable = false | ||||
| 		gtsAdvancedVis.Federated = true | ||||
| 		gtsAdvancedVis.Likeable = true | ||||
| 	} | ||||
| 
 | ||||
| 	status.Visibility = gtsBasicVis | ||||
| 	status.VisibilityAdvanced = gtsAdvancedVis | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) processReplyToID(form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error { | ||||
| 	if form.InReplyToID == "" { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	// If this status is a reply to another status, we need to do a bit of work to establish whether or not this status can be posted: | ||||
| 	// | ||||
| 	// 1. Does the replied status exist in the database? | ||||
| 	// 2. Is the replied status marked as replyable? | ||||
| 	// 3. Does a block exist between either the current account or the account that posted the status it's replying to? | ||||
| 	// | ||||
| 	// If this is all OK, then we fetch the repliedStatus and the repliedAccount for later processing. | ||||
| 	repliedStatus := >smodel.Status{} | ||||
| 	repliedAccount := >smodel.Account{} | ||||
| 	// check replied status exists + is replyable | ||||
| 	if err := p.db.GetByID(form.InReplyToID, repliedStatus); err != nil { | ||||
| 		if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 			return fmt.Errorf("status with id %s not replyable because it doesn't exist", form.InReplyToID) | ||||
| 		} | ||||
| 		return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	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 | ||||
| 	if err := p.db.GetByID(repliedStatus.AccountID, repliedAccount); err != nil { | ||||
| 		if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 			return fmt.Errorf("status with id %s not replyable because account id %s is not known", form.InReplyToID, repliedStatus.AccountID) | ||||
| 		} | ||||
| 		return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err) | ||||
| 	} | ||||
| 	// check if a block exists | ||||
| 	if blocked, err := p.db.Blocked(thisAccountID, repliedAccount.ID); err != nil { | ||||
| 		if _, ok := err.(db.ErrNoEntries); !ok { | ||||
| 			return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err) | ||||
| 		} | ||||
| 	} else if blocked { | ||||
| 		return fmt.Errorf("status with id %s not replyable", form.InReplyToID) | ||||
| 	} | ||||
| 	status.InReplyToID = repliedStatus.ID | ||||
| 	status.InReplyToAccountID = repliedAccount.ID | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) processMediaIDs(form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error { | ||||
| 	if form.MediaIDs == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	gtsMediaAttachments := []*gtsmodel.MediaAttachment{} | ||||
| 	attachments := []string{} | ||||
| 	for _, mediaID := range form.MediaIDs { | ||||
| 		// check these attachments exist | ||||
| 		a := >smodel.MediaAttachment{} | ||||
| 		if err := p.db.GetByID(mediaID, a); err != nil { | ||||
| 			return fmt.Errorf("invalid media type or media not found for media id %s", mediaID) | ||||
| 		} | ||||
| 		// check they belong to the requesting account id | ||||
| 		if a.AccountID != thisAccountID { | ||||
| 			return fmt.Errorf("media with id %s does not belong to account %s", mediaID, thisAccountID) | ||||
| 		} | ||||
| 		// check they're not already used in a status | ||||
| 		if a.StatusID != "" || a.ScheduledStatusID != "" { | ||||
| 			return fmt.Errorf("media with id %s is already attached to a status", mediaID) | ||||
| 		} | ||||
| 		gtsMediaAttachments = append(gtsMediaAttachments, a) | ||||
| 		attachments = append(attachments, a.ID) | ||||
| 	} | ||||
| 	status.GTSMediaAttachments = gtsMediaAttachments | ||||
| 	status.Attachments = attachments | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) processLanguage(form *apimodel.AdvancedStatusCreateForm, accountDefaultLanguage string, status *gtsmodel.Status) error { | ||||
| 	if form.Language != "" { | ||||
| 		status.Language = form.Language | ||||
| 	} else { | ||||
| 		status.Language = accountDefaultLanguage | ||||
| 	} | ||||
| 	if status.Language == "" { | ||||
| 		return errors.New("no language given either in status create form or account default") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) processMentions(form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error { | ||||
| 	menchies := []string{} | ||||
| 	gtsMenchies, err := p.db.MentionStringsToMentions(util.DeriveMentionsFromStatus(form.Status), accountID, status.ID) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("error generating mentions from status: %s", err) | ||||
| 	} | ||||
| 	for _, menchie := range gtsMenchies { | ||||
| 		if err := p.db.Put(menchie); err != nil { | ||||
| 			return fmt.Errorf("error putting mentions in db: %s", err) | ||||
| 		} | ||||
| 		menchies = append(menchies, menchie.ID) | ||||
| 	} | ||||
| 	// add full populated gts menchies to the status for passing them around conveniently | ||||
| 	status.GTSMentions = gtsMenchies | ||||
| 	// add just the ids of the mentioned accounts to the status for putting in the db | ||||
| 	status.Mentions = menchies | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) processTags(form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error { | ||||
| 	tags := []string{} | ||||
| 	gtsTags, err := p.db.TagStringsToTags(util.DeriveHashtagsFromStatus(form.Status), accountID, status.ID) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("error generating hashtags from status: %s", err) | ||||
| 	} | ||||
| 	for _, tag := range gtsTags { | ||||
| 		if err := p.db.Upsert(tag, "name"); err != nil { | ||||
| 			return fmt.Errorf("error putting tags in db: %s", err) | ||||
| 		} | ||||
| 		tags = append(tags, tag.ID) | ||||
| 	} | ||||
| 	// add full populated gts tags to the status for passing them around conveniently | ||||
| 	status.GTSTags = gtsTags | ||||
| 	// add just the ids of the used tags to the status for putting in the db | ||||
| 	status.Tags = tags | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) processEmojis(form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error { | ||||
| 	emojis := []string{} | ||||
| 	gtsEmojis, err := p.db.EmojiStringsToEmojis(util.DeriveEmojisFromStatus(form.Status), accountID, status.ID) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("error generating emojis from status: %s", err) | ||||
| 	} | ||||
| 	for _, e := range gtsEmojis { | ||||
| 		emojis = append(emojis, e.ID) | ||||
| 	} | ||||
| 	// add full populated gts emojis to the status for passing them around conveniently | ||||
| 	status.GTSEmojis = gtsEmojis | ||||
| 	// add just the ids of the used emojis to the status for putting in the db | ||||
| 	status.Emojis = emojis | ||||
| 	return nil | ||||
| } | ||||
|  | @ -25,29 +25,30 @@ import ( | |||
| 	"github.com/sirupsen/logrus" | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) ([]*apimodel.Status, ErrorWithCode) { | ||||
| func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) ([]*apimodel.Status, gtserror.WithCode) { | ||||
| 
 | ||||
| 	statuses, err := p.timelineManager.HomeTimeline(authed.Account.ID, maxID, sinceID, minID, limit, local) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	return statuses, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) PublicTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) ([]*apimodel.Status, ErrorWithCode) { | ||||
| func (p *processor) PublicTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) ([]*apimodel.Status, gtserror.WithCode) { | ||||
| 	statuses, err := p.db.GetPublicTimelineForAccount(authed.Account.ID, maxID, sinceID, minID, limit, local) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	s, err := p.filterStatuses(authed, statuses) | ||||
| 	if err != nil { | ||||
| 		return nil, NewErrorInternalError(err) | ||||
| 		return nil, gtserror.NewErrorInternalError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	return s, nil | ||||
|  | @ -64,7 +65,7 @@ func (p *processor) filterStatuses(authed *oauth.Auth, statuses []*gtsmodel.Stat | |||
| 				l.Debugf("skipping status %s because account %s can't be found in the db", s.ID, s.AccountID) | ||||
| 				continue | ||||
| 			} | ||||
| 			return nil, NewErrorInternalError(fmt.Errorf("HomeTimelineGet: error getting status author: %s", err)) | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("HomeTimelineGet: error getting status author: %s", err)) | ||||
| 		} | ||||
| 
 | ||||
| 		relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(s) | ||||
|  | @ -75,7 +76,7 @@ func (p *processor) filterStatuses(authed *oauth.Auth, statuses []*gtsmodel.Stat | |||
| 
 | ||||
| 		visible, err := p.db.StatusVisible(s, targetAccount, authed.Account, relevantAccounts) | ||||
| 		if err != nil { | ||||
| 			return nil, NewErrorInternalError(fmt.Errorf("HomeTimelineGet: error checking status visibility: %s", err)) | ||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("HomeTimelineGet: error checking status visibility: %s", err)) | ||||
| 		} | ||||
| 		if !visible { | ||||
| 			continue | ||||
|  | @ -89,7 +90,7 @@ func (p *processor) filterStatuses(authed *oauth.Auth, statuses []*gtsmodel.Stat | |||
| 					l.Debugf("skipping status %s because status %s can't be found in the db", s.ID, s.BoostOfID) | ||||
| 					continue | ||||
| 				} | ||||
| 				return nil, NewErrorInternalError(fmt.Errorf("HomeTimelineGet: error getting boosted status: %s", err)) | ||||
| 				return nil, gtserror.NewErrorInternalError(fmt.Errorf("HomeTimelineGet: error getting boosted status: %s", err)) | ||||
| 			} | ||||
| 			boostedRelevantAccounts, err := p.db.PullRelevantAccountsFromStatus(bs) | ||||
| 			if err != nil { | ||||
|  | @ -99,7 +100,7 @@ func (p *processor) filterStatuses(authed *oauth.Auth, statuses []*gtsmodel.Stat | |||
| 
 | ||||
| 			boostedVisible, err := p.db.StatusVisible(bs, relevantAccounts.BoostedAccount, authed.Account, boostedRelevantAccounts) | ||||
| 			if err != nil { | ||||
| 				return nil, NewErrorInternalError(fmt.Errorf("HomeTimelineGet: error checking boosted status visibility: %s", err)) | ||||
| 				return nil, gtserror.NewErrorInternalError(fmt.Errorf("HomeTimelineGet: error checking boosted status visibility: %s", err)) | ||||
| 			} | ||||
| 
 | ||||
| 			if boostedVisible { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue