diff --git a/internal/api/activitypub.go b/internal/api/activitypub.go index 72a8f6e26..a1081b6ab 100644 --- a/internal/api/activitypub.go +++ b/internal/api/activitypub.go @@ -59,7 +59,7 @@ func (a *ActivityPub) RoutePublicKey(r router.Router, m ...gin.HandlerFunc) { a.publicKey.Route(publicKeyGroup.Handle) } -func NewActivityPub(db db.DB, p processing.Processor) *ActivityPub { +func NewActivityPub(db db.DB, p *processing.Processor) *ActivityPub { return &ActivityPub{ emoji: emoji.New(p), users: users.New(p), diff --git a/internal/api/activitypub/emoji/emoji.go b/internal/api/activitypub/emoji/emoji.go index b9b3f7eb9..0efd4bf88 100644 --- a/internal/api/activitypub/emoji/emoji.go +++ b/internal/api/activitypub/emoji/emoji.go @@ -33,10 +33,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/activitypub/emoji/emojiget.go b/internal/api/activitypub/emoji/emojiget.go index e9e9eff11..e66a854c7 100644 --- a/internal/api/activitypub/emoji/emojiget.go +++ b/internal/api/activitypub/emoji/emojiget.go @@ -43,7 +43,7 @@ func (m *Module) EmojiGetHandler(c *gin.Context) { return } - resp, errWithCode := m.processor.GetFediEmoji(apiutil.TransferSignatureContext(c), requestedEmojiID, c.Request.URL) + resp, errWithCode := m.processor.Fedi().EmojiGet(apiutil.TransferSignatureContext(c), requestedEmojiID, c.Request.URL) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/activitypub/emoji/emojiget_test.go b/internal/api/activitypub/emoji/emojiget_test.go index 3e05a5737..cd7333955 100644 --- a/internal/api/activitypub/emoji/emojiget_test.go +++ b/internal/api/activitypub/emoji/emojiget_test.go @@ -48,7 +48,7 @@ type EmojiGetTestSuite struct { mediaManager media.Manager federator federation.Federator emailSender email.Sender - processor processing.Processor + processor *processing.Processor storage *storage.Driver testEmojis map[string]*gtsmodel.Emoji diff --git a/internal/api/activitypub/publickey/publickey.go b/internal/api/activitypub/publickey/publickey.go index 7b3882628..39712cf88 100644 --- a/internal/api/activitypub/publickey/publickey.go +++ b/internal/api/activitypub/publickey/publickey.go @@ -34,10 +34,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/activitypub/publickey/publickeyget.go b/internal/api/activitypub/publickey/publickeyget.go index 36e1c3569..8cc0d346f 100644 --- a/internal/api/activitypub/publickey/publickeyget.go +++ b/internal/api/activitypub/publickey/publickeyget.go @@ -55,7 +55,7 @@ func (m *Module) PublicKeyGETHandler(c *gin.Context) { return } - resp, errWithCode := m.processor.GetFediUser(apiutil.TransferSignatureContext(c), requestedUsername, c.Request.URL) + resp, errWithCode := m.processor.Fedi().UserGet(apiutil.TransferSignatureContext(c), requestedUsername, c.Request.URL) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/activitypub/users/followers.go b/internal/api/activitypub/users/followers.go index 040e1cff7..649e20e45 100644 --- a/internal/api/activitypub/users/followers.go +++ b/internal/api/activitypub/users/followers.go @@ -51,7 +51,7 @@ func (m *Module) FollowersGETHandler(c *gin.Context) { return } - resp, errWithCode := m.processor.GetFediFollowers(apiutil.TransferSignatureContext(c), requestedUsername, c.Request.URL) + resp, errWithCode := m.processor.Fedi().FollowersGet(apiutil.TransferSignatureContext(c), requestedUsername, c.Request.URL) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/activitypub/users/following.go b/internal/api/activitypub/users/following.go index 629df2503..1a6e99a53 100644 --- a/internal/api/activitypub/users/following.go +++ b/internal/api/activitypub/users/following.go @@ -51,7 +51,7 @@ func (m *Module) FollowingGETHandler(c *gin.Context) { return } - resp, errWithCode := m.processor.GetFediFollowing(apiutil.TransferSignatureContext(c), requestedUsername, c.Request.URL) + resp, errWithCode := m.processor.Fedi().FollowingGet(apiutil.TransferSignatureContext(c), requestedUsername, c.Request.URL) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/activitypub/users/inboxpost.go b/internal/api/activitypub/users/inboxpost.go index 0815394b7..b70516905 100644 --- a/internal/api/activitypub/users/inboxpost.go +++ b/internal/api/activitypub/users/inboxpost.go @@ -38,7 +38,7 @@ func (m *Module) InboxPOSTHandler(c *gin.Context) { return } - if posted, err := m.processor.InboxPost(apiutil.TransferSignatureContext(c), c.Writer, c.Request); err != nil { + if posted, err := m.processor.Fedi().InboxPost(apiutil.TransferSignatureContext(c), c.Writer, c.Request); err != nil { if withCode, ok := err.(gtserror.WithCode); ok { apiutil.ErrorHandler(c, withCode, m.processor.InstanceGetV1) } else { diff --git a/internal/api/activitypub/users/outboxget.go b/internal/api/activitypub/users/outboxget.go index ad9a3829f..c081e4f92 100644 --- a/internal/api/activitypub/users/outboxget.go +++ b/internal/api/activitypub/users/outboxget.go @@ -129,7 +129,7 @@ func (m *Module) OutboxGETHandler(c *gin.Context) { maxID = maxIDString } - resp, errWithCode := m.processor.GetFediOutbox(apiutil.TransferSignatureContext(c), requestedUsername, page, maxID, minID, c.Request.URL) + resp, errWithCode := m.processor.Fedi().OutboxGet(apiutil.TransferSignatureContext(c), requestedUsername, page, maxID, minID, c.Request.URL) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/activitypub/users/repliesget.go b/internal/api/activitypub/users/repliesget.go index 7e3d459db..2c17a99d1 100644 --- a/internal/api/activitypub/users/repliesget.go +++ b/internal/api/activitypub/users/repliesget.go @@ -150,7 +150,7 @@ func (m *Module) StatusRepliesGETHandler(c *gin.Context) { minID = minIDString } - resp, errWithCode := m.processor.GetFediStatusReplies(apiutil.TransferSignatureContext(c), requestedUsername, requestedStatusID, page, onlyOtherAccounts, minID, c.Request.URL) + resp, errWithCode := m.processor.Fedi().StatusRepliesGet(apiutil.TransferSignatureContext(c), requestedUsername, requestedStatusID, page, onlyOtherAccounts, minID, c.Request.URL) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/activitypub/users/statusget.go b/internal/api/activitypub/users/statusget.go index 766fee429..69d873efa 100644 --- a/internal/api/activitypub/users/statusget.go +++ b/internal/api/activitypub/users/statusget.go @@ -59,7 +59,7 @@ func (m *Module) StatusGETHandler(c *gin.Context) { return } - resp, errWithCode := m.processor.GetFediStatus(apiutil.TransferSignatureContext(c), requestedUsername, requestedStatusID, c.Request.URL) + resp, errWithCode := m.processor.Fedi().StatusGet(apiutil.TransferSignatureContext(c), requestedUsername, requestedStatusID, c.Request.URL) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/activitypub/users/user.go b/internal/api/activitypub/users/user.go index 257453bcb..b31017866 100644 --- a/internal/api/activitypub/users/user.go +++ b/internal/api/activitypub/users/user.go @@ -57,10 +57,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/activitypub/users/user_test.go b/internal/api/activitypub/users/user_test.go index 991844b78..0124925b9 100644 --- a/internal/api/activitypub/users/user_test.go +++ b/internal/api/activitypub/users/user_test.go @@ -44,7 +44,7 @@ type UserStandardTestSuite struct { mediaManager media.Manager federator federation.Federator emailSender email.Sender - processor processing.Processor + processor *processing.Processor storage *storage.Driver // standard suite models diff --git a/internal/api/activitypub/users/userget.go b/internal/api/activitypub/users/userget.go index 97f101cfc..5c8f689f3 100644 --- a/internal/api/activitypub/users/userget.go +++ b/internal/api/activitypub/users/userget.go @@ -59,7 +59,7 @@ func (m *Module) UsersGETHandler(c *gin.Context) { return } - resp, errWithCode := m.processor.GetFediUser(apiutil.TransferSignatureContext(c), requestedUsername, c.Request.URL) + resp, errWithCode := m.processor.Fedi().UserGet(apiutil.TransferSignatureContext(c), requestedUsername, c.Request.URL) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/activitypub/users/userget_test.go b/internal/api/activitypub/users/userget_test.go index 60fa0c4db..a5467b99a 100644 --- a/internal/api/activitypub/users/userget_test.go +++ b/internal/api/activitypub/users/userget_test.go @@ -32,7 +32,6 @@ "github.com/superseriousbusiness/activity/streams/vocab" "github.com/superseriousbusiness/gotosocial/internal/api/activitypub/users" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/testrig" ) @@ -100,13 +99,7 @@ func (suite *UserGetTestSuite) TestGetUserPublicKeyDeleted() { userModule := users.New(suite.processor) targetAccount := suite.testAccounts["local_account_1"] - // first delete the account, as though zork had deleted himself - authed := &oauth.Auth{ - Application: suite.testApplications["local_account_1"], - User: suite.testUsers["local_account_1"], - Account: suite.testAccounts["local_account_1"], - } - suite.processor.AccountDeleteLocal(context.Background(), authed, &apimodel.AccountDeleteRequest{ + suite.processor.Account().DeleteLocal(context.Background(), suite.testAccounts["local_account_1"], &apimodel.AccountDeleteRequest{ Password: "password", DeleteOriginID: targetAccount.ID, }) diff --git a/internal/api/auth.go b/internal/api/auth.go index 022185223..adde87636 100644 --- a/internal/api/auth.go +++ b/internal/api/auth.go @@ -56,7 +56,7 @@ func (a *Auth) Route(r router.Router, m ...gin.HandlerFunc) { a.auth.RouteOauth(oauthGroup.Handle) } -func NewAuth(db db.DB, p processing.Processor, idp oidc.IDP, routerSession *gtsmodel.RouterSession, sessionName string) *Auth { +func NewAuth(db db.DB, p *processing.Processor, idp oidc.IDP, routerSession *gtsmodel.RouterSession, sessionName string) *Auth { return &Auth{ routerSession: routerSession, sessionName: sessionName, diff --git a/internal/api/auth/auth.go b/internal/api/auth/auth.go index 4d025ed62..2d0814782 100644 --- a/internal/api/auth/auth.go +++ b/internal/api/auth/auth.go @@ -78,14 +78,14 @@ type Module struct { db db.DB - processor processing.Processor + processor *processing.Processor idp oidc.IDP } // New returns an Auth module which provides both 'oauth' and 'auth' endpoints. // // It is safe to pass a nil idp if oidc is disabled. -func New(db db.DB, processor processing.Processor, idp oidc.IDP) *Module { +func New(db db.DB, processor *processing.Processor, idp oidc.IDP) *Module { return &Module{ db: db, processor: processor, diff --git a/internal/api/auth/auth_test.go b/internal/api/auth/auth_test.go index 337d29a3c..a5e518cda 100644 --- a/internal/api/auth/auth_test.go +++ b/internal/api/auth/auth_test.go @@ -49,7 +49,7 @@ type AuthStandardTestSuite struct { storage *storage.Driver mediaManager media.Manager federator federation.Federator - processor processing.Processor + processor *processing.Processor emailSender email.Sender idp oidc.IDP diff --git a/internal/api/client.go b/internal/api/client.go index 2ce3cdc97..57f648634 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -49,7 +49,7 @@ ) type Client struct { - processor processing.Processor + processor *processing.Processor db db.DB accounts *accounts.Module // api/v1/accounts @@ -110,7 +110,7 @@ func (c *Client) Route(r router.Router, m ...gin.HandlerFunc) { c.user.Route(h) } -func NewClient(db db.DB, p processing.Processor) *Client { +func NewClient(db db.DB, p *processing.Processor) *Client { return &Client{ processor: p, db: db, diff --git a/internal/api/client/accounts/account_test.go b/internal/api/client/accounts/account_test.go index 8a319e7fd..5a25c12f1 100644 --- a/internal/api/client/accounts/account_test.go +++ b/internal/api/client/accounts/account_test.go @@ -48,7 +48,7 @@ type AccountStandardTestSuite struct { storage *storage.Driver mediaManager media.Manager federator federation.Federator - processor processing.Processor + processor *processing.Processor emailSender email.Sender sentEmails map[string]string diff --git a/internal/api/client/accounts/accountcreate.go b/internal/api/client/accounts/accountcreate.go index 873d00beb..409105a2d 100644 --- a/internal/api/client/accounts/accountcreate.go +++ b/internal/api/client/accounts/accountcreate.go @@ -102,7 +102,7 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) { } form.IP = signUpIP - ti, errWithCode := m.processor.AccountCreate(c.Request.Context(), authed, form) + ti, errWithCode := m.processor.Account().Create(c.Request.Context(), authed.Token, authed.Application, form) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/accounts/accountdelete.go b/internal/api/client/accounts/accountdelete.go index b0c5e48de..64ea5e548 100644 --- a/internal/api/client/accounts/accountdelete.go +++ b/internal/api/client/accounts/accountdelete.go @@ -86,7 +86,7 @@ func (m *Module) AccountDeletePOSTHandler(c *gin.Context) { form.DeleteOriginID = authed.Account.ID - if errWithCode := m.processor.AccountDeleteLocal(c.Request.Context(), authed, form); errWithCode != nil { + if errWithCode := m.processor.Account().DeleteLocal(c.Request.Context(), authed.Account, form); errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } diff --git a/internal/api/client/accounts/accountget.go b/internal/api/client/accounts/accountget.go index ca6b9c661..9fcbf7a4b 100644 --- a/internal/api/client/accounts/accountget.go +++ b/internal/api/client/accounts/accountget.go @@ -85,7 +85,7 @@ func (m *Module) AccountGETHandler(c *gin.Context) { return } - acctInfo, errWithCode := m.processor.AccountGet(c.Request.Context(), authed, targetAcctID) + acctInfo, errWithCode := m.processor.Account().Get(c.Request.Context(), authed.Account, targetAcctID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/accounts/accounts.go b/internal/api/client/accounts/accounts.go index 4006b607d..02f900df5 100644 --- a/internal/api/client/accounts/accounts.go +++ b/internal/api/client/accounts/accounts.go @@ -74,10 +74,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/accounts/accountupdate.go b/internal/api/client/accounts/accountupdate.go index 65a950514..3983fe8e1 100644 --- a/internal/api/client/accounts/accountupdate.go +++ b/internal/api/client/accounts/accountupdate.go @@ -153,7 +153,7 @@ func (m *Module) AccountUpdateCredentialsPATCHHandler(c *gin.Context) { return } - acctSensitive, errWithCode := m.processor.AccountUpdate(c.Request.Context(), authed, form) + acctSensitive, errWithCode := m.processor.Account().Update(c.Request.Context(), authed.Account, form) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/accounts/accountverify.go b/internal/api/client/accounts/accountverify.go index 868e095b8..59fc5b2b1 100644 --- a/internal/api/client/accounts/accountverify.go +++ b/internal/api/client/accounts/accountverify.go @@ -68,7 +68,7 @@ func (m *Module) AccountVerifyGETHandler(c *gin.Context) { return } - acctSensitive, errWithCode := m.processor.AccountGet(c.Request.Context(), authed, authed.Account.ID) + acctSensitive, errWithCode := m.processor.Account().Get(c.Request.Context(), authed.Account, authed.Account.ID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/accounts/block.go b/internal/api/client/accounts/block.go index cfa452a5e..2cd7b5448 100644 --- a/internal/api/client/accounts/block.go +++ b/internal/api/client/accounts/block.go @@ -85,7 +85,7 @@ func (m *Module) AccountBlockPOSTHandler(c *gin.Context) { return } - relationship, errWithCode := m.processor.AccountBlockCreate(c.Request.Context(), authed, targetAcctID) + relationship, errWithCode := m.processor.Account().BlockCreate(c.Request.Context(), authed.Account, targetAcctID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/accounts/follow.go b/internal/api/client/accounts/follow.go index 60e526ca1..d08f034e2 100644 --- a/internal/api/client/accounts/follow.go +++ b/internal/api/client/accounts/follow.go @@ -114,7 +114,7 @@ func (m *Module) AccountFollowPOSTHandler(c *gin.Context) { } form.ID = targetAcctID - relationship, errWithCode := m.processor.AccountFollowCreate(c.Request.Context(), authed, form) + relationship, errWithCode := m.processor.Account().FollowCreate(c.Request.Context(), authed.Account, form) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/accounts/followers.go b/internal/api/client/accounts/followers.go index a9b188d7b..f6d16705c 100644 --- a/internal/api/client/accounts/followers.go +++ b/internal/api/client/accounts/followers.go @@ -88,7 +88,7 @@ func (m *Module) AccountFollowersGETHandler(c *gin.Context) { return } - followers, errWithCode := m.processor.AccountFollowersGet(c.Request.Context(), authed, targetAcctID) + followers, errWithCode := m.processor.Account().FollowersGet(c.Request.Context(), authed.Account, targetAcctID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/accounts/following.go b/internal/api/client/accounts/following.go index 9717816b0..eb46acb33 100644 --- a/internal/api/client/accounts/following.go +++ b/internal/api/client/accounts/following.go @@ -88,7 +88,7 @@ func (m *Module) AccountFollowingGETHandler(c *gin.Context) { return } - following, errWithCode := m.processor.AccountFollowingGet(c.Request.Context(), authed, targetAcctID) + following, errWithCode := m.processor.Account().FollowingGet(c.Request.Context(), authed.Account, targetAcctID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/accounts/relationships.go b/internal/api/client/accounts/relationships.go index 1d176cfe0..1f75399ee 100644 --- a/internal/api/client/accounts/relationships.go +++ b/internal/api/client/accounts/relationships.go @@ -81,7 +81,7 @@ func (m *Module) AccountRelationshipsGETHandler(c *gin.Context) { relationships := []apimodel.Relationship{} for _, targetAccountID := range targetAccountIDs { - r, errWithCode := m.processor.AccountRelationshipGet(c.Request.Context(), authed, targetAccountID) + r, errWithCode := m.processor.Account().RelationshipGet(c.Request.Context(), authed.Account, targetAccountID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/accounts/statuses.go b/internal/api/client/accounts/statuses.go index ba35d5c49..e5d5b0b23 100644 --- a/internal/api/client/accounts/statuses.go +++ b/internal/api/client/accounts/statuses.go @@ -233,7 +233,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) { publicOnly = i } - resp, errWithCode := m.processor.AccountStatusesGet(c.Request.Context(), authed, targetAcctID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly) + resp, errWithCode := m.processor.Account().StatusesGet(c.Request.Context(), authed.Account, targetAcctID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/accounts/unblock.go b/internal/api/client/accounts/unblock.go index 50213ad80..c0003771c 100644 --- a/internal/api/client/accounts/unblock.go +++ b/internal/api/client/accounts/unblock.go @@ -86,7 +86,7 @@ func (m *Module) AccountUnblockPOSTHandler(c *gin.Context) { return } - relationship, errWithCode := m.processor.AccountBlockRemove(c.Request.Context(), authed, targetAcctID) + relationship, errWithCode := m.processor.Account().BlockRemove(c.Request.Context(), authed.Account, targetAcctID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/accounts/unfollow.go b/internal/api/client/accounts/unfollow.go index 77dab0b07..ab480cf4f 100644 --- a/internal/api/client/accounts/unfollow.go +++ b/internal/api/client/accounts/unfollow.go @@ -86,7 +86,7 @@ func (m *Module) AccountUnfollowPOSTHandler(c *gin.Context) { return } - relationship, errWithCode := m.processor.AccountFollowRemove(c.Request.Context(), authed, targetAcctID) + relationship, errWithCode := m.processor.Account().FollowRemove(c.Request.Context(), authed.Account, targetAcctID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/admin/accountaction.go b/internal/api/client/admin/accountaction.go index 32bb135d1..0b4a50330 100644 --- a/internal/api/client/admin/accountaction.go +++ b/internal/api/client/admin/accountaction.go @@ -115,7 +115,7 @@ func (m *Module) AccountActionPOSTHandler(c *gin.Context) { } form.TargetAccountID = targetAcctID - if errWithCode := m.processor.AdminAccountAction(c.Request.Context(), authed, form); errWithCode != nil { + if errWithCode := m.processor.Admin().AccountAction(c.Request.Context(), authed.Account, form); errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } diff --git a/internal/api/client/admin/admin.go b/internal/api/client/admin/admin.go index f7e54d271..1b9ea302d 100644 --- a/internal/api/client/admin/admin.go +++ b/internal/api/client/admin/admin.go @@ -83,10 +83,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/admin/admin_test.go b/internal/api/client/admin/admin_test.go index 000ca1927..4f3f48904 100644 --- a/internal/api/client/admin/admin_test.go +++ b/internal/api/client/admin/admin_test.go @@ -48,7 +48,7 @@ type AdminStandardTestSuite struct { storage *storage.Driver mediaManager media.Manager federator federation.Federator - processor processing.Processor + processor *processing.Processor emailSender email.Sender sentEmails map[string]string diff --git a/internal/api/client/admin/domainblockcreate.go b/internal/api/client/admin/domainblockcreate.go index b46d71f91..ab65dd62e 100644 --- a/internal/api/client/admin/domainblockcreate.go +++ b/internal/api/client/admin/domainblockcreate.go @@ -167,7 +167,7 @@ func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) { if imp { // we're importing multiple blocks - domainBlocks, errWithCode := m.processor.AdminDomainBlocksImport(c.Request.Context(), authed, form) + domainBlocks, errWithCode := m.processor.Admin().DomainBlocksImport(c.Request.Context(), authed.Account, form.Domains) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return @@ -177,7 +177,7 @@ func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) { } // we're just creating one block - domainBlock, errWithCode := m.processor.AdminDomainBlockCreate(c.Request.Context(), authed, form) + domainBlock, errWithCode := m.processor.Admin().DomainBlockCreate(c.Request.Context(), authed.Account, form.Domain, form.Obfuscate, form.PublicComment, form.PrivateComment, "") if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/admin/domainblockdelete.go b/internal/api/client/admin/domainblockdelete.go index e360d11cd..89dfa93a5 100644 --- a/internal/api/client/admin/domainblockdelete.go +++ b/internal/api/client/admin/domainblockdelete.go @@ -94,7 +94,7 @@ func (m *Module) DomainBlockDELETEHandler(c *gin.Context) { return } - domainBlock, errWithCode := m.processor.AdminDomainBlockDelete(c.Request.Context(), authed, domainBlockID) + domainBlock, errWithCode := m.processor.Admin().DomainBlockDelete(c.Request.Context(), authed.Account, domainBlockID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/admin/domainblockget.go b/internal/api/client/admin/domainblockget.go index dd329fa48..aaf23684a 100644 --- a/internal/api/client/admin/domainblockget.go +++ b/internal/api/client/admin/domainblockget.go @@ -107,7 +107,7 @@ func (m *Module) DomainBlockGETHandler(c *gin.Context) { export = i } - domainBlock, errWithCode := m.processor.AdminDomainBlockGet(c.Request.Context(), authed, domainBlockID, export) + domainBlock, errWithCode := m.processor.Admin().DomainBlockGet(c.Request.Context(), authed.Account, domainBlockID, export) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/admin/domainblocksget.go b/internal/api/client/admin/domainblocksget.go index 5e5fdc9b1..b459b2961 100644 --- a/internal/api/client/admin/domainblocksget.go +++ b/internal/api/client/admin/domainblocksget.go @@ -105,7 +105,7 @@ func (m *Module) DomainBlocksGETHandler(c *gin.Context) { export = i } - domainBlocks, errWithCode := m.processor.AdminDomainBlocksGet(c.Request.Context(), authed, export) + domainBlocks, errWithCode := m.processor.Admin().DomainBlocksGet(c.Request.Context(), authed.Account, export) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/admin/emojicategoriesget.go b/internal/api/client/admin/emojicategoriesget.go index 890889314..1bb5e5d40 100644 --- a/internal/api/client/admin/emojicategoriesget.go +++ b/internal/api/client/admin/emojicategoriesget.go @@ -84,7 +84,7 @@ func (m *Module) EmojiCategoriesGETHandler(c *gin.Context) { return } - categories, errWithCode := m.processor.AdminEmojiCategoriesGet(c.Request.Context()) + categories, errWithCode := m.processor.Admin().EmojiCategoriesGet(c.Request.Context()) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/admin/emojicreate.go b/internal/api/client/admin/emojicreate.go index 758da3d5b..2ef5f518e 100644 --- a/internal/api/client/admin/emojicreate.go +++ b/internal/api/client/admin/emojicreate.go @@ -126,7 +126,7 @@ func (m *Module) EmojiCreatePOSTHandler(c *gin.Context) { return } - apiEmoji, errWithCode := m.processor.AdminEmojiCreate(c.Request.Context(), authed, form) + apiEmoji, errWithCode := m.processor.Admin().EmojiCreate(c.Request.Context(), authed.Account, authed.User, form) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/admin/emojidelete.go b/internal/api/client/admin/emojidelete.go index 0fe28b8c3..a402edda4 100644 --- a/internal/api/client/admin/emojidelete.go +++ b/internal/api/client/admin/emojidelete.go @@ -100,7 +100,7 @@ func (m *Module) EmojiDELETEHandler(c *gin.Context) { return } - emoji, errWithCode := m.processor.AdminEmojiDelete(c.Request.Context(), authed, emojiID) + emoji, errWithCode := m.processor.Admin().EmojiDelete(c.Request.Context(), emojiID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/admin/emojiget.go b/internal/api/client/admin/emojiget.go index 44f63e3b6..ab03f637d 100644 --- a/internal/api/client/admin/emojiget.go +++ b/internal/api/client/admin/emojiget.go @@ -90,7 +90,7 @@ func (m *Module) EmojiGETHandler(c *gin.Context) { return } - emoji, errWithCode := m.processor.AdminEmojiGet(c.Request.Context(), authed, emojiID) + emoji, errWithCode := m.processor.Admin().EmojiGet(c.Request.Context(), authed.Account, authed.User, emojiID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/admin/emojisget.go b/internal/api/client/admin/emojisget.go index 455683efb..e727f4915 100644 --- a/internal/api/client/admin/emojisget.go +++ b/internal/api/client/admin/emojisget.go @@ -198,7 +198,7 @@ func (m *Module) EmojisGETHandler(c *gin.Context) { includeEnabled = true } - resp, errWithCode := m.processor.AdminEmojisGet(c.Request.Context(), authed, domain, includeDisabled, includeEnabled, shortcode, maxShortcodeDomain, minShortcodeDomain, limit) + resp, errWithCode := m.processor.Admin().EmojisGet(c.Request.Context(), authed.Account, authed.User, domain, includeDisabled, includeEnabled, shortcode, maxShortcodeDomain, minShortcodeDomain, limit) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/admin/emojiupdate.go b/internal/api/client/admin/emojiupdate.go index e94eacb83..b850e1181 100644 --- a/internal/api/client/admin/emojiupdate.go +++ b/internal/api/client/admin/emojiupdate.go @@ -156,7 +156,7 @@ func (m *Module) EmojiPATCHHandler(c *gin.Context) { return } - emoji, errWithCode := m.processor.AdminEmojiUpdate(c.Request.Context(), emojiID, form) + emoji, errWithCode := m.processor.Admin().EmojiUpdate(c.Request.Context(), emojiID, form) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/admin/mediacleanup.go b/internal/api/client/admin/mediacleanup.go index 3d10deb4a..98bebd70a 100644 --- a/internal/api/client/admin/mediacleanup.go +++ b/internal/api/client/admin/mediacleanup.go @@ -98,7 +98,7 @@ func (m *Module) MediaCleanupPOSTHandler(c *gin.Context) { remoteCacheDays = 0 } - if errWithCode := m.processor.AdminMediaPrune(c.Request.Context(), remoteCacheDays); errWithCode != nil { + if errWithCode := m.processor.Admin().MediaPrune(c.Request.Context(), remoteCacheDays); errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } diff --git a/internal/api/client/admin/mediarefetch.go b/internal/api/client/admin/mediarefetch.go index 9d86c60dd..746fe6e75 100644 --- a/internal/api/client/admin/mediarefetch.go +++ b/internal/api/client/admin/mediarefetch.go @@ -84,7 +84,7 @@ func (m *Module) MediaRefetchPOSTHandler(c *gin.Context) { return } - if errWithCode := m.processor.AdminMediaRefetch(c.Request.Context(), authed, c.Query(DomainQueryKey)); errWithCode != nil { + if errWithCode := m.processor.Admin().MediaRefetch(c.Request.Context(), authed.Account, c.Query(DomainQueryKey)); errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } diff --git a/internal/api/client/admin/reportget.go b/internal/api/client/admin/reportget.go index 8b8040b8e..b9c25d895 100644 --- a/internal/api/client/admin/reportget.go +++ b/internal/api/client/admin/reportget.go @@ -93,7 +93,7 @@ func (m *Module) ReportGETHandler(c *gin.Context) { return } - report, errWithCode := m.processor.AdminReportGet(c.Request.Context(), authed, reportID) + report, errWithCode := m.processor.Admin().ReportGet(c.Request.Context(), authed.Account, reportID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/admin/reportresolve.go b/internal/api/client/admin/reportresolve.go index 485a5d45d..494f26dd0 100644 --- a/internal/api/client/admin/reportresolve.go +++ b/internal/api/client/admin/reportresolve.go @@ -115,7 +115,7 @@ func (m *Module) ReportResolvePOSTHandler(c *gin.Context) { return } - report, errWithCode := m.processor.AdminReportResolve(c.Request.Context(), authed, reportID, form.ActionTakenComment) + report, errWithCode := m.processor.Admin().ReportResolve(c.Request.Context(), authed.Account, reportID, form.ActionTakenComment) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/admin/reportsget.go b/internal/api/client/admin/reportsget.go index 3c867d3f9..b41877b84 100644 --- a/internal/api/client/admin/reportsget.go +++ b/internal/api/client/admin/reportsget.go @@ -171,7 +171,7 @@ func (m *Module) ReportsGETHandler(c *gin.Context) { limit = i } - resp, errWithCode := m.processor.AdminReportsGet(c.Request.Context(), authed, resolved, c.Query(AccountIDKey), c.Query(TargetAccountIDKey), c.Query(MaxIDKey), c.Query(SinceIDKey), c.Query(MinIDKey), limit) + resp, errWithCode := m.processor.Admin().ReportsGet(c.Request.Context(), authed.Account, resolved, c.Query(AccountIDKey), c.Query(TargetAccountIDKey), c.Query(MaxIDKey), c.Query(SinceIDKey), c.Query(MinIDKey), limit) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/apps/apps.go b/internal/api/client/apps/apps.go index 0a4926622..c7301d88b 100644 --- a/internal/api/client/apps/apps.go +++ b/internal/api/client/apps/apps.go @@ -29,10 +29,10 @@ const BasePath = "/v1/apps" type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/blocks/blocks.go b/internal/api/client/blocks/blocks.go index bf304e477..b1ee74a10 100644 --- a/internal/api/client/blocks/blocks.go +++ b/internal/api/client/blocks/blocks.go @@ -38,10 +38,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/bookmarks/bookmarks.go b/internal/api/client/bookmarks/bookmarks.go index 612bec223..6be8919bd 100644 --- a/internal/api/client/bookmarks/bookmarks.go +++ b/internal/api/client/bookmarks/bookmarks.go @@ -31,10 +31,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/bookmarks/bookmarks_test.go b/internal/api/client/bookmarks/bookmarks_test.go index f4b82a8e1..fac67ffe7 100644 --- a/internal/api/client/bookmarks/bookmarks_test.go +++ b/internal/api/client/bookmarks/bookmarks_test.go @@ -53,7 +53,7 @@ type BookmarkTestSuite struct { mediaManager media.Manager federator federation.Federator emailSender email.Sender - processor processing.Processor + processor *processing.Processor storage *storage.Driver // standard suite models diff --git a/internal/api/client/bookmarks/bookmarksget.go b/internal/api/client/bookmarks/bookmarksget.go index f5f43f0f4..23c32eba8 100644 --- a/internal/api/client/bookmarks/bookmarksget.go +++ b/internal/api/client/bookmarks/bookmarksget.go @@ -89,7 +89,7 @@ func (m *Module) BookmarksGETHandler(c *gin.Context) { minID = minIDString } - resp, errWithCode := m.processor.BookmarksGet(c.Request.Context(), authed, maxID, minID, limit) + resp, errWithCode := m.processor.Account().BookmarksGet(c.Request.Context(), authed.Account, limit, maxID, minID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/customemojis/customemojis.go b/internal/api/client/customemojis/customemojis.go index 3bddf7db8..528dd25f4 100644 --- a/internal/api/client/customemojis/customemojis.go +++ b/internal/api/client/customemojis/customemojis.go @@ -31,10 +31,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/customemojis/customemojisget.go b/internal/api/client/customemojis/customemojisget.go index 79cb32acf..167c499d2 100644 --- a/internal/api/client/customemojis/customemojisget.go +++ b/internal/api/client/customemojis/customemojisget.go @@ -66,7 +66,7 @@ func (m *Module) CustomEmojisGETHandler(c *gin.Context) { return } - emojis, errWithCode := m.processor.CustomEmojisGet(c) + emojis, errWithCode := m.processor.Media().GetCustomEmojis(c) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/favourites/favourites.go b/internal/api/client/favourites/favourites.go index 5b0a10bca..80204a63e 100644 --- a/internal/api/client/favourites/favourites.go +++ b/internal/api/client/favourites/favourites.go @@ -42,10 +42,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/favourites/favourites_test.go b/internal/api/client/favourites/favourites_test.go index 5390706ae..7949aa38c 100644 --- a/internal/api/client/favourites/favourites_test.go +++ b/internal/api/client/favourites/favourites_test.go @@ -42,7 +42,7 @@ type FavouritesStandardTestSuite struct { mediaManager media.Manager federator federation.Federator emailSender email.Sender - processor processing.Processor + processor *processing.Processor storage *storage.Driver // standard suite models diff --git a/internal/api/client/featuredtags/featuredtags.go b/internal/api/client/featuredtags/featuredtags.go index 7cd61837c..a003e4c39 100644 --- a/internal/api/client/featuredtags/featuredtags.go +++ b/internal/api/client/featuredtags/featuredtags.go @@ -30,10 +30,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/filters/filter.go b/internal/api/client/filters/filter.go index d66ff2ea5..dc6db2e63 100644 --- a/internal/api/client/filters/filter.go +++ b/internal/api/client/filters/filter.go @@ -31,10 +31,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/followrequests/followrequest.go b/internal/api/client/followrequests/followrequest.go index 4898e9153..1d0c46ffc 100644 --- a/internal/api/client/followrequests/followrequest.go +++ b/internal/api/client/followrequests/followrequest.go @@ -40,10 +40,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/followrequests/followrequest_test.go b/internal/api/client/followrequests/followrequest_test.go index 2af59ca3c..7a08479ab 100644 --- a/internal/api/client/followrequests/followrequest_test.go +++ b/internal/api/client/followrequests/followrequest_test.go @@ -46,7 +46,7 @@ type FollowRequestStandardTestSuite struct { storage *storage.Driver mediaManager media.Manager federator federation.Federator - processor processing.Processor + processor *processing.Processor emailSender email.Sender // standard suite models diff --git a/internal/api/client/instance/instance.go b/internal/api/client/instance/instance.go index d6f54a1ed..899dbfa77 100644 --- a/internal/api/client/instance/instance.go +++ b/internal/api/client/instance/instance.go @@ -33,10 +33,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/instance/instance_test.go b/internal/api/client/instance/instance_test.go index a828a6114..ff622febe 100644 --- a/internal/api/client/instance/instance_test.go +++ b/internal/api/client/instance/instance_test.go @@ -47,7 +47,7 @@ type InstanceStandardTestSuite struct { storage *storage.Driver mediaManager media.Manager federator federation.Federator - processor processing.Processor + processor *processing.Processor emailSender email.Sender sentEmails map[string]string diff --git a/internal/api/client/lists/list.go b/internal/api/client/lists/list.go index 457d7a3ea..6e09313ae 100644 --- a/internal/api/client/lists/list.go +++ b/internal/api/client/lists/list.go @@ -31,10 +31,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/media/media.go b/internal/api/client/media/media.go index e6d0c212c..65c12a329 100644 --- a/internal/api/client/media/media.go +++ b/internal/api/client/media/media.go @@ -35,10 +35,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/media/mediacreate.go b/internal/api/client/media/mediacreate.go index cfd547c19..71a43eafd 100644 --- a/internal/api/client/media/mediacreate.go +++ b/internal/api/client/media/mediacreate.go @@ -123,7 +123,7 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) { return } - apiAttachment, errWithCode := m.processor.MediaCreate(c.Request.Context(), authed, form) + apiAttachment, errWithCode := m.processor.Media().Create(c.Request.Context(), authed.Account, form) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/media/mediacreate_test.go b/internal/api/client/media/mediacreate_test.go index 8a94646ac..aaace7a61 100644 --- a/internal/api/client/media/mediacreate_test.go +++ b/internal/api/client/media/mediacreate_test.go @@ -59,7 +59,7 @@ type MediaCreateTestSuite struct { tc typeutils.TypeConverter oauthServer oauth.Server emailSender email.Sender - processor processing.Processor + processor *processing.Processor // standard suite models testTokens map[string]*gtsmodel.Token diff --git a/internal/api/client/media/mediaget.go b/internal/api/client/media/mediaget.go index 2c07024c2..f7656b574 100644 --- a/internal/api/client/media/mediaget.go +++ b/internal/api/client/media/mediaget.go @@ -91,7 +91,7 @@ func (m *Module) MediaGETHandler(c *gin.Context) { return } - attachment, errWithCode := m.processor.MediaGet(c.Request.Context(), authed, attachmentID) + attachment, errWithCode := m.processor.Media().Get(c.Request.Context(), authed.Account, attachmentID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/media/mediaupdate.go b/internal/api/client/media/mediaupdate.go index a0c44caf3..fa0b10ab5 100644 --- a/internal/api/client/media/mediaupdate.go +++ b/internal/api/client/media/mediaupdate.go @@ -134,7 +134,7 @@ func (m *Module) MediaPUTHandler(c *gin.Context) { return } - attachment, errWithCode := m.processor.MediaUpdate(c.Request.Context(), authed, attachmentID, form) + attachment, errWithCode := m.processor.Media().Update(c.Request.Context(), authed.Account, attachmentID, form) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/media/mediaupdate_test.go b/internal/api/client/media/mediaupdate_test.go index 8f4470132..cb96e8aa1 100644 --- a/internal/api/client/media/mediaupdate_test.go +++ b/internal/api/client/media/mediaupdate_test.go @@ -57,7 +57,7 @@ type MediaUpdateTestSuite struct { mediaManager media.Manager oauthServer oauth.Server emailSender email.Sender - processor processing.Processor + processor *processing.Processor // standard suite models testTokens map[string]*gtsmodel.Token diff --git a/internal/api/client/notifications/notifications.go b/internal/api/client/notifications/notifications.go index 85d52f249..2292f8c9a 100644 --- a/internal/api/client/notifications/notifications.go +++ b/internal/api/client/notifications/notifications.go @@ -46,10 +46,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/reports/reportcreate.go b/internal/api/client/reports/reportcreate.go index a5f14c6b3..44a62dbc2 100644 --- a/internal/api/client/reports/reportcreate.go +++ b/internal/api/client/reports/reportcreate.go @@ -102,7 +102,7 @@ func (m *Module) ReportPOSTHandler(c *gin.Context) { return } - apiReport, errWithCode := m.processor.ReportCreate(c.Request.Context(), authed, form) + apiReport, errWithCode := m.processor.Report().Create(c.Request.Context(), authed.Account, form) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/reports/reportget.go b/internal/api/client/reports/reportget.go index 8b08da048..0c478e045 100644 --- a/internal/api/client/reports/reportget.go +++ b/internal/api/client/reports/reportget.go @@ -85,7 +85,7 @@ func (m *Module) ReportGETHandler(c *gin.Context) { return } - report, errWithCode := m.processor.ReportGet(c.Request.Context(), authed, targetReportID) + report, errWithCode := m.processor.Report().Get(c.Request.Context(), authed.Account, targetReportID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/reports/reports.go b/internal/api/client/reports/reports.go index 41b61582c..3dcdb8d0f 100644 --- a/internal/api/client/reports/reports.go +++ b/internal/api/client/reports/reports.go @@ -38,10 +38,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/reports/reports_test.go b/internal/api/client/reports/reports_test.go index f5f5bf91e..1c5a532b9 100644 --- a/internal/api/client/reports/reports_test.go +++ b/internal/api/client/reports/reports_test.go @@ -39,7 +39,7 @@ type ReportsStandardTestSuite struct { storage *storage.Driver mediaManager media.Manager federator federation.Federator - processor processing.Processor + processor *processing.Processor emailSender email.Sender sentEmails map[string]string diff --git a/internal/api/client/reports/reportsget.go b/internal/api/client/reports/reportsget.go index b53c9527d..ca135be21 100644 --- a/internal/api/client/reports/reportsget.go +++ b/internal/api/client/reports/reportsget.go @@ -160,7 +160,7 @@ func (m *Module) ReportsGETHandler(c *gin.Context) { limit = i } - resp, errWithCode := m.processor.ReportsGet(c.Request.Context(), authed, resolved, c.Query(TargetAccountIDKey), c.Query(MaxIDKey), c.Query(SinceIDKey), c.Query(MinIDKey), limit) + resp, errWithCode := m.processor.Report().GetMultiple(c.Request.Context(), authed.Account, resolved, c.Query(TargetAccountIDKey), c.Query(MaxIDKey), c.Query(SinceIDKey), c.Query(MinIDKey), limit) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/search/search.go b/internal/api/client/search/search.go index c9f23595a..1ab9e0739 100644 --- a/internal/api/client/search/search.go +++ b/internal/api/client/search/search.go @@ -62,10 +62,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/search/search_test.go b/internal/api/client/search/search_test.go index 194115525..4580f6f9d 100644 --- a/internal/api/client/search/search_test.go +++ b/internal/api/client/search/search_test.go @@ -47,7 +47,7 @@ type SearchStandardTestSuite struct { storage *storage.Driver mediaManager media.Manager federator federation.Federator - processor processing.Processor + processor *processing.Processor emailSender email.Sender sentEmails map[string]string diff --git a/internal/api/client/statuses/status.go b/internal/api/client/statuses/status.go index 65129ee7e..380846ed4 100644 --- a/internal/api/client/statuses/status.go +++ b/internal/api/client/statuses/status.go @@ -68,10 +68,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/statuses/status_test.go b/internal/api/client/statuses/status_test.go index 29746ed80..a87fd36f7 100644 --- a/internal/api/client/statuses/status_test.go +++ b/internal/api/client/statuses/status_test.go @@ -42,7 +42,7 @@ type StatusStandardTestSuite struct { mediaManager media.Manager federator federation.Federator emailSender email.Sender - processor processing.Processor + processor *processing.Processor storage *storage.Driver // standard suite models diff --git a/internal/api/client/statuses/statusbookmark.go b/internal/api/client/statuses/statusbookmark.go index ca2597c03..0b969f3f2 100644 --- a/internal/api/client/statuses/statusbookmark.go +++ b/internal/api/client/statuses/statusbookmark.go @@ -88,7 +88,7 @@ func (m *Module) StatusBookmarkPOSTHandler(c *gin.Context) { return } - apiStatus, errWithCode := m.processor.StatusBookmark(c.Request.Context(), authed, targetStatusID) + apiStatus, errWithCode := m.processor.Status().BookmarkCreate(c.Request.Context(), authed.Account, targetStatusID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/statuses/statusboost.go b/internal/api/client/statuses/statusboost.go index fe66afb54..f4152a9b0 100644 --- a/internal/api/client/statuses/statusboost.go +++ b/internal/api/client/statuses/statusboost.go @@ -91,7 +91,7 @@ func (m *Module) StatusBoostPOSTHandler(c *gin.Context) { return } - apiStatus, errWithCode := m.processor.StatusBoost(c.Request.Context(), authed, targetStatusID) + apiStatus, errWithCode := m.processor.Status().BoostCreate(c.Request.Context(), authed.Account, authed.Application, targetStatusID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/statuses/statusboostedby.go b/internal/api/client/statuses/statusboostedby.go index 1131a3ef0..b9720a749 100644 --- a/internal/api/client/statuses/statusboostedby.go +++ b/internal/api/client/statuses/statusboostedby.go @@ -79,7 +79,7 @@ func (m *Module) StatusBoostedByGETHandler(c *gin.Context) { return } - apiAccounts, errWithCode := m.processor.StatusBoostedBy(c.Request.Context(), authed, targetStatusID) + apiAccounts, errWithCode := m.processor.Status().StatusBoostedBy(c.Request.Context(), authed.Account, targetStatusID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/statuses/statuscontext.go b/internal/api/client/statuses/statuscontext.go index 60f122cb6..8a293ea38 100644 --- a/internal/api/client/statuses/statuscontext.go +++ b/internal/api/client/statuses/statuscontext.go @@ -90,7 +90,7 @@ func (m *Module) StatusContextGETHandler(c *gin.Context) { return } - statusContext, errWithCode := m.processor.StatusGetContext(c.Request.Context(), authed, targetStatusID) + statusContext, errWithCode := m.processor.Status().ContextGet(c.Request.Context(), authed.Account, targetStatusID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/statuses/statuscreate.go b/internal/api/client/statuses/statuscreate.go index c9dc3a593..02d782592 100644 --- a/internal/api/client/statuses/statuscreate.go +++ b/internal/api/client/statuses/statuscreate.go @@ -104,7 +104,7 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) { return } - apiStatus, errWithCode := m.processor.StatusCreate(c.Request.Context(), authed, form) + apiStatus, errWithCode := m.processor.Status().Create(c.Request.Context(), authed.Account, authed.Application, form) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/statuses/statusdelete.go b/internal/api/client/statuses/statusdelete.go index 44241eeb2..1e9c11499 100644 --- a/internal/api/client/statuses/statusdelete.go +++ b/internal/api/client/statuses/statusdelete.go @@ -90,7 +90,7 @@ func (m *Module) StatusDELETEHandler(c *gin.Context) { return } - apiStatus, errWithCode := m.processor.StatusDelete(c.Request.Context(), authed, targetStatusID) + apiStatus, errWithCode := m.processor.Status().Delete(c.Request.Context(), authed.Account, targetStatusID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/statuses/statusfave.go b/internal/api/client/statuses/statusfave.go index ea111757d..47e6f1eb1 100644 --- a/internal/api/client/statuses/statusfave.go +++ b/internal/api/client/statuses/statusfave.go @@ -87,7 +87,7 @@ func (m *Module) StatusFavePOSTHandler(c *gin.Context) { return } - apiStatus, errWithCode := m.processor.StatusFave(c.Request.Context(), authed, targetStatusID) + apiStatus, errWithCode := m.processor.Status().FaveCreate(c.Request.Context(), authed.Account, targetStatusID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/statuses/statusfavedby.go b/internal/api/client/statuses/statusfavedby.go index 1991402cb..45b86c22f 100644 --- a/internal/api/client/statuses/statusfavedby.go +++ b/internal/api/client/statuses/statusfavedby.go @@ -88,7 +88,7 @@ func (m *Module) StatusFavedByGETHandler(c *gin.Context) { return } - apiAccounts, errWithCode := m.processor.StatusFavedBy(c.Request.Context(), authed, targetStatusID) + apiAccounts, errWithCode := m.processor.Status().FavedBy(c.Request.Context(), authed.Account, targetStatusID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/statuses/statusget.go b/internal/api/client/statuses/statusget.go index 17016bf48..55827d620 100644 --- a/internal/api/client/statuses/statusget.go +++ b/internal/api/client/statuses/statusget.go @@ -87,7 +87,7 @@ func (m *Module) StatusGETHandler(c *gin.Context) { return } - apiStatus, errWithCode := m.processor.StatusGet(c.Request.Context(), authed, targetStatusID) + apiStatus, errWithCode := m.processor.Status().Get(c.Request.Context(), authed.Account, targetStatusID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/statuses/statusunbookmark.go b/internal/api/client/statuses/statusunbookmark.go index 8fbaadbe6..647ea7637 100644 --- a/internal/api/client/statuses/statusunbookmark.go +++ b/internal/api/client/statuses/statusunbookmark.go @@ -88,7 +88,7 @@ func (m *Module) StatusUnbookmarkPOSTHandler(c *gin.Context) { return } - apiStatus, errWithCode := m.processor.StatusUnbookmark(c.Request.Context(), authed, targetStatusID) + apiStatus, errWithCode := m.processor.Status().BookmarkRemove(c.Request.Context(), authed.Account, targetStatusID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/statuses/statusunboost.go b/internal/api/client/statuses/statusunboost.go index 41fa187da..911d65b4a 100644 --- a/internal/api/client/statuses/statusunboost.go +++ b/internal/api/client/statuses/statusunboost.go @@ -88,7 +88,7 @@ func (m *Module) StatusUnboostPOSTHandler(c *gin.Context) { return } - apiStatus, errWithCode := m.processor.StatusUnboost(c.Request.Context(), authed, targetStatusID) + apiStatus, errWithCode := m.processor.Status().BoostRemove(c.Request.Context(), authed.Account, authed.Application, targetStatusID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/statuses/statusunfave.go b/internal/api/client/statuses/statusunfave.go index e73500347..e54dd9d6e 100644 --- a/internal/api/client/statuses/statusunfave.go +++ b/internal/api/client/statuses/statusunfave.go @@ -87,7 +87,7 @@ func (m *Module) StatusUnfavePOSTHandler(c *gin.Context) { return } - apiStatus, errWithCode := m.processor.StatusUnfave(c.Request.Context(), authed, targetStatusID) + apiStatus, errWithCode := m.processor.Status().FaveRemove(c.Request.Context(), authed.Account, targetStatusID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/streaming/stream.go b/internal/api/client/streaming/stream.go index a03f36d16..444157c1b 100644 --- a/internal/api/client/streaming/stream.go +++ b/internal/api/client/streaming/stream.go @@ -154,13 +154,13 @@ func (m *Module) StreamGETHandler(c *gin.Context) { } } - account, errWithCode := m.processor.AuthorizeStreamingRequest(c.Request.Context(), token) + account, errWithCode := m.processor.Stream().Authorize(c.Request.Context(), token) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } - stream, errWithCode := m.processor.OpenStreamForAccount(c.Request.Context(), account, streamType) + stream, errWithCode := m.processor.Stream().Open(c.Request.Context(), account, streamType) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/streaming/streaming.go b/internal/api/client/streaming/streaming.go index d4c61f7a0..1c755a2ca 100644 --- a/internal/api/client/streaming/streaming.go +++ b/internal/api/client/streaming/streaming.go @@ -42,12 +42,12 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor dTicker time.Duration wsUpgrade websocket.Upgrader } -func New(processor processing.Processor, dTicker time.Duration, wsBuf int) *Module { +func New(processor *processing.Processor, dTicker time.Duration, wsBuf int) *Module { return &Module{ processor: processor, dTicker: dTicker, diff --git a/internal/api/client/streaming/streaming_test.go b/internal/api/client/streaming/streaming_test.go index f713607bb..5fb470af8 100644 --- a/internal/api/client/streaming/streaming_test.go +++ b/internal/api/client/streaming/streaming_test.go @@ -54,7 +54,7 @@ type StreamingTestSuite struct { mediaManager media.Manager federator federation.Federator emailSender email.Sender - processor processing.Processor + processor *processing.Processor storage *storage.Driver // standard suite models diff --git a/internal/api/client/timelines/timeline.go b/internal/api/client/timelines/timeline.go index de494320a..276460d98 100644 --- a/internal/api/client/timelines/timeline.go +++ b/internal/api/client/timelines/timeline.go @@ -45,10 +45,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/user/passwordchange.go b/internal/api/client/user/passwordchange.go index c2b5c598d..f0cfe3dd6 100644 --- a/internal/api/client/user/passwordchange.go +++ b/internal/api/client/user/passwordchange.go @@ -95,7 +95,7 @@ func (m *Module) PasswordChangePOSTHandler(c *gin.Context) { return } - if errWithCode := m.processor.UserChangePassword(c.Request.Context(), authed, form); errWithCode != nil { + if errWithCode := m.processor.User().PasswordChange(c.Request.Context(), authed.User, form.OldPassword, form.NewPassword); errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } diff --git a/internal/api/client/user/user.go b/internal/api/client/user/user.go index f09984380..1117db08f 100644 --- a/internal/api/client/user/user.go +++ b/internal/api/client/user/user.go @@ -33,10 +33,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/client/user/user_test.go b/internal/api/client/user/user_test.go index 27058d5ca..c990abb56 100644 --- a/internal/api/client/user/user_test.go +++ b/internal/api/client/user/user_test.go @@ -41,7 +41,7 @@ type UserStandardTestSuite struct { mediaManager media.Manager federator federation.Federator emailSender email.Sender - processor processing.Processor + processor *processing.Processor storage *storage.Driver testTokens map[string]*gtsmodel.Token diff --git a/internal/api/fileserver.go b/internal/api/fileserver.go index b1ebae045..cdc252780 100644 --- a/internal/api/fileserver.go +++ b/internal/api/fileserver.go @@ -54,7 +54,7 @@ func (f *Fileserver) Route(r router.Router, m ...gin.HandlerFunc) { f.fileserver.Route(fileserverGroup.Handle) } -func NewFileserver(p processing.Processor) *Fileserver { +func NewFileserver(p *processing.Processor) *Fileserver { return &Fileserver{ fileserver: fileserver.New(p), } diff --git a/internal/api/fileserver/fileserver.go b/internal/api/fileserver/fileserver.go index ba2552caa..aa4696f6f 100644 --- a/internal/api/fileserver/fileserver.go +++ b/internal/api/fileserver/fileserver.go @@ -39,10 +39,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/fileserver/fileserver_test.go b/internal/api/fileserver/fileserver_test.go index 5b139454e..0a6879e70 100644 --- a/internal/api/fileserver/fileserver_test.go +++ b/internal/api/fileserver/fileserver_test.go @@ -45,7 +45,7 @@ type FileserverTestSuite struct { storage *storage.Driver federator federation.Federator tc typeutils.TypeConverter - processor processing.Processor + processor *processing.Processor mediaManager media.Manager oauthServer oauth.Server emailSender email.Sender diff --git a/internal/api/fileserver/servefile.go b/internal/api/fileserver/servefile.go index a344e3e53..d963df749 100644 --- a/internal/api/fileserver/servefile.go +++ b/internal/api/fileserver/servefile.go @@ -80,7 +80,7 @@ func (m *Module) ServeFile(c *gin.Context) { // Acquire context from gin request. ctx := c.Request.Context() - content, errWithCode := m.processor.FileGet(ctx, authed, &apimodel.GetContentRequestForm{ + content, errWithCode := m.processor.Media().GetFile(ctx, authed.Account, &apimodel.GetContentRequestForm{ AccountID: accountID, MediaType: mediaType, MediaSize: mediaSize, diff --git a/internal/api/nodeinfo.go b/internal/api/nodeinfo.go index 717d00359..bf0ff5815 100644 --- a/internal/api/nodeinfo.go +++ b/internal/api/nodeinfo.go @@ -44,7 +44,7 @@ func (w *NodeInfo) Route(r router.Router, m ...gin.HandlerFunc) { w.nodeInfo.Route(nodeInfoGroup.Handle) } -func NewNodeInfo(p processing.Processor) *NodeInfo { +func NewNodeInfo(p *processing.Processor) *NodeInfo { return &NodeInfo{ nodeInfo: nodeinfo.New(p), } diff --git a/internal/api/nodeinfo/nodeinfo.go b/internal/api/nodeinfo/nodeinfo.go index ba125d894..07ed471b9 100644 --- a/internal/api/nodeinfo/nodeinfo.go +++ b/internal/api/nodeinfo/nodeinfo.go @@ -32,10 +32,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/nodeinfo/nodeinfoget.go b/internal/api/nodeinfo/nodeinfoget.go index 59fcf2f55..9660d46dc 100644 --- a/internal/api/nodeinfo/nodeinfoget.go +++ b/internal/api/nodeinfo/nodeinfoget.go @@ -50,7 +50,7 @@ func (m *Module) NodeInfo2GETHandler(c *gin.Context) { return } - nodeInfo, errWithCode := m.processor.GetNodeInfo(c.Request.Context()) + nodeInfo, errWithCode := m.processor.Fedi().NodeInfoGet(c.Request.Context()) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/wellknown.go b/internal/api/wellknown.go index 91433643a..7edbb4d7d 100644 --- a/internal/api/wellknown.go +++ b/internal/api/wellknown.go @@ -47,7 +47,7 @@ func (w *WellKnown) Route(r router.Router, m ...gin.HandlerFunc) { w.webfinger.Route(wellKnownGroup.Handle) } -func NewWellKnown(p processing.Processor) *WellKnown { +func NewWellKnown(p *processing.Processor) *WellKnown { return &WellKnown{ nodeInfo: nodeinfo.New(p), webfinger: webfinger.New(p), diff --git a/internal/api/wellknown/nodeinfo/nodeinfo.go b/internal/api/wellknown/nodeinfo/nodeinfo.go index 70cbb2770..2ce172c42 100644 --- a/internal/api/wellknown/nodeinfo/nodeinfo.go +++ b/internal/api/wellknown/nodeinfo/nodeinfo.go @@ -32,11 +32,11 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } // New returns a new nodeinfo module -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/wellknown/nodeinfo/nodeinfoget.go b/internal/api/wellknown/nodeinfo/nodeinfoget.go index 8bfab072d..63ef264fa 100644 --- a/internal/api/wellknown/nodeinfo/nodeinfoget.go +++ b/internal/api/wellknown/nodeinfo/nodeinfoget.go @@ -50,7 +50,7 @@ func (m *Module) NodeInfoWellKnownGETHandler(c *gin.Context) { return } - resp, errWithCode := m.processor.GetNodeInfoRel(c.Request.Context()) + resp, errWithCode := m.processor.Fedi().NodeInfoRelGet(c.Request.Context()) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/wellknown/webfinger/webfinger.go b/internal/api/wellknown/webfinger/webfinger.go index d3f9287e5..ac2ba2acd 100644 --- a/internal/api/wellknown/webfinger/webfinger.go +++ b/internal/api/wellknown/webfinger/webfinger.go @@ -32,10 +32,10 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor } -func New(processor processing.Processor) *Module { +func New(processor *processing.Processor) *Module { return &Module{ processor: processor, } diff --git a/internal/api/wellknown/webfinger/webfinger_test.go b/internal/api/wellknown/webfinger/webfinger_test.go index df72e1722..38228e928 100644 --- a/internal/api/wellknown/webfinger/webfinger_test.go +++ b/internal/api/wellknown/webfinger/webfinger_test.go @@ -48,7 +48,7 @@ type WebfingerStandardTestSuite struct { mediaManager media.Manager federator federation.Federator emailSender email.Sender - processor processing.Processor + processor *processing.Processor storage *storage.Driver oauthServer oauth.Server diff --git a/internal/api/wellknown/webfinger/webfingerget.go b/internal/api/wellknown/webfinger/webfingerget.go index 8c0d92e23..60bc316b1 100644 --- a/internal/api/wellknown/webfinger/webfingerget.go +++ b/internal/api/wellknown/webfinger/webfingerget.go @@ -81,7 +81,7 @@ func (m *Module) WebfingerGETRequest(c *gin.Context) { return } - resp, errWithCode := m.processor.GetWebfingerAccount(c.Request.Context(), requestedUsername) + resp, errWithCode := m.processor.Fedi().WebfingerGet(c.Request.Context(), requestedUsername) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/processing/account.go b/internal/processing/account.go deleted file mode 100644 index 37e352f6d..000000000 --- a/internal/processing/account.go +++ /dev/null @@ -1,92 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package processing - -import ( - "context" - "time" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -func (p *processor) AccountCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.AccountCreateRequest) (*apimodel.Token, gtserror.WithCode) { - return p.accountProcessor.Create(ctx, authed.Token, authed.Application, form) -} - -func (p *processor) AccountDeleteLocal(ctx context.Context, authed *oauth.Auth, form *apimodel.AccountDeleteRequest) gtserror.WithCode { - return p.accountProcessor.DeleteLocal(ctx, authed.Account, form) -} - -func (p *processor) AccountGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Account, gtserror.WithCode) { - return p.accountProcessor.Get(ctx, authed.Account, targetAccountID) -} - -func (p *processor) AccountGetLocalByUsername(ctx context.Context, authed *oauth.Auth, username string) (*apimodel.Account, gtserror.WithCode) { - return p.accountProcessor.GetLocalByUsername(ctx, authed.Account, username) -} - -func (p *processor) AccountGetCustomCSSForUsername(ctx context.Context, username string) (string, gtserror.WithCode) { - return p.accountProcessor.GetCustomCSSForUsername(ctx, username) -} - -func (p *processor) AccountGetRSSFeedForUsername(ctx context.Context, username string) (func() (string, gtserror.WithCode), time.Time, gtserror.WithCode) { - return p.accountProcessor.GetRSSFeedForUsername(ctx, username) -} - -func (p *processor) AccountUpdate(ctx context.Context, authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode) { - return p.accountProcessor.Update(ctx, authed.Account, form) -} - -func (p *processor) AccountStatusesGet(ctx context.Context, authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) (*apimodel.PageableResponse, gtserror.WithCode) { - return p.accountProcessor.StatusesGet(ctx, authed.Account, targetAccountID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly) -} - -func (p *processor) AccountWebStatusesGet(ctx context.Context, targetAccountID string, maxID string) (*apimodel.PageableResponse, gtserror.WithCode) { - return p.accountProcessor.WebStatusesGet(ctx, targetAccountID, maxID) -} - -func (p *processor) AccountFollowersGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) { - return p.accountProcessor.FollowersGet(ctx, authed.Account, targetAccountID) -} - -func (p *processor) AccountFollowingGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) { - return p.accountProcessor.FollowingGet(ctx, authed.Account, targetAccountID) -} - -func (p *processor) AccountRelationshipGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { - return p.accountProcessor.RelationshipGet(ctx, authed.Account, targetAccountID) -} - -func (p *processor) AccountFollowCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.AccountFollowRequest) (*apimodel.Relationship, gtserror.WithCode) { - return p.accountProcessor.FollowCreate(ctx, authed.Account, form) -} - -func (p *processor) AccountFollowRemove(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { - return p.accountProcessor.FollowRemove(ctx, authed.Account, targetAccountID) -} - -func (p *processor) AccountBlockCreate(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { - return p.accountProcessor.BlockCreate(ctx, authed.Account, targetAccountID) -} - -func (p *processor) AccountBlockRemove(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { - return p.accountProcessor.BlockRemove(ctx, authed.Account, targetAccountID) -} diff --git a/internal/processing/account/account.go b/internal/processing/account/account.go index 7263bacd2..41315d483 100644 --- a/internal/processing/account/account.go +++ b/internal/processing/account/account.go @@ -19,15 +19,9 @@ package account import ( - "context" - "mime/multipart" - "time" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/concurrency" "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/messages" @@ -35,63 +29,12 @@ "github.com/superseriousbusiness/gotosocial/internal/text" "github.com/superseriousbusiness/gotosocial/internal/typeutils" "github.com/superseriousbusiness/gotosocial/internal/visibility" - "github.com/superseriousbusiness/oauth2/v4" ) -// Processor wraps a bunch of functions for processing account actions. -type Processor interface { - // Create processes the given form for creating a new account, returning an oauth token for that account if successful. - Create(ctx context.Context, applicationToken oauth2.TokenInfo, application *gtsmodel.Application, form *apimodel.AccountCreateRequest) (*apimodel.Token, gtserror.WithCode) - // Delete deletes an account, and all of that account's statuses, media, follows, notifications, etc etc etc. - // The origin passed here should be either the ID of the account doing the delete (can be itself), or the ID of a domain block. - Delete(ctx context.Context, account *gtsmodel.Account, origin string) gtserror.WithCode - // DeleteLocal is like delete, but specifically for deletion of local accounts rather than federated ones. - // Unlike Delete, it will propagate the deletion out across the federating API to other instances. - DeleteLocal(ctx context.Context, account *gtsmodel.Account, form *apimodel.AccountDeleteRequest) gtserror.WithCode - // Get processes the given request for account information. - Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Account, gtserror.WithCode) - // GetLocalByUsername processes the given request for account information targeting a local account by username. - GetLocalByUsername(ctx context.Context, requestingAccount *gtsmodel.Account, username string) (*apimodel.Account, gtserror.WithCode) - // GetCustomCSSForUsername returns custom css for the given local username. - GetCustomCSSForUsername(ctx context.Context, username string) (string, gtserror.WithCode) - // GetRSSFeedForUsername returns RSS feed for the given local username. - GetRSSFeedForUsername(ctx context.Context, username string) (func() (string, gtserror.WithCode), time.Time, gtserror.WithCode) - // Update processes the update of an account with the given form - Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode) - // StatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for - // the account given in authed. - StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) (*apimodel.PageableResponse, gtserror.WithCode) - // WebStatusesGet fetches a number of statuses (in descending order) from the given account. It selects only - // statuses which are suitable for showing on the public web profile of an account. - WebStatusesGet(ctx context.Context, targetAccountID string, maxID string) (*apimodel.PageableResponse, gtserror.WithCode) - // StatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for - // the account given in authed. - BookmarksGet(ctx context.Context, requestingAccount *gtsmodel.Account, limit int, maxID string, minID string) (*apimodel.PageableResponse, gtserror.WithCode) - // FollowersGet fetches a list of the target account's followers. - FollowersGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) - // FollowingGet fetches a list of the accounts that target account is following. - FollowingGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) - // RelationshipGet returns a relationship model describing the relationship of the targetAccount to the Authed account. - RelationshipGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) - // FollowCreate handles a follow request to an account, either remote or local. - FollowCreate(ctx context.Context, requestingAccount *gtsmodel.Account, form *apimodel.AccountFollowRequest) (*apimodel.Relationship, gtserror.WithCode) - // FollowRemove handles the removal of a follow/follow request to an account, either remote or local. - FollowRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) - // BlockCreate handles the creation of a block from requestingAccount to targetAccountID, either remote or local. - BlockCreate(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) - // BlockRemove handles the removal of a block from requestingAccount to targetAccountID, either remote or local. - BlockRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) - // UpdateAvatar does the dirty work of checking the avatar part of an account update form, - // parsing and checking the image, and doing the necessary updates in the database for this to become - // the account's new avatar image. - UpdateAvatar(ctx context.Context, avatar *multipart.FileHeader, description *string, accountID string) (*gtsmodel.MediaAttachment, error) - // UpdateHeader does the dirty work of checking the header part of an account update form, - // parsing and checking the image, and doing the necessary updates in the database for this to become - // the account's new header image. - UpdateHeader(ctx context.Context, header *multipart.FileHeader, description *string, accountID string) (*gtsmodel.MediaAttachment, error) -} - -type processor struct { +// Processor wraps functionality for updating, creating, and deleting accounts in response to API requests. +// +// It also contains logic for actions towards accounts such as following, blocking, seeing follows, etc. +type Processor struct { tc typeutils.TypeConverter mediaManager media.Manager clientWorker *concurrency.WorkerPool[messages.FromClientAPI] @@ -104,8 +47,16 @@ type processor struct { } // New returns a new account processor. -func New(db db.DB, tc typeutils.TypeConverter, mediaManager media.Manager, oauthServer oauth.Server, clientWorker *concurrency.WorkerPool[messages.FromClientAPI], federator federation.Federator, parseMention gtsmodel.ParseMentionFunc) Processor { - return &processor{ +func New( + db db.DB, + tc typeutils.TypeConverter, + mediaManager media.Manager, + oauthServer oauth.Server, + clientWorker *concurrency.WorkerPool[messages.FromClientAPI], + federator federation.Federator, + parseMention gtsmodel.ParseMentionFunc, +) Processor { + return Processor{ tc: tc, mediaManager: mediaManager, clientWorker: clientWorker, diff --git a/internal/processing/account/createblock.go b/internal/processing/account/block.go similarity index 77% rename from internal/processing/account/createblock.go rename to internal/processing/account/block.go index 68f28fafe..99effd3a3 100644 --- a/internal/processing/account/createblock.go +++ b/internal/processing/account/block.go @@ -20,6 +20,7 @@ import ( "context" + "errors" "fmt" "github.com/superseriousbusiness/gotosocial/internal/ap" @@ -32,7 +33,8 @@ "github.com/superseriousbusiness/gotosocial/internal/uris" ) -func (p *processor) BlockCreate(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { +// BlockCreate handles the creation of a block from requestingAccount to targetAccountID, either remote or local. +func (p *Processor) BlockCreate(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { // make sure the target account actually exists in our db targetAccount, err := p.db.GetAccountByID(ctx, targetAccountID) if err != nil { @@ -154,3 +156,37 @@ func (p *processor) BlockCreate(ctx context.Context, requestingAccount *gtsmodel return p.RelationshipGet(ctx, requestingAccount, targetAccountID) } + +// BlockRemove handles the removal of a block from requestingAccount to targetAccountID, either remote or local. +func (p *Processor) BlockRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { + // make sure the target account actually exists in our db + targetAccount, err := p.db.GetAccountByID(ctx, targetAccountID) + if err != nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("BlockCreate: error getting account %s from the db: %s", targetAccountID, err)) + } + + // check if a block exists, and remove it if it does + block, err := p.db.GetBlock(ctx, requestingAccount.ID, targetAccountID) + if err == nil { + // we got a block, remove it + block.Account = requestingAccount + block.TargetAccount = targetAccount + if err := p.db.DeleteBlockByID(ctx, block.ID); err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("BlockRemove: error removing block from db: %s", err)) + } + + // send the UNDO activity to the client worker for async processing + p.clientWorker.Queue(messages.FromClientAPI{ + APObjectType: ap.ActivityBlock, + APActivityType: ap.ActivityUndo, + GTSModel: block, + OriginAccount: requestingAccount, + TargetAccount: targetAccount, + }) + } else if !errors.Is(err, db.ErrNoEntries) { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("BlockRemove: error getting possible block from db: %s", err)) + } + + // return whatever relationship results from all this + return p.RelationshipGet(ctx, requestingAccount, targetAccountID) +} diff --git a/internal/processing/account/getbookmarks.go b/internal/processing/account/bookmarks.go similarity index 97% rename from internal/processing/account/getbookmarks.go rename to internal/processing/account/bookmarks.go index 0a63a074f..7551b1e0c 100644 --- a/internal/processing/account/getbookmarks.go +++ b/internal/processing/account/bookmarks.go @@ -28,7 +28,7 @@ "github.com/superseriousbusiness/gotosocial/internal/util" ) -func (p *processor) BookmarksGet(ctx context.Context, requestingAccount *gtsmodel.Account, limit int, maxID string, minID string) (*apimodel.PageableResponse, gtserror.WithCode) { +func (p *Processor) BookmarksGet(ctx context.Context, requestingAccount *gtsmodel.Account, limit int, maxID string, minID string) (*apimodel.PageableResponse, gtserror.WithCode) { if requestingAccount == nil { return nil, gtserror.NewErrorForbidden(fmt.Errorf("cannot retrieve bookmarks without a requesting account")) } diff --git a/internal/processing/account/create.go b/internal/processing/account/create.go index b0efccf7e..8b82bc681 100644 --- a/internal/processing/account/create.go +++ b/internal/processing/account/create.go @@ -33,7 +33,8 @@ "github.com/superseriousbusiness/oauth2/v4" ) -func (p *processor) Create(ctx context.Context, applicationToken oauth2.TokenInfo, application *gtsmodel.Application, form *apimodel.AccountCreateRequest) (*apimodel.Token, gtserror.WithCode) { +// Create processes the given form for creating a new account, returning an oauth token for that account if successful. +func (p *Processor) Create(ctx context.Context, applicationToken oauth2.TokenInfo, application *gtsmodel.Application, form *apimodel.AccountCreateRequest) (*apimodel.Token, gtserror.WithCode) { emailAvailable, err := p.db.IsEmailAvailable(ctx, form.Email) if err != nil { return nil, gtserror.NewErrorBadRequest(err) diff --git a/internal/processing/account/delete.go b/internal/processing/account/delete.go index 32321e196..7a31b45d4 100644 --- a/internal/processing/account/delete.go +++ b/internal/processing/account/delete.go @@ -34,28 +34,9 @@ "golang.org/x/crypto/bcrypt" ) -// Delete handles the complete deletion of an account. -// -// To be done in this function: -// 1. Delete account's application(s), clients, and oauth tokens -// 2. Delete account's blocks -// 3. Delete account's emoji -// 4. Delete account's follow requests -// 5. Delete account's follows -// 6. Delete account's statuses -// 7. Delete account's media attachments -// 8. Delete account's mentions -// 9. Delete account's polls -// 10. Delete account's notifications -// 11. Delete account's bookmarks -// 12. Delete account's faves -// 13. Delete account's mutes -// 14. Delete account's streams -// 15. Delete account's tags -// 16. Delete account's user -// 17. Delete account's timeline -// 18. Delete account itself -func (p *processor) Delete(ctx context.Context, account *gtsmodel.Account, origin string) gtserror.WithCode { +// Delete deletes an account, and all of that account's statuses, media, follows, notifications, etc etc etc. +// The origin passed here should be either the ID of the account doing the delete (can be itself), or the ID of a domain block. +func (p *Processor) Delete(ctx context.Context, account *gtsmodel.Account, origin string) gtserror.WithCode { fields := kv.Fields{{"username", account.Username}} if account.Domain != "" { @@ -289,7 +270,9 @@ func (p *processor) Delete(ctx context.Context, account *gtsmodel.Account, origi return nil } -func (p *processor) DeleteLocal(ctx context.Context, account *gtsmodel.Account, form *apimodel.AccountDeleteRequest) gtserror.WithCode { +// DeleteLocal is like Delete, but specifically for deletion of local accounts rather than federated ones. +// Unlike Delete, it will propagate the deletion out across the federating API to other instances. +func (p *Processor) DeleteLocal(ctx context.Context, account *gtsmodel.Account, form *apimodel.AccountDeleteRequest) gtserror.WithCode { fromClientAPIMessage := messages.FromClientAPI{ APObjectType: ap.ActorPerson, APActivityType: ap.ActivityDelete, diff --git a/internal/processing/account/createfollow.go b/internal/processing/account/follow.go similarity index 60% rename from internal/processing/account/createfollow.go rename to internal/processing/account/follow.go index 721cf39c7..d4d479be7 100644 --- a/internal/processing/account/createfollow.go +++ b/internal/processing/account/follow.go @@ -32,7 +32,8 @@ "github.com/superseriousbusiness/gotosocial/internal/uris" ) -func (p *processor) FollowCreate(ctx context.Context, requestingAccount *gtsmodel.Account, form *apimodel.AccountFollowRequest) (*apimodel.Relationship, gtserror.WithCode) { +// FollowCreate handles a follow request to an account, either remote or local. +func (p *Processor) FollowCreate(ctx context.Context, requestingAccount *gtsmodel.Account, form *apimodel.AccountFollowRequest) (*apimodel.Relationship, gtserror.WithCode) { // if there's a block between the accounts we shouldn't create the request ofc if blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, form.ID, true); err != nil { return nil, gtserror.NewErrorInternalError(err) @@ -119,3 +120,86 @@ func (p *processor) FollowCreate(ctx context.Context, requestingAccount *gtsmode // return whatever relationship results from this return p.RelationshipGet(ctx, requestingAccount, form.ID) } + +// FollowRemove handles the removal of a follow/follow request to an account, either remote or local. +func (p *Processor) FollowRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { + // if there's a block between the accounts we shouldn't do anything + blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, targetAccountID, true) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + if blocked { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("AccountFollowRemove: block exists between accounts")) + } + + // make sure the target account actually exists in our db + targetAcct, err := p.db.GetAccountByID(ctx, targetAccountID) + if err != nil { + if err == db.ErrNoEntries { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("AccountFollowRemove: account %s not found in the db: %s", targetAccountID, err)) + } + } + + // check if a follow request exists, and remove it if it does (storing the URI for later) + var frChanged bool + var frURI string + fr := >smodel.FollowRequest{} + if err := p.db.GetWhere(ctx, []db.Where{ + {Key: "account_id", Value: requestingAccount.ID}, + {Key: "target_account_id", Value: targetAccountID}, + }, fr); err == nil { + frURI = fr.URI + if err := p.db.DeleteByID(ctx, fr.ID, fr); err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("AccountFollowRemove: error removing follow request from db: %s", err)) + } + frChanged = true + } + + // now do the same thing for any existing follow + var fChanged bool + var fURI string + f := >smodel.Follow{} + if err := p.db.GetWhere(ctx, []db.Where{ + {Key: "account_id", Value: requestingAccount.ID}, + {Key: "target_account_id", Value: targetAccountID}, + }, f); err == nil { + fURI = f.URI + if err := p.db.DeleteByID(ctx, f.ID, f); err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("AccountFollowRemove: error removing follow from db: %s", err)) + } + fChanged = true + } + + // follow request status changed so send the UNDO activity to the channel for async processing + if frChanged { + p.clientWorker.Queue(messages.FromClientAPI{ + APObjectType: ap.ActivityFollow, + APActivityType: ap.ActivityUndo, + GTSModel: >smodel.Follow{ + AccountID: requestingAccount.ID, + TargetAccountID: targetAccountID, + URI: frURI, + }, + OriginAccount: requestingAccount, + TargetAccount: targetAcct, + }) + } + + // follow status changed so send the UNDO activity to the channel for async processing + if fChanged { + p.clientWorker.Queue(messages.FromClientAPI{ + APObjectType: ap.ActivityFollow, + APActivityType: ap.ActivityUndo, + GTSModel: >smodel.Follow{ + AccountID: requestingAccount.ID, + TargetAccountID: targetAccountID, + URI: fURI, + }, + OriginAccount: requestingAccount, + TargetAccount: targetAcct, + }) + } + + // return whatever relationship results from all this + return p.RelationshipGet(ctx, requestingAccount, targetAccountID) +} diff --git a/internal/processing/account/get.go b/internal/processing/account/get.go index 0592555da..11de1ddac 100644 --- a/internal/processing/account/get.go +++ b/internal/processing/account/get.go @@ -31,7 +31,8 @@ "github.com/superseriousbusiness/gotosocial/internal/transport" ) -func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Account, gtserror.WithCode) { +// Get processes the given request for account information. +func (p *Processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Account, gtserror.WithCode) { targetAccount, err := p.db.GetAccountByID(ctx, targetAccountID) if err != nil { if err == db.ErrNoEntries { @@ -40,10 +41,11 @@ func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error: %s", err)) } - return p.getAccountFor(ctx, requestingAccount, targetAccount) + return p.getFor(ctx, requestingAccount, targetAccount) } -func (p *processor) GetLocalByUsername(ctx context.Context, requestingAccount *gtsmodel.Account, username string) (*apimodel.Account, gtserror.WithCode) { +// GetLocalByUsername processes the given request for account information targeting a local account by username. +func (p *Processor) GetLocalByUsername(ctx context.Context, requestingAccount *gtsmodel.Account, username string) (*apimodel.Account, gtserror.WithCode) { targetAccount, err := p.db.GetAccountByUsernameDomain(ctx, username, "") if err != nil { if err == db.ErrNoEntries { @@ -52,10 +54,11 @@ func (p *processor) GetLocalByUsername(ctx context.Context, requestingAccount *g return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error: %s", err)) } - return p.getAccountFor(ctx, requestingAccount, targetAccount) + return p.getFor(ctx, requestingAccount, targetAccount) } -func (p *processor) GetCustomCSSForUsername(ctx context.Context, username string) (string, gtserror.WithCode) { +// GetCustomCSSForUsername returns custom css for the given local username. +func (p *Processor) GetCustomCSSForUsername(ctx context.Context, username string) (string, gtserror.WithCode) { customCSS, err := p.db.GetAccountCustomCSSByUsername(ctx, username) if err != nil { if err == db.ErrNoEntries { @@ -67,7 +70,7 @@ func (p *processor) GetCustomCSSForUsername(ctx context.Context, username string return customCSS, nil } -func (p *processor) getAccountFor(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (*apimodel.Account, gtserror.WithCode) { +func (p *Processor) getFor(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (*apimodel.Account, gtserror.WithCode) { var blocked bool var err error if requestingAccount != nil { diff --git a/internal/processing/account/getfollowers.go b/internal/processing/account/getfollowers.go deleted file mode 100644 index df6fcc3f9..000000000 --- a/internal/processing/account/getfollowers.go +++ /dev/null @@ -1,74 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package account - -import ( - "context" - "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) FollowersGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) { - if blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, targetAccountID, true); err != nil { - return nil, gtserror.NewErrorInternalError(err) - } else if blocked { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("block exists between accounts")) - } - - accounts := []apimodel.Account{} - follows, err := p.db.GetAccountFollowedBy(ctx, targetAccountID, false) - if err != nil { - if err == db.ErrNoEntries { - return accounts, nil - } - return nil, gtserror.NewErrorInternalError(err) - } - - for _, f := range follows { - blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, f.AccountID, true) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - if blocked { - continue - } - - if f.Account == nil { - a, err := p.db.GetAccountByID(ctx, f.AccountID) - if err != nil { - if err == db.ErrNoEntries { - continue - } - return nil, gtserror.NewErrorInternalError(err) - } - f.Account = a - } - - account, err := p.tc.AccountToAPIAccountPublic(ctx, f.Account) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - accounts = append(accounts, *account) - } - return accounts, nil -} diff --git a/internal/processing/account/getrelationship.go b/internal/processing/account/getrelationship.go deleted file mode 100644 index b8406c38a..000000000 --- a/internal/processing/account/getrelationship.go +++ /dev/null @@ -1,47 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package account - -import ( - "context" - "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) RelationshipGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { - if requestingAccount == nil { - return nil, gtserror.NewErrorForbidden(errors.New("not authed")) - } - - gtsR, err := p.db.GetRelationship(ctx, requestingAccount.ID, targetAccountID) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error getting relationship: %s", err)) - } - - r, err := p.tc.RelationshipToAPIRelationship(ctx, gtsR) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting relationship: %s", err)) - } - - return r, nil -} diff --git a/internal/processing/account/getfollowing.go b/internal/processing/account/relationships.go similarity index 50% rename from internal/processing/account/getfollowing.go rename to internal/processing/account/relationships.go index fc584c778..cb2789829 100644 --- a/internal/processing/account/getfollowing.go +++ b/internal/processing/account/relationships.go @@ -20,6 +20,7 @@ import ( "context" + "errors" "fmt" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" @@ -28,7 +29,54 @@ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) -func (p *processor) FollowingGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) { +// FollowersGet fetches a list of the target account's followers. +func (p *Processor) FollowersGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) { + if blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, targetAccountID, true); err != nil { + return nil, gtserror.NewErrorInternalError(err) + } else if blocked { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("block exists between accounts")) + } + + accounts := []apimodel.Account{} + follows, err := p.db.GetAccountFollowedBy(ctx, targetAccountID, false) + if err != nil { + if err == db.ErrNoEntries { + return accounts, nil + } + return nil, gtserror.NewErrorInternalError(err) + } + + for _, f := range follows { + blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, f.AccountID, true) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + if blocked { + continue + } + + if f.Account == nil { + a, err := p.db.GetAccountByID(ctx, f.AccountID) + if err != nil { + if err == db.ErrNoEntries { + continue + } + return nil, gtserror.NewErrorInternalError(err) + } + f.Account = a + } + + account, err := p.tc.AccountToAPIAccountPublic(ctx, f.Account) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + accounts = append(accounts, *account) + } + return accounts, nil +} + +// FollowingGet fetches a list of the accounts that target account is following. +func (p *Processor) FollowingGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) { if blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, targetAccountID, true); err != nil { return nil, gtserror.NewErrorInternalError(err) } else if blocked { @@ -72,3 +120,22 @@ func (p *processor) FollowingGet(ctx context.Context, requestingAccount *gtsmode } return accounts, nil } + +// RelationshipGet returns a relationship model describing the relationship of the targetAccount to the Authed account. +func (p *Processor) RelationshipGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { + if requestingAccount == nil { + return nil, gtserror.NewErrorForbidden(errors.New("not authed")) + } + + gtsR, err := p.db.GetRelationship(ctx, requestingAccount.ID, targetAccountID) + if err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error getting relationship: %s", err)) + } + + r, err := p.tc.RelationshipToAPIRelationship(ctx, gtsR) + if err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting relationship: %s", err)) + } + + return r, nil +} diff --git a/internal/processing/account/removeblock.go b/internal/processing/account/removeblock.go deleted file mode 100644 index 4316af10f..000000000 --- a/internal/processing/account/removeblock.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package account - -import ( - "context" - "errors" - "fmt" - - "github.com/superseriousbusiness/gotosocial/internal/ap" - 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/messages" -) - -func (p *processor) BlockRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { - // make sure the target account actually exists in our db - targetAccount, err := p.db.GetAccountByID(ctx, targetAccountID) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("BlockCreate: error getting account %s from the db: %s", targetAccountID, err)) - } - - // check if a block exists, and remove it if it does - block, err := p.db.GetBlock(ctx, requestingAccount.ID, targetAccountID) - if err == nil { - // we got a block, remove it - block.Account = requestingAccount - block.TargetAccount = targetAccount - if err := p.db.DeleteBlockByID(ctx, block.ID); err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("BlockRemove: error removing block from db: %s", err)) - } - - // send the UNDO activity to the client worker for async processing - p.clientWorker.Queue(messages.FromClientAPI{ - APObjectType: ap.ActivityBlock, - APActivityType: ap.ActivityUndo, - GTSModel: block, - OriginAccount: requestingAccount, - TargetAccount: targetAccount, - }) - } else if !errors.Is(err, db.ErrNoEntries) { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("BlockRemove: error getting possible block from db: %s", err)) - } - - // return whatever relationship results from all this - return p.RelationshipGet(ctx, requestingAccount, targetAccountID) -} diff --git a/internal/processing/account/removefollow.go b/internal/processing/account/removefollow.go deleted file mode 100644 index 83ced1238..000000000 --- a/internal/processing/account/removefollow.go +++ /dev/null @@ -1,113 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package account - -import ( - "context" - "fmt" - - "github.com/superseriousbusiness/gotosocial/internal/ap" - 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/messages" -) - -func (p *processor) FollowRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { - // if there's a block between the accounts we shouldn't do anything - blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, targetAccountID, true) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - if blocked { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("AccountFollowRemove: block exists between accounts")) - } - - // make sure the target account actually exists in our db - targetAcct, err := p.db.GetAccountByID(ctx, targetAccountID) - if err != nil { - if err == db.ErrNoEntries { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("AccountFollowRemove: account %s not found in the db: %s", targetAccountID, err)) - } - } - - // check if a follow request exists, and remove it if it does (storing the URI for later) - var frChanged bool - var frURI string - fr := >smodel.FollowRequest{} - if err := p.db.GetWhere(ctx, []db.Where{ - {Key: "account_id", Value: requestingAccount.ID}, - {Key: "target_account_id", Value: targetAccountID}, - }, fr); err == nil { - frURI = fr.URI - if err := p.db.DeleteByID(ctx, fr.ID, fr); err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("AccountFollowRemove: error removing follow request from db: %s", err)) - } - frChanged = true - } - - // now do the same thing for any existing follow - var fChanged bool - var fURI string - f := >smodel.Follow{} - if err := p.db.GetWhere(ctx, []db.Where{ - {Key: "account_id", Value: requestingAccount.ID}, - {Key: "target_account_id", Value: targetAccountID}, - }, f); err == nil { - fURI = f.URI - if err := p.db.DeleteByID(ctx, f.ID, f); err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("AccountFollowRemove: error removing follow from db: %s", err)) - } - fChanged = true - } - - // follow request status changed so send the UNDO activity to the channel for async processing - if frChanged { - p.clientWorker.Queue(messages.FromClientAPI{ - APObjectType: ap.ActivityFollow, - APActivityType: ap.ActivityUndo, - GTSModel: >smodel.Follow{ - AccountID: requestingAccount.ID, - TargetAccountID: targetAccountID, - URI: frURI, - }, - OriginAccount: requestingAccount, - TargetAccount: targetAcct, - }) - } - - // follow status changed so send the UNDO activity to the channel for async processing - if fChanged { - p.clientWorker.Queue(messages.FromClientAPI{ - APObjectType: ap.ActivityFollow, - APActivityType: ap.ActivityUndo, - GTSModel: >smodel.Follow{ - AccountID: requestingAccount.ID, - TargetAccountID: targetAccountID, - URI: fURI, - }, - OriginAccount: requestingAccount, - TargetAccount: targetAcct, - }) - } - - // return whatever relationship results from all this - return p.RelationshipGet(ctx, requestingAccount, targetAccountID) -} diff --git a/internal/processing/account/getrss.go b/internal/processing/account/rss.go similarity index 96% rename from internal/processing/account/getrss.go rename to internal/processing/account/rss.go index 2298f39ae..22065cf8e 100644 --- a/internal/processing/account/getrss.go +++ b/internal/processing/account/rss.go @@ -32,7 +32,8 @@ const rssFeedLength = 20 -func (p *processor) GetRSSFeedForUsername(ctx context.Context, username string) (func() (string, gtserror.WithCode), time.Time, gtserror.WithCode) { +// GetRSSFeedForUsername returns RSS feed for the given local username. +func (p *Processor) GetRSSFeedForUsername(ctx context.Context, username string) (func() (string, gtserror.WithCode), time.Time, gtserror.WithCode) { account, err := p.db.GetAccountByUsernameDomain(ctx, username, "") if err != nil { if err == db.ErrNoEntries { diff --git a/internal/processing/account/getrss_test.go b/internal/processing/account/rss_test.go similarity index 100% rename from internal/processing/account/getrss_test.go rename to internal/processing/account/rss_test.go diff --git a/internal/processing/account/getstatuses.go b/internal/processing/account/statuses.go similarity index 90% rename from internal/processing/account/getstatuses.go rename to internal/processing/account/statuses.go index b231bb450..29833086d 100644 --- a/internal/processing/account/getstatuses.go +++ b/internal/processing/account/statuses.go @@ -29,7 +29,9 @@ "github.com/superseriousbusiness/gotosocial/internal/util" ) -func (p *processor) StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) (*apimodel.PageableResponse, gtserror.WithCode) { +// StatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for +// the account given in authed. +func (p *Processor) StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) (*apimodel.PageableResponse, gtserror.WithCode) { if requestingAccount != nil { if blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, targetAccountID, true); err != nil { return nil, gtserror.NewErrorInternalError(err) @@ -96,7 +98,9 @@ func (p *processor) StatusesGet(ctx context.Context, requestingAccount *gtsmodel }) } -func (p *processor) WebStatusesGet(ctx context.Context, targetAccountID string, maxID string) (*apimodel.PageableResponse, gtserror.WithCode) { +// WebStatusesGet fetches a number of statuses (in descending order) from the given account. It selects only +// statuses which are suitable for showing on the public web profile of an account. +func (p *Processor) WebStatusesGet(ctx context.Context, targetAccountID string, maxID string) (*apimodel.PageableResponse, gtserror.WithCode) { acct, err := p.db.GetAccountByID(ctx, targetAccountID) if err != nil { if err == db.ErrNoEntries { diff --git a/internal/processing/account/update.go b/internal/processing/account/update.go index e6867bfd3..cffbbb0c5 100644 --- a/internal/processing/account/update.go +++ b/internal/processing/account/update.go @@ -33,10 +33,12 @@ "github.com/superseriousbusiness/gotosocial/internal/media" "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/text" + "github.com/superseriousbusiness/gotosocial/internal/typeutils" "github.com/superseriousbusiness/gotosocial/internal/validate" ) -func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode) { +// Update processes the update of an account with the given form. +func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode) { if form.Discoverable != nil { account.Discoverable = form.Discoverable } @@ -138,7 +140,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form if err := validate.Privacy(*form.Source.Privacy); err != nil { return nil, gtserror.NewErrorBadRequest(err) } - privacy := p.tc.APIVisToVis(apimodel.Visibility(*form.Source.Privacy)) + privacy := typeutils.APIVisToVis(apimodel.Visibility(*form.Source.Privacy)) account.Privacy = privacy } @@ -185,7 +187,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form // UpdateAvatar does the dirty work of checking the avatar part of an account update form, // parsing and checking the image, and doing the necessary updates in the database for this to become // the account's new avatar image. -func (p *processor) UpdateAvatar(ctx context.Context, avatar *multipart.FileHeader, description *string, accountID string) (*gtsmodel.MediaAttachment, error) { +func (p *Processor) UpdateAvatar(ctx context.Context, avatar *multipart.FileHeader, description *string, accountID string) (*gtsmodel.MediaAttachment, error) { maxImageSize := config.GetMediaImageMaxSize() if avatar.Size > int64(maxImageSize) { return nil, fmt.Errorf("UpdateAvatar: avatar with size %d exceeded max image size of %d bytes", avatar.Size, maxImageSize) @@ -213,7 +215,7 @@ func (p *processor) UpdateAvatar(ctx context.Context, avatar *multipart.FileHead // UpdateHeader does the dirty work of checking the header part of an account update form, // parsing and checking the image, and doing the necessary updates in the database for this to become // the account's new header image. -func (p *processor) UpdateHeader(ctx context.Context, header *multipart.FileHeader, description *string, accountID string) (*gtsmodel.MediaAttachment, error) { +func (p *Processor) UpdateHeader(ctx context.Context, header *multipart.FileHeader, description *string, accountID string) (*gtsmodel.MediaAttachment, error) { maxImageSize := config.GetMediaImageMaxSize() if header.Size > int64(maxImageSize) { return nil, fmt.Errorf("UpdateHeader: header with size %d exceeded max image size of %d bytes", header.Size, maxImageSize) diff --git a/internal/processing/account_test.go b/internal/processing/account_test.go index 6ddb2d809..5a171e621 100644 --- a/internal/processing/account_test.go +++ b/internal/processing/account_test.go @@ -53,7 +53,7 @@ func (suite *AccountTestSuite) TestAccountDeleteLocal() { err := suite.db.Put(ctx, follow) suite.NoError(err) - errWithCode := suite.processor.AccountDeleteLocal(ctx, suite.testAutheds["local_account_1"], &apimodel.AccountDeleteRequest{ + errWithCode := suite.processor.Account().DeleteLocal(ctx, suite.testAccounts["local_account_1"], &apimodel.AccountDeleteRequest{ Password: "password", DeleteOriginID: deletingAccount.ID, }) diff --git a/internal/processing/admin.go b/internal/processing/admin.go deleted file mode 100644 index b85185290..000000000 --- a/internal/processing/admin.go +++ /dev/null @@ -1,95 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package processing - -import ( - "context" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -func (p *processor) AdminAccountAction(ctx context.Context, authed *oauth.Auth, form *apimodel.AdminAccountActionRequest) gtserror.WithCode { - return p.adminProcessor.AccountAction(ctx, authed.Account, form) -} - -func (p *processor) AdminEmojiCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, gtserror.WithCode) { - return p.adminProcessor.EmojiCreate(ctx, authed.Account, authed.User, form) -} - -func (p *processor) AdminEmojisGet(ctx context.Context, authed *oauth.Auth, domain string, includeDisabled bool, includeEnabled bool, shortcode string, maxShortcodeDomain string, minShortcodeDomain string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) { - return p.adminProcessor.EmojisGet(ctx, authed.Account, authed.User, domain, includeDisabled, includeEnabled, shortcode, maxShortcodeDomain, minShortcodeDomain, limit) -} - -func (p *processor) AdminEmojiGet(ctx context.Context, authed *oauth.Auth, id string) (*apimodel.AdminEmoji, gtserror.WithCode) { - return p.adminProcessor.EmojiGet(ctx, authed.Account, authed.User, id) -} - -func (p *processor) AdminEmojiUpdate(ctx context.Context, id string, form *apimodel.EmojiUpdateRequest) (*apimodel.AdminEmoji, gtserror.WithCode) { - return p.adminProcessor.EmojiUpdate(ctx, id, form) -} - -func (p *processor) AdminEmojiDelete(ctx context.Context, authed *oauth.Auth, id string) (*apimodel.AdminEmoji, gtserror.WithCode) { - return p.adminProcessor.EmojiDelete(ctx, id) -} - -func (p *processor) AdminEmojiCategoriesGet(ctx context.Context) ([]*apimodel.EmojiCategory, gtserror.WithCode) { - return p.adminProcessor.EmojiCategoriesGet(ctx) -} - -func (p *processor) AdminDomainBlockCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.DomainBlockCreateRequest) (*apimodel.DomainBlock, gtserror.WithCode) { - return p.adminProcessor.DomainBlockCreate(ctx, authed.Account, form.Domain, form.Obfuscate, form.PublicComment, form.PrivateComment, "") -} - -func (p *processor) AdminDomainBlocksImport(ctx context.Context, authed *oauth.Auth, form *apimodel.DomainBlockCreateRequest) ([]*apimodel.DomainBlock, gtserror.WithCode) { - return p.adminProcessor.DomainBlocksImport(ctx, authed.Account, form.Domains) -} - -func (p *processor) AdminDomainBlocksGet(ctx context.Context, authed *oauth.Auth, export bool) ([]*apimodel.DomainBlock, gtserror.WithCode) { - return p.adminProcessor.DomainBlocksGet(ctx, authed.Account, export) -} - -func (p *processor) AdminDomainBlockGet(ctx context.Context, authed *oauth.Auth, id string, export bool) (*apimodel.DomainBlock, gtserror.WithCode) { - return p.adminProcessor.DomainBlockGet(ctx, authed.Account, id, export) -} - -func (p *processor) AdminDomainBlockDelete(ctx context.Context, authed *oauth.Auth, id string) (*apimodel.DomainBlock, gtserror.WithCode) { - return p.adminProcessor.DomainBlockDelete(ctx, authed.Account, id) -} - -func (p *processor) AdminMediaPrune(ctx context.Context, mediaRemoteCacheDays int) gtserror.WithCode { - return p.adminProcessor.MediaPrune(ctx, mediaRemoteCacheDays) -} - -func (p *processor) AdminMediaRefetch(ctx context.Context, authed *oauth.Auth, domain string) gtserror.WithCode { - return p.adminProcessor.MediaRefetch(ctx, authed.Account, domain) -} - -func (p *processor) AdminReportsGet(ctx context.Context, authed *oauth.Auth, resolved *bool, accountID string, targetAccountID string, maxID string, sinceID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) { - return p.adminProcessor.ReportsGet(ctx, authed.Account, resolved, accountID, targetAccountID, maxID, sinceID, minID, limit) -} - -func (p *processor) AdminReportGet(ctx context.Context, authed *oauth.Auth, id string) (*apimodel.AdminReport, gtserror.WithCode) { - return p.adminProcessor.ReportGet(ctx, authed.Account, id) -} - -func (p *processor) AdminReportResolve(ctx context.Context, authed *oauth.Auth, id string, actionTakenComment *string) (*apimodel.AdminReport, gtserror.WithCode) { - return p.adminProcessor.ReportResolve(ctx, authed.Account, id, actionTakenComment) -} diff --git a/internal/processing/admin/accountaction.go b/internal/processing/admin/account.go similarity index 63% rename from internal/processing/admin/accountaction.go rename to internal/processing/admin/account.go index e4e6bbd97..d23d1fbfe 100644 --- a/internal/processing/admin/accountaction.go +++ b/internal/processing/admin/account.go @@ -1,3 +1,21 @@ +/* + GoToSocial + Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + package admin import ( @@ -12,7 +30,7 @@ "github.com/superseriousbusiness/gotosocial/internal/messages" ) -func (p *processor) AccountAction(ctx context.Context, account *gtsmodel.Account, form *apimodel.AdminAccountActionRequest) gtserror.WithCode { +func (p *Processor) AccountAction(ctx context.Context, account *gtsmodel.Account, form *apimodel.AdminAccountActionRequest) gtserror.WithCode { targetAccount, err := p.db.GetAccountByID(ctx, form.TargetAccountID) if err != nil { return gtserror.NewErrorInternalError(err) diff --git a/internal/processing/admin/admin.go b/internal/processing/admin/admin.go index b08b589bb..54827b8fd 100644 --- a/internal/processing/admin/admin.go +++ b/internal/processing/admin/admin.go @@ -19,14 +19,8 @@ package admin import ( - "context" - "mime/multipart" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/concurrency" "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/messages" "github.com/superseriousbusiness/gotosocial/internal/storage" @@ -34,28 +28,7 @@ "github.com/superseriousbusiness/gotosocial/internal/typeutils" ) -// Processor wraps a bunch of functions for processing admin actions. -type Processor interface { - DomainBlockCreate(ctx context.Context, account *gtsmodel.Account, domain string, obfuscate bool, publicComment string, privateComment string, subscriptionID string) (*apimodel.DomainBlock, gtserror.WithCode) - DomainBlocksImport(ctx context.Context, account *gtsmodel.Account, domains *multipart.FileHeader) ([]*apimodel.DomainBlock, gtserror.WithCode) - DomainBlocksGet(ctx context.Context, account *gtsmodel.Account, export bool) ([]*apimodel.DomainBlock, gtserror.WithCode) - DomainBlockGet(ctx context.Context, account *gtsmodel.Account, id string, export bool) (*apimodel.DomainBlock, gtserror.WithCode) - DomainBlockDelete(ctx context.Context, account *gtsmodel.Account, id string) (*apimodel.DomainBlock, gtserror.WithCode) - AccountAction(ctx context.Context, account *gtsmodel.Account, form *apimodel.AdminAccountActionRequest) gtserror.WithCode - EmojiCreate(ctx context.Context, account *gtsmodel.Account, user *gtsmodel.User, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, gtserror.WithCode) - EmojisGet(ctx context.Context, account *gtsmodel.Account, user *gtsmodel.User, domain string, includeDisabled bool, includeEnabled bool, shortcode string, maxShortcodeDomain string, minShortcodeDomain string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) - EmojiGet(ctx context.Context, account *gtsmodel.Account, user *gtsmodel.User, id string) (*apimodel.AdminEmoji, gtserror.WithCode) - EmojiDelete(ctx context.Context, id string) (*apimodel.AdminEmoji, gtserror.WithCode) - EmojiUpdate(ctx context.Context, id string, form *apimodel.EmojiUpdateRequest) (*apimodel.AdminEmoji, gtserror.WithCode) - EmojiCategoriesGet(ctx context.Context) ([]*apimodel.EmojiCategory, gtserror.WithCode) - MediaPrune(ctx context.Context, mediaRemoteCacheDays int) gtserror.WithCode - MediaRefetch(ctx context.Context, requestingAccount *gtsmodel.Account, domain string) gtserror.WithCode - ReportsGet(ctx context.Context, account *gtsmodel.Account, resolved *bool, accountID string, targetAccountID string, maxID string, sinceID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) - ReportGet(ctx context.Context, account *gtsmodel.Account, id string) (*apimodel.AdminReport, gtserror.WithCode) - ReportResolve(ctx context.Context, account *gtsmodel.Account, id string, actionTakenComment *string) (*apimodel.AdminReport, gtserror.WithCode) -} - -type processor struct { +type Processor struct { tc typeutils.TypeConverter mediaManager media.Manager transportController transport.Controller @@ -66,7 +39,7 @@ type processor struct { // New returns a new admin processor. func New(db db.DB, tc typeutils.TypeConverter, mediaManager media.Manager, transportController transport.Controller, storage *storage.Driver, clientWorker *concurrency.WorkerPool[messages.FromClientAPI]) Processor { - return &processor{ + return Processor{ tc: tc, mediaManager: mediaManager, transportController: transportController, diff --git a/internal/processing/admin/createemoji.go b/internal/processing/admin/createemoji.go deleted file mode 100644 index b2a7bfc86..000000000 --- a/internal/processing/admin/createemoji.go +++ /dev/null @@ -1,89 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package admin - -import ( - "context" - "fmt" - "io" - - 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/id" - "github.com/superseriousbusiness/gotosocial/internal/media" - "github.com/superseriousbusiness/gotosocial/internal/uris" -) - -func (p *processor) EmojiCreate(ctx context.Context, account *gtsmodel.Account, user *gtsmodel.User, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, gtserror.WithCode) { - if !*user.Admin { - return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("user %s not an admin", user.ID), "user is not an admin") - } - - maybeExisting, err := p.db.GetEmojiByShortcodeDomain(ctx, form.Shortcode, "") - if maybeExisting != nil { - return nil, gtserror.NewErrorConflict(fmt.Errorf("emoji with shortcode %s already exists", form.Shortcode), fmt.Sprintf("emoji with shortcode %s already exists", form.Shortcode)) - } - - if err != nil && err != db.ErrNoEntries { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking existence of emoji with shortcode %s: %s", form.Shortcode, err)) - } - - emojiID, err := id.NewRandomULID() - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error creating id for new emoji: %s", err), "error creating emoji ID") - } - - emojiURI := uris.GenerateURIForEmoji(emojiID) - - data := func(innerCtx context.Context) (io.ReadCloser, int64, error) { - f, err := form.Image.Open() - return f, form.Image.Size, err - } - - var ai *media.AdditionalEmojiInfo - if form.CategoryName != "" { - category, err := p.GetOrCreateEmojiCategory(ctx, form.CategoryName) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error putting id in category: %s", err), "error putting id in category") - } - - ai = &media.AdditionalEmojiInfo{ - CategoryID: &category.ID, - } - } - - processingEmoji, err := p.mediaManager.PreProcessEmoji(ctx, data, nil, form.Shortcode, emojiID, emojiURI, ai, false) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error processing emoji: %s", err), "error processing emoji") - } - - emoji, err := processingEmoji.LoadEmoji(ctx) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error loading emoji: %s", err), "error loading emoji") - } - - apiEmoji, err := p.tc.EmojiToAPIEmoji(ctx, emoji) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting emoji: %s", err), "error converting emoji to api representation") - } - - return &apiEmoji, nil -} diff --git a/internal/processing/admin/deletedomainblock.go b/internal/processing/admin/deletedomainblock.go deleted file mode 100644 index 412a01b8b..000000000 --- a/internal/processing/admin/deletedomainblock.go +++ /dev/null @@ -1,86 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package admin - -import ( - "context" - "fmt" - "time" - - 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) DomainBlockDelete(ctx context.Context, account *gtsmodel.Account, id string) (*apimodel.DomainBlock, gtserror.WithCode) { - domainBlock := >smodel.DomainBlock{} - - if err := p.db.GetByID(ctx, id, domainBlock); err != nil { - if err != db.ErrNoEntries { - // something has gone really wrong - return nil, gtserror.NewErrorInternalError(err) - } - // there are no entries for this ID - return nil, gtserror.NewErrorNotFound(fmt.Errorf("no entry for ID %s", id)) - } - - // prepare the domain block to return - apiDomainBlock, err := p.tc.DomainBlockToAPIDomainBlock(ctx, domainBlock, false) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - // Delete the domain block - if err := p.db.DeleteDomainBlock(ctx, domainBlock.Domain); err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - // remove the domain block reference from the instance, if we have an entry for it - i := >smodel.Instance{} - if err := p.db.GetWhere(ctx, []db.Where{ - {Key: "domain", Value: domainBlock.Domain}, - {Key: "domain_block_id", Value: id}, - }, i); err == nil { - updatingColumns := []string{"suspended_at", "domain_block_id", "updated_at"} - i.SuspendedAt = time.Time{} - i.DomainBlockID = "" - i.UpdatedAt = time.Now() - if err := p.db.UpdateByID(ctx, i, i.ID, updatingColumns...); err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("couldn't update database entry for instance %s: %s", domainBlock.Domain, err)) - } - } - - // unsuspend all accounts whose suspension origin was this domain block - // 1. remove the 'suspended_at' entry from their accounts - if err := p.db.UpdateWhere(ctx, []db.Where{ - {Key: "suspension_origin", Value: domainBlock.ID}, - }, "suspended_at", nil, &[]*gtsmodel.Account{}); err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("database error removing suspended_at from accounts: %s", err)) - } - - // 2. remove the 'suspension_origin' entry from their accounts - if err := p.db.UpdateWhere(ctx, []db.Where{ - {Key: "suspension_origin", Value: domainBlock.ID}, - }, "suspension_origin", nil, &[]*gtsmodel.Account{}); err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("database error removing suspension_origin from accounts: %s", err)) - } - - return apiDomainBlock, nil -} diff --git a/internal/processing/admin/deleteemoji.go b/internal/processing/admin/deleteemoji.go deleted file mode 100644 index 17c3a0ca0..000000000 --- a/internal/processing/admin/deleteemoji.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package admin - -import ( - "context" - "errors" - "fmt" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" -) - -func (p *processor) EmojiDelete(ctx context.Context, id string) (*apimodel.AdminEmoji, gtserror.WithCode) { - emoji, err := p.db.GetEmojiByID(ctx, id) - if err != nil { - if errors.Is(err, db.ErrNoEntries) { - err = fmt.Errorf("EmojiDelete: no emoji with id %s found in the db", id) - return nil, gtserror.NewErrorNotFound(err) - } - err := fmt.Errorf("EmojiDelete: db error: %s", err) - return nil, gtserror.NewErrorInternalError(err) - } - - if emoji.Domain != "" { - err = fmt.Errorf("EmojiDelete: emoji with id %s was not a local emoji, will not delete", id) - return nil, gtserror.NewErrorBadRequest(err, err.Error()) - } - - adminEmoji, err := p.tc.EmojiToAdminAPIEmoji(ctx, emoji) - if err != nil { - err = fmt.Errorf("EmojiDelete: error converting emoji to admin api emoji: %s", err) - return nil, gtserror.NewErrorInternalError(err) - } - - if err := p.db.DeleteEmojiByID(ctx, id); err != nil { - err := fmt.Errorf("EmojiDelete: db error: %s", err) - return nil, gtserror.NewErrorInternalError(err) - } - - return adminEmoji, nil -} diff --git a/internal/processing/admin/createdomainblock.go b/internal/processing/admin/domainblock.go similarity index 51% rename from internal/processing/admin/createdomainblock.go rename to internal/processing/admin/domainblock.go index 5c3542f79..415ac610f 100644 --- a/internal/processing/admin/createdomainblock.go +++ b/internal/processing/admin/domainblock.go @@ -1,27 +1,13 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - package admin import ( + "bytes" "context" + "encoding/json" "errors" "fmt" + "io" + "mime/multipart" "strings" "time" @@ -37,7 +23,7 @@ "github.com/superseriousbusiness/gotosocial/internal/text" ) -func (p *processor) DomainBlockCreate(ctx context.Context, account *gtsmodel.Account, domain string, obfuscate bool, publicComment string, privateComment string, subscriptionID string) (*apimodel.DomainBlock, gtserror.WithCode) { +func (p *Processor) DomainBlockCreate(ctx context.Context, account *gtsmodel.Account, domain string, obfuscate bool, publicComment string, privateComment string, subscriptionID string) (*apimodel.DomainBlock, gtserror.WithCode) { // domain blocks will always be lowercase domain = strings.ToLower(domain) @@ -88,12 +74,8 @@ func (p *processor) DomainBlockCreate(ctx context.Context, account *gtsmodel.Acc // 1. Strip most info away from the instance entry for the domain. // 2. Delete the instance account for that instance if it exists. // 3. Select all accounts from this instance and pass them through the delete functionality of the processor. -func (p *processor) initiateDomainBlockSideEffects(ctx context.Context, account *gtsmodel.Account, block *gtsmodel.DomainBlock) { - l := log.WithContext(ctx). - WithFields(kv.Fields{ - {"domain", block.Domain}, - }...) - +func (p *Processor) initiateDomainBlockSideEffects(ctx context.Context, account *gtsmodel.Account, block *gtsmodel.DomainBlock) { + l := log.WithContext(ctx).WithFields(kv.Fields{{"domain", block.Domain}}...) l.Debug("processing domain block side effects") // if we have an instance entry for this domain, update it with the new block ID and clear all fields @@ -174,3 +156,139 @@ func (p *processor) initiateDomainBlockSideEffects(ctx context.Context, account } } } + +// DomainBlocksImport handles the import of a bunch of domain blocks at once, by calling the DomainBlockCreate function for each domain in the provided file. +func (p *Processor) DomainBlocksImport(ctx context.Context, account *gtsmodel.Account, domains *multipart.FileHeader) ([]*apimodel.DomainBlock, gtserror.WithCode) { + f, err := domains.Open() + if err != nil { + return nil, gtserror.NewErrorBadRequest(fmt.Errorf("DomainBlocksImport: error opening attachment: %s", err)) + } + buf := new(bytes.Buffer) + size, err := io.Copy(buf, f) + if err != nil { + return nil, gtserror.NewErrorBadRequest(fmt.Errorf("DomainBlocksImport: error reading attachment: %s", err)) + } + if size == 0 { + return nil, gtserror.NewErrorBadRequest(errors.New("DomainBlocksImport: could not read provided attachment: size 0 bytes")) + } + + d := []apimodel.DomainBlock{} + if err := json.Unmarshal(buf.Bytes(), &d); err != nil { + return nil, gtserror.NewErrorBadRequest(fmt.Errorf("DomainBlocksImport: could not read provided attachment: %s", err)) + } + + blocks := []*apimodel.DomainBlock{} + for _, d := range d { + block, err := p.DomainBlockCreate(ctx, account, d.Domain.Domain, false, d.PublicComment, "", "") + if err != nil { + return nil, err + } + + blocks = append(blocks, block) + } + + return blocks, nil +} + +// DomainBlocksGet returns all existing domain blocks. +// If export is true, the format will be suitable for writing out to an export. +func (p *Processor) DomainBlocksGet(ctx context.Context, account *gtsmodel.Account, export bool) ([]*apimodel.DomainBlock, gtserror.WithCode) { + domainBlocks := []*gtsmodel.DomainBlock{} + + if err := p.db.GetAll(ctx, &domainBlocks); err != nil { + if !errors.Is(err, db.ErrNoEntries) { + // something has gone really wrong + return nil, gtserror.NewErrorInternalError(err) + } + } + + apiDomainBlocks := []*apimodel.DomainBlock{} + for _, b := range domainBlocks { + apiDomainBlock, err := p.tc.DomainBlockToAPIDomainBlock(ctx, b, export) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + apiDomainBlocks = append(apiDomainBlocks, apiDomainBlock) + } + + return apiDomainBlocks, nil +} + +// DomainBlockGet returns one domain block with the given id. +// If export is true, the format will be suitable for writing out to an export. +func (p *Processor) DomainBlockGet(ctx context.Context, account *gtsmodel.Account, id string, export bool) (*apimodel.DomainBlock, gtserror.WithCode) { + domainBlock := >smodel.DomainBlock{} + + if err := p.db.GetByID(ctx, id, domainBlock); err != nil { + if !errors.Is(err, db.ErrNoEntries) { + // something has gone really wrong + return nil, gtserror.NewErrorInternalError(err) + } + // there are no entries for this ID + return nil, gtserror.NewErrorNotFound(fmt.Errorf("no entry for ID %s", id)) + } + + apiDomainBlock, err := p.tc.DomainBlockToAPIDomainBlock(ctx, domainBlock, export) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + return apiDomainBlock, nil +} + +// DomainBlockDelete removes one domain block with the given ID. +func (p *Processor) DomainBlockDelete(ctx context.Context, account *gtsmodel.Account, id string) (*apimodel.DomainBlock, gtserror.WithCode) { + domainBlock := >smodel.DomainBlock{} + + if err := p.db.GetByID(ctx, id, domainBlock); err != nil { + if !errors.Is(err, db.ErrNoEntries) { + // something has gone really wrong + return nil, gtserror.NewErrorInternalError(err) + } + // there are no entries for this ID + return nil, gtserror.NewErrorNotFound(fmt.Errorf("no entry for ID %s", id)) + } + + // prepare the domain block to return + apiDomainBlock, err := p.tc.DomainBlockToAPIDomainBlock(ctx, domainBlock, false) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + // Delete the domain block + if err := p.db.DeleteDomainBlock(ctx, domainBlock.Domain); err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + // remove the domain block reference from the instance, if we have an entry for it + i := >smodel.Instance{} + if err := p.db.GetWhere(ctx, []db.Where{ + {Key: "domain", Value: domainBlock.Domain}, + {Key: "domain_block_id", Value: id}, + }, i); err == nil { + updatingColumns := []string{"suspended_at", "domain_block_id", "updated_at"} + i.SuspendedAt = time.Time{} + i.DomainBlockID = "" + i.UpdatedAt = time.Now() + if err := p.db.UpdateByID(ctx, i, i.ID, updatingColumns...); err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("couldn't update database entry for instance %s: %s", domainBlock.Domain, err)) + } + } + + // unsuspend all accounts whose suspension origin was this domain block + // 1. remove the 'suspended_at' entry from their accounts + if err := p.db.UpdateWhere(ctx, []db.Where{ + {Key: "suspension_origin", Value: domainBlock.ID}, + }, "suspended_at", nil, &[]*gtsmodel.Account{}); err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("database error removing suspended_at from accounts: %s", err)) + } + + // 2. remove the 'suspension_origin' entry from their accounts + if err := p.db.UpdateWhere(ctx, []db.Where{ + {Key: "suspension_origin", Value: domainBlock.ID}, + }, "suspension_origin", nil, &[]*gtsmodel.Account{}); err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("database error removing suspension_origin from accounts: %s", err)) + } + + return apiDomainBlock, nil +} diff --git a/internal/processing/admin/emoji.go b/internal/processing/admin/emoji.go new file mode 100644 index 000000000..391d18525 --- /dev/null +++ b/internal/processing/admin/emoji.go @@ -0,0 +1,485 @@ +/* + GoToSocial + Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package admin + +import ( + "context" + "errors" + "fmt" + "io" + "mime/multipart" + "strings" + + 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/id" + "github.com/superseriousbusiness/gotosocial/internal/media" + "github.com/superseriousbusiness/gotosocial/internal/uris" + "github.com/superseriousbusiness/gotosocial/internal/util" +) + +// EmojiCreate creates a custom emoji on this instance. +func (p *Processor) EmojiCreate(ctx context.Context, account *gtsmodel.Account, user *gtsmodel.User, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, gtserror.WithCode) { + if !*user.Admin { + return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("user %s not an admin", user.ID), "user is not an admin") + } + + maybeExisting, err := p.db.GetEmojiByShortcodeDomain(ctx, form.Shortcode, "") + if maybeExisting != nil { + return nil, gtserror.NewErrorConflict(fmt.Errorf("emoji with shortcode %s already exists", form.Shortcode), fmt.Sprintf("emoji with shortcode %s already exists", form.Shortcode)) + } + + if err != nil && err != db.ErrNoEntries { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking existence of emoji with shortcode %s: %s", form.Shortcode, err)) + } + + emojiID, err := id.NewRandomULID() + if err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error creating id for new emoji: %s", err), "error creating emoji ID") + } + + emojiURI := uris.GenerateURIForEmoji(emojiID) + + data := func(innerCtx context.Context) (io.ReadCloser, int64, error) { + f, err := form.Image.Open() + return f, form.Image.Size, err + } + + var ai *media.AdditionalEmojiInfo + if form.CategoryName != "" { + category, err := p.getOrCreateEmojiCategory(ctx, form.CategoryName) + if err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error putting id in category: %s", err), "error putting id in category") + } + + ai = &media.AdditionalEmojiInfo{ + CategoryID: &category.ID, + } + } + + processingEmoji, err := p.mediaManager.PreProcessEmoji(ctx, data, nil, form.Shortcode, emojiID, emojiURI, ai, false) + if err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error processing emoji: %s", err), "error processing emoji") + } + + emoji, err := processingEmoji.LoadEmoji(ctx) + if err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error loading emoji: %s", err), "error loading emoji") + } + + apiEmoji, err := p.tc.EmojiToAPIEmoji(ctx, emoji) + if err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting emoji: %s", err), "error converting emoji to api representation") + } + + return &apiEmoji, nil +} + +// EmojisGet returns an admin view of custom emojis, filtered with the given parameters. +func (p *Processor) EmojisGet( + ctx context.Context, + account *gtsmodel.Account, + user *gtsmodel.User, + domain string, + includeDisabled bool, + includeEnabled bool, + shortcode string, + maxShortcodeDomain string, + minShortcodeDomain string, + limit int, +) (*apimodel.PageableResponse, gtserror.WithCode) { + if !*user.Admin { + return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("user %s not an admin", user.ID), "user is not an admin") + } + + emojis, err := p.db.GetEmojis(ctx, domain, includeDisabled, includeEnabled, shortcode, maxShortcodeDomain, minShortcodeDomain, limit) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + err := fmt.Errorf("EmojisGet: db error: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + + count := len(emojis) + if count == 0 { + return util.EmptyPageableResponse(), nil + } + + items := make([]interface{}, 0, count) + for _, emoji := range emojis { + adminEmoji, err := p.tc.EmojiToAdminAPIEmoji(ctx, emoji) + if err != nil { + err := fmt.Errorf("EmojisGet: error converting emoji to admin model emoji: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + items = append(items, adminEmoji) + } + + filterBuilder := strings.Builder{} + filterBuilder.WriteString("filter=") + + switch domain { + case "", "local": + filterBuilder.WriteString("domain:local") + case db.EmojiAllDomains: + filterBuilder.WriteString("domain:all") + default: + filterBuilder.WriteString("domain:") + filterBuilder.WriteString(domain) + } + + if includeDisabled != includeEnabled { + if includeDisabled { + filterBuilder.WriteString(",disabled") + } + if includeEnabled { + filterBuilder.WriteString(",enabled") + } + } + + if shortcode != "" { + filterBuilder.WriteString(",shortcode:") + filterBuilder.WriteString(shortcode) + } + + return util.PackagePageableResponse(util.PageableResponseParams{ + Items: items, + Path: "api/v1/admin/custom_emojis", + NextMaxIDKey: "max_shortcode_domain", + NextMaxIDValue: util.ShortcodeDomain(emojis[count-1]), + PrevMinIDKey: "min_shortcode_domain", + PrevMinIDValue: util.ShortcodeDomain(emojis[0]), + Limit: limit, + ExtraQueryParams: []string{filterBuilder.String()}, + }) +} + +// EmojiGet returns the admin view of one custom emoji with the given id. +func (p *Processor) EmojiGet(ctx context.Context, account *gtsmodel.Account, user *gtsmodel.User, id string) (*apimodel.AdminEmoji, gtserror.WithCode) { + if !*user.Admin { + return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("user %s not an admin", user.ID), "user is not an admin") + } + + emoji, err := p.db.GetEmojiByID(ctx, id) + if err != nil { + if errors.Is(err, db.ErrNoEntries) { + err = fmt.Errorf("EmojiGet: no emoji with id %s found in the db", id) + return nil, gtserror.NewErrorNotFound(err) + } + err := fmt.Errorf("EmojiGet: db error: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + + adminEmoji, err := p.tc.EmojiToAdminAPIEmoji(ctx, emoji) + if err != nil { + err = fmt.Errorf("EmojiGet: error converting emoji to admin api emoji: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + + return adminEmoji, nil +} + +// EmojiDelete deletes one emoji from the database, with the given id. +func (p *Processor) EmojiDelete(ctx context.Context, id string) (*apimodel.AdminEmoji, gtserror.WithCode) { + emoji, err := p.db.GetEmojiByID(ctx, id) + if err != nil { + if errors.Is(err, db.ErrNoEntries) { + err = fmt.Errorf("EmojiDelete: no emoji with id %s found in the db", id) + return nil, gtserror.NewErrorNotFound(err) + } + err := fmt.Errorf("EmojiDelete: db error: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + + if emoji.Domain != "" { + err = fmt.Errorf("EmojiDelete: emoji with id %s was not a local emoji, will not delete", id) + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } + + adminEmoji, err := p.tc.EmojiToAdminAPIEmoji(ctx, emoji) + if err != nil { + err = fmt.Errorf("EmojiDelete: error converting emoji to admin api emoji: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + + if err := p.db.DeleteEmojiByID(ctx, id); err != nil { + err := fmt.Errorf("EmojiDelete: db error: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + + return adminEmoji, nil +} + +// EmojiUpdate updates one emoji with the given id, using the provided form parameters. +func (p *Processor) EmojiUpdate(ctx context.Context, id string, form *apimodel.EmojiUpdateRequest) (*apimodel.AdminEmoji, gtserror.WithCode) { + emoji, err := p.db.GetEmojiByID(ctx, id) + if err != nil { + if errors.Is(err, db.ErrNoEntries) { + err = fmt.Errorf("EmojiUpdate: no emoji with id %s found in the db", id) + return nil, gtserror.NewErrorNotFound(err) + } + err := fmt.Errorf("EmojiUpdate: db error: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + + switch form.Type { + case apimodel.EmojiUpdateCopy: + return p.emojiUpdateCopy(ctx, emoji, form.Shortcode, form.CategoryName) + case apimodel.EmojiUpdateDisable: + return p.emojiUpdateDisable(ctx, emoji) + case apimodel.EmojiUpdateModify: + return p.emojiUpdateModify(ctx, emoji, form.Image, form.CategoryName) + default: + err := errors.New("unrecognized emoji action type") + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } +} + +// EmojiCategoriesGet returns all custom emoji categories that exist on this instance. +func (p *Processor) EmojiCategoriesGet(ctx context.Context) ([]*apimodel.EmojiCategory, gtserror.WithCode) { + categories, err := p.db.GetEmojiCategories(ctx) + if err != nil { + err := fmt.Errorf("EmojiCategoriesGet: db error: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + + apiCategories := make([]*apimodel.EmojiCategory, 0, len(categories)) + for _, category := range categories { + apiCategory, err := p.tc.EmojiCategoryToAPIEmojiCategory(ctx, category) + if err != nil { + err := fmt.Errorf("EmojiCategoriesGet: error converting emoji category to api emoji category: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + apiCategories = append(apiCategories, apiCategory) + } + + return apiCategories, nil +} + +/* + UTIL FUNCTIONS +*/ + +func (p *Processor) getOrCreateEmojiCategory(ctx context.Context, name string) (*gtsmodel.EmojiCategory, error) { + category, err := p.db.GetEmojiCategoryByName(ctx, name) + if err == nil { + return category, nil + } + + if err != nil && !errors.Is(err, db.ErrNoEntries) { + err = fmt.Errorf("GetOrCreateEmojiCategory: database error trying get emoji category by name: %s", err) + return nil, err + } + + // we don't have the category yet, just create it with the given name + categoryID, err := id.NewRandomULID() + if err != nil { + err = fmt.Errorf("GetOrCreateEmojiCategory: error generating id for new emoji category: %s", err) + return nil, err + } + + category = >smodel.EmojiCategory{ + ID: categoryID, + Name: name, + } + + if err := p.db.PutEmojiCategory(ctx, category); err != nil { + err = fmt.Errorf("GetOrCreateEmojiCategory: error putting new emoji category in the database: %s", err) + return nil, err + } + + return category, nil +} + +// copy an emoji from remote to local +func (p *Processor) emojiUpdateCopy(ctx context.Context, emoji *gtsmodel.Emoji, shortcode *string, categoryName *string) (*apimodel.AdminEmoji, gtserror.WithCode) { + if emoji.Domain == "" { + err := fmt.Errorf("emojiUpdateCopy: emoji %s is not a remote emoji, cannot copy it to local", emoji.ID) + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } + + if shortcode == nil { + err := fmt.Errorf("emojiUpdateCopy: emoji %s could not be copied, no shortcode provided", emoji.ID) + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } + + maybeExisting, err := p.db.GetEmojiByShortcodeDomain(ctx, *shortcode, "") + if maybeExisting != nil { + err := fmt.Errorf("emojiUpdateCopy: emoji %s could not be copied, emoji with shortcode %s already exists on this instance", emoji.ID, *shortcode) + return nil, gtserror.NewErrorConflict(err, err.Error()) + } + + if err != nil && err != db.ErrNoEntries { + err := fmt.Errorf("emojiUpdateCopy: emoji %s could not be copied, error checking existence of emoji with shortcode %s: %s", emoji.ID, *shortcode, err) + return nil, gtserror.NewErrorInternalError(err) + } + + newEmojiID, err := id.NewRandomULID() + if err != nil { + err := fmt.Errorf("emojiUpdateCopy: emoji %s could not be copied, error creating id for new emoji: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + newEmojiURI := uris.GenerateURIForEmoji(newEmojiID) + + data := func(ctx context.Context) (reader io.ReadCloser, fileSize int64, err error) { + rc, err := p.storage.GetStream(ctx, emoji.ImagePath) + return rc, int64(emoji.ImageFileSize), err + } + + var ai *media.AdditionalEmojiInfo + if categoryName != nil { + category, err := p.getOrCreateEmojiCategory(ctx, *categoryName) + if err != nil { + err = fmt.Errorf("emojiUpdateCopy: error getting or creating category: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + + ai = &media.AdditionalEmojiInfo{ + CategoryID: &category.ID, + } + } + + processingEmoji, err := p.mediaManager.PreProcessEmoji(ctx, data, nil, *shortcode, newEmojiID, newEmojiURI, ai, false) + if err != nil { + err = fmt.Errorf("emojiUpdateCopy: error processing emoji %s: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + newEmoji, err := processingEmoji.LoadEmoji(ctx) + if err != nil { + err = fmt.Errorf("emojiUpdateCopy: error loading processed emoji %s: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + adminEmoji, err := p.tc.EmojiToAdminAPIEmoji(ctx, newEmoji) + if err != nil { + err = fmt.Errorf("emojiUpdateCopy: error converting updated emoji %s to admin emoji: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + return adminEmoji, nil +} + +// disable a remote emoji +func (p *Processor) emojiUpdateDisable(ctx context.Context, emoji *gtsmodel.Emoji) (*apimodel.AdminEmoji, gtserror.WithCode) { + if emoji.Domain == "" { + err := fmt.Errorf("emojiUpdateDisable: emoji %s is not a remote emoji, cannot disable it via this endpoint", emoji.ID) + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } + + emojiDisabled := true + emoji.Disabled = &emojiDisabled + updatedEmoji, err := p.db.UpdateEmoji(ctx, emoji, "updated_at", "disabled") + if err != nil { + err = fmt.Errorf("emojiUpdateDisable: error updating emoji %s: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + adminEmoji, err := p.tc.EmojiToAdminAPIEmoji(ctx, updatedEmoji) + if err != nil { + err = fmt.Errorf("emojiUpdateDisable: error converting updated emoji %s to admin emoji: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + return adminEmoji, nil +} + +// modify a local emoji +func (p *Processor) emojiUpdateModify(ctx context.Context, emoji *gtsmodel.Emoji, image *multipart.FileHeader, categoryName *string) (*apimodel.AdminEmoji, gtserror.WithCode) { + if emoji.Domain != "" { + err := fmt.Errorf("emojiUpdateModify: emoji %s is not a local emoji, cannot do a modify action on it", emoji.ID) + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } + + var updatedEmoji *gtsmodel.Emoji + + // keep existing categoryID unless a new one is defined + var ( + updatedCategoryID = emoji.CategoryID + updateCategoryID bool + ) + if categoryName != nil { + category, err := p.getOrCreateEmojiCategory(ctx, *categoryName) + if err != nil { + err = fmt.Errorf("emojiUpdateModify: error getting or creating category: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + + updatedCategoryID = category.ID + updateCategoryID = true + } + + // only update image if provided with one + var updateImage bool + if image != nil && image.Size != 0 { + updateImage = true + } + + if !updateImage { + // only updating fields, we only need + // to do a database update for this + columns := []string{"updated_at"} + + if updateCategoryID { + emoji.CategoryID = updatedCategoryID + columns = append(columns, "category_id") + } + + var err error + updatedEmoji, err = p.db.UpdateEmoji(ctx, emoji, columns...) + if err != nil { + err = fmt.Errorf("emojiUpdateModify: error updating emoji %s: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + } else { + // new image, so we need to reprocess the emoji + data := func(ctx context.Context) (reader io.ReadCloser, fileSize int64, err error) { + i, err := image.Open() + return i, image.Size, err + } + + var ai *media.AdditionalEmojiInfo + if updateCategoryID { + ai = &media.AdditionalEmojiInfo{ + CategoryID: &updatedCategoryID, + } + } + + processingEmoji, err := p.mediaManager.PreProcessEmoji(ctx, data, nil, emoji.Shortcode, emoji.ID, emoji.URI, ai, true) + if err != nil { + err = fmt.Errorf("emojiUpdateModify: error processing emoji %s: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + updatedEmoji, err = processingEmoji.LoadEmoji(ctx) + if err != nil { + err = fmt.Errorf("emojiUpdateModify: error loading processed emoji %s: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + } + + adminEmoji, err := p.tc.EmojiToAdminAPIEmoji(ctx, updatedEmoji) + if err != nil { + err = fmt.Errorf("emojiUpdateModify: error converting updated emoji %s to admin emoji: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + return adminEmoji, nil +} diff --git a/internal/processing/admin/emojicategory.go b/internal/processing/admin/emojicategory.go deleted file mode 100644 index 67bfece20..000000000 --- a/internal/processing/admin/emojicategory.go +++ /dev/null @@ -1,60 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package admin - -import ( - "context" - "errors" - "fmt" - - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/id" -) - -func (p *processor) GetOrCreateEmojiCategory(ctx context.Context, name string) (*gtsmodel.EmojiCategory, error) { - category, err := p.db.GetEmojiCategoryByName(ctx, name) - if err == nil { - return category, nil - } - - if err != nil && !errors.Is(err, db.ErrNoEntries) { - err = fmt.Errorf("GetOrCreateEmojiCategory: database error trying get emoji category by name: %s", err) - return nil, err - } - - // we don't have the category yet, just create it with the given name - categoryID, err := id.NewRandomULID() - if err != nil { - err = fmt.Errorf("GetOrCreateEmojiCategory: error generating id for new emoji category: %s", err) - return nil, err - } - - category = >smodel.EmojiCategory{ - ID: categoryID, - Name: name, - } - - if err := p.db.PutEmojiCategory(ctx, category); err != nil { - err = fmt.Errorf("GetOrCreateEmojiCategory: error putting new emoji category in the database: %s", err) - return nil, err - } - - return category, nil -} diff --git a/internal/processing/admin/getcategories.go b/internal/processing/admin/getcategories.go deleted file mode 100644 index 999898827..000000000 --- a/internal/processing/admin/getcategories.go +++ /dev/null @@ -1,47 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package admin - -import ( - "context" - "fmt" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" -) - -func (p *processor) EmojiCategoriesGet(ctx context.Context) ([]*apimodel.EmojiCategory, gtserror.WithCode) { - categories, err := p.db.GetEmojiCategories(ctx) - if err != nil { - err := fmt.Errorf("EmojiCategoriesGet: db error: %s", err) - return nil, gtserror.NewErrorInternalError(err) - } - - apiCategories := make([]*apimodel.EmojiCategory, 0, len(categories)) - for _, category := range categories { - apiCategory, err := p.tc.EmojiCategoryToAPIEmojiCategory(ctx, category) - if err != nil { - err := fmt.Errorf("EmojiCategoriesGet: error converting emoji category to api emoji category: %s", err) - return nil, gtserror.NewErrorInternalError(err) - } - apiCategories = append(apiCategories, apiCategory) - } - - return apiCategories, nil -} diff --git a/internal/processing/admin/getdomainblock.go b/internal/processing/admin/getdomainblock.go deleted file mode 100644 index 073fd87ac..000000000 --- a/internal/processing/admin/getdomainblock.go +++ /dev/null @@ -1,49 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package admin - -import ( - "context" - "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) DomainBlockGet(ctx context.Context, account *gtsmodel.Account, id string, export bool) (*apimodel.DomainBlock, gtserror.WithCode) { - domainBlock := >smodel.DomainBlock{} - - if err := p.db.GetByID(ctx, id, domainBlock); err != nil { - if err != db.ErrNoEntries { - // something has gone really wrong - return nil, gtserror.NewErrorInternalError(err) - } - // there are no entries for this ID - return nil, gtserror.NewErrorNotFound(fmt.Errorf("no entry for ID %s", id)) - } - - apiDomainBlock, err := p.tc.DomainBlockToAPIDomainBlock(ctx, domainBlock, export) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - return apiDomainBlock, nil -} diff --git a/internal/processing/admin/getdomainblocks.go b/internal/processing/admin/getdomainblocks.go deleted file mode 100644 index 2e8dcf881..000000000 --- a/internal/processing/admin/getdomainblocks.go +++ /dev/null @@ -1,50 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package admin - -import ( - "context" - - 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) DomainBlocksGet(ctx context.Context, account *gtsmodel.Account, export bool) ([]*apimodel.DomainBlock, gtserror.WithCode) { - domainBlocks := []*gtsmodel.DomainBlock{} - - if err := p.db.GetAll(ctx, &domainBlocks); err != nil { - if err != db.ErrNoEntries { - // something has gone really wrong - return nil, gtserror.NewErrorInternalError(err) - } - } - - apiDomainBlocks := []*apimodel.DomainBlock{} - for _, b := range domainBlocks { - apiDomainBlock, err := p.tc.DomainBlockToAPIDomainBlock(ctx, b, export) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - apiDomainBlocks = append(apiDomainBlocks, apiDomainBlock) - } - - return apiDomainBlocks, nil -} diff --git a/internal/processing/admin/getemoji.go b/internal/processing/admin/getemoji.go deleted file mode 100644 index b37cc807f..000000000 --- a/internal/processing/admin/getemoji.go +++ /dev/null @@ -1,54 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package admin - -import ( - "context" - "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) EmojiGet(ctx context.Context, account *gtsmodel.Account, user *gtsmodel.User, id string) (*apimodel.AdminEmoji, gtserror.WithCode) { - if !*user.Admin { - return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("user %s not an admin", user.ID), "user is not an admin") - } - - emoji, err := p.db.GetEmojiByID(ctx, id) - if err != nil { - if errors.Is(err, db.ErrNoEntries) { - err = fmt.Errorf("EmojiGet: no emoji with id %s found in the db", id) - return nil, gtserror.NewErrorNotFound(err) - } - err := fmt.Errorf("EmojiGet: db error: %s", err) - return nil, gtserror.NewErrorInternalError(err) - } - - adminEmoji, err := p.tc.EmojiToAdminAPIEmoji(ctx, emoji) - if err != nil { - err = fmt.Errorf("EmojiGet: error converting emoji to admin api emoji: %s", err) - return nil, gtserror.NewErrorInternalError(err) - } - - return adminEmoji, nil -} diff --git a/internal/processing/admin/getemojis.go b/internal/processing/admin/getemojis.go deleted file mode 100644 index 7d3470dae..000000000 --- a/internal/processing/admin/getemojis.go +++ /dev/null @@ -1,97 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package admin - -import ( - "context" - "errors" - "fmt" - "strings" - - 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) EmojisGet(ctx context.Context, account *gtsmodel.Account, user *gtsmodel.User, domain string, includeDisabled bool, includeEnabled bool, shortcode string, maxShortcodeDomain string, minShortcodeDomain string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) { - if !*user.Admin { - return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("user %s not an admin", user.ID), "user is not an admin") - } - - emojis, err := p.db.GetEmojis(ctx, domain, includeDisabled, includeEnabled, shortcode, maxShortcodeDomain, minShortcodeDomain, limit) - if err != nil && !errors.Is(err, db.ErrNoEntries) { - err := fmt.Errorf("EmojisGet: db error: %s", err) - return nil, gtserror.NewErrorInternalError(err) - } - - count := len(emojis) - if count == 0 { - return util.EmptyPageableResponse(), nil - } - - items := make([]interface{}, 0, count) - for _, emoji := range emojis { - adminEmoji, err := p.tc.EmojiToAdminAPIEmoji(ctx, emoji) - if err != nil { - err := fmt.Errorf("EmojisGet: error converting emoji to admin model emoji: %s", err) - return nil, gtserror.NewErrorInternalError(err) - } - items = append(items, adminEmoji) - } - - filterBuilder := strings.Builder{} - filterBuilder.WriteString("filter=") - - switch domain { - case "", "local": - filterBuilder.WriteString("domain:local") - case db.EmojiAllDomains: - filterBuilder.WriteString("domain:all") - default: - filterBuilder.WriteString("domain:") - filterBuilder.WriteString(domain) - } - - if includeDisabled != includeEnabled { - if includeDisabled { - filterBuilder.WriteString(",disabled") - } - if includeEnabled { - filterBuilder.WriteString(",enabled") - } - } - - if shortcode != "" { - filterBuilder.WriteString(",shortcode:") - filterBuilder.WriteString(shortcode) - } - - return util.PackagePageableResponse(util.PageableResponseParams{ - Items: items, - Path: "api/v1/admin/custom_emojis", - NextMaxIDKey: "max_shortcode_domain", - NextMaxIDValue: util.ShortcodeDomain(emojis[count-1]), - PrevMinIDKey: "min_shortcode_domain", - PrevMinIDValue: util.ShortcodeDomain(emojis[0]), - Limit: limit, - ExtraQueryParams: []string{filterBuilder.String()}, - }) -} diff --git a/internal/processing/admin/getreport.go b/internal/processing/admin/getreport.go deleted file mode 100644 index 6c2f93935..000000000 --- a/internal/processing/admin/getreport.go +++ /dev/null @@ -1,45 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package admin - -import ( - "context" - - 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) ReportGet(ctx context.Context, account *gtsmodel.Account, id string) (*apimodel.AdminReport, gtserror.WithCode) { - report, err := p.db.GetReportByID(ctx, id) - if err != nil { - if err == db.ErrNoEntries { - return nil, gtserror.NewErrorNotFound(err) - } - return nil, gtserror.NewErrorInternalError(err) - } - - apimodelReport, err := p.tc.ReportToAdminAPIReport(ctx, report, account) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - return apimodelReport, nil -} diff --git a/internal/processing/admin/importdomainblocks.go b/internal/processing/admin/importdomainblocks.go deleted file mode 100644 index 5118b4826..000000000 --- a/internal/processing/admin/importdomainblocks.go +++ /dev/null @@ -1,66 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package admin - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "mime/multipart" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -// DomainBlocksImport handles the import of a bunch of domain blocks at once, by calling the DomainBlockCreate function for each domain in the provided file. -func (p *processor) DomainBlocksImport(ctx context.Context, account *gtsmodel.Account, domains *multipart.FileHeader) ([]*apimodel.DomainBlock, gtserror.WithCode) { - f, err := domains.Open() - if err != nil { - return nil, gtserror.NewErrorBadRequest(fmt.Errorf("DomainBlocksImport: error opening attachment: %s", err)) - } - buf := new(bytes.Buffer) - size, err := io.Copy(buf, f) - if err != nil { - return nil, gtserror.NewErrorBadRequest(fmt.Errorf("DomainBlocksImport: error reading attachment: %s", err)) - } - if size == 0 { - return nil, gtserror.NewErrorBadRequest(errors.New("DomainBlocksImport: could not read provided attachment: size 0 bytes")) - } - - d := []apimodel.DomainBlock{} - if err := json.Unmarshal(buf.Bytes(), &d); err != nil { - return nil, gtserror.NewErrorBadRequest(fmt.Errorf("DomainBlocksImport: could not read provided attachment: %s", err)) - } - - blocks := []*apimodel.DomainBlock{} - for _, d := range d { - block, err := p.DomainBlockCreate(ctx, account, d.Domain.Domain, false, d.PublicComment, "", "") - if err != nil { - return nil, err - } - - blocks = append(blocks, block) - } - - return blocks, nil -} diff --git a/internal/processing/admin/mediarefetch.go b/internal/processing/admin/media.go similarity index 69% rename from internal/processing/admin/mediarefetch.go rename to internal/processing/admin/media.go index a73580d98..6064e4300 100644 --- a/internal/processing/admin/mediarefetch.go +++ b/internal/processing/admin/media.go @@ -27,7 +27,8 @@ "github.com/superseriousbusiness/gotosocial/internal/log" ) -func (p *processor) MediaRefetch(ctx context.Context, requestingAccount *gtsmodel.Account, domain string) gtserror.WithCode { +// MediaRefetch forces a refetch of remote emojis. +func (p *Processor) MediaRefetch(ctx context.Context, requestingAccount *gtsmodel.Account, domain string) gtserror.WithCode { transport, err := p.transportController.NewTransportForUsername(ctx, requestingAccount.Username) if err != nil { err = fmt.Errorf("error getting transport for user %s during media refetch request: %w", requestingAccount.Username, err) @@ -46,3 +47,18 @@ func (p *processor) MediaRefetch(ctx context.Context, requestingAccount *gtsmode return nil } + +// MediaPrune triggers a non-blocking prune of remote media, local unused media, etc. +func (p *Processor) MediaPrune(ctx context.Context, mediaRemoteCacheDays int) gtserror.WithCode { + if mediaRemoteCacheDays < 0 { + err := fmt.Errorf("MediaPrune: invalid value for mediaRemoteCacheDays prune: value was %d, cannot be less than 0", mediaRemoteCacheDays) + return gtserror.NewErrorBadRequest(err, err.Error()) + } + + if err := p.mediaManager.PruneAll(ctx, mediaRemoteCacheDays, false); err != nil { + err = fmt.Errorf("MediaPrune: %w", err) + return gtserror.NewErrorInternalError(err) + } + + return nil +} diff --git a/internal/processing/admin/getreports.go b/internal/processing/admin/report.go similarity index 60% rename from internal/processing/admin/getreports.go rename to internal/processing/admin/report.go index fbc4b45b2..3a6028bca 100644 --- a/internal/processing/admin/getreports.go +++ b/internal/processing/admin/report.go @@ -22,6 +22,7 @@ "context" "fmt" "strconv" + "time" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -30,7 +31,8 @@ "github.com/superseriousbusiness/gotosocial/internal/util" ) -func (p *processor) ReportsGet( +// ReportsGet returns all reports stored on this instance, with the given parameters. +func (p *Processor) ReportsGet( ctx context.Context, account *gtsmodel.Account, resolved *bool, @@ -90,3 +92,57 @@ func (p *processor) ReportsGet( ExtraQueryParams: extraQueryParams, }) } + +// ReportGet returns one report, with the given ID. +func (p *Processor) ReportGet(ctx context.Context, account *gtsmodel.Account, id string) (*apimodel.AdminReport, gtserror.WithCode) { + report, err := p.db.GetReportByID(ctx, id) + if err != nil { + if err == db.ErrNoEntries { + return nil, gtserror.NewErrorNotFound(err) + } + return nil, gtserror.NewErrorInternalError(err) + } + + apimodelReport, err := p.tc.ReportToAdminAPIReport(ctx, report, account) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + return apimodelReport, nil +} + +// ReportResolve marks a report with the given id as resolved, and stores the provided actionTakenComment (if not null). +func (p *Processor) ReportResolve(ctx context.Context, account *gtsmodel.Account, id string, actionTakenComment *string) (*apimodel.AdminReport, gtserror.WithCode) { + report, err := p.db.GetReportByID(ctx, id) + if err != nil { + if err == db.ErrNoEntries { + return nil, gtserror.NewErrorNotFound(err) + } + return nil, gtserror.NewErrorInternalError(err) + } + + columns := []string{ + "action_taken_at", + "action_taken_by_account_id", + } + + report.ActionTakenAt = time.Now() + report.ActionTakenByAccountID = account.ID + + if actionTakenComment != nil { + report.ActionTaken = *actionTakenComment + columns = append(columns, "action_taken") + } + + updatedReport, err := p.db.UpdateReport(ctx, report, columns...) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + apimodelReport, err := p.tc.ReportToAdminAPIReport(ctx, updatedReport, account) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + return apimodelReport, nil +} diff --git a/internal/processing/admin/resolvereport.go b/internal/processing/admin/resolvereport.go deleted file mode 100644 index 5c1dca1b0..000000000 --- a/internal/processing/admin/resolvereport.go +++ /dev/null @@ -1,64 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package admin - -import ( - "context" - "time" - - 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) ReportResolve(ctx context.Context, account *gtsmodel.Account, id string, actionTakenComment *string) (*apimodel.AdminReport, gtserror.WithCode) { - report, err := p.db.GetReportByID(ctx, id) - if err != nil { - if err == db.ErrNoEntries { - return nil, gtserror.NewErrorNotFound(err) - } - return nil, gtserror.NewErrorInternalError(err) - } - - columns := []string{ - "action_taken_at", - "action_taken_by_account_id", - } - - report.ActionTakenAt = time.Now() - report.ActionTakenByAccountID = account.ID - - if actionTakenComment != nil { - report.ActionTaken = *actionTakenComment - columns = append(columns, "action_taken") - } - - updatedReport, err := p.db.UpdateReport(ctx, report, columns...) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - apimodelReport, err := p.tc.ReportToAdminAPIReport(ctx, updatedReport, account) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - return apimodelReport, nil -} diff --git a/internal/processing/admin/updateemoji.go b/internal/processing/admin/updateemoji.go deleted file mode 100644 index 41ccd609c..000000000 --- a/internal/processing/admin/updateemoji.go +++ /dev/null @@ -1,236 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package admin - -import ( - "context" - "errors" - "fmt" - "io" - "mime/multipart" - - 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/id" - "github.com/superseriousbusiness/gotosocial/internal/media" - "github.com/superseriousbusiness/gotosocial/internal/uris" -) - -func (p *processor) EmojiUpdate(ctx context.Context, id string, form *apimodel.EmojiUpdateRequest) (*apimodel.AdminEmoji, gtserror.WithCode) { - emoji, err := p.db.GetEmojiByID(ctx, id) - if err != nil { - if errors.Is(err, db.ErrNoEntries) { - err = fmt.Errorf("EmojiUpdate: no emoji with id %s found in the db", id) - return nil, gtserror.NewErrorNotFound(err) - } - err := fmt.Errorf("EmojiUpdate: db error: %s", err) - return nil, gtserror.NewErrorInternalError(err) - } - - switch form.Type { - case apimodel.EmojiUpdateCopy: - return p.emojiUpdateCopy(ctx, emoji, form.Shortcode, form.CategoryName) - case apimodel.EmojiUpdateDisable: - return p.emojiUpdateDisable(ctx, emoji) - case apimodel.EmojiUpdateModify: - return p.emojiUpdateModify(ctx, emoji, form.Image, form.CategoryName) - default: - err := errors.New("unrecognized emoji action type") - return nil, gtserror.NewErrorBadRequest(err, err.Error()) - } -} - -// copy an emoji from remote to local -func (p *processor) emojiUpdateCopy(ctx context.Context, emoji *gtsmodel.Emoji, shortcode *string, categoryName *string) (*apimodel.AdminEmoji, gtserror.WithCode) { - if emoji.Domain == "" { - err := fmt.Errorf("emojiUpdateCopy: emoji %s is not a remote emoji, cannot copy it to local", emoji.ID) - return nil, gtserror.NewErrorBadRequest(err, err.Error()) - } - - if shortcode == nil { - err := fmt.Errorf("emojiUpdateCopy: emoji %s could not be copied, no shortcode provided", emoji.ID) - return nil, gtserror.NewErrorBadRequest(err, err.Error()) - } - - maybeExisting, err := p.db.GetEmojiByShortcodeDomain(ctx, *shortcode, "") - if maybeExisting != nil { - err := fmt.Errorf("emojiUpdateCopy: emoji %s could not be copied, emoji with shortcode %s already exists on this instance", emoji.ID, *shortcode) - return nil, gtserror.NewErrorConflict(err, err.Error()) - } - - if err != nil && err != db.ErrNoEntries { - err := fmt.Errorf("emojiUpdateCopy: emoji %s could not be copied, error checking existence of emoji with shortcode %s: %s", emoji.ID, *shortcode, err) - return nil, gtserror.NewErrorInternalError(err) - } - - newEmojiID, err := id.NewRandomULID() - if err != nil { - err := fmt.Errorf("emojiUpdateCopy: emoji %s could not be copied, error creating id for new emoji: %s", emoji.ID, err) - return nil, gtserror.NewErrorInternalError(err) - } - - newEmojiURI := uris.GenerateURIForEmoji(newEmojiID) - - data := func(ctx context.Context) (reader io.ReadCloser, fileSize int64, err error) { - rc, err := p.storage.GetStream(ctx, emoji.ImagePath) - return rc, int64(emoji.ImageFileSize), err - } - - var ai *media.AdditionalEmojiInfo - if categoryName != nil { - category, err := p.GetOrCreateEmojiCategory(ctx, *categoryName) - if err != nil { - err = fmt.Errorf("emojiUpdateCopy: error getting or creating category: %s", err) - return nil, gtserror.NewErrorInternalError(err) - } - - ai = &media.AdditionalEmojiInfo{ - CategoryID: &category.ID, - } - } - - processingEmoji, err := p.mediaManager.PreProcessEmoji(ctx, data, nil, *shortcode, newEmojiID, newEmojiURI, ai, false) - if err != nil { - err = fmt.Errorf("emojiUpdateCopy: error processing emoji %s: %s", emoji.ID, err) - return nil, gtserror.NewErrorInternalError(err) - } - - newEmoji, err := processingEmoji.LoadEmoji(ctx) - if err != nil { - err = fmt.Errorf("emojiUpdateCopy: error loading processed emoji %s: %s", emoji.ID, err) - return nil, gtserror.NewErrorInternalError(err) - } - - adminEmoji, err := p.tc.EmojiToAdminAPIEmoji(ctx, newEmoji) - if err != nil { - err = fmt.Errorf("emojiUpdateCopy: error converting updated emoji %s to admin emoji: %s", emoji.ID, err) - return nil, gtserror.NewErrorInternalError(err) - } - - return adminEmoji, nil -} - -// disable a remote emoji -func (p *processor) emojiUpdateDisable(ctx context.Context, emoji *gtsmodel.Emoji) (*apimodel.AdminEmoji, gtserror.WithCode) { - if emoji.Domain == "" { - err := fmt.Errorf("emojiUpdateDisable: emoji %s is not a remote emoji, cannot disable it via this endpoint", emoji.ID) - return nil, gtserror.NewErrorBadRequest(err, err.Error()) - } - - emojiDisabled := true - emoji.Disabled = &emojiDisabled - updatedEmoji, err := p.db.UpdateEmoji(ctx, emoji, "updated_at", "disabled") - if err != nil { - err = fmt.Errorf("emojiUpdateDisable: error updating emoji %s: %s", emoji.ID, err) - return nil, gtserror.NewErrorInternalError(err) - } - - adminEmoji, err := p.tc.EmojiToAdminAPIEmoji(ctx, updatedEmoji) - if err != nil { - err = fmt.Errorf("emojiUpdateDisable: error converting updated emoji %s to admin emoji: %s", emoji.ID, err) - return nil, gtserror.NewErrorInternalError(err) - } - - return adminEmoji, nil -} - -// modify a local emoji -func (p *processor) emojiUpdateModify(ctx context.Context, emoji *gtsmodel.Emoji, image *multipart.FileHeader, categoryName *string) (*apimodel.AdminEmoji, gtserror.WithCode) { - if emoji.Domain != "" { - err := fmt.Errorf("emojiUpdateModify: emoji %s is not a local emoji, cannot do a modify action on it", emoji.ID) - return nil, gtserror.NewErrorBadRequest(err, err.Error()) - } - - var updatedEmoji *gtsmodel.Emoji - - // keep existing categoryID unless a new one is defined - var ( - updatedCategoryID = emoji.CategoryID - updateCategoryID bool - ) - if categoryName != nil { - category, err := p.GetOrCreateEmojiCategory(ctx, *categoryName) - if err != nil { - err = fmt.Errorf("emojiUpdateModify: error getting or creating category: %s", err) - return nil, gtserror.NewErrorInternalError(err) - } - - updatedCategoryID = category.ID - updateCategoryID = true - } - - // only update image if provided with one - var updateImage bool - if image != nil && image.Size != 0 { - updateImage = true - } - - if !updateImage { - // only updating fields, we only need - // to do a database update for this - columns := []string{"updated_at"} - - if updateCategoryID { - emoji.CategoryID = updatedCategoryID - columns = append(columns, "category_id") - } - - var err error - updatedEmoji, err = p.db.UpdateEmoji(ctx, emoji, columns...) - if err != nil { - err = fmt.Errorf("emojiUpdateModify: error updating emoji %s: %s", emoji.ID, err) - return nil, gtserror.NewErrorInternalError(err) - } - } else { - // new image, so we need to reprocess the emoji - data := func(ctx context.Context) (reader io.ReadCloser, fileSize int64, err error) { - i, err := image.Open() - return i, image.Size, err - } - - var ai *media.AdditionalEmojiInfo - if updateCategoryID { - ai = &media.AdditionalEmojiInfo{ - CategoryID: &updatedCategoryID, - } - } - - processingEmoji, err := p.mediaManager.PreProcessEmoji(ctx, data, nil, emoji.Shortcode, emoji.ID, emoji.URI, ai, true) - if err != nil { - err = fmt.Errorf("emojiUpdateModify: error processing emoji %s: %s", emoji.ID, err) - return nil, gtserror.NewErrorInternalError(err) - } - - updatedEmoji, err = processingEmoji.LoadEmoji(ctx) - if err != nil { - err = fmt.Errorf("emojiUpdateModify: error loading processed emoji %s: %s", emoji.ID, err) - return nil, gtserror.NewErrorInternalError(err) - } - } - - adminEmoji, err := p.tc.EmojiToAdminAPIEmoji(ctx, updatedEmoji) - if err != nil { - err = fmt.Errorf("emojiUpdateModify: error converting updated emoji %s to admin emoji: %s", emoji.ID, err) - return nil, gtserror.NewErrorInternalError(err) - } - - return adminEmoji, nil -} diff --git a/internal/processing/app.go b/internal/processing/app.go index 0b1a4046b..f2a938b22 100644 --- a/internal/processing/app.go +++ b/internal/processing/app.go @@ -29,7 +29,7 @@ "github.com/superseriousbusiness/gotosocial/internal/oauth" ) -func (p *processor) AppCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.ApplicationCreateRequest) (*apimodel.Application, gtserror.WithCode) { +func (p *Processor) AppCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.ApplicationCreateRequest) (*apimodel.Application, gtserror.WithCode) { // set default 'read' for scopes if it's not set var scopes string if form.Scopes == "" { diff --git a/internal/processing/blocks.go b/internal/processing/blocks.go index 1df5f44a2..6dd9c3de9 100644 --- a/internal/processing/blocks.go +++ b/internal/processing/blocks.go @@ -30,7 +30,7 @@ "github.com/superseriousbusiness/gotosocial/internal/oauth" ) -func (p *processor) BlocksGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, limit int) (*apimodel.BlocksResponse, gtserror.WithCode) { +func (p *Processor) BlocksGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, limit int) (*apimodel.BlocksResponse, gtserror.WithCode) { accounts, nextMaxID, prevMinID, err := p.db.GetAccountBlocks(ctx, authed.Account.ID, maxID, sinceID, limit) if err != nil { if err == db.ErrNoEntries { @@ -55,7 +55,7 @@ func (p *processor) BlocksGet(ctx context.Context, authed *oauth.Auth, maxID str return p.packageBlocksResponse(apiAccounts, "/api/v1/blocks", nextMaxID, prevMinID, limit) } -func (p *processor) packageBlocksResponse(accounts []*apimodel.Account, path string, nextMaxID string, prevMinID string, limit int) (*apimodel.BlocksResponse, gtserror.WithCode) { +func (p *Processor) packageBlocksResponse(accounts []*apimodel.Account, path string, nextMaxID string, prevMinID string, limit int) (*apimodel.BlocksResponse, gtserror.WithCode) { resp := &apimodel.BlocksResponse{ Accounts: []*apimodel.Account{}, } diff --git a/internal/processing/bookmark.go b/internal/processing/bookmark.go deleted file mode 100644 index 64c311a9b..000000000 --- a/internal/processing/bookmark.go +++ /dev/null @@ -1,31 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package processing - -import ( - "context" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -func (p *processor) BookmarksGet(ctx context.Context, authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) { - return p.accountProcessor.BookmarksGet(ctx, authed.Account, limit, maxID, minID) -} diff --git a/internal/processing/federation.go b/internal/processing/federation.go deleted file mode 100644 index de1eba81c..000000000 --- a/internal/processing/federation.go +++ /dev/null @@ -1,72 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package processing - -import ( - "context" - "net/http" - "net/url" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" -) - -func (p *processor) GetFediUser(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) { - return p.federationProcessor.GetUser(ctx, requestedUsername, requestURL) -} - -func (p *processor) GetFediFollowers(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) { - return p.federationProcessor.GetFollowers(ctx, requestedUsername, requestURL) -} - -func (p *processor) GetFediFollowing(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) { - return p.federationProcessor.GetFollowing(ctx, requestedUsername, requestURL) -} - -func (p *processor) GetFediStatus(ctx context.Context, requestedUsername string, requestedStatusID string, requestURL *url.URL) (interface{}, gtserror.WithCode) { - return p.federationProcessor.GetStatus(ctx, requestedUsername, requestedStatusID, requestURL) -} - -func (p *processor) GetFediStatusReplies(ctx context.Context, requestedUsername string, requestedStatusID string, page bool, onlyOtherAccounts bool, minID string, requestURL *url.URL) (interface{}, gtserror.WithCode) { - return p.federationProcessor.GetStatusReplies(ctx, requestedUsername, requestedStatusID, page, onlyOtherAccounts, minID, requestURL) -} - -func (p *processor) GetFediOutbox(ctx context.Context, requestedUsername string, page bool, maxID string, minID string, requestURL *url.URL) (interface{}, gtserror.WithCode) { - return p.federationProcessor.GetOutbox(ctx, requestedUsername, page, maxID, minID, requestURL) -} - -func (p *processor) GetFediEmoji(ctx context.Context, requestedEmojiID string, requestURL *url.URL) (interface{}, gtserror.WithCode) { - return p.federationProcessor.GetEmoji(ctx, requestedEmojiID, requestURL) -} - -func (p *processor) GetWebfingerAccount(ctx context.Context, requestedUsername string) (*apimodel.WellKnownResponse, gtserror.WithCode) { - return p.federationProcessor.GetWebfingerAccount(ctx, requestedUsername) -} - -func (p *processor) GetNodeInfoRel(ctx context.Context) (*apimodel.WellKnownResponse, gtserror.WithCode) { - return p.federationProcessor.GetNodeInfoRel(ctx) -} - -func (p *processor) GetNodeInfo(ctx context.Context) (*apimodel.Nodeinfo, gtserror.WithCode) { - return p.federationProcessor.GetNodeInfo(ctx) -} - -func (p *processor) InboxPost(ctx context.Context, w http.ResponseWriter, r *http.Request) (bool, error) { - return p.federationProcessor.PostInbox(ctx, w, r) -} diff --git a/internal/processing/federation/federation.go b/internal/processing/federation/federation.go deleted file mode 100644 index df5121486..000000000 --- a/internal/processing/federation/federation.go +++ /dev/null @@ -1,100 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package federation - -import ( - "context" - "net/http" - "net/url" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/federation" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/typeutils" - "github.com/superseriousbusiness/gotosocial/internal/visibility" -) - -// Processor wraps functions for processing federation API requests. -type Processor interface { - // GetUser handles the getting of a fedi/activitypub representation of a user/account, performing appropriate authentication - // before returning a JSON serializable interface to the caller. - GetUser(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) - - // GetFollowers 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. - GetFollowers(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) - - // GetFollowing 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. - GetFollowing(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) - - // GetStatus handles the getting of a fedi/activitypub representation of a particular status, performing appropriate - // authentication before returning a JSON serializable interface to the caller. - GetStatus(ctx context.Context, requestedUsername string, requestedStatusID string, requestURL *url.URL) (interface{}, gtserror.WithCode) - - // GetStatus handles the getting of a fedi/activitypub representation of replies to a status, performing appropriate - // authentication before returning a JSON serializable interface to the caller. - GetStatusReplies(ctx context.Context, requestedUsername string, requestedStatusID string, page bool, onlyOtherAccounts bool, minID string, requestURL *url.URL) (interface{}, gtserror.WithCode) - - // GetWebfingerAccount handles the GET for a webfinger resource. Most commonly, it will be used for returning account lookups. - GetWebfingerAccount(ctx context.Context, requestedUsername string) (*apimodel.WellKnownResponse, gtserror.WithCode) - - // GetFediEmoji handles the GET for a federated emoji originating from this instance. - GetEmoji(ctx context.Context, requestedEmojiID string, requestURL *url.URL) (interface{}, gtserror.WithCode) - - // GetNodeInfoRel returns a well known response giving the path to node info. - GetNodeInfoRel(ctx context.Context) (*apimodel.WellKnownResponse, gtserror.WithCode) - - // GetNodeInfo returns a node info struct in response to a node info request. - GetNodeInfo(ctx context.Context) (*apimodel.Nodeinfo, gtserror.WithCode) - - // GetOutbox returns the activitypub representation of a local user's outbox. - // This contains links to PUBLIC posts made by this user. - GetOutbox(ctx context.Context, requestedUsername string, page bool, maxID string, minID string, requestURL *url.URL) (interface{}, gtserror.WithCode) - - // PostInbox handles POST requests to a user's inbox for new activitypub messages. - // - // PostInbox returns true if the request was handled as an ActivityPub POST to an actor's inbox. - // If false, the request was not an ActivityPub request and may still be handled by the caller in another way, such as serving a web page. - // - // If the error is nil, then the ResponseWriter's headers and response has already been written. If a non-nil error is returned, then no response has been written. - // - // If the Actor was constructed with the Federated Protocol enabled, side effects will occur. - // - // If the Federated Protocol is not enabled, writes the http.StatusMethodNotAllowed status code in the response. No side effects occur. - PostInbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (bool, error) -} - -type processor struct { - db db.DB - federator federation.Federator - tc typeutils.TypeConverter - filter visibility.Filter -} - -// New returns a new federation processor. -func New(db db.DB, tc typeutils.TypeConverter, federator federation.Federator) Processor { - return &processor{ - db: db, - federator: federator, - tc: tc, - filter: visibility.NewFilter(db), - } -} diff --git a/internal/processing/federation/getfollowers.go b/internal/processing/federation/getfollowers.go deleted file mode 100644 index 991954389..000000000 --- a/internal/processing/federation/getfollowers.go +++ /dev/null @@ -1,76 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package federation - -import ( - "context" - "fmt" - "net/url" - - "github.com/superseriousbusiness/activity/streams" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/transport" -) - -func (p *processor) GetFollowers(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) { - // get the account the request is referring to - requestedAccount, err := p.db.GetAccountByUsernameDomain(ctx, requestedUsername, "") - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err)) - } - - // authenticate the request - requestingAccountURI, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername) - if errWithCode != nil { - return nil, errWithCode - } - - requestingAccount, err := p.federator.GetAccountByURI( - transport.WithFastfail(ctx), requestedUsername, requestingAccountURI, false, - ) - if err != nil { - return nil, gtserror.NewErrorUnauthorized(err) - } - - blocked, err := p.db.IsBlocked(ctx, requestedAccount.ID, requestingAccount.ID, true) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - if blocked { - return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)) - } - - requestedAccountURI, err := url.Parse(requestedAccount.URI) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error parsing url %s: %s", requestedAccount.URI, err)) - } - - requestedFollowers, err := p.federator.FederatingDB().Followers(ctx, requestedAccountURI) - if err != nil { - 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, gtserror.NewErrorInternalError(err) - } - - return data, nil -} diff --git a/internal/processing/federation/getfollowing.go b/internal/processing/federation/getfollowing.go deleted file mode 100644 index 06acdad32..000000000 --- a/internal/processing/federation/getfollowing.go +++ /dev/null @@ -1,76 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package federation - -import ( - "context" - "fmt" - "net/url" - - "github.com/superseriousbusiness/activity/streams" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/transport" -) - -func (p *processor) GetFollowing(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) { - // get the account the request is referring to - requestedAccount, err := p.db.GetAccountByUsernameDomain(ctx, requestedUsername, "") - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err)) - } - - // authenticate the request - requestingAccountURI, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername) - if errWithCode != nil { - return nil, errWithCode - } - - requestingAccount, err := p.federator.GetAccountByURI( - transport.WithFastfail(ctx), requestedUsername, requestingAccountURI, false, - ) - if err != nil { - return nil, gtserror.NewErrorUnauthorized(err) - } - - blocked, err := p.db.IsBlocked(ctx, requestedAccount.ID, requestingAccount.ID, true) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - if blocked { - return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)) - } - - requestedAccountURI, err := url.Parse(requestedAccount.URI) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error parsing url %s: %s", requestedAccount.URI, err)) - } - - requestedFollowing, err := p.federator.FederatingDB().Following(ctx, requestedAccountURI) - if err != nil { - 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, gtserror.NewErrorInternalError(err) - } - - return data, nil -} diff --git a/internal/processing/federation/getnodeinfo.go b/internal/processing/federation/getnodeinfo.go deleted file mode 100644 index a15c6fa10..000000000 --- a/internal/processing/federation/getnodeinfo.go +++ /dev/null @@ -1,89 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package federation - -import ( - "context" - "fmt" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" -) - -const ( - nodeInfoVersion = "2.0" - nodeInfoSoftwareName = "gotosocial" -) - -var ( - nodeInfoRel = fmt.Sprintf("http://nodeinfo.diaspora.software/ns/schema/%s", nodeInfoVersion) - nodeInfoProtocols = []string{"activitypub"} -) - -func (p *processor) GetNodeInfoRel(ctx context.Context) (*apimodel.WellKnownResponse, gtserror.WithCode) { - protocol := config.GetProtocol() - host := config.GetHost() - - return &apimodel.WellKnownResponse{ - Links: []apimodel.Link{ - { - Rel: nodeInfoRel, - Href: fmt.Sprintf("%s://%s/nodeinfo/%s", protocol, host, nodeInfoVersion), - }, - }, - }, nil -} - -func (p *processor) GetNodeInfo(ctx context.Context) (*apimodel.Nodeinfo, gtserror.WithCode) { - openRegistration := config.GetAccountsRegistrationOpen() - softwareVersion := config.GetSoftwareVersion() - - host := config.GetHost() - userCount, err := p.db.CountInstanceUsers(ctx, host) - if err != nil { - return nil, gtserror.NewErrorInternalError(err, "Unable to query instance user count") - } - - postCount, err := p.db.CountInstanceStatuses(ctx, host) - if err != nil { - return nil, gtserror.NewErrorInternalError(err, "Unable to query instance status count") - } - - return &apimodel.Nodeinfo{ - Version: nodeInfoVersion, - Software: apimodel.NodeInfoSoftware{ - Name: nodeInfoSoftwareName, - Version: softwareVersion, - }, - Protocols: nodeInfoProtocols, - Services: apimodel.NodeInfoServices{ - Inbound: []string{}, - Outbound: []string{}, - }, - OpenRegistrations: openRegistration, - Usage: apimodel.NodeInfoUsage{ - Users: apimodel.NodeInfoUsers{ - Total: userCount, - }, - LocalPosts: postCount, - }, - Metadata: make(map[string]interface{}), - }, nil -} diff --git a/internal/processing/federation/getoutbox.go b/internal/processing/federation/getoutbox.go deleted file mode 100644 index 6d0f2f3fe..000000000 --- a/internal/processing/federation/getoutbox.go +++ /dev/null @@ -1,109 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package federation - -import ( - "context" - "fmt" - "net/url" - - "github.com/superseriousbusiness/activity/streams" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/transport" -) - -func (p *processor) GetOutbox(ctx context.Context, requestedUsername string, page bool, maxID string, minID string, requestURL *url.URL) (interface{}, gtserror.WithCode) { - // get the account the request is referring to - requestedAccount, err := p.db.GetAccountByUsernameDomain(ctx, requestedUsername, "") - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err)) - } - - // authenticate the request - requestingAccountURI, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername) - if errWithCode != nil { - return nil, errWithCode - } - - requestingAccount, err := p.federator.GetAccountByURI( - transport.WithFastfail(ctx), requestedUsername, requestingAccountURI, false, - ) - if err != nil { - return nil, gtserror.NewErrorUnauthorized(err) - } - - // authorize the request: - // 1. check if a block exists between the requester and the requestee - blocked, err := p.db.IsBlocked(ctx, requestedAccount.ID, requestingAccount.ID, true) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - if blocked { - return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)) - } - - var data map[string]interface{} - // now there are two scenarios: - // 1. we're asked for the whole collection and not a page -- we can just return the collection, with no items, but a link to 'first' page. - // 2. we're asked for a specific page; this can be either the first page or any other page - - if !page { - /* - scenario 1: return the collection with no items - we want something that looks like this: - { - "@context": "https://www.w3.org/ns/activitystreams", - "id": "https://example.org/users/whatever/outbox", - "type": "OrderedCollection", - "first": "https://example.org/users/whatever/outbox?page=true", - "last": "https://example.org/users/whatever/outbox?min_id=0&page=true" - } - */ - collection, err := p.tc.OutboxToASCollection(ctx, requestedAccount.OutboxURI) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - data, err = streams.Serialize(collection) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - return data, nil - } - - // scenario 2 -- get the requested page - // limit pages to 30 entries per page - publicStatuses, err := p.db.GetAccountStatuses(ctx, requestedAccount.ID, 30, true, true, maxID, minID, false, false, true) - if err != nil && err != db.ErrNoEntries { - return nil, gtserror.NewErrorInternalError(err) - } - - outboxPage, err := p.tc.StatusesToASOutboxPage(ctx, requestedAccount.OutboxURI, maxID, minID, publicStatuses) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - data, err = streams.Serialize(outboxPage) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - return data, nil -} diff --git a/internal/processing/federation/getstatus.go b/internal/processing/federation/getstatus.go deleted file mode 100644 index b54e17b11..000000000 --- a/internal/processing/federation/getstatus.go +++ /dev/null @@ -1,92 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package federation - -import ( - "context" - "fmt" - "net/url" - - "github.com/superseriousbusiness/activity/streams" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/transport" -) - -func (p *processor) GetStatus(ctx context.Context, requestedUsername string, requestedStatusID string, requestURL *url.URL) (interface{}, gtserror.WithCode) { - // get the account the request is referring to - requestedAccount, err := p.db.GetAccountByUsernameDomain(ctx, requestedUsername, "") - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err)) - } - - // authenticate the request - requestingAccountURI, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername) - if errWithCode != nil { - return nil, errWithCode - } - - requestingAccount, err := p.federator.GetAccountByURI( - transport.WithFastfail(ctx), requestedUsername, requestingAccountURI, false, - ) - if err != nil { - return nil, gtserror.NewErrorUnauthorized(err) - } - - // authorize the request: - // 1. check if a block exists between the requester and the requestee - blocked, err := p.db.IsBlocked(ctx, requestedAccount.ID, requestingAccount.ID, true) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - if blocked { - return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)) - } - - // get the status out of the database here - s, err := p.db.GetStatusByID(ctx, requestedStatusID) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting status with id %s and account id %s: %s", requestedStatusID, requestedAccount.ID, err)) - } - - if s.AccountID != requestedAccount.ID { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("status with id %s does not belong to account with id %s", s.ID, requestedAccount.ID)) - } - - visible, err := p.filter.StatusVisible(ctx, s, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - if !visible { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("status with id %s not visible to user with id %s", s.ID, requestingAccount.ID)) - } - - // requester is authorized to view the status, so convert it to AP representation and serialize it - asStatus, err := p.tc.StatusToAS(ctx, s) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - data, err := streams.Serialize(asStatus) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - return data, nil -} diff --git a/internal/processing/federation/getwebfinger.go b/internal/processing/federation/getwebfinger.go deleted file mode 100644 index 305ef3235..000000000 --- a/internal/processing/federation/getwebfinger.go +++ /dev/null @@ -1,70 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package federation - -import ( - "context" - "fmt" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" -) - -const ( - webfingerProfilePage = "http://webfinger.net/rel/profile-page" - webFingerProfilePageContentType = "text/html" - webfingerSelf = "self" - webFingerSelfContentType = "application/activity+json" - webfingerAccount = "acct" -) - -func (p *processor) GetWebfingerAccount(ctx context.Context, requestedUsername string) (*apimodel.WellKnownResponse, gtserror.WithCode) { - // get the account the request is referring to - requestedAccount, err := p.db.GetAccountByUsernameDomain(ctx, requestedUsername, "") - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err)) - } - - accountDomain := config.GetAccountDomain() - if accountDomain == "" { - accountDomain = config.GetHost() - } - - // return the webfinger representation - return &apimodel.WellKnownResponse{ - Subject: fmt.Sprintf("%s:%s@%s", webfingerAccount, requestedAccount.Username, accountDomain), - Aliases: []string{ - requestedAccount.URI, - requestedAccount.URL, - }, - Links: []apimodel.Link{ - { - Rel: webfingerProfilePage, - Type: webFingerProfilePageContentType, - Href: requestedAccount.URL, - }, - { - Rel: webfingerSelf, - Type: webFingerSelfContentType, - Href: requestedAccount.URI, - }, - }, - }, nil -} diff --git a/internal/processing/federation/postinbox.go b/internal/processing/federation/postinbox.go deleted file mode 100644 index 268bc8675..000000000 --- a/internal/processing/federation/postinbox.go +++ /dev/null @@ -1,28 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package federation - -import ( - "context" - "net/http" -) - -func (p *processor) PostInbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (bool, error) { - return p.federator.FederatingActor().PostInbox(ctx, w, r) -} diff --git a/internal/processing/fedi/collections.go b/internal/processing/fedi/collections.go new file mode 100644 index 000000000..62fc9d7b8 --- /dev/null +++ b/internal/processing/fedi/collections.go @@ -0,0 +1,224 @@ +/* + GoToSocial + Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package fedi + +import ( + "context" + "fmt" + "net/http" + "net/url" + + "github.com/superseriousbusiness/activity/streams" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/transport" +) + +// FollowersGet 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. +func (p *Processor) FollowersGet(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) { + // get the account the request is referring to + requestedAccount, err := p.db.GetAccountByUsernameDomain(ctx, requestedUsername, "") + if err != nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err)) + } + + // authenticate the request + requestingAccountURI, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername) + if errWithCode != nil { + return nil, errWithCode + } + + requestingAccount, err := p.federator.GetAccountByURI( + transport.WithFastfail(ctx), requestedUsername, requestingAccountURI, false, + ) + if err != nil { + return nil, gtserror.NewErrorUnauthorized(err) + } + + blocked, err := p.db.IsBlocked(ctx, requestedAccount.ID, requestingAccount.ID, true) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + if blocked { + return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)) + } + + requestedAccountURI, err := url.Parse(requestedAccount.URI) + if err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error parsing url %s: %s", requestedAccount.URI, err)) + } + + requestedFollowers, err := p.federator.FederatingDB().Followers(ctx, requestedAccountURI) + if err != nil { + 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, gtserror.NewErrorInternalError(err) + } + + return data, nil +} + +// FollowingGet 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. +func (p *Processor) FollowingGet(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) { + // get the account the request is referring to + requestedAccount, err := p.db.GetAccountByUsernameDomain(ctx, requestedUsername, "") + if err != nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err)) + } + + // authenticate the request + requestingAccountURI, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername) + if errWithCode != nil { + return nil, errWithCode + } + + requestingAccount, err := p.federator.GetAccountByURI( + transport.WithFastfail(ctx), requestedUsername, requestingAccountURI, false, + ) + if err != nil { + return nil, gtserror.NewErrorUnauthorized(err) + } + + blocked, err := p.db.IsBlocked(ctx, requestedAccount.ID, requestingAccount.ID, true) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + if blocked { + return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)) + } + + requestedAccountURI, err := url.Parse(requestedAccount.URI) + if err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error parsing url %s: %s", requestedAccount.URI, err)) + } + + requestedFollowing, err := p.federator.FederatingDB().Following(ctx, requestedAccountURI) + if err != nil { + 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, gtserror.NewErrorInternalError(err) + } + + return data, nil +} + +// OutboxGet returns the activitypub representation of a local user's outbox. +// This contains links to PUBLIC posts made by this user. +func (p *Processor) OutboxGet(ctx context.Context, requestedUsername string, page bool, maxID string, minID string, requestURL *url.URL) (interface{}, gtserror.WithCode) { + // get the account the request is referring to + requestedAccount, err := p.db.GetAccountByUsernameDomain(ctx, requestedUsername, "") + if err != nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err)) + } + + // authenticate the request + requestingAccountURI, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername) + if errWithCode != nil { + return nil, errWithCode + } + + requestingAccount, err := p.federator.GetAccountByURI( + transport.WithFastfail(ctx), requestedUsername, requestingAccountURI, false, + ) + if err != nil { + return nil, gtserror.NewErrorUnauthorized(err) + } + + // authorize the request: + // 1. check if a block exists between the requester and the requestee + blocked, err := p.db.IsBlocked(ctx, requestedAccount.ID, requestingAccount.ID, true) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + if blocked { + return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)) + } + + var data map[string]interface{} + // now there are two scenarios: + // 1. we're asked for the whole collection and not a page -- we can just return the collection, with no items, but a link to 'first' page. + // 2. we're asked for a specific page; this can be either the first page or any other page + + if !page { + /* + scenario 1: return the collection with no items + we want something that looks like this: + { + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.org/users/whatever/outbox", + "type": "OrderedCollection", + "first": "https://example.org/users/whatever/outbox?page=true", + "last": "https://example.org/users/whatever/outbox?min_id=0&page=true" + } + */ + collection, err := p.tc.OutboxToASCollection(ctx, requestedAccount.OutboxURI) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + data, err = streams.Serialize(collection) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + return data, nil + } + + // scenario 2 -- get the requested page + // limit pages to 30 entries per page + publicStatuses, err := p.db.GetAccountStatuses(ctx, requestedAccount.ID, 30, true, true, maxID, minID, false, false, true) + if err != nil && err != db.ErrNoEntries { + return nil, gtserror.NewErrorInternalError(err) + } + + outboxPage, err := p.tc.StatusesToASOutboxPage(ctx, requestedAccount.OutboxURI, maxID, minID, publicStatuses) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + data, err = streams.Serialize(outboxPage) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + return data, nil +} + +// InboxPost handles POST requests to a user's inbox for new activitypub messages. +// +// InboxPost returns true if the request was handled as an ActivityPub POST to an actor's inbox. +// If false, the request was not an ActivityPub request and may still be handled by the caller in another way, such as serving a web page. +// +// If the error is nil, then the ResponseWriter's headers and response has already been written. If a non-nil error is returned, then no response has been written. +// +// If the Actor was constructed with the Federated Protocol enabled, side effects will occur. +// +// If the Federated Protocol is not enabled, writes the http.StatusMethodNotAllowed status code in the response. No side effects occur. +func (p *Processor) InboxPost(ctx context.Context, w http.ResponseWriter, r *http.Request) (bool, error) { + return p.federator.FederatingActor().PostInbox(ctx, w, r) +} diff --git a/internal/processing/federation/getemoji.go b/internal/processing/fedi/emoji.go similarity index 92% rename from internal/processing/federation/getemoji.go rename to internal/processing/fedi/emoji.go index 7f5ad81a9..a2eb2688f 100644 --- a/internal/processing/federation/getemoji.go +++ b/internal/processing/fedi/emoji.go @@ -16,7 +16,7 @@ along with this program. If not, see . */ -package federation +package fedi import ( "context" @@ -27,7 +27,8 @@ "github.com/superseriousbusiness/gotosocial/internal/gtserror" ) -func (p *processor) GetEmoji(ctx context.Context, requestedEmojiID string, requestURL *url.URL) (interface{}, gtserror.WithCode) { +// EmojiGet handles the GET for a federated emoji originating from this instance. +func (p *Processor) EmojiGet(ctx context.Context, requestedEmojiID string, requestURL *url.URL) (interface{}, gtserror.WithCode) { if _, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, ""); errWithCode != nil { return nil, errWithCode } diff --git a/internal/processing/admin/mediaprune.go b/internal/processing/fedi/fedi.go similarity index 54% rename from internal/processing/admin/mediaprune.go rename to internal/processing/fedi/fedi.go index c8157d576..e72d037f5 100644 --- a/internal/processing/admin/mediaprune.go +++ b/internal/processing/fedi/fedi.go @@ -16,25 +16,28 @@ along with this program. If not, see . */ -package admin +package fedi import ( - "context" - "fmt" - - "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/federation" + "github.com/superseriousbusiness/gotosocial/internal/typeutils" + "github.com/superseriousbusiness/gotosocial/internal/visibility" ) -func (p *processor) MediaPrune(ctx context.Context, mediaRemoteCacheDays int) gtserror.WithCode { - if mediaRemoteCacheDays < 0 { - err := fmt.Errorf("MediaPrune: invalid value for mediaRemoteCacheDays prune: value was %d, cannot be less than 0", mediaRemoteCacheDays) - return gtserror.NewErrorBadRequest(err, err.Error()) - } - - if err := p.mediaManager.PruneAll(ctx, mediaRemoteCacheDays, false); err != nil { - err = fmt.Errorf("MediaPrune: %w", err) - return gtserror.NewErrorInternalError(err) - } - - return nil +type Processor struct { + db db.DB + federator federation.Federator + tc typeutils.TypeConverter + filter visibility.Filter +} + +// New returns a new fedi processor. +func New(db db.DB, tc typeutils.TypeConverter, federator federation.Federator) Processor { + return Processor{ + db: db, + federator: federator, + tc: tc, + filter: visibility.NewFilter(db), + } } diff --git a/internal/processing/federation/getstatusreplies.go b/internal/processing/fedi/status.go similarity index 67% rename from internal/processing/federation/getstatusreplies.go rename to internal/processing/fedi/status.go index 08c9b1119..0e4c99b60 100644 --- a/internal/processing/federation/getstatusreplies.go +++ b/internal/processing/fedi/status.go @@ -16,7 +16,7 @@ along with this program. If not, see . */ -package federation +package fedi import ( "context" @@ -30,7 +30,74 @@ "github.com/superseriousbusiness/gotosocial/internal/transport" ) -func (p *processor) GetStatusReplies(ctx context.Context, requestedUsername string, requestedStatusID string, page bool, onlyOtherAccounts bool, minID string, requestURL *url.URL) (interface{}, gtserror.WithCode) { +// StatusGet handles the getting of a fedi/activitypub representation of a particular status, performing appropriate +// authentication before returning a JSON serializable interface to the caller. +func (p *Processor) StatusGet(ctx context.Context, requestedUsername string, requestedStatusID string, requestURL *url.URL) (interface{}, gtserror.WithCode) { + // get the account the request is referring to + requestedAccount, err := p.db.GetAccountByUsernameDomain(ctx, requestedUsername, "") + if err != nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err)) + } + + // authenticate the request + requestingAccountURI, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername) + if errWithCode != nil { + return nil, errWithCode + } + + requestingAccount, err := p.federator.GetAccountByURI( + transport.WithFastfail(ctx), requestedUsername, requestingAccountURI, false, + ) + if err != nil { + return nil, gtserror.NewErrorUnauthorized(err) + } + + // authorize the request: + // 1. check if a block exists between the requester and the requestee + blocked, err := p.db.IsBlocked(ctx, requestedAccount.ID, requestingAccount.ID, true) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + if blocked { + return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)) + } + + // get the status out of the database here + s, err := p.db.GetStatusByID(ctx, requestedStatusID) + if err != nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting status with id %s and account id %s: %s", requestedStatusID, requestedAccount.ID, err)) + } + + if s.AccountID != requestedAccount.ID { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("status with id %s does not belong to account with id %s", s.ID, requestedAccount.ID)) + } + + visible, err := p.filter.StatusVisible(ctx, s, requestingAccount) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + if !visible { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("status with id %s not visible to user with id %s", s.ID, requestingAccount.ID)) + } + + // requester is authorized to view the status, so convert it to AP representation and serialize it + asStatus, err := p.tc.StatusToAS(ctx, s) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + data, err := streams.Serialize(asStatus) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + return data, nil +} + +// GetStatus handles the getting of a fedi/activitypub representation of replies to a status, performing appropriate +// authentication before returning a JSON serializable interface to the caller. +func (p *Processor) StatusRepliesGet(ctx context.Context, requestedUsername string, requestedStatusID string, page bool, onlyOtherAccounts bool, minID string, requestURL *url.URL) (interface{}, gtserror.WithCode) { // get the account the request is referring to requestedAccount, err := p.db.GetAccountByUsernameDomain(ctx, requestedUsername, "") if err != nil { diff --git a/internal/processing/federation/getuser.go b/internal/processing/fedi/user.go similarity index 92% rename from internal/processing/federation/getuser.go rename to internal/processing/fedi/user.go index d3fb7bdf6..899d063d1 100644 --- a/internal/processing/federation/getuser.go +++ b/internal/processing/fedi/user.go @@ -16,7 +16,7 @@ along with this program. If not, see . */ -package federation +package fedi import ( "context" @@ -30,7 +30,9 @@ "github.com/superseriousbusiness/gotosocial/internal/uris" ) -func (p *processor) GetUser(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) { +// UserGet handles the getting of a fedi/activitypub representation of a user/account, performing appropriate authentication +// before returning a JSON serializable interface to the caller. +func (p *Processor) UserGet(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) { // Get the instance-local account the request is referring to. requestedAccount, err := p.db.GetAccountByUsernameDomain(ctx, requestedUsername, "") if err != nil { diff --git a/internal/processing/fedi/wellknown.go b/internal/processing/fedi/wellknown.go new file mode 100644 index 000000000..75ed34ec2 --- /dev/null +++ b/internal/processing/fedi/wellknown.go @@ -0,0 +1,126 @@ +/* + GoToSocial + Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package fedi + +import ( + "context" + "fmt" + + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" +) + +const ( + nodeInfoVersion = "2.0" + nodeInfoSoftwareName = "gotosocial" + nodeInfoRel = "http://nodeinfo.diaspora.software/ns/schema/" + nodeInfoVersion + webfingerProfilePage = "http://webfinger.net/rel/profile-page" + webFingerProfilePageContentType = "text/html" + webfingerSelf = "self" + webFingerSelfContentType = "application/activity+json" + webfingerAccount = "acct" +) + +var ( + nodeInfoProtocols = []string{"activitypub"} + nodeInfoInbound = []string{} + nodeInfoOutbound = []string{} + nodeInfoMetadata = make(map[string]interface{}) +) + +// NodeInfoRelGet returns a well known response giving the path to node info. +func (p *Processor) NodeInfoRelGet(ctx context.Context) (*apimodel.WellKnownResponse, gtserror.WithCode) { + protocol := config.GetProtocol() + host := config.GetHost() + + return &apimodel.WellKnownResponse{ + Links: []apimodel.Link{ + { + Rel: nodeInfoRel, + Href: fmt.Sprintf("%s://%s/nodeinfo/%s", protocol, host, nodeInfoVersion), + }, + }, + }, nil +} + +// NodeInfoGet returns a node info struct in response to a node info request. +func (p *Processor) NodeInfoGet(ctx context.Context) (*apimodel.Nodeinfo, gtserror.WithCode) { + host := config.GetHost() + + userCount, err := p.db.CountInstanceUsers(ctx, host) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + postCount, err := p.db.CountInstanceStatuses(ctx, host) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + return &apimodel.Nodeinfo{ + Version: nodeInfoVersion, + Software: apimodel.NodeInfoSoftware{ + Name: nodeInfoSoftwareName, + Version: config.GetSoftwareVersion(), + }, + Protocols: nodeInfoProtocols, + Services: apimodel.NodeInfoServices{ + Inbound: nodeInfoInbound, + Outbound: nodeInfoOutbound, + }, + OpenRegistrations: config.GetAccountsRegistrationOpen(), + Usage: apimodel.NodeInfoUsage{ + Users: apimodel.NodeInfoUsers{ + Total: userCount, + }, + LocalPosts: postCount, + }, + Metadata: nodeInfoMetadata, + }, nil +} + +// WebfingerGet handles the GET for a webfinger resource. Most commonly, it will be used for returning account lookups. +func (p *Processor) WebfingerGet(ctx context.Context, requestedUsername string) (*apimodel.WellKnownResponse, gtserror.WithCode) { + // Get the local account the request is referring to. + requestedAccount, err := p.db.GetAccountByUsernameDomain(ctx, requestedUsername, "") + if err != nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err)) + } + + return &apimodel.WellKnownResponse{ + Subject: webfingerAccount + ":" + requestedAccount.Username + "@" + config.GetAccountDomain(), + Aliases: []string{ + requestedAccount.URI, + requestedAccount.URL, + }, + Links: []apimodel.Link{ + { + Rel: webfingerProfilePage, + Type: webFingerProfilePageContentType, + Href: requestedAccount.URL, + }, + { + Rel: webfingerSelf, + Type: webFingerSelfContentType, + Href: requestedAccount.URI, + }, + }, + }, nil +} diff --git a/internal/processing/followrequest.go b/internal/processing/followrequest.go index 51dda3ce0..1f1b7f3c2 100644 --- a/internal/processing/followrequest.go +++ b/internal/processing/followrequest.go @@ -29,7 +29,7 @@ "github.com/superseriousbusiness/gotosocial/internal/oauth" ) -func (p *processor) FollowRequestsGet(ctx context.Context, auth *oauth.Auth) ([]apimodel.Account, gtserror.WithCode) { +func (p *Processor) FollowRequestsGet(ctx context.Context, auth *oauth.Auth) ([]apimodel.Account, gtserror.WithCode) { frs, err := p.db.GetAccountFollowRequests(ctx, auth.Account.ID) if err != nil { if err != db.ErrNoEntries { @@ -56,7 +56,7 @@ func (p *processor) FollowRequestsGet(ctx context.Context, auth *oauth.Auth) ([] return accts, nil } -func (p *processor) FollowRequestAccept(ctx context.Context, auth *oauth.Auth, accountID string) (*apimodel.Relationship, gtserror.WithCode) { +func (p *Processor) FollowRequestAccept(ctx context.Context, auth *oauth.Auth, accountID string) (*apimodel.Relationship, gtserror.WithCode) { follow, err := p.db.AcceptFollowRequest(ctx, accountID, auth.Account.ID) if err != nil { return nil, gtserror.NewErrorNotFound(err) @@ -99,7 +99,7 @@ func (p *processor) FollowRequestAccept(ctx context.Context, auth *oauth.Auth, a return r, nil } -func (p *processor) FollowRequestReject(ctx context.Context, auth *oauth.Auth, accountID string) (*apimodel.Relationship, gtserror.WithCode) { +func (p *Processor) FollowRequestReject(ctx context.Context, auth *oauth.Auth, accountID string) (*apimodel.Relationship, gtserror.WithCode) { followRequest, err := p.db.RejectFollowRequest(ctx, accountID, auth.Account.ID) if err != nil { return nil, gtserror.NewErrorNotFound(err) diff --git a/internal/processing/fromclientapi.go b/internal/processing/fromclientapi.go index ef834a570..701f425f6 100644 --- a/internal/processing/fromclientapi.go +++ b/internal/processing/fromclientapi.go @@ -34,7 +34,7 @@ "github.com/superseriousbusiness/gotosocial/internal/messages" ) -func (p *processor) ProcessFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) ProcessFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { // Allocate new log fields slice fields := make([]kv.Field, 3, 4) fields[0] = kv.Field{"activityType", clientMsg.APActivityType} @@ -131,7 +131,7 @@ func (p *processor) ProcessFromClientAPI(ctx context.Context, clientMsg messages return nil } -func (p *processor) processCreateAccountFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processCreateAccountFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { account, ok := clientMsg.GTSModel.(*gtsmodel.Account) if !ok { return errors.New("account was not parseable as *gtsmodel.Account") @@ -149,10 +149,10 @@ func (p *processor) processCreateAccountFromClientAPI(ctx context.Context, clien } // email a confirmation to this user - return p.userProcessor.SendConfirmEmail(ctx, user, account.Username) + return p.User().EmailSendConfirmation(ctx, user, account.Username) } -func (p *processor) processCreateStatusFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processCreateStatusFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { status, ok := clientMsg.GTSModel.(*gtsmodel.Status) if !ok { return errors.New("note was not parseable as *gtsmodel.Status") @@ -169,7 +169,7 @@ func (p *processor) processCreateStatusFromClientAPI(ctx context.Context, client return p.federateStatus(ctx, status) } -func (p *processor) processCreateFollowRequestFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processCreateFollowRequestFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { followRequest, ok := clientMsg.GTSModel.(*gtsmodel.FollowRequest) if !ok { return errors.New("followrequest was not parseable as *gtsmodel.FollowRequest") @@ -182,7 +182,7 @@ func (p *processor) processCreateFollowRequestFromClientAPI(ctx context.Context, return p.federateFollow(ctx, followRequest, clientMsg.OriginAccount, clientMsg.TargetAccount) } -func (p *processor) processCreateFaveFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processCreateFaveFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { fave, ok := clientMsg.GTSModel.(*gtsmodel.StatusFave) if !ok { return errors.New("fave was not parseable as *gtsmodel.StatusFave") @@ -195,7 +195,7 @@ func (p *processor) processCreateFaveFromClientAPI(ctx context.Context, clientMs return p.federateFave(ctx, fave, clientMsg.OriginAccount, clientMsg.TargetAccount) } -func (p *processor) processCreateAnnounceFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processCreateAnnounceFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { boostWrapperStatus, ok := clientMsg.GTSModel.(*gtsmodel.Status) if !ok { return errors.New("boost was not parseable as *gtsmodel.Status") @@ -212,7 +212,7 @@ func (p *processor) processCreateAnnounceFromClientAPI(ctx context.Context, clie return p.federateAnnounce(ctx, boostWrapperStatus, clientMsg.OriginAccount, clientMsg.TargetAccount) } -func (p *processor) processCreateBlockFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processCreateBlockFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { block, ok := clientMsg.GTSModel.(*gtsmodel.Block) if !ok { return errors.New("block was not parseable as *gtsmodel.Block") @@ -232,7 +232,7 @@ func (p *processor) processCreateBlockFromClientAPI(ctx context.Context, clientM return p.federateBlock(ctx, block) } -func (p *processor) processUpdateAccountFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processUpdateAccountFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { account, ok := clientMsg.GTSModel.(*gtsmodel.Account) if !ok { return errors.New("account was not parseable as *gtsmodel.Account") @@ -241,7 +241,7 @@ func (p *processor) processUpdateAccountFromClientAPI(ctx context.Context, clien return p.federateAccountUpdate(ctx, account, clientMsg.OriginAccount) } -func (p *processor) processAcceptFollowFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processAcceptFollowFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow) if !ok { return errors.New("accept was not parseable as *gtsmodel.Follow") @@ -254,7 +254,7 @@ func (p *processor) processAcceptFollowFromClientAPI(ctx context.Context, client return p.federateAcceptFollowRequest(ctx, follow) } -func (p *processor) processRejectFollowFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processRejectFollowFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { followRequest, ok := clientMsg.GTSModel.(*gtsmodel.FollowRequest) if !ok { return errors.New("reject was not parseable as *gtsmodel.FollowRequest") @@ -263,7 +263,7 @@ func (p *processor) processRejectFollowFromClientAPI(ctx context.Context, client return p.federateRejectFollowRequest(ctx, followRequest) } -func (p *processor) processUndoFollowFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processUndoFollowFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow) if !ok { return errors.New("undo was not parseable as *gtsmodel.Follow") @@ -271,7 +271,7 @@ func (p *processor) processUndoFollowFromClientAPI(ctx context.Context, clientMs return p.federateUnfollow(ctx, follow, clientMsg.OriginAccount, clientMsg.TargetAccount) } -func (p *processor) processUndoBlockFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processUndoBlockFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { block, ok := clientMsg.GTSModel.(*gtsmodel.Block) if !ok { return errors.New("undo was not parseable as *gtsmodel.Block") @@ -279,7 +279,7 @@ func (p *processor) processUndoBlockFromClientAPI(ctx context.Context, clientMsg return p.federateUnblock(ctx, block) } -func (p *processor) processUndoFaveFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processUndoFaveFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { fave, ok := clientMsg.GTSModel.(*gtsmodel.StatusFave) if !ok { return errors.New("undo was not parseable as *gtsmodel.StatusFave") @@ -287,7 +287,7 @@ func (p *processor) processUndoFaveFromClientAPI(ctx context.Context, clientMsg return p.federateUnfave(ctx, fave, clientMsg.OriginAccount, clientMsg.TargetAccount) } -func (p *processor) processUndoAnnounceFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processUndoAnnounceFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { boost, ok := clientMsg.GTSModel.(*gtsmodel.Status) if !ok { return errors.New("undo was not parseable as *gtsmodel.Status") @@ -304,7 +304,7 @@ func (p *processor) processUndoAnnounceFromClientAPI(ctx context.Context, client return p.federateUnannounce(ctx, boost, clientMsg.OriginAccount, clientMsg.TargetAccount) } -func (p *processor) processDeleteStatusFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processDeleteStatusFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { statusToDelete, ok := clientMsg.GTSModel.(*gtsmodel.Status) if !ok { return errors.New("note was not parseable as *gtsmodel.Status") @@ -326,7 +326,7 @@ func (p *processor) processDeleteStatusFromClientAPI(ctx context.Context, client return p.federateStatusDelete(ctx, statusToDelete) } -func (p *processor) processDeleteAccountFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processDeleteAccountFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { // the origin of the delete could be either a domain block, or an action by another (or this) account var origin string if domainBlock, ok := clientMsg.GTSModel.(*gtsmodel.DomainBlock); ok { @@ -341,10 +341,10 @@ func (p *processor) processDeleteAccountFromClientAPI(ctx context.Context, clien return err } - return p.accountProcessor.Delete(ctx, clientMsg.TargetAccount, origin) + return p.account.Delete(ctx, clientMsg.TargetAccount, origin) } -func (p *processor) processReportAccountFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { +func (p *Processor) processReportAccountFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { report, ok := clientMsg.GTSModel.(*gtsmodel.Report) if !ok { return errors.New("report was not parseable as *gtsmodel.Report") @@ -362,7 +362,7 @@ func (p *processor) processReportAccountFromClientAPI(ctx context.Context, clien // TODO: move all the below functions into federation.Federator -func (p *processor) federateAccountDelete(ctx context.Context, account *gtsmodel.Account) error { +func (p *Processor) federateAccountDelete(ctx context.Context, account *gtsmodel.Account) error { // do nothing if this isn't our account if account.Domain != "" { return nil @@ -415,7 +415,7 @@ func (p *processor) federateAccountDelete(ctx context.Context, account *gtsmodel return err } -func (p *processor) federateStatus(ctx context.Context, status *gtsmodel.Status) error { +func (p *Processor) federateStatus(ctx context.Context, status *gtsmodel.Status) error { // do nothing if the status shouldn't be federated if !*status.Federated { return nil @@ -453,7 +453,7 @@ func (p *processor) federateStatus(ctx context.Context, status *gtsmodel.Status) return err } -func (p *processor) federateStatusDelete(ctx context.Context, status *gtsmodel.Status) error { +func (p *Processor) federateStatusDelete(ctx context.Context, status *gtsmodel.Status) error { if status.Account == nil { statusAccount, err := p.db.GetAccountByID(ctx, status.AccountID) if err != nil { @@ -503,7 +503,7 @@ func (p *processor) federateStatusDelete(ctx context.Context, status *gtsmodel.S return err } -func (p *processor) federateFollow(ctx context.Context, followRequest *gtsmodel.FollowRequest, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error { +func (p *Processor) federateFollow(ctx context.Context, followRequest *gtsmodel.FollowRequest, 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 @@ -525,7 +525,7 @@ func (p *processor) federateFollow(ctx context.Context, followRequest *gtsmodel. return err } -func (p *processor) federateUnfollow(ctx context.Context, follow *gtsmodel.Follow, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error { +func (p *Processor) federateUnfollow(ctx context.Context, follow *gtsmodel.Follow, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error { // if both accounts are local there's nothing to do here if originAccount.Domain == "" && targetAccount.Domain == "" { return nil @@ -566,7 +566,7 @@ func (p *processor) federateUnfollow(ctx context.Context, follow *gtsmodel.Follo return err } -func (p *processor) federateUnfave(ctx context.Context, fave *gtsmodel.StatusFave, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error { +func (p *Processor) federateUnfave(ctx context.Context, 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 @@ -605,7 +605,7 @@ func (p *processor) federateUnfave(ctx context.Context, fave *gtsmodel.StatusFav return err } -func (p *processor) federateUnannounce(ctx context.Context, boost *gtsmodel.Status, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error { +func (p *Processor) federateUnannounce(ctx context.Context, boost *gtsmodel.Status, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error { if originAccount.Domain != "" { // nothing to do here return nil @@ -640,7 +640,7 @@ func (p *processor) federateUnannounce(ctx context.Context, boost *gtsmodel.Stat return err } -func (p *processor) federateAcceptFollowRequest(ctx context.Context, follow *gtsmodel.Follow) error { +func (p *Processor) federateAcceptFollowRequest(ctx context.Context, follow *gtsmodel.Follow) error { if follow.Account == nil { a, err := p.db.GetAccountByID(ctx, follow.AccountID) if err != nil { @@ -713,7 +713,7 @@ func (p *processor) federateAcceptFollowRequest(ctx context.Context, follow *gts return err } -func (p *processor) federateRejectFollowRequest(ctx context.Context, followRequest *gtsmodel.FollowRequest) error { +func (p *Processor) federateRejectFollowRequest(ctx context.Context, followRequest *gtsmodel.FollowRequest) error { if followRequest.Account == nil { a, err := p.db.GetAccountByID(ctx, followRequest.AccountID) if err != nil { @@ -787,7 +787,7 @@ func (p *processor) federateRejectFollowRequest(ctx context.Context, followReque return err } -func (p *processor) federateFave(ctx context.Context, fave *gtsmodel.StatusFave, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error { +func (p *Processor) federateFave(ctx context.Context, 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 @@ -807,7 +807,7 @@ func (p *processor) federateFave(ctx context.Context, fave *gtsmodel.StatusFave, return err } -func (p *processor) federateAnnounce(ctx context.Context, boostWrapperStatus *gtsmodel.Status, boostingAccount *gtsmodel.Account, boostedAccount *gtsmodel.Account) error { +func (p *Processor) federateAnnounce(ctx context.Context, boostWrapperStatus *gtsmodel.Status, boostingAccount *gtsmodel.Account, boostedAccount *gtsmodel.Account) error { announce, err := p.tc.BoostToAS(ctx, boostWrapperStatus, boostingAccount, boostedAccount) if err != nil { return fmt.Errorf("federateAnnounce: error converting status to announce: %s", err) @@ -822,7 +822,7 @@ func (p *processor) federateAnnounce(ctx context.Context, boostWrapperStatus *gt return err } -func (p *processor) federateAccountUpdate(ctx context.Context, updatedAccount *gtsmodel.Account, originAccount *gtsmodel.Account) error { +func (p *Processor) federateAccountUpdate(ctx context.Context, updatedAccount *gtsmodel.Account, originAccount *gtsmodel.Account) error { person, err := p.tc.AccountToAS(ctx, updatedAccount) if err != nil { return fmt.Errorf("federateAccountUpdate: error converting account to person: %s", err) @@ -842,7 +842,7 @@ func (p *processor) federateAccountUpdate(ctx context.Context, updatedAccount *g return err } -func (p *processor) federateBlock(ctx context.Context, block *gtsmodel.Block) error { +func (p *Processor) federateBlock(ctx context.Context, block *gtsmodel.Block) error { if block.Account == nil { blockAccount, err := p.db.GetAccountByID(ctx, block.AccountID) if err != nil { @@ -878,7 +878,7 @@ func (p *processor) federateBlock(ctx context.Context, block *gtsmodel.Block) er return err } -func (p *processor) federateUnblock(ctx context.Context, block *gtsmodel.Block) error { +func (p *Processor) federateUnblock(ctx context.Context, block *gtsmodel.Block) error { if block.Account == nil { blockAccount, err := p.db.GetAccountByID(ctx, block.AccountID) if err != nil { @@ -932,7 +932,7 @@ func (p *processor) federateUnblock(ctx context.Context, block *gtsmodel.Block) return err } -func (p *processor) federateReport(ctx context.Context, report *gtsmodel.Report) error { +func (p *Processor) federateReport(ctx context.Context, report *gtsmodel.Report) error { if report.TargetAccount == nil { reportTargetAccount, err := p.db.GetAccountByID(ctx, report.TargetAccountID) if err != nil { diff --git a/internal/processing/fromclientapi_test.go b/internal/processing/fromclientapi_test.go index fe18babbe..2349febf5 100644 --- a/internal/processing/fromclientapi_test.go +++ b/internal/processing/fromclientapi_test.go @@ -46,12 +46,12 @@ func (suite *FromClientAPITestSuite) TestProcessStreamNewStatus() { receivingAccount := suite.testAccounts["local_account_1"] // open a home timeline stream for zork - wssStream, errWithCode := suite.processor.OpenStreamForAccount(ctx, receivingAccount, stream.TimelineHome) + wssStream, errWithCode := suite.processor.Stream().Open(ctx, receivingAccount, stream.TimelineHome) suite.NoError(errWithCode) // open another stream for zork, but for a different timeline; // this shouldn't get stuff streamed into it, since it's for the public timeline - irrelevantStream, errWithCode := suite.processor.OpenStreamForAccount(ctx, receivingAccount, stream.TimelinePublic) + irrelevantStream, errWithCode := suite.processor.Stream().Open(ctx, receivingAccount, stream.TimelinePublic) suite.NoError(errWithCode) // make a new status from admin account @@ -125,7 +125,7 @@ func (suite *FromClientAPITestSuite) TestProcessStatusDelete() { boostOfDeletedStatus := suite.testStatuses["admin_account_status_4"] // open a home timeline stream for turtle, who follows zork - wssStream, errWithCode := suite.processor.OpenStreamForAccount(ctx, receivingAccount, stream.TimelineHome) + wssStream, errWithCode := suite.processor.Stream().Open(ctx, receivingAccount, stream.TimelineHome) suite.NoError(errWithCode) // delete the status from the db first, to mimic what would have already happened earlier up the flow diff --git a/internal/processing/fromcommon.go b/internal/processing/fromcommon.go index 1c6176565..a09d428e8 100644 --- a/internal/processing/fromcommon.go +++ b/internal/processing/fromcommon.go @@ -30,7 +30,7 @@ "github.com/superseriousbusiness/gotosocial/internal/stream" ) -func (p *processor) notifyStatus(ctx context.Context, status *gtsmodel.Status) error { +func (p *Processor) notifyStatus(ctx context.Context, status *gtsmodel.Status) error { // if there are no mentions in this status then just bail if len(status.MentionIDs) == 0 { return nil @@ -97,7 +97,7 @@ func (p *processor) notifyStatus(ctx context.Context, status *gtsmodel.Status) e return fmt.Errorf("notifyStatus: error converting notification to api representation: %s", err) } - if err := p.streamingProcessor.StreamNotificationToAccount(apiNotif, m.TargetAccount); err != nil { + if err := p.stream.Notify(apiNotif, m.TargetAccount); err != nil { return fmt.Errorf("notifyStatus: error streaming notification to account: %s", err) } } @@ -105,7 +105,7 @@ func (p *processor) notifyStatus(ctx context.Context, status *gtsmodel.Status) e return nil } -func (p *processor) notifyFollowRequest(ctx context.Context, followRequest *gtsmodel.FollowRequest) error { +func (p *Processor) notifyFollowRequest(ctx context.Context, followRequest *gtsmodel.FollowRequest) error { // make sure we have the target account pinned on the follow request if followRequest.TargetAccount == nil { a, err := p.db.GetAccountByID(ctx, followRequest.TargetAccountID) @@ -139,14 +139,14 @@ func (p *processor) notifyFollowRequest(ctx context.Context, followRequest *gtsm return fmt.Errorf("notifyStatus: error converting notification to api representation: %s", err) } - if err := p.streamingProcessor.StreamNotificationToAccount(apiNotif, targetAccount); err != nil { + if err := p.stream.Notify(apiNotif, targetAccount); err != nil { return fmt.Errorf("notifyStatus: error streaming notification to account: %s", err) } return nil } -func (p *processor) notifyFollow(ctx context.Context, follow *gtsmodel.Follow, targetAccount *gtsmodel.Account) error { +func (p *Processor) notifyFollow(ctx context.Context, follow *gtsmodel.Follow, targetAccount *gtsmodel.Account) error { // return if this isn't a local account if targetAccount.Domain != "" { return nil @@ -180,14 +180,14 @@ func (p *processor) notifyFollow(ctx context.Context, follow *gtsmodel.Follow, t return fmt.Errorf("notifyStatus: error converting notification to api representation: %s", err) } - if err := p.streamingProcessor.StreamNotificationToAccount(apiNotif, targetAccount); err != nil { + if err := p.stream.Notify(apiNotif, targetAccount); err != nil { return fmt.Errorf("notifyStatus: error streaming notification to account: %s", err) } return nil } -func (p *processor) notifyFave(ctx context.Context, fave *gtsmodel.StatusFave) error { +func (p *Processor) notifyFave(ctx context.Context, fave *gtsmodel.StatusFave) error { // ignore self-faves if fave.TargetAccountID == fave.AccountID { return nil @@ -228,14 +228,14 @@ func (p *processor) notifyFave(ctx context.Context, fave *gtsmodel.StatusFave) e return fmt.Errorf("notifyStatus: error converting notification to api representation: %s", err) } - if err := p.streamingProcessor.StreamNotificationToAccount(apiNotif, targetAccount); err != nil { + if err := p.stream.Notify(apiNotif, targetAccount); err != nil { return fmt.Errorf("notifyStatus: error streaming notification to account: %s", err) } return nil } -func (p *processor) notifyAnnounce(ctx context.Context, status *gtsmodel.Status) error { +func (p *Processor) notifyAnnounce(ctx context.Context, status *gtsmodel.Status) error { if status.BoostOfID == "" { // not a boost, nothing to do return nil @@ -302,7 +302,7 @@ func (p *processor) notifyAnnounce(ctx context.Context, status *gtsmodel.Status) return fmt.Errorf("notifyStatus: error converting notification to api representation: %s", err) } - if err := p.streamingProcessor.StreamNotificationToAccount(apiNotif, status.BoostOfAccount); err != nil { + if err := p.stream.Notify(apiNotif, status.BoostOfAccount); err != nil { return fmt.Errorf("notifyStatus: error streaming notification to account: %s", err) } @@ -311,7 +311,7 @@ func (p *processor) notifyAnnounce(ctx context.Context, status *gtsmodel.Status) // timelineStatus processes the given new status and inserts it into // the HOME timelines of accounts that follow the status author. -func (p *processor) timelineStatus(ctx context.Context, status *gtsmodel.Status) error { +func (p *Processor) timelineStatus(ctx context.Context, status *gtsmodel.Status) error { // make sure the author account is pinned onto the status if status.Account == nil { a, err := p.db.GetAccountByID(ctx, status.AccountID) @@ -370,7 +370,7 @@ func (p *processor) timelineStatus(ctx context.Context, status *gtsmodel.Status) // // If the status was inserted into the home timeline of the given account, // it will also be streamed via websockets to the user. -func (p *processor) timelineStatusForAccount(ctx context.Context, status *gtsmodel.Status, accountID string, errors chan error, wg *sync.WaitGroup) { +func (p *Processor) timelineStatusForAccount(ctx context.Context, status *gtsmodel.Status, accountID string, errors chan error, wg *sync.WaitGroup) { defer wg.Done() // get the timeline owner account @@ -406,7 +406,7 @@ func (p *processor) timelineStatusForAccount(ctx context.Context, status *gtsmod return } - if err := p.streamingProcessor.StreamUpdateToAccount(apiStatus, timelineAccount, stream.TimelineHome); err != nil { + if err := p.stream.Update(apiStatus, timelineAccount, stream.TimelineHome); err != nil { errors <- fmt.Errorf("timelineStatusForAccount: error streaming status %s: %s", status.ID, err) } } @@ -414,17 +414,17 @@ func (p *processor) timelineStatusForAccount(ctx context.Context, status *gtsmod // deleteStatusFromTimelines completely removes the given status from all timelines. // It will also stream deletion of the status to all open streams. -func (p *processor) deleteStatusFromTimelines(ctx context.Context, status *gtsmodel.Status) error { +func (p *Processor) deleteStatusFromTimelines(ctx context.Context, status *gtsmodel.Status) error { if err := p.statusTimelines.WipeItemFromAllTimelines(ctx, status.ID); err != nil { return err } - return p.streamingProcessor.StreamDelete(status.ID) + return p.stream.Delete(status.ID) } // wipeStatus contains common logic used to totally delete a status // + all its attachments, notifications, boosts, and timeline entries. -func (p *processor) wipeStatus(ctx context.Context, statusToDelete *gtsmodel.Status, deleteAttachments bool) error { +func (p *Processor) wipeStatus(ctx context.Context, statusToDelete *gtsmodel.Status, deleteAttachments bool) error { // either delete all attachments for this status, or simply // unattach all attachments for this status, so they'll be // cleaned later by a separate process; reason to unattach rather @@ -432,13 +432,13 @@ func (p *processor) wipeStatus(ctx context.Context, statusToDelete *gtsmodel.Sta // to another status immediately (in case of delete + redraft) if deleteAttachments { for _, a := range statusToDelete.AttachmentIDs { - if err := p.mediaProcessor.Delete(ctx, a); err != nil { + if err := p.media.Delete(ctx, a); err != nil { return err } } } else { for _, a := range statusToDelete.AttachmentIDs { - if _, err := p.mediaProcessor.Unattach(ctx, statusToDelete.Account, a); err != nil { + if _, err := p.media.Unattach(ctx, statusToDelete.Account, a); err != nil { return err } } diff --git a/internal/processing/fromfederator.go b/internal/processing/fromfederator.go index 7a3508840..eea3c529d 100644 --- a/internal/processing/fromfederator.go +++ b/internal/processing/fromfederator.go @@ -36,7 +36,7 @@ // ProcessFromFederator reads the APActivityType and APObjectType of an incoming message from the federator, // and directs the message into the appropriate side effect handler function, or simply does nothing if there's // no handler function defined for the combination of Activity and Object. -func (p *processor) ProcessFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { +func (p *Processor) ProcessFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { // Allocate new log fields slice fields := make([]kv.Field, 3, 5) fields[0] = kv.Field{"activityType", federatorMsg.APActivityType} @@ -108,7 +108,7 @@ func (p *processor) ProcessFromFederator(ctx context.Context, federatorMsg messa } // processCreateStatusFromFederator handles Activity Create and Object Note -func (p *processor) processCreateStatusFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { +func (p *Processor) processCreateStatusFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { // check for either an IRI that we still need to dereference, OR an already dereferenced // and converted status pinned to the message. var status *gtsmodel.Status @@ -177,7 +177,7 @@ func (p *processor) processCreateStatusFromFederator(ctx context.Context, federa } // processCreateFaveFromFederator handles Activity Create and Object Like -func (p *processor) processCreateFaveFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { +func (p *Processor) processCreateFaveFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { incomingFave, ok := federatorMsg.GTSModel.(*gtsmodel.StatusFave) if !ok { return errors.New("like was not parseable as *gtsmodel.StatusFave") @@ -219,7 +219,7 @@ func (p *processor) processCreateFaveFromFederator(ctx context.Context, federato } // processCreateFollowRequestFromFederator handles Activity Create and Object Follow -func (p *processor) processCreateFollowRequestFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { +func (p *Processor) processCreateFollowRequestFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { followRequest, ok := federatorMsg.GTSModel.(*gtsmodel.FollowRequest) if !ok { return errors.New("incomingFollowRequest was not parseable as *gtsmodel.FollowRequest") @@ -280,7 +280,7 @@ func (p *processor) processCreateFollowRequestFromFederator(ctx context.Context, } // processCreateAnnounceFromFederator handles Activity Create and Object Announce -func (p *processor) processCreateAnnounceFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { +func (p *Processor) processCreateAnnounceFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { incomingAnnounce, ok := federatorMsg.GTSModel.(*gtsmodel.Status) if !ok { return errors.New("announce was not parseable as *gtsmodel.Status") @@ -340,7 +340,7 @@ func (p *processor) processCreateAnnounceFromFederator(ctx context.Context, fede } // processCreateBlockFromFederator handles Activity Create and Object Block -func (p *processor) processCreateBlockFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { +func (p *Processor) processCreateBlockFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { block, ok := federatorMsg.GTSModel.(*gtsmodel.Block) if !ok { return errors.New("block was not parseable as *gtsmodel.Block") @@ -359,7 +359,7 @@ func (p *processor) processCreateBlockFromFederator(ctx context.Context, federat return nil } -func (p *processor) processCreateFlagFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { +func (p *Processor) processCreateFlagFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { // TODO: handle side effects of flag creation: // - send email to admins // - notify admins @@ -367,7 +367,7 @@ func (p *processor) processCreateFlagFromFederator(ctx context.Context, federato } // processUpdateAccountFromFederator handles Activity Update and Object Profile -func (p *processor) processUpdateAccountFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { +func (p *Processor) processUpdateAccountFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { incomingAccount, ok := federatorMsg.GTSModel.(*gtsmodel.Account) if !ok { return errors.New("profile was not parseable as *gtsmodel.Account") @@ -391,7 +391,7 @@ func (p *processor) processUpdateAccountFromFederator(ctx context.Context, feder } // processDeleteStatusFromFederator handles Activity Delete and Object Note -func (p *processor) processDeleteStatusFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { +func (p *Processor) processDeleteStatusFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { statusToDelete, ok := federatorMsg.GTSModel.(*gtsmodel.Status) if !ok { return errors.New("note was not parseable as *gtsmodel.Status") @@ -405,11 +405,11 @@ func (p *processor) processDeleteStatusFromFederator(ctx context.Context, federa } // processDeleteAccountFromFederator handles Activity Delete and Object Profile -func (p *processor) processDeleteAccountFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { +func (p *Processor) processDeleteAccountFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { account, ok := federatorMsg.GTSModel.(*gtsmodel.Account) if !ok { return errors.New("account delete was not parseable as *gtsmodel.Account") } - return p.accountProcessor.Delete(ctx, account, account.ID) + return p.account.Delete(ctx, account, account.ID) } diff --git a/internal/processing/fromfederator_test.go b/internal/processing/fromfederator_test.go index 94e4032e8..9999c7054 100644 --- a/internal/processing/fromfederator_test.go +++ b/internal/processing/fromfederator_test.go @@ -117,7 +117,7 @@ func (suite *FromFederatorTestSuite) TestProcessReplyMention() { Likeable: testrig.FalseBool(), } - wssStream, errWithCode := suite.processor.OpenStreamForAccount(context.Background(), repliedAccount, stream.TimelineHome) + wssStream, errWithCode := suite.processor.Stream().Open(context.Background(), repliedAccount, stream.TimelineHome) suite.NoError(errWithCode) // id the status based on the time it was created @@ -183,7 +183,7 @@ func (suite *FromFederatorTestSuite) TestProcessFave() { favedStatus := suite.testStatuses["local_account_1_status_1"] favingAccount := suite.testAccounts["remote_account_1"] - wssStream, errWithCode := suite.processor.OpenStreamForAccount(context.Background(), favedAccount, stream.TimelineNotifications) + wssStream, errWithCode := suite.processor.Stream().Open(context.Background(), favedAccount, stream.TimelineNotifications) suite.NoError(errWithCode) fave := >smodel.StatusFave{ @@ -256,7 +256,7 @@ func (suite *FromFederatorTestSuite) TestProcessFaveWithDifferentReceivingAccoun favedStatus := suite.testStatuses["local_account_1_status_1"] favingAccount := suite.testAccounts["remote_account_1"] - wssStream, errWithCode := suite.processor.OpenStreamForAccount(context.Background(), receivingAccount, stream.TimelineHome) + wssStream, errWithCode := suite.processor.Stream().Open(context.Background(), receivingAccount, stream.TimelineHome) suite.NoError(errWithCode) fave := >smodel.StatusFave{ @@ -400,7 +400,7 @@ func (suite *FromFederatorTestSuite) TestProcessFollowRequestLocked() { // target is a locked account targetAccount := suite.testAccounts["local_account_2"] - wssStream, errWithCode := suite.processor.OpenStreamForAccount(context.Background(), targetAccount, stream.TimelineHome) + wssStream, errWithCode := suite.processor.Stream().Open(context.Background(), targetAccount, stream.TimelineHome) suite.NoError(errWithCode) // put the follow request in the database as though it had passed through the federating db already @@ -457,7 +457,7 @@ func (suite *FromFederatorTestSuite) TestProcessFollowRequestUnlocked() { // target is an unlocked account targetAccount := suite.testAccounts["local_account_1"] - wssStream, errWithCode := suite.processor.OpenStreamForAccount(context.Background(), targetAccount, stream.TimelineHome) + wssStream, errWithCode := suite.processor.Stream().Open(context.Background(), targetAccount, stream.TimelineHome) suite.NoError(errWithCode) // put the follow request in the database as though it had passed through the federating db already diff --git a/internal/processing/instance.go b/internal/processing/instance.go index 8cc7eaa64..c3dc4dcea 100644 --- a/internal/processing/instance.go +++ b/internal/processing/instance.go @@ -33,7 +33,7 @@ "github.com/superseriousbusiness/gotosocial/internal/validate" ) -func (p *processor) getThisInstance(ctx context.Context) (*gtsmodel.Instance, error) { +func (p *Processor) getThisInstance(ctx context.Context) (*gtsmodel.Instance, error) { i := >smodel.Instance{} if err := p.db.GetWhere(ctx, []db.Where{{Key: "domain", Value: config.GetHost()}}, i); err != nil { return nil, err @@ -41,7 +41,7 @@ func (p *processor) getThisInstance(ctx context.Context) (*gtsmodel.Instance, er return i, nil } -func (p *processor) InstanceGetV1(ctx context.Context) (*apimodel.InstanceV1, gtserror.WithCode) { +func (p *Processor) InstanceGetV1(ctx context.Context) (*apimodel.InstanceV1, gtserror.WithCode) { i, err := p.getThisInstance(ctx) if err != nil { return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error fetching instance: %s", err)) @@ -55,7 +55,7 @@ func (p *processor) InstanceGetV1(ctx context.Context) (*apimodel.InstanceV1, gt return ai, nil } -func (p *processor) InstanceGetV2(ctx context.Context) (*apimodel.InstanceV2, gtserror.WithCode) { +func (p *Processor) InstanceGetV2(ctx context.Context) (*apimodel.InstanceV2, gtserror.WithCode) { i, err := p.getThisInstance(ctx) if err != nil { return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error fetching instance: %s", err)) @@ -69,7 +69,7 @@ func (p *processor) InstanceGetV2(ctx context.Context) (*apimodel.InstanceV2, gt return ai, nil } -func (p *processor) InstancePeersGet(ctx context.Context, includeSuspended bool, includeOpen bool, flat bool) (interface{}, gtserror.WithCode) { +func (p *Processor) InstancePeersGet(ctx context.Context, includeSuspended bool, includeOpen bool, flat bool) (interface{}, gtserror.WithCode) { domains := []*apimodel.Domain{} if includeOpen { @@ -120,7 +120,7 @@ func (p *processor) InstancePeersGet(ctx context.Context, includeSuspended bool, return domains, nil } -func (p *processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSettingsUpdateRequest) (*apimodel.InstanceV1, gtserror.WithCode) { +func (p *Processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSettingsUpdateRequest) (*apimodel.InstanceV1, gtserror.WithCode) { // fetch the instance entry from the db for processing i := >smodel.Instance{} host := config.GetHost() @@ -223,7 +223,7 @@ func (p *processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe if form.Avatar != nil && form.Avatar.Size != 0 { // process instance avatar image + description - avatarInfo, err := p.accountProcessor.UpdateAvatar(ctx, form.Avatar, form.AvatarDescription, ia.ID) + avatarInfo, err := p.account.UpdateAvatar(ctx, form.Avatar, form.AvatarDescription, ia.ID) if err != nil { return nil, gtserror.NewErrorBadRequest(err, "error processing avatar") } @@ -240,7 +240,7 @@ func (p *processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe if form.Header != nil && form.Header.Size != 0 { // process instance header image - headerInfo, err := p.accountProcessor.UpdateHeader(ctx, form.Header, nil, ia.ID) + headerInfo, err := p.account.UpdateHeader(ctx, form.Header, nil, ia.ID) if err != nil { return nil, gtserror.NewErrorBadRequest(err, "error processing header") } diff --git a/internal/processing/media.go b/internal/processing/media.go deleted file mode 100644 index ac1d30bd1..000000000 --- a/internal/processing/media.go +++ /dev/null @@ -1,47 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package processing - -import ( - "context" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -func (p *processor) MediaCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.AttachmentRequest) (*apimodel.Attachment, gtserror.WithCode) { - return p.mediaProcessor.Create(ctx, authed.Account, form) -} - -func (p *processor) MediaGet(ctx context.Context, authed *oauth.Auth, mediaAttachmentID string) (*apimodel.Attachment, gtserror.WithCode) { - return p.mediaProcessor.GetMedia(ctx, authed.Account, mediaAttachmentID) -} - -func (p *processor) MediaUpdate(ctx context.Context, authed *oauth.Auth, mediaAttachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode) { - return p.mediaProcessor.Update(ctx, authed.Account, mediaAttachmentID, form) -} - -func (p *processor) FileGet(ctx context.Context, authed *oauth.Auth, form *apimodel.GetContentRequestForm) (*apimodel.Content, gtserror.WithCode) { - return p.mediaProcessor.GetFile(ctx, authed.Account, form) -} - -func (p *processor) CustomEmojisGet(ctx context.Context) ([]*apimodel.Emoji, gtserror.WithCode) { - return p.mediaProcessor.GetCustomEmojis(ctx) -} diff --git a/internal/processing/media/create.go b/internal/processing/media/create.go index 494434acb..dc26989f8 100644 --- a/internal/processing/media/create.go +++ b/internal/processing/media/create.go @@ -29,7 +29,8 @@ "github.com/superseriousbusiness/gotosocial/internal/media" ) -func (p *processor) Create(ctx context.Context, account *gtsmodel.Account, form *apimodel.AttachmentRequest) (*apimodel.Attachment, gtserror.WithCode) { +// Create creates a new media attachment belonging to the given account, using the request form. +func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form *apimodel.AttachmentRequest) (*apimodel.Attachment, gtserror.WithCode) { data := func(innerCtx context.Context) (io.ReadCloser, int64, error) { f, err := form.File.Open() return f, form.File.Size, err diff --git a/internal/processing/media/delete.go b/internal/processing/media/delete.go index 31e733bc7..6507fcae4 100644 --- a/internal/processing/media/delete.go +++ b/internal/processing/media/delete.go @@ -11,7 +11,8 @@ "github.com/superseriousbusiness/gotosocial/internal/gtserror" ) -func (p *processor) Delete(ctx context.Context, mediaAttachmentID string) gtserror.WithCode { +// Delete deletes the media attachment with the given ID, including all files pertaining to that attachment. +func (p *Processor) Delete(ctx context.Context, mediaAttachmentID string) gtserror.WithCode { attachment, err := p.db.GetAttachmentByID(ctx, mediaAttachmentID) if err != nil { if err == db.ErrNoEntries { diff --git a/internal/processing/media/getemoji.go b/internal/processing/media/getemoji.go index 4b1e76772..4c0ce9930 100644 --- a/internal/processing/media/getemoji.go +++ b/internal/processing/media/getemoji.go @@ -28,7 +28,9 @@ "github.com/superseriousbusiness/gotosocial/internal/log" ) -func (p *processor) GetCustomEmojis(ctx context.Context) ([]*apimodel.Emoji, gtserror.WithCode) { +// GetCustomEmojis returns a list of all useable local custom emojis stored on this instance. +// 'useable' in this context means visible and picker, and not disabled. +func (p *Processor) GetCustomEmojis(ctx context.Context) ([]*apimodel.Emoji, gtserror.WithCode) { emojis, err := p.db.GetUseableEmojis(ctx) if err != nil { if err != db.ErrNoEntries { diff --git a/internal/processing/media/getfile.go b/internal/processing/media/getfile.go index 41250b3f5..2a4ef2097 100644 --- a/internal/processing/media/getfile.go +++ b/internal/processing/media/getfile.go @@ -33,42 +33,15 @@ "github.com/superseriousbusiness/gotosocial/internal/uris" ) -// ParseMediaType converts s to a recognized MediaType, or returns an error if unrecognized -func parseMediaType(s string) (media.Type, error) { - switch s { - case string(media.TypeAttachment): - return media.TypeAttachment, nil - case string(media.TypeHeader): - return media.TypeHeader, nil - case string(media.TypeAvatar): - return media.TypeAvatar, nil - case string(media.TypeEmoji): - return media.TypeEmoji, nil - } - return "", fmt.Errorf("%s not a recognized media.Type", s) -} - -// ParseMediaSize converts s to a recognized MediaSize, or returns an error if unrecognized -func parseMediaSize(s string) (media.Size, error) { - switch s { - case string(media.SizeSmall): - return media.SizeSmall, nil - case string(media.SizeOriginal): - return media.SizeOriginal, nil - case string(media.SizeStatic): - return media.SizeStatic, nil - } - return "", fmt.Errorf("%s not a recognized media.Size", s) -} - -func (p *processor) GetFile(ctx context.Context, requestingAccount *gtsmodel.Account, form *apimodel.GetContentRequestForm) (*apimodel.Content, gtserror.WithCode) { +// GetFile retrieves a file from storage and streams it back to the caller via an io.reader embedded in *apimodel.Content. +func (p *Processor) GetFile(ctx context.Context, requestingAccount *gtsmodel.Account, form *apimodel.GetContentRequestForm) (*apimodel.Content, gtserror.WithCode) { // parse the form fields - mediaSize, err := parseMediaSize(form.MediaSize) + mediaSize, err := parseSize(form.MediaSize) if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("media size %s not valid", form.MediaSize)) } - mediaType, err := parseMediaType(form.MediaType) + mediaType, err := parseType(form.MediaType) if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("media type %s not valid", form.MediaType)) } @@ -112,7 +85,37 @@ func (p *processor) GetFile(ctx context.Context, requestingAccount *gtsmodel.Acc } } -func (p *processor) getAttachmentContent(ctx context.Context, requestingAccount *gtsmodel.Account, wantedMediaID string, owningAccountID string, mediaSize media.Size) (*apimodel.Content, gtserror.WithCode) { +/* + UTIL FUNCTIONS +*/ + +func parseType(s string) (media.Type, error) { + switch s { + case string(media.TypeAttachment): + return media.TypeAttachment, nil + case string(media.TypeHeader): + return media.TypeHeader, nil + case string(media.TypeAvatar): + return media.TypeAvatar, nil + case string(media.TypeEmoji): + return media.TypeEmoji, nil + } + return "", fmt.Errorf("%s not a recognized media.Type", s) +} + +func parseSize(s string) (media.Size, error) { + switch s { + case string(media.SizeSmall): + return media.SizeSmall, nil + case string(media.SizeOriginal): + return media.SizeOriginal, nil + case string(media.SizeStatic): + return media.SizeStatic, nil + } + return "", fmt.Errorf("%s not a recognized media.Size", s) +} + +func (p *Processor) getAttachmentContent(ctx context.Context, requestingAccount *gtsmodel.Account, wantedMediaID string, owningAccountID string, mediaSize media.Size) (*apimodel.Content, gtserror.WithCode) { // retrieve attachment from the database and do basic checks on it a, err := p.db.GetAttachmentByID(ctx, wantedMediaID) if err != nil { @@ -196,7 +199,7 @@ func (p *processor) getAttachmentContent(ctx context.Context, requestingAccount return p.retrieveFromStorage(ctx, storagePath, attachmentContent) } -func (p *processor) getEmojiContent(ctx context.Context, fileName string, owningAccountID string, emojiSize media.Size) (*apimodel.Content, gtserror.WithCode) { +func (p *Processor) getEmojiContent(ctx context.Context, fileName string, owningAccountID string, emojiSize media.Size) (*apimodel.Content, gtserror.WithCode) { emojiContent := &apimodel.Content{} var storagePath string @@ -231,7 +234,7 @@ func (p *processor) getEmojiContent(ctx context.Context, fileName string, owning return p.retrieveFromStorage(ctx, storagePath, emojiContent) } -func (p *processor) retrieveFromStorage(ctx context.Context, storagePath string, content *apimodel.Content) (*apimodel.Content, gtserror.WithCode) { +func (p *Processor) retrieveFromStorage(ctx context.Context, storagePath string, content *apimodel.Content) (*apimodel.Content, gtserror.WithCode) { // If running on S3 storage with proxying disabled then // just fetch a pre-signed URL instead of serving the content. if url := p.storage.URL(ctx, storagePath); url != nil { diff --git a/internal/processing/media/getmedia.go b/internal/processing/media/getmedia.go index 1ae3770f6..03d5ba770 100644 --- a/internal/processing/media/getmedia.go +++ b/internal/processing/media/getmedia.go @@ -29,7 +29,7 @@ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) -func (p *processor) GetMedia(ctx context.Context, account *gtsmodel.Account, mediaAttachmentID string) (*apimodel.Attachment, gtserror.WithCode) { +func (p *Processor) Get(ctx context.Context, account *gtsmodel.Account, mediaAttachmentID string) (*apimodel.Attachment, gtserror.WithCode) { attachment, err := p.db.GetAttachmentByID(ctx, mediaAttachmentID) if err != nil { if err == db.ErrNoEntries { diff --git a/internal/processing/media/media.go b/internal/processing/media/media.go index 2456ebcfa..ca95e276f 100644 --- a/internal/processing/media/media.go +++ b/internal/processing/media/media.go @@ -19,35 +19,14 @@ package media import ( - "context" - - 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/storage" "github.com/superseriousbusiness/gotosocial/internal/transport" "github.com/superseriousbusiness/gotosocial/internal/typeutils" ) -// Processor wraps a bunch of functions for processing media actions. -type Processor interface { - // Create creates a new media attachment belonging to the given account, using the request form. - Create(ctx context.Context, account *gtsmodel.Account, form *apimodel.AttachmentRequest) (*apimodel.Attachment, gtserror.WithCode) - // Delete deletes the media attachment with the given ID, including all files pertaining to that attachment. - Delete(ctx context.Context, mediaAttachmentID string) gtserror.WithCode - // Unattach unattaches the media attachment with the given ID from any statuses it was attached to, making it available - // for reattachment again. - Unattach(ctx context.Context, account *gtsmodel.Account, mediaAttachmentID string) (*apimodel.Attachment, gtserror.WithCode) - // GetFile retrieves a file from storage and streams it back to the caller via an io.reader embedded in *apimodel.Content. - GetFile(ctx context.Context, account *gtsmodel.Account, form *apimodel.GetContentRequestForm) (*apimodel.Content, gtserror.WithCode) - GetCustomEmojis(ctx context.Context) ([]*apimodel.Emoji, gtserror.WithCode) - GetMedia(ctx context.Context, account *gtsmodel.Account, mediaAttachmentID string) (*apimodel.Attachment, gtserror.WithCode) - Update(ctx context.Context, account *gtsmodel.Account, mediaAttachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode) -} - -type processor struct { +type Processor struct { tc typeutils.TypeConverter mediaManager media.Manager transportController transport.Controller @@ -57,7 +36,7 @@ type processor struct { // New returns a new media processor. func New(db db.DB, tc typeutils.TypeConverter, mediaManager media.Manager, transportController transport.Controller, storage *storage.Driver) Processor { - return &processor{ + return Processor{ tc: tc, mediaManager: mediaManager, transportController: transportController, diff --git a/internal/processing/media/unattach.go b/internal/processing/media/unattach.go index 421b64b2f..816b5134e 100644 --- a/internal/processing/media/unattach.go +++ b/internal/processing/media/unattach.go @@ -30,7 +30,9 @@ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) -func (p *processor) Unattach(ctx context.Context, account *gtsmodel.Account, mediaAttachmentID string) (*apimodel.Attachment, gtserror.WithCode) { +// Unattach unattaches the media attachment with the given ID from any statuses it was attached to, making it available +// for reattachment again. +func (p *Processor) Unattach(ctx context.Context, account *gtsmodel.Account, mediaAttachmentID string) (*apimodel.Attachment, gtserror.WithCode) { attachment, err := p.db.GetAttachmentByID(ctx, mediaAttachmentID) if err != nil { if err == db.ErrNoEntries { diff --git a/internal/processing/media/update.go b/internal/processing/media/update.go index 17b86aa11..c03df705b 100644 --- a/internal/processing/media/update.go +++ b/internal/processing/media/update.go @@ -30,7 +30,8 @@ "github.com/superseriousbusiness/gotosocial/internal/text" ) -func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, mediaAttachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode) { +// Update updates a media attachment with the given id, using the provided form parameters. +func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, mediaAttachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode) { attachment, err := p.db.GetAttachmentByID(ctx, mediaAttachmentID) if err != nil { if err == db.ErrNoEntries { diff --git a/internal/processing/notification.go b/internal/processing/notification.go index b13ab0ca0..05d0e82ee 100644 --- a/internal/processing/notification.go +++ b/internal/processing/notification.go @@ -28,7 +28,7 @@ "github.com/superseriousbusiness/gotosocial/internal/util" ) -func (p *processor) NotificationsGet(ctx context.Context, authed *oauth.Auth, excludeTypes []string, limit int, maxID string, sinceID string) (*apimodel.PageableResponse, gtserror.WithCode) { +func (p *Processor) NotificationsGet(ctx context.Context, authed *oauth.Auth, excludeTypes []string, limit int, maxID string, sinceID string) (*apimodel.PageableResponse, gtserror.WithCode) { notifs, err := p.db.GetNotifications(ctx, authed.Account.ID, excludeTypes, limit, maxID, sinceID) if err != nil { return nil, gtserror.NewErrorInternalError(err) @@ -71,7 +71,7 @@ func (p *processor) NotificationsGet(ctx context.Context, authed *oauth.Auth, ex }) } -func (p *processor) NotificationsClear(ctx context.Context, authed *oauth.Auth) gtserror.WithCode { +func (p *Processor) NotificationsClear(ctx context.Context, authed *oauth.Auth) gtserror.WithCode { err := p.db.ClearNotifications(ctx, authed.Account.ID) if err != nil { return gtserror.NewErrorInternalError(err) diff --git a/internal/processing/oauth.go b/internal/processing/oauth.go index 3df4824b2..144503bbd 100644 --- a/internal/processing/oauth.go +++ b/internal/processing/oauth.go @@ -25,17 +25,17 @@ "github.com/superseriousbusiness/oauth2/v4" ) -func (p *processor) OAuthHandleAuthorizeRequest(w http.ResponseWriter, r *http.Request) gtserror.WithCode { +func (p *Processor) OAuthHandleAuthorizeRequest(w http.ResponseWriter, r *http.Request) gtserror.WithCode { // todo: some kind of metrics stuff here return p.oauthServer.HandleAuthorizeRequest(w, r) } -func (p *processor) OAuthHandleTokenRequest(r *http.Request) (map[string]interface{}, gtserror.WithCode) { +func (p *Processor) OAuthHandleTokenRequest(r *http.Request) (map[string]interface{}, gtserror.WithCode) { // todo: some kind of metrics stuff here return p.oauthServer.HandleTokenRequest(r) } -func (p *processor) OAuthValidateBearerToken(r *http.Request) (oauth2.TokenInfo, error) { +func (p *Processor) OAuthValidateBearerToken(r *http.Request) (oauth2.TokenInfo, error) { // todo: some kind of metrics stuff here return p.oauthServer.ValidationBearerToken(r) } diff --git a/internal/processing/processor.go b/internal/processing/processor.go index 6ea860c78..07fcdb8b3 100644 --- a/internal/processing/processor.go +++ b/internal/processing/processor.go @@ -19,291 +19,35 @@ package processing import ( - "context" - "net/http" - "net/url" - "time" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/concurrency" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/media" + mm "github.com/superseriousbusiness/gotosocial/internal/media" "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/processing/account" "github.com/superseriousbusiness/gotosocial/internal/processing/admin" - federationProcessor "github.com/superseriousbusiness/gotosocial/internal/processing/federation" - mediaProcessor "github.com/superseriousbusiness/gotosocial/internal/processing/media" + "github.com/superseriousbusiness/gotosocial/internal/processing/fedi" + "github.com/superseriousbusiness/gotosocial/internal/processing/media" "github.com/superseriousbusiness/gotosocial/internal/processing/report" "github.com/superseriousbusiness/gotosocial/internal/processing/status" - "github.com/superseriousbusiness/gotosocial/internal/processing/streaming" + "github.com/superseriousbusiness/gotosocial/internal/processing/stream" "github.com/superseriousbusiness/gotosocial/internal/processing/user" "github.com/superseriousbusiness/gotosocial/internal/storage" - "github.com/superseriousbusiness/gotosocial/internal/stream" "github.com/superseriousbusiness/gotosocial/internal/timeline" "github.com/superseriousbusiness/gotosocial/internal/typeutils" "github.com/superseriousbusiness/gotosocial/internal/visibility" - "github.com/superseriousbusiness/oauth2/v4" ) -// Processor should be passed to api modules (see internal/apimodule/...). It is used for -// passing messages back and forth from the client API and the federating interface, via channels. -// It also contains logic for filtering which messages should end up where. -// It is designed to be used asynchronously: the client API and the federating API should just be able to -// fire messages into the processor and not wait for a reply before proceeding with other work. This allows -// for clean distribution of messages without slowing down the client API and harming the user experience. -type Processor interface { - // Start starts the Processor, reading from its channels and passing messages back and forth. - Start() error - // Stop stops the processor cleanly, finishing handling any remaining messages before closing down. - Stop() error - // ProcessFromClientAPI processes one message coming from the clientAPI channel, and triggers appropriate side effects. - ProcessFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error - // ProcessFromFederator processes one message coming from the federator channel, and triggers appropriate side effects. - ProcessFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error - - /* - CLIENT API-FACING PROCESSING FUNCTIONS - These functions are intended to be called when the API client needs an immediate (ie., synchronous) reply - to an HTTP request. As such, they will only do the bare-minimum of work necessary to give a properly - formed reply. For more intensive (and time-consuming) calls, where you don't require an immediate - response, pass work to the processor using a channel instead. - */ - - // AccountCreate processes the given form for creating a new account, returning an oauth token for that account if successful. - AccountCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.AccountCreateRequest) (*apimodel.Token, gtserror.WithCode) - // AccountDeleteLocal processes the delete of a LOCAL account using the given form. - AccountDeleteLocal(ctx context.Context, authed *oauth.Auth, form *apimodel.AccountDeleteRequest) gtserror.WithCode - // AccountGet processes the given request for account information. - AccountGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Account, gtserror.WithCode) - // AccountGet processes the given request for account information. - AccountGetLocalByUsername(ctx context.Context, authed *oauth.Auth, username string) (*apimodel.Account, gtserror.WithCode) - AccountGetCustomCSSForUsername(ctx context.Context, username string) (string, gtserror.WithCode) - // AccountGetRSSFeedForUsername returns a function to get the RSS feed of latest posts for given local account username. - // This function should only be called if necessary: the given lastModified time can be used to check this. - // Will return 404 if an rss feed for that user is not available, or a different error if something else goes wrong. - AccountGetRSSFeedForUsername(ctx context.Context, username string) (func() (string, gtserror.WithCode), time.Time, gtserror.WithCode) - // AccountUpdate processes the update of an account with the given form - AccountUpdate(ctx context.Context, authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode) - // AccountStatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for - // the account given in authed. - AccountStatusesGet(ctx context.Context, authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) (*apimodel.PageableResponse, gtserror.WithCode) - // AccountWebStatusesGet fetches a number of statuses (in descending order) from the given account. It selects only - // statuses which are suitable for showing on the public web profile of an account. - AccountWebStatusesGet(ctx context.Context, targetAccountID string, maxID string) (*apimodel.PageableResponse, gtserror.WithCode) - // AccountFollowersGet fetches a list of the target account's followers. - AccountFollowersGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) - // AccountFollowingGet fetches a list of the accounts that target account is following. - AccountFollowingGet(ctx context.Context, 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(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) - // AccountFollowCreate handles a follow request to an account, either remote or local. - AccountFollowCreate(ctx context.Context, 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(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) - // AccountBlockCreate handles the creation of a block from authed account to target account, either remote or local. - AccountBlockCreate(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) - // AccountBlockRemove handles the removal of a block from authed account to target account, either remote or local. - AccountBlockRemove(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) - - // AdminAccountAction handles the creation/execution of an action on an account. - AdminAccountAction(ctx context.Context, authed *oauth.Auth, form *apimodel.AdminAccountActionRequest) gtserror.WithCode - // AdminEmojiCreate handles the creation of a new instance emoji by an admin, using the given form. - AdminEmojiCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, gtserror.WithCode) - // AdminEmojisGet allows admins to view emojis based on various filters. - AdminEmojisGet(ctx context.Context, authed *oauth.Auth, domain string, includeDisabled bool, includeEnabled bool, shortcode string, maxShortcodeDomain string, minShortcodeDomain string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) - // AdminEmojiGet returns the admin view of an emoji with the given ID - AdminEmojiGet(ctx context.Context, authed *oauth.Auth, id string) (*apimodel.AdminEmoji, gtserror.WithCode) - // AdminEmojiDelete deletes one *local* emoji with the given key. Remote emojis will not be deleted this way. - // Only admin users in good standing should be allowed to access this function -- check this before calling it. - AdminEmojiDelete(ctx context.Context, authed *oauth.Auth, id string) (*apimodel.AdminEmoji, gtserror.WithCode) - // AdminEmojiUpdate updates one local or remote emoji with the given key. - // Only admin users in good standing should be allowed to access this function -- check this before calling it. - AdminEmojiUpdate(ctx context.Context, id string, form *apimodel.EmojiUpdateRequest) (*apimodel.AdminEmoji, gtserror.WithCode) - // AdminEmojiCategoriesGet gets a list of all existing emoji categories. - AdminEmojiCategoriesGet(ctx context.Context) ([]*apimodel.EmojiCategory, gtserror.WithCode) - // AdminDomainBlockCreate handles the creation of a new domain block by an admin, using the given form. - AdminDomainBlockCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.DomainBlockCreateRequest) (*apimodel.DomainBlock, gtserror.WithCode) - // AdminDomainBlocksImport handles the import of multiple domain blocks by an admin, using the given form. - AdminDomainBlocksImport(ctx context.Context, authed *oauth.Auth, form *apimodel.DomainBlockCreateRequest) ([]*apimodel.DomainBlock, gtserror.WithCode) - // AdminDomainBlocksGet returns a list of currently blocked domains. - AdminDomainBlocksGet(ctx context.Context, authed *oauth.Auth, export bool) ([]*apimodel.DomainBlock, gtserror.WithCode) - // AdminDomainBlockGet returns one domain block, specified by ID. - AdminDomainBlockGet(ctx context.Context, authed *oauth.Auth, id string, export bool) (*apimodel.DomainBlock, gtserror.WithCode) - // AdminDomainBlockDelete deletes one domain block, specified by ID, returning the deleted domain block. - AdminDomainBlockDelete(ctx context.Context, authed *oauth.Auth, id string) (*apimodel.DomainBlock, gtserror.WithCode) - // AdminMediaRemotePrune triggers a prune of remote media according to the given number of mediaRemoteCacheDays - AdminMediaPrune(ctx context.Context, mediaRemoteCacheDays int) gtserror.WithCode - // AdminMediaRefetch triggers a refetch of remote media for the given domain (or all if domain is empty). - AdminMediaRefetch(ctx context.Context, authed *oauth.Auth, domain string) gtserror.WithCode - // AdminReportsGet returns a list of user moderation reports. - AdminReportsGet(ctx context.Context, authed *oauth.Auth, resolved *bool, accountID string, targetAccountID string, maxID string, sinceID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) - // AdminReportGet returns a single user moderation report, specified by id. - AdminReportGet(ctx context.Context, authed *oauth.Auth, id string) (*apimodel.AdminReport, gtserror.WithCode) - // AdminReportResolve marks a single user moderation report as resolved, with the given id. - // actionTakenComment is optional: if set, this will be stored as a comment on the action taken. - AdminReportResolve(ctx context.Context, authed *oauth.Auth, id string, actionTakenComment *string) (*apimodel.AdminReport, gtserror.WithCode) - - // AppCreate processes the creation of a new API application - AppCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.ApplicationCreateRequest) (*apimodel.Application, gtserror.WithCode) - - // BlocksGet returns a list of accounts blocked by the requesting account. - BlocksGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, limit int) (*apimodel.BlocksResponse, gtserror.WithCode) - - // CustomEmojisGet returns an array of info about the custom emojis on this server - CustomEmojisGet(ctx context.Context) ([]*apimodel.Emoji, gtserror.WithCode) - - // BookmarksGet returns a pageable response of statuses that have been bookmarked - BookmarksGet(ctx context.Context, authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) - - // FileGet handles the fetching of a media attachment file via the fileserver. - FileGet(ctx context.Context, authed *oauth.Auth, form *apimodel.GetContentRequestForm) (*apimodel.Content, gtserror.WithCode) - - // FollowRequestsGet handles the getting of the authed account's incoming follow requests - FollowRequestsGet(ctx context.Context, auth *oauth.Auth) ([]apimodel.Account, gtserror.WithCode) - // FollowRequestAccept handles the acceptance of a follow request from the given account ID. - FollowRequestAccept(ctx context.Context, auth *oauth.Auth, accountID string) (*apimodel.Relationship, gtserror.WithCode) - // FollowRequestReject handles the rejection of a follow request from the given account ID. - FollowRequestReject(ctx context.Context, auth *oauth.Auth, accountID string) (*apimodel.Relationship, gtserror.WithCode) - - // InstanceGetV1 retrieves instance information for serving at api/v1/instance - InstanceGetV1(ctx context.Context) (*apimodel.InstanceV1, gtserror.WithCode) - // InstanceGetV1 retrieves instance information for serving at api/v2/instance - InstanceGetV2(ctx context.Context) (*apimodel.InstanceV2, gtserror.WithCode) - InstancePeersGet(ctx context.Context, includeSuspended bool, includeOpen bool, flat bool) (interface{}, gtserror.WithCode) - // InstancePatch updates this instance according to the given form. - // - // It should already be ascertained that the requesting account is authenticated and an admin. - InstancePatch(ctx context.Context, form *apimodel.InstanceSettingsUpdateRequest) (*apimodel.InstanceV1, gtserror.WithCode) - - // MediaCreate handles the creation of a media attachment, using the given form. - MediaCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.AttachmentRequest) (*apimodel.Attachment, gtserror.WithCode) - // MediaGet handles the GET of a media attachment with the given ID - MediaGet(ctx context.Context, authed *oauth.Auth, attachmentID string) (*apimodel.Attachment, gtserror.WithCode) - // MediaUpdate handles the PUT of a media attachment with the given ID and form - MediaUpdate(ctx context.Context, authed *oauth.Auth, attachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode) - - // NotificationsGet - NotificationsGet(ctx context.Context, authed *oauth.Auth, excludeTypes []string, limit int, maxID string, sinceID string) (*apimodel.PageableResponse, gtserror.WithCode) - // NotificationsClear - NotificationsClear(ctx context.Context, authed *oauth.Auth) gtserror.WithCode - - OAuthHandleTokenRequest(r *http.Request) (map[string]interface{}, gtserror.WithCode) - OAuthHandleAuthorizeRequest(w http.ResponseWriter, r *http.Request) gtserror.WithCode - OAuthValidateBearerToken(r *http.Request) (oauth2.TokenInfo, error) - - // SearchGet performs a search with the given params, resolving/dereferencing remotely as desired - SearchGet(ctx context.Context, 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(ctx context.Context, authed *oauth.Auth, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, gtserror.WithCode) - // StatusDelete processes the delete of a given status, returning the deleted status if the delete goes through. - StatusDelete(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) - // StatusFave processes the faving of a given status, returning the updated status if the fave goes through. - StatusFave(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) - // StatusBoost processes the boost/reblog of a given status, returning the newly-created boost if all is well. - StatusBoost(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) - // StatusUnboost processes the unboost/unreblog of a given status, returning the status if all is well. - StatusUnboost(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, authed *oauth.Auth, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) - // StatusGet gets the given status, taking account of privacy settings and blocks etc. - StatusGet(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) - // StatusUnfave processes the unfaving of a given status, returning the updated status if the fave goes through. - StatusUnfave(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) - // StatusGetContext returns the context (previous and following posts) from the given status ID - StatusGetContext(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Context, gtserror.WithCode) - // StatusBookmark process a bookmark for a status - StatusBookmark(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) - // StatusUnbookmark removes a bookmark for a status - StatusUnbookmark(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) - - // HomeTimelineGet returns statuses from the home timeline, with the given filters/parameters. - HomeTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.PageableResponse, gtserror.WithCode) - // PublicTimelineGet returns statuses from the public/local timeline, with the given filters/parameters. - PublicTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.PageableResponse, gtserror.WithCode) - // FavedTimelineGet returns faved statuses, with the given filters/parameters. - FavedTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) - - // AuthorizeStreamingRequest returns a gotosocial account in exchange for an access token, or an error if the given token is not valid. - AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, gtserror.WithCode) - // OpenStreamForAccount opens a new stream for the given account, with the given stream type. - OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*stream.Stream, gtserror.WithCode) - - // UserChangePassword changes the password for the given user, with the given form. - UserChangePassword(ctx context.Context, authed *oauth.Auth, form *apimodel.PasswordChangeRequest) gtserror.WithCode - // UserConfirmEmail confirms an email address using the given token. - // The user belonging to the confirmed email is also returned. - UserConfirmEmail(ctx context.Context, token string) (*gtsmodel.User, gtserror.WithCode) - - // ReportsGet returns reports created by the given user. - ReportsGet(ctx context.Context, authed *oauth.Auth, resolved *bool, targetAccountID string, maxID string, sinceID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) - // ReportGet returns one report created by the given user. - ReportGet(ctx context.Context, authed *oauth.Auth, id string) (*apimodel.Report, gtserror.WithCode) - // ReportCreate creates a new report using the given account and form. - ReportCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.ReportCreateRequest) (*apimodel.Report, gtserror.WithCode) - - /* - FEDERATION API-FACING PROCESSING FUNCTIONS - These functions are intended to be called when the federating client needs an immediate (ie., synchronous) reply - to an HTTP request. As such, they will only do the bare-minimum of work necessary to give a properly - formed reply. For more intensive (and time-consuming) calls, where you don't require an immediate - response, pass work to the processor using a channel instead. - */ - - // 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(ctx context.Context, requestedUsername string, requestURL *url.URL) (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(ctx context.Context, requestedUsername string, requestURL *url.URL) (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(ctx context.Context, requestedUsername string, requestURL *url.URL) (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(ctx context.Context, requestedUsername string, requestedStatusID string, requestURL *url.URL) (interface{}, gtserror.WithCode) - // GetFediStatus handles the getting of a fedi/activitypub representation of replies to a status, performing appropriate - // authentication before returning a JSON serializable interface to the caller. - GetFediStatusReplies(ctx context.Context, requestedUsername string, requestedStatusID string, page bool, onlyOtherAccounts bool, minID string, requestURL *url.URL) (interface{}, gtserror.WithCode) - // GetFediOutbox returns the public outbox of the requested user, with the given parameters. - GetFediOutbox(ctx context.Context, requestedUsername string, page bool, maxID string, minID string, requestURL *url.URL) (interface{}, gtserror.WithCode) - // GetFediEmoji returns the AP representation of an emoji on this instance. - GetFediEmoji(ctx context.Context, requestedEmojiID string, requestURL *url.URL) (interface{}, gtserror.WithCode) - // GetWebfingerAccount handles the GET for a webfinger resource. Most commonly, it will be used for returning account lookups. - GetWebfingerAccount(ctx context.Context, requestedUsername string) (*apimodel.WellKnownResponse, gtserror.WithCode) - // GetNodeInfoRel returns a well known response giving the path to node info. - GetNodeInfoRel(ctx context.Context) (*apimodel.WellKnownResponse, gtserror.WithCode) - // GetNodeInfo returns a node info struct in response to a node info request. - GetNodeInfo(ctx context.Context) (*apimodel.Nodeinfo, gtserror.WithCode) - // InboxPost handles POST requests to a user's inbox for new activitypub messages. - // - // InboxPost returns true if the request was handled as an ActivityPub POST to an actor's inbox. - // If false, the request was not an ActivityPub request and may still be handled by the caller in another way, such as serving a web page. - // - // If the error is nil, then the ResponseWriter's headers and response has already been written. If a non-nil error is returned, then no response has been written. - // - // If the Actor was constructed with the Federated Protocol enabled, side effects will occur. - // - // If the Federated Protocol is not enabled, writes the http.StatusMethodNotAllowed status code in the response. No side effects occur. - InboxPost(ctx context.Context, w http.ResponseWriter, r *http.Request) (bool, error) -} - -// processor just implements the Processor interface -type processor struct { +type Processor struct { clientWorker *concurrency.WorkerPool[messages.FromClientAPI] fedWorker *concurrency.WorkerPool[messages.FromFederator] federator federation.Federator tc typeutils.TypeConverter oauthServer oauth.Server - mediaManager media.Manager + mediaManager mm.Manager storage *storage.Driver statusTimelines timeline.Manager db db.DB @@ -313,14 +57,46 @@ type processor struct { SUB-PROCESSORS */ - accountProcessor account.Processor - adminProcessor admin.Processor - statusProcessor status.Processor - streamingProcessor streaming.Processor - mediaProcessor mediaProcessor.Processor - userProcessor user.Processor - federationProcessor federationProcessor.Processor - reportProcessor report.Processor + account account.Processor + admin admin.Processor + fedi fedi.Processor + media media.Processor + report report.Processor + status status.Processor + stream stream.Processor + user user.Processor +} + +func (p *Processor) Account() *account.Processor { + return &p.account +} + +func (p *Processor) Admin() *admin.Processor { + return &p.admin +} + +func (p *Processor) Fedi() *fedi.Processor { + return &p.fedi +} + +func (p *Processor) Media() *media.Processor { + return &p.media +} + +func (p *Processor) Report() *report.Processor { + return &p.report +} + +func (p *Processor) Status() *status.Processor { + return &p.status +} + +func (p *Processor) Stream() *stream.Processor { + return &p.stream +} + +func (p *Processor) User() *user.Processor { + return &p.user } // NewProcessor returns a new Processor. @@ -328,26 +104,18 @@ func NewProcessor( tc typeutils.TypeConverter, federator federation.Federator, oauthServer oauth.Server, - mediaManager media.Manager, + mediaManager mm.Manager, storage *storage.Driver, db db.DB, emailSender email.Sender, clientWorker *concurrency.WorkerPool[messages.FromClientAPI], fedWorker *concurrency.WorkerPool[messages.FromFederator], -) Processor { +) *Processor { parseMentionFunc := GetParseMentionFunc(db, federator) - statusProcessor := status.New(db, tc, clientWorker, parseMentionFunc) - streamingProcessor := streaming.New(db, oauthServer) - accountProcessor := account.New(db, tc, mediaManager, oauthServer, clientWorker, federator, parseMentionFunc) - adminProcessor := admin.New(db, tc, mediaManager, federator.TransportController(), storage, clientWorker) - mediaProcessor := mediaProcessor.New(db, tc, mediaManager, federator.TransportController(), storage) - userProcessor := user.New(db, emailSender) - federationProcessor := federationProcessor.New(db, tc, federator) - reportProcessor := report.New(db, tc, clientWorker) filter := visibility.NewFilter(db) - return &processor{ + return &Processor{ clientWorker: clientWorker, fedWorker: fedWorker, @@ -358,21 +126,22 @@ func NewProcessor( storage: storage, statusTimelines: timeline.NewManager(StatusGrabFunction(db), StatusFilterFunction(db, filter), StatusPrepareFunction(db, tc), StatusSkipInsertFunction()), db: db, - filter: visibility.NewFilter(db), + filter: filter, - accountProcessor: accountProcessor, - adminProcessor: adminProcessor, - statusProcessor: statusProcessor, - streamingProcessor: streamingProcessor, - mediaProcessor: mediaProcessor, - userProcessor: userProcessor, - federationProcessor: federationProcessor, - reportProcessor: reportProcessor, + // sub processors + account: account.New(db, tc, mediaManager, oauthServer, clientWorker, federator, parseMentionFunc), + admin: admin.New(db, tc, mediaManager, federator.TransportController(), storage, clientWorker), + fedi: fedi.New(db, tc, federator), + media: media.New(db, tc, mediaManager, federator.TransportController(), storage), + report: report.New(db, tc, clientWorker), + status: status.New(db, tc, clientWorker, parseMentionFunc), + stream: stream.New(db, oauthServer), + user: user.New(db, emailSender), } } // Start starts the Processor, reading from its channels and passing messages back and forth. -func (p *processor) Start() error { +func (p *Processor) Start() error { // Setup and start the client API worker pool p.clientWorker.SetProcessor(p.ProcessFromClientAPI) if err := p.clientWorker.Start(); err != nil { @@ -394,7 +163,7 @@ func (p *processor) Start() error { } // Stop stops the processor cleanly, finishing handling any remaining messages before closing down. -func (p *processor) Stop() error { +func (p *Processor) Stop() error { if err := p.clientWorker.Stop(); err != nil { return err } diff --git a/internal/processing/processor_test.go b/internal/processing/processor_test.go index 36588f04c..44857cb47 100644 --- a/internal/processing/processor_test.go +++ b/internal/processing/processor_test.go @@ -62,7 +62,7 @@ type ProcessingStandardTestSuite struct { testBlocks map[string]*gtsmodel.Block testActivities map[string]testrig.ActivityWithSignature - processor processing.Processor + processor *processing.Processor } func (suite *ProcessingStandardTestSuite) SetupSuite() { diff --git a/internal/processing/report.go b/internal/processing/report.go deleted file mode 100644 index 9bbaa3226..000000000 --- a/internal/processing/report.go +++ /dev/null @@ -1,39 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package processing - -import ( - "context" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -func (p *processor) ReportsGet(ctx context.Context, authed *oauth.Auth, resolved *bool, targetAccountID string, maxID string, sinceID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) { - return p.reportProcessor.ReportsGet(ctx, authed.Account, resolved, targetAccountID, maxID, sinceID, minID, limit) -} - -func (p *processor) ReportGet(ctx context.Context, authed *oauth.Auth, id string) (*apimodel.Report, gtserror.WithCode) { - return p.reportProcessor.ReportGet(ctx, authed.Account, id) -} - -func (p *processor) ReportCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.ReportCreateRequest) (*apimodel.Report, gtserror.WithCode) { - return p.reportProcessor.Create(ctx, authed.Account, form) -} diff --git a/internal/processing/report/create.go b/internal/processing/report/create.go index a7e83b656..726d11666 100644 --- a/internal/processing/report/create.go +++ b/internal/processing/report/create.go @@ -33,7 +33,8 @@ "github.com/superseriousbusiness/gotosocial/internal/uris" ) -func (p *processor) Create(ctx context.Context, account *gtsmodel.Account, form *apimodel.ReportCreateRequest) (*apimodel.Report, gtserror.WithCode) { +// Create creates one user report / flag, using the provided form parameters. +func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form *apimodel.ReportCreateRequest) (*apimodel.Report, gtserror.WithCode) { if account.ID == form.AccountID { err := errors.New("cannot report your own account") return nil, gtserror.NewErrorBadRequest(err, err.Error()) diff --git a/internal/processing/report/getreports.go b/internal/processing/report/get.go similarity index 67% rename from internal/processing/report/getreports.go rename to internal/processing/report/get.go index e58e847a2..af2079b8a 100644 --- a/internal/processing/report/getreports.go +++ b/internal/processing/report/get.go @@ -30,7 +30,40 @@ "github.com/superseriousbusiness/gotosocial/internal/util" ) -func (p *processor) ReportsGet(ctx context.Context, account *gtsmodel.Account, resolved *bool, targetAccountID string, maxID string, sinceID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) { +// Get returns the user view of a moderation report, with the given id. +func (p *Processor) Get(ctx context.Context, account *gtsmodel.Account, id string) (*apimodel.Report, gtserror.WithCode) { + report, err := p.db.GetReportByID(ctx, id) + if err != nil { + if err == db.ErrNoEntries { + return nil, gtserror.NewErrorNotFound(err) + } + return nil, gtserror.NewErrorInternalError(err) + } + + if report.AccountID != account.ID { + err = fmt.Errorf("report with id %s does not belong to account %s", report.ID, account.ID) + return nil, gtserror.NewErrorNotFound(err) + } + + apiReport, err := p.tc.ReportToAPIReport(ctx, report) + if err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting report to api: %s", err)) + } + + return apiReport, nil +} + +// GetMultiple returns multiple reports created by the given account, filtered according to the provided parameters. +func (p *Processor) GetMultiple( + ctx context.Context, + account *gtsmodel.Account, + resolved *bool, + targetAccountID string, + maxID string, + sinceID string, + minID string, + limit int, +) (*apimodel.PageableResponse, gtserror.WithCode) { reports, err := p.db.GetReports(ctx, resolved, account.ID, targetAccountID, maxID, sinceID, minID, limit) if err != nil { if err == db.ErrNoEntries { diff --git a/internal/processing/report/getreport.go b/internal/processing/report/getreport.go deleted file mode 100644 index 6d4a18daa..000000000 --- a/internal/processing/report/getreport.go +++ /dev/null @@ -1,51 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package report - -import ( - "context" - "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) ReportGet(ctx context.Context, account *gtsmodel.Account, id string) (*apimodel.Report, gtserror.WithCode) { - report, err := p.db.GetReportByID(ctx, id) - if err != nil { - if err == db.ErrNoEntries { - return nil, gtserror.NewErrorNotFound(err) - } - return nil, gtserror.NewErrorInternalError(err) - } - - if report.AccountID != account.ID { - err = fmt.Errorf("report with id %s does not belong to account %s", report.ID, account.ID) - return nil, gtserror.NewErrorNotFound(err) - } - - apiReport, err := p.tc.ReportToAPIReport(ctx, report) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting report to api: %s", err)) - } - - return apiReport, nil -} diff --git a/internal/processing/report/report.go b/internal/processing/report/report.go index 8658ac808..b5f4b301e 100644 --- a/internal/processing/report/report.go +++ b/internal/processing/report/report.go @@ -19,31 +19,20 @@ package report import ( - "context" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/concurrency" "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/typeutils" ) -type Processor interface { - ReportsGet(ctx context.Context, account *gtsmodel.Account, resolved *bool, targetAccountID string, maxID string, sinceID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) - ReportGet(ctx context.Context, account *gtsmodel.Account, id string) (*apimodel.Report, gtserror.WithCode) - Create(ctx context.Context, account *gtsmodel.Account, form *apimodel.ReportCreateRequest) (*apimodel.Report, gtserror.WithCode) -} - -type processor struct { +type Processor struct { db db.DB tc typeutils.TypeConverter clientWorker *concurrency.WorkerPool[messages.FromClientAPI] } func New(db db.DB, tc typeutils.TypeConverter, clientWorker *concurrency.WorkerPool[messages.FromClientAPI]) Processor { - return &processor{ + return Processor{ tc: tc, db: db, clientWorker: clientWorker, diff --git a/internal/processing/search.go b/internal/processing/search.go index 44f38db12..05a1fe353 100644 --- a/internal/processing/search.go +++ b/internal/processing/search.go @@ -49,7 +49,7 @@ // The only exception to this is when we get a malformed query, in // which case we return a bad request error so the user knows they // did something funky. -func (p *processor) SearchGet(ctx context.Context, authed *oauth.Auth, search *apimodel.SearchQuery) (*apimodel.SearchResult, gtserror.WithCode) { +func (p *Processor) SearchGet(ctx context.Context, authed *oauth.Auth, search *apimodel.SearchQuery) (*apimodel.SearchResult, gtserror.WithCode) { // tidy up the query and make sure it wasn't just spaces query := strings.TrimSpace(search.Query) if query == "" { @@ -223,7 +223,7 @@ func (p *processor) SearchGet(ctx context.Context, authed *oauth.Auth, search *a return searchResult, nil } -func (p *processor) searchStatusByURI(ctx context.Context, authed *oauth.Auth, uri *url.URL) (*gtsmodel.Status, error) { +func (p *Processor) searchStatusByURI(ctx context.Context, authed *oauth.Auth, uri *url.URL) (*gtsmodel.Status, error) { status, statusable, err := p.federator.GetStatus(transport.WithFastfail(ctx), authed.Account.Username, uri, true, true) if err != nil { return nil, err @@ -237,7 +237,7 @@ func (p *processor) searchStatusByURI(ctx context.Context, authed *oauth.Auth, u return status, nil } -func (p *processor) searchAccountByURI(ctx context.Context, authed *oauth.Auth, uri *url.URL, resolve bool) (*gtsmodel.Account, error) { +func (p *Processor) searchAccountByURI(ctx context.Context, authed *oauth.Auth, uri *url.URL, resolve bool) (*gtsmodel.Account, error) { if !resolve { var ( account *gtsmodel.Account @@ -272,7 +272,7 @@ func (p *processor) searchAccountByURI(ctx context.Context, authed *oauth.Auth, ) } -func (p *processor) searchAccountByUsernameDomain(ctx context.Context, authed *oauth.Auth, username string, domain string, resolve bool) (*gtsmodel.Account, error) { +func (p *Processor) searchAccountByUsernameDomain(ctx context.Context, authed *oauth.Auth, username string, domain string, resolve bool) (*gtsmodel.Account, error) { if !resolve { if domain == config.GetHost() || domain == config.GetAccountDomain() { // We do local lookups using an empty domain, diff --git a/internal/processing/status.go b/internal/processing/status.go deleted file mode 100644 index a972e1bb1..000000000 --- a/internal/processing/status.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package processing - -import ( - "context" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -func (p *processor) StatusCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, gtserror.WithCode) { - return p.statusProcessor.Create(ctx, authed.Account, authed.Application, form) -} - -func (p *processor) StatusDelete(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - return p.statusProcessor.Delete(ctx, authed.Account, targetStatusID) -} - -func (p *processor) StatusFave(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - return p.statusProcessor.Fave(ctx, authed.Account, targetStatusID) -} - -func (p *processor) StatusBoost(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - return p.statusProcessor.Boost(ctx, authed.Account, authed.Application, targetStatusID) -} - -func (p *processor) StatusUnboost(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - return p.statusProcessor.Unboost(ctx, authed.Account, authed.Application, targetStatusID) -} - -func (p *processor) StatusBoostedBy(ctx context.Context, authed *oauth.Auth, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) { - return p.statusProcessor.BoostedBy(ctx, authed.Account, targetStatusID) -} - -func (p *processor) StatusFavedBy(ctx context.Context, authed *oauth.Auth, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) { - return p.statusProcessor.FavedBy(ctx, authed.Account, targetStatusID) -} - -func (p *processor) StatusGet(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - return p.statusProcessor.Get(ctx, authed.Account, targetStatusID) -} - -func (p *processor) StatusUnfave(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - return p.statusProcessor.Unfave(ctx, authed.Account, targetStatusID) -} - -func (p *processor) StatusGetContext(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Context, gtserror.WithCode) { - return p.statusProcessor.Context(ctx, authed.Account, targetStatusID) -} - -func (p *processor) StatusBookmark(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - return p.statusProcessor.Bookmark(ctx, authed.Account, targetStatusID) -} - -func (p *processor) StatusUnbookmark(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - return p.statusProcessor.Unbookmark(ctx, authed.Account, targetStatusID) -} diff --git a/internal/processing/status/bookmark.go b/internal/processing/status/bookmark.go index 3cf64490a..dde31ea7d 100644 --- a/internal/processing/status/bookmark.go +++ b/internal/processing/status/bookmark.go @@ -30,7 +30,8 @@ "github.com/superseriousbusiness/gotosocial/internal/id" ) -func (p *processor) Bookmark(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { +// BookmarkCreate adds a bookmark for the requestingAccount, targeting the given status (no-op if bookmark already exists). +func (p *Processor) BookmarkCreate(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) @@ -79,3 +80,43 @@ func (p *processor) Bookmark(ctx context.Context, requestingAccount *gtsmodel.Ac return apiStatus, nil } + +// BookmarkRemove removes a bookmark for the requesting account, targeting the given status (no-op if bookmark doesn't exist). +func (p *Processor) BookmarkRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { + targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) + if err != nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) + } + if targetStatus.Account == nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) + } + visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) + 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")) + } + + // first check if the status is actually bookmarked + toUnbookmark := false + gtsBookmark := >smodel.StatusBookmark{} + if err := p.db.GetWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsBookmark); err == nil { + // we have a bookmark for this status + toUnbookmark = true + } + + if toUnbookmark { + if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsBookmark); err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error unfaveing status: %s", err)) + } + } + + // return the api representation of the target status + apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) + if err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) + } + + return apiStatus, nil +} diff --git a/internal/processing/status/bookmark_test.go b/internal/processing/status/bookmark_test.go index bfb652279..a05e19e8b 100644 --- a/internal/processing/status/bookmark_test.go +++ b/internal/processing/status/bookmark_test.go @@ -36,13 +36,33 @@ func (suite *StatusBookmarkTestSuite) TestBookmark() { bookmarkingAccount1 := suite.testAccounts["local_account_1"] targetStatus1 := suite.testStatuses["admin_account_status_1"] - bookmark1, err := suite.status.Bookmark(ctx, bookmarkingAccount1, targetStatus1.ID) + bookmark1, err := suite.status.BookmarkCreate(ctx, bookmarkingAccount1, targetStatus1.ID) suite.NoError(err) suite.NotNil(bookmark1) suite.True(bookmark1.Bookmarked) suite.Equal(targetStatus1.ID, bookmark1.ID) } +func (suite *StatusBookmarkTestSuite) TestUnbookmark() { + ctx := context.Background() + + // bookmark a status + bookmarkingAccount1 := suite.testAccounts["local_account_1"] + targetStatus1 := suite.testStatuses["admin_account_status_1"] + + bookmark1, err := suite.status.BookmarkCreate(ctx, bookmarkingAccount1, targetStatus1.ID) + suite.NoError(err) + suite.NotNil(bookmark1) + suite.True(bookmark1.Bookmarked) + suite.Equal(targetStatus1.ID, bookmark1.ID) + + bookmark2, err := suite.status.BookmarkRemove(ctx, bookmarkingAccount1, targetStatus1.ID) + suite.NoError(err) + suite.NotNil(bookmark2) + suite.False(bookmark2.Bookmarked) + suite.Equal(targetStatus1.ID, bookmark1.ID) +} + func TestStatusBookmarkTestSuite(t *testing.T) { suite.Run(t, new(StatusBookmarkTestSuite)) } diff --git a/internal/processing/status/boost.go b/internal/processing/status/boost.go index 81456abd7..4dfe17019 100644 --- a/internal/processing/status/boost.go +++ b/internal/processing/status/boost.go @@ -25,12 +25,14 @@ "github.com/superseriousbusiness/gotosocial/internal/ap" 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/messages" ) -func (p *processor) Boost(ctx context.Context, requestingAccount *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { +// BoostCreate processes the boost/reblog of a given status, returning the newly-created boost if all is well. +func (p *Processor) BoostCreate(ctx context.Context, requestingAccount *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) @@ -93,3 +95,153 @@ func (p *processor) Boost(ctx context.Context, requestingAccount *gtsmodel.Accou return apiStatus, nil } + +// BoostRemove processes the unboost/unreblog of a given status, returning the status if all is well. +func (p *Processor) BoostRemove(ctx context.Context, requestingAccount *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { + targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) + if err != nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) + } + if targetStatus.Account == nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) + } + + visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) + 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 boost for this status + var toUnboost bool + + gtsBoost := >smodel.Status{} + where := []db.Where{ + { + Key: "boost_of_id", + Value: targetStatusID, + }, + { + Key: "account_id", + Value: requestingAccount.ID, + }, + } + err = p.db.GetWhere(ctx, where, gtsBoost) + if err == nil { + // we have a boost + toUnboost = true + } + + if err != nil { + // something went wrong in the db finding the boost + if err != db.ErrNoEntries { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error fetching existing boost from database: %s", err)) + } + // we just don't have a boost + toUnboost = false + } + + if toUnboost { + // pin some stuff onto the boost while we have it out of the db + gtsBoost.Account = requestingAccount + gtsBoost.BoostOf = targetStatus + gtsBoost.BoostOfAccount = targetStatus.Account + gtsBoost.BoostOf.Account = targetStatus.Account + + // send it back to the processor for async processing + p.clientWorker.Queue(messages.FromClientAPI{ + APObjectType: ap.ActivityAnnounce, + APActivityType: ap.ActivityUndo, + GTSModel: gtsBoost, + OriginAccount: requestingAccount, + TargetAccount: targetStatus.Account, + }) + } + + apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) + if err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) + } + + return apiStatus, nil +} + +// StatusBoostedBy returns a slice of accounts that have boosted the given status, filtered according to privacy settings. +func (p *Processor) StatusBoostedBy(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) { + targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) + if err != nil { + wrapped := fmt.Errorf("BoostedBy: error fetching status %s: %s", targetStatusID, err) + if !errors.Is(err, db.ErrNoEntries) { + return nil, gtserror.NewErrorInternalError(wrapped) + } + return nil, gtserror.NewErrorNotFound(wrapped) + } + + if boostOfID := targetStatus.BoostOfID; boostOfID != "" { + // the target status is a boost wrapper, redirect this request to the status it boosts + boostedStatus, err := p.db.GetStatusByID(ctx, boostOfID) + if err != nil { + wrapped := fmt.Errorf("BoostedBy: error fetching status %s: %s", boostOfID, err) + if !errors.Is(err, db.ErrNoEntries) { + return nil, gtserror.NewErrorInternalError(wrapped) + } + return nil, gtserror.NewErrorNotFound(wrapped) + } + targetStatus = boostedStatus + } + + visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) + if err != nil { + err = fmt.Errorf("BoostedBy: error seeing if status %s is visible: %s", targetStatus.ID, err) + return nil, gtserror.NewErrorNotFound(err) + } + if !visible { + err = errors.New("BoostedBy: status is not visible") + return nil, gtserror.NewErrorNotFound(err) + } + + statusReblogs, err := p.db.GetStatusReblogs(ctx, targetStatus) + if err != nil { + err = fmt.Errorf("BoostedBy: error seeing who boosted status: %s", err) + return nil, gtserror.NewErrorNotFound(err) + } + + // filter account IDs so the user doesn't see accounts they blocked or which blocked them + accountIDs := make([]string, 0, len(statusReblogs)) + for _, s := range statusReblogs { + blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, s.AccountID, true) + if err != nil { + err = fmt.Errorf("BoostedBy: error checking blocks: %s", err) + return nil, gtserror.NewErrorNotFound(err) + } + if !blocked { + accountIDs = append(accountIDs, s.AccountID) + } + } + + // TODO: filter other things here? suspended? muted? silenced? + + // fetch accounts + create their API representations + apiAccounts := make([]*apimodel.Account, 0, len(accountIDs)) + for _, accountID := range accountIDs { + account, err := p.db.GetAccountByID(ctx, accountID) + if err != nil { + wrapped := fmt.Errorf("BoostedBy: error fetching account %s: %s", accountID, err) + if !errors.Is(err, db.ErrNoEntries) { + return nil, gtserror.NewErrorInternalError(wrapped) + } + return nil, gtserror.NewErrorNotFound(wrapped) + } + + apiAccount, err := p.tc.AccountToAPIAccountPublic(ctx, account) + if err != nil { + err = fmt.Errorf("BoostedBy: error converting account to api model: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + apiAccounts = append(apiAccounts, apiAccount) + } + + return apiAccounts, nil +} diff --git a/internal/processing/status/boost_test.go b/internal/processing/status/boost_test.go index 4913ff4d0..1a5596cab 100644 --- a/internal/processing/status/boost_test.go +++ b/internal/processing/status/boost_test.go @@ -37,7 +37,7 @@ func (suite *StatusBoostTestSuite) TestBoostOfBoost() { application1 := suite.testApplications["application_1"] targetStatus1 := suite.testStatuses["admin_account_status_1"] - boost1, err := suite.status.Boost(ctx, boostingAccount1, application1, targetStatus1.ID) + boost1, err := suite.status.BoostCreate(ctx, boostingAccount1, application1, targetStatus1.ID) suite.NoError(err) suite.NotNil(boost1) suite.Equal(targetStatus1.ID, boost1.Reblog.ID) @@ -47,7 +47,7 @@ func (suite *StatusBoostTestSuite) TestBoostOfBoost() { application2 := suite.testApplications["application_2"] targetStatus2ID := boost1.ID - boost2, err := suite.status.Boost(ctx, boostingAccount2, application2, targetStatus2ID) + boost2, err := suite.status.BoostCreate(ctx, boostingAccount2, application2, targetStatus2ID) suite.NoError(err) suite.NotNil(boost2) // the boosted status should not be the boost, diff --git a/internal/processing/status/boostedby.go b/internal/processing/status/boostedby.go deleted file mode 100644 index 97c4e8634..000000000 --- a/internal/processing/status/boostedby.go +++ /dev/null @@ -1,107 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package status - -import ( - "context" - "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) BoostedBy(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) { - targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) - if err != nil { - wrapped := fmt.Errorf("BoostedBy: error fetching status %s: %s", targetStatusID, err) - if !errors.Is(err, db.ErrNoEntries) { - return nil, gtserror.NewErrorInternalError(wrapped) - } - return nil, gtserror.NewErrorNotFound(wrapped) - } - - if boostOfID := targetStatus.BoostOfID; boostOfID != "" { - // the target status is a boost wrapper, redirect this request to the status it boosts - boostedStatus, err := p.db.GetStatusByID(ctx, boostOfID) - if err != nil { - wrapped := fmt.Errorf("BoostedBy: error fetching status %s: %s", boostOfID, err) - if !errors.Is(err, db.ErrNoEntries) { - return nil, gtserror.NewErrorInternalError(wrapped) - } - return nil, gtserror.NewErrorNotFound(wrapped) - } - targetStatus = boostedStatus - } - - visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) - if err != nil { - err = fmt.Errorf("BoostedBy: error seeing if status %s is visible: %s", targetStatus.ID, err) - return nil, gtserror.NewErrorNotFound(err) - } - if !visible { - err = errors.New("BoostedBy: status is not visible") - return nil, gtserror.NewErrorNotFound(err) - } - - statusReblogs, err := p.db.GetStatusReblogs(ctx, targetStatus) - if err != nil { - err = fmt.Errorf("BoostedBy: error seeing who boosted status: %s", err) - return nil, gtserror.NewErrorNotFound(err) - } - - // filter account IDs so the user doesn't see accounts they blocked or which blocked them - accountIDs := make([]string, 0, len(statusReblogs)) - for _, s := range statusReblogs { - blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, s.AccountID, true) - if err != nil { - err = fmt.Errorf("BoostedBy: error checking blocks: %s", err) - return nil, gtserror.NewErrorNotFound(err) - } - if !blocked { - accountIDs = append(accountIDs, s.AccountID) - } - } - - // TODO: filter other things here? suspended? muted? silenced? - - // fetch accounts + create their API representations - apiAccounts := make([]*apimodel.Account, 0, len(accountIDs)) - for _, accountID := range accountIDs { - account, err := p.db.GetAccountByID(ctx, accountID) - if err != nil { - wrapped := fmt.Errorf("BoostedBy: error fetching account %s: %s", accountID, err) - if !errors.Is(err, db.ErrNoEntries) { - return nil, gtserror.NewErrorInternalError(wrapped) - } - return nil, gtserror.NewErrorNotFound(wrapped) - } - - apiAccount, err := p.tc.AccountToAPIAccountPublic(ctx, account) - if err != nil { - err = fmt.Errorf("BoostedBy: error converting account to api model: %s", err) - return nil, gtserror.NewErrorInternalError(err) - } - apiAccounts = append(apiAccounts, apiAccount) - } - - return apiAccounts, nil -} diff --git a/internal/processing/status/context.go b/internal/processing/status/context.go deleted file mode 100644 index 8d6f1d6ea..000000000 --- a/internal/processing/status/context.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package status - -import ( - "context" - "errors" - "fmt" - "sort" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func (p *processor) Context(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Context, gtserror.WithCode) { - targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) - } - if targetStatus.Account == nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) - } - - visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) - 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")) - } - - context := &apimodel.Context{ - Ancestors: []apimodel.Status{}, - Descendants: []apimodel.Status{}, - } - - parents, err := p.db.GetStatusParents(ctx, targetStatus, false) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - for _, status := range parents { - if v, err := p.filter.StatusVisible(ctx, status, requestingAccount); err == nil && v { - apiStatus, err := p.tc.StatusToAPIStatus(ctx, status, requestingAccount) - if err == nil { - context.Ancestors = append(context.Ancestors, *apiStatus) - } - } - } - - sort.Slice(context.Ancestors, func(i int, j int) bool { - return context.Ancestors[i].ID < context.Ancestors[j].ID - }) - - children, err := p.db.GetStatusChildren(ctx, targetStatus, false, "") - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - for _, status := range children { - if v, err := p.filter.StatusVisible(ctx, status, requestingAccount); err == nil && v { - apiStatus, err := p.tc.StatusToAPIStatus(ctx, status, requestingAccount) - if err == nil { - context.Descendants = append(context.Descendants, *apiStatus) - } - } - } - - return context, nil -} diff --git a/internal/processing/status/create.go b/internal/processing/status/create.go index 5bc1629c4..f47c850dd 100644 --- a/internal/processing/status/create.go +++ b/internal/processing/status/create.go @@ -20,20 +20,25 @@ import ( "context" + "errors" "fmt" "time" "github.com/superseriousbusiness/gotosocial/internal/ap" 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/id" "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/text" + "github.com/superseriousbusiness/gotosocial/internal/typeutils" "github.com/superseriousbusiness/gotosocial/internal/uris" ) -func (p *processor) Create(ctx context.Context, account *gtsmodel.Account, application *gtsmodel.Application, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, gtserror.WithCode) { +// Create processes the given form to create a new status, returning the api model representation of that status if it's OK. +func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, application *gtsmodel.Application, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, gtserror.WithCode) { accountURIs := uris.GenerateURIsForAccount(account.Username) thisStatusID := id.NewULID() local := true @@ -56,23 +61,23 @@ func (p *processor) Create(ctx context.Context, account *gtsmodel.Account, appli Text: form.Status, } - if errWithCode := p.ProcessReplyToID(ctx, form, account.ID, newStatus); errWithCode != nil { + if errWithCode := processReplyToID(ctx, p.db, form, account.ID, newStatus); errWithCode != nil { return nil, errWithCode } - if errWithCode := p.ProcessMediaIDs(ctx, form, account.ID, newStatus); errWithCode != nil { + if errWithCode := processMediaIDs(ctx, p.db, form, account.ID, newStatus); errWithCode != nil { return nil, errWithCode } - if err := p.ProcessVisibility(ctx, form, account.Privacy, newStatus); err != nil { + if err := processVisibility(ctx, form, account.Privacy, newStatus); err != nil { return nil, gtserror.NewErrorInternalError(err) } - if err := p.ProcessLanguage(ctx, form, account.Language, newStatus); err != nil { + if err := processLanguage(ctx, form, account.Language, newStatus); err != nil { return nil, gtserror.NewErrorInternalError(err) } - if err := p.ProcessContent(ctx, form, account.ID, newStatus); err != nil { + if err := processContent(ctx, p.db, p.formatter, p.parseMention, form, account.ID, newStatus); err != nil { return nil, gtserror.NewErrorInternalError(err) } @@ -97,3 +102,249 @@ func (p *processor) Create(ctx context.Context, account *gtsmodel.Account, appli return apiStatus, nil } + +func processReplyToID(ctx context.Context, dbService db.DB, form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) gtserror.WithCode { + 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{} + + if err := dbService.GetByID(ctx, form.InReplyToID, repliedStatus); err != nil { + if err == db.ErrNoEntries { + err := fmt.Errorf("status with id %s not replyable because it doesn't exist", form.InReplyToID) + return gtserror.NewErrorBadRequest(err, err.Error()) + } + err := fmt.Errorf("db error fetching status with id %s: %s", form.InReplyToID, err) + return gtserror.NewErrorInternalError(err) + } + if !*repliedStatus.Replyable { + err := fmt.Errorf("status with id %s is marked as not replyable", form.InReplyToID) + return gtserror.NewErrorForbidden(err, err.Error()) + } + + if err := dbService.GetByID(ctx, repliedStatus.AccountID, repliedAccount); err != nil { + if err == db.ErrNoEntries { + err := fmt.Errorf("status with id %s not replyable because account id %s is not known", form.InReplyToID, repliedStatus.AccountID) + return gtserror.NewErrorBadRequest(err, err.Error()) + } + err := fmt.Errorf("db error fetching account with id %s: %s", repliedStatus.AccountID, err) + return gtserror.NewErrorInternalError(err) + } + + if blocked, err := dbService.IsBlocked(ctx, thisAccountID, repliedAccount.ID, true); err != nil { + err := fmt.Errorf("db error checking block: %s", err) + return gtserror.NewErrorInternalError(err) + } else if blocked { + err := fmt.Errorf("status with id %s not replyable", form.InReplyToID) + return gtserror.NewErrorNotFound(err) + } + + status.InReplyToID = repliedStatus.ID + status.InReplyToURI = repliedStatus.URI + status.InReplyToAccountID = repliedAccount.ID + + return nil +} + +func processMediaIDs(ctx context.Context, dbService db.DB, form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) gtserror.WithCode { + if form.MediaIDs == nil { + return nil + } + + attachments := []*gtsmodel.MediaAttachment{} + attachmentIDs := []string{} + for _, mediaID := range form.MediaIDs { + attachment, err := dbService.GetAttachmentByID(ctx, mediaID) + if err != nil { + if errors.Is(err, db.ErrNoEntries) { + err = fmt.Errorf("ProcessMediaIDs: media not found for media id %s", mediaID) + return gtserror.NewErrorBadRequest(err, err.Error()) + } + err = fmt.Errorf("ProcessMediaIDs: db error for media id %s", mediaID) + return gtserror.NewErrorInternalError(err) + } + + if attachment.AccountID != thisAccountID { + err = fmt.Errorf("ProcessMediaIDs: media with id %s does not belong to account %s", mediaID, thisAccountID) + return gtserror.NewErrorBadRequest(err, err.Error()) + } + + if attachment.StatusID != "" || attachment.ScheduledStatusID != "" { + err = fmt.Errorf("ProcessMediaIDs: media with id %s is already attached to a status", mediaID) + return gtserror.NewErrorBadRequest(err, err.Error()) + } + + minDescriptionChars := config.GetMediaDescriptionMinChars() + if descriptionLength := len([]rune(attachment.Description)); descriptionLength < minDescriptionChars { + err = fmt.Errorf("ProcessMediaIDs: description too short! media description of at least %d chararacters is required but %d was provided for media with id %s", minDescriptionChars, descriptionLength, mediaID) + return gtserror.NewErrorBadRequest(err, err.Error()) + } + + attachments = append(attachments, attachment) + attachmentIDs = append(attachmentIDs, attachment.ID) + } + + status.Attachments = attachments + status.AttachmentIDs = attachmentIDs + return nil +} + +func processVisibility(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountDefaultVis gtsmodel.Visibility, status *gtsmodel.Status) error { + // by default all flags are set to true + federated := true + boostable := true + replyable := true + likeable := true + + // If visibility isn't set on the form, then just take the account default. + // If that's also not set, take the default for the whole instance. + var vis gtsmodel.Visibility + switch { + case form.Visibility != "": + vis = typeutils.APIVisToVis(form.Visibility) + case accountDefaultVis != "": + vis = accountDefaultVis + default: + vis = gtsmodel.VisibilityDefault + } + + switch vis { + 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 { + federated = *form.Federated + } + + if form.Boostable != nil { + boostable = *form.Boostable + } + + if form.Replyable != nil { + replyable = *form.Replyable + } + + if form.Likeable != nil { + 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 + boostable = false + + if form.Federated != nil { + federated = *form.Federated + } + + if form.Replyable != nil { + replyable = *form.Replyable + } + + if form.Likeable != nil { + likeable = *form.Likeable + } + + case gtsmodel.VisibilityDirect: + // direct is pretty easy: there's only one possible setting so return it + federated = true + boostable = false + replyable = true + likeable = true + } + + status.Visibility = vis + status.Federated = &federated + status.Boostable = &boostable + status.Replyable = &replyable + status.Likeable = &likeable + return nil +} + +func processLanguage(ctx context.Context, 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 processContent(ctx context.Context, dbService db.DB, formatter text.Formatter, parseMention gtsmodel.ParseMentionFunc, form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error { + // if there's nothing in the status at all we can just return early + if form.Status == "" { + status.Content = "" + return nil + } + + // if format wasn't specified we should try to figure out what format this user prefers + if form.Format == "" { + acct, err := dbService.GetAccountByID(ctx, accountID) + if err != nil { + return fmt.Errorf("error processing new content: couldn't retrieve account from db to check post format: %s", err) + } + + switch acct.StatusFormat { + case "plain": + form.Format = apimodel.StatusFormatPlain + case "markdown": + form.Format = apimodel.StatusFormatMarkdown + default: + form.Format = apimodel.StatusFormatDefault + } + } + + // parse content out of the status depending on what format has been submitted + var f text.FormatFunc + switch form.Format { + case apimodel.StatusFormatPlain: + f = formatter.FromPlain + case apimodel.StatusFormatMarkdown: + f = formatter.FromMarkdown + default: + return fmt.Errorf("format %s not recognised as a valid status format", form.Format) + } + formatted := f(ctx, parseMention, accountID, status.ID, form.Status) + + // add full populated gts {mentions, tags, emojis} to the status for passing them around conveniently + // add just their ids to the status for putting in the db + status.Mentions = formatted.Mentions + status.MentionIDs = make([]string, 0, len(formatted.Mentions)) + for _, gtsmention := range formatted.Mentions { + status.MentionIDs = append(status.MentionIDs, gtsmention.ID) + } + + status.Tags = formatted.Tags + status.TagIDs = make([]string, 0, len(formatted.Tags)) + for _, gtstag := range formatted.Tags { + status.TagIDs = append(status.TagIDs, gtstag.ID) + } + + status.Emojis = formatted.Emojis + status.EmojiIDs = make([]string, 0, len(formatted.Emojis)) + for _, gtsemoji := range formatted.Emojis { + status.EmojiIDs = append(status.EmojiIDs, gtsemoji.ID) + } + + spoilerformatted := formatter.FromPlainEmojiOnly(ctx, parseMention, accountID, status.ID, form.SpoilerText) + for _, gtsemoji := range spoilerformatted.Emojis { + status.Emojis = append(status.Emojis, gtsemoji) + status.EmojiIDs = append(status.EmojiIDs, gtsemoji.ID) + } + + status.Content = formatted.HTML + return nil +} diff --git a/internal/processing/status/delete.go b/internal/processing/status/delete.go index 0042c043d..d3a03aad6 100644 --- a/internal/processing/status/delete.go +++ b/internal/processing/status/delete.go @@ -30,7 +30,8 @@ "github.com/superseriousbusiness/gotosocial/internal/messages" ) -func (p *processor) Delete(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { +// Delete processes the delete of a given status, returning the deleted status if the delete goes through. +func (p *Processor) Delete(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) diff --git a/internal/processing/status/fave.go b/internal/processing/status/fave.go index dd5d338b3..3bcb1835f 100644 --- a/internal/processing/status/fave.go +++ b/internal/processing/status/fave.go @@ -33,7 +33,8 @@ "github.com/superseriousbusiness/gotosocial/internal/uris" ) -func (p *processor) Fave(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { +// FaveCreate processes the faving of a given status, returning the updated status if the fave goes through. +func (p *Processor) FaveCreate(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) @@ -98,3 +99,111 @@ func (p *processor) Fave(ctx context.Context, requestingAccount *gtsmodel.Accoun return apiStatus, nil } + +// FaveRemove processes the unfaving of a given status, returning the updated status if the fave goes through. +func (p *Processor) FaveRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { + targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) + if err != nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) + } + if targetStatus.Account == nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) + } + + visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) + 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(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsFave) + if err == nil { + // we have a fave + toUnfave = true + } + if err != nil { + // something went wrong in the db finding the fave + if err != db.ErrNoEntries { + 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 + if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsFave); err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error unfaveing status: %s", err)) + } + + // send it back to the processor for async processing + p.clientWorker.Queue(messages.FromClientAPI{ + APObjectType: ap.ActivityLike, + APActivityType: ap.ActivityUndo, + GTSModel: gtsFave, + OriginAccount: requestingAccount, + TargetAccount: targetStatus.Account, + }) + } + + apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) + if err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) + } + + return apiStatus, nil +} + +// FavedBy returns a slice of accounts that have liked the given status, filtered according to privacy settings. +func (p *Processor) FavedBy(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) { + targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) + if err != nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) + } + if targetStatus.Account == nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) + } + + visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) + 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")) + } + + statusFaves, err := p.db.GetStatusFaves(ctx, 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 _, fave := range statusFaves { + blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, fave.AccountID, true) + if err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking blocks: %s", err)) + } + if !blocked { + filteredAccounts = append(filteredAccounts, fave.Account) + } + } + + // now we can return the api representation of those accounts + apiAccounts := []*apimodel.Account{} + for _, acc := range filteredAccounts { + apiAccount, err := p.tc.AccountToAPIAccountPublic(ctx, acc) + if err != nil { + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) + } + apiAccounts = append(apiAccounts, apiAccount) + } + + return apiAccounts, nil +} diff --git a/internal/processing/status/favedby.go b/internal/processing/status/favedby.go deleted file mode 100644 index 2de4aff56..000000000 --- a/internal/processing/status/favedby.go +++ /dev/null @@ -1,76 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package status - -import ( - "context" - "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(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) { - targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) - } - if targetStatus.Account == nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) - } - - visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) - 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")) - } - - statusFaves, err := p.db.GetStatusFaves(ctx, 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 _, fave := range statusFaves { - blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, fave.AccountID, true) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking blocks: %s", err)) - } - if !blocked { - filteredAccounts = append(filteredAccounts, fave.Account) - } - } - - // now we can return the api representation of those accounts - apiAccounts := []*apimodel.Account{} - for _, acc := range filteredAccounts { - apiAccount, err := p.tc.AccountToAPIAccountPublic(ctx, acc) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) - } - apiAccounts = append(apiAccounts, apiAccount) - } - - return apiAccounts, nil -} diff --git a/internal/processing/status/get.go b/internal/processing/status/get.go index c79f0d4d6..edefeb440 100644 --- a/internal/processing/status/get.go +++ b/internal/processing/status/get.go @@ -22,13 +22,15 @@ "context" "errors" "fmt" + "sort" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) -func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { +// Get gets the given status, taking account of privacy settings and blocks etc. +func (p *Processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) @@ -52,3 +54,61 @@ func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account return apiStatus, nil } + +// ContextGet returns the context (previous and following posts) from the given status ID. +func (p *Processor) ContextGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Context, gtserror.WithCode) { + targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) + if err != nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) + } + if targetStatus.Account == nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) + } + + visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) + 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")) + } + + context := &apimodel.Context{ + Ancestors: []apimodel.Status{}, + Descendants: []apimodel.Status{}, + } + + parents, err := p.db.GetStatusParents(ctx, targetStatus, false) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + for _, status := range parents { + if v, err := p.filter.StatusVisible(ctx, status, requestingAccount); err == nil && v { + apiStatus, err := p.tc.StatusToAPIStatus(ctx, status, requestingAccount) + if err == nil { + context.Ancestors = append(context.Ancestors, *apiStatus) + } + } + } + + sort.Slice(context.Ancestors, func(i int, j int) bool { + return context.Ancestors[i].ID < context.Ancestors[j].ID + }) + + children, err := p.db.GetStatusChildren(ctx, targetStatus, false, "") + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + for _, status := range children { + if v, err := p.filter.StatusVisible(ctx, status, requestingAccount); err == nil && v { + apiStatus, err := p.tc.StatusToAPIStatus(ctx, status, requestingAccount) + if err == nil { + context.Descendants = append(context.Descendants, *apiStatus) + } + } + } + + return context, nil +} diff --git a/internal/processing/status/status.go b/internal/processing/status/status.go index 56b8b23eb..c91fd85d1 100644 --- a/internal/processing/status/status.go +++ b/internal/processing/status/status.go @@ -19,12 +19,8 @@ package status import ( - "context" - - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/concurrency" "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/text" @@ -32,45 +28,7 @@ "github.com/superseriousbusiness/gotosocial/internal/visibility" ) -// 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(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, account *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) - // Unboost processes the unboost/unreblog of a given status, returning the status if all is well. - Unboost(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, account *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) - // Get gets the given status, taking account of privacy settings and blocks etc. - Get(ctx context.Context, 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(ctx context.Context, account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) - // Context returns the context (previous and following posts) from the given status ID - Context(ctx context.Context, account *gtsmodel.Account, targetStatusID string) (*apimodel.Context, gtserror.WithCode) - // Bookmarks a status - Bookmark(ctx context.Context, account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) - // Removes a bookmark for a status - Unbookmark(ctx context.Context, account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) - - /* - PROCESSING UTILS - */ - - ProcessVisibility(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountDefaultVis gtsmodel.Visibility, status *gtsmodel.Status) error - ProcessReplyToID(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) gtserror.WithCode - ProcessMediaIDs(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) gtserror.WithCode - ProcessLanguage(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountDefaultLanguage string, status *gtsmodel.Status) error - ProcessContent(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error -} - -type processor struct { +type Processor struct { tc typeutils.TypeConverter db db.DB filter visibility.Filter @@ -81,7 +39,7 @@ type processor struct { // New returns a new status processor. func New(db db.DB, tc typeutils.TypeConverter, clientWorker *concurrency.WorkerPool[messages.FromClientAPI], parseMention gtsmodel.ParseMentionFunc) Processor { - return &processor{ + return Processor{ tc: tc, db: db, filter: visibility.NewFilter(db), diff --git a/internal/processing/status/unbookmark.go b/internal/processing/status/unbookmark.go deleted file mode 100644 index 497af0e07..000000000 --- a/internal/processing/status/unbookmark.go +++ /dev/null @@ -1,69 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package status - -import ( - "context" - "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) Unbookmark(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) - } - if targetStatus.Account == nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) - } - visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) - 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")) - } - - // first check if the status is already bookmarked - toUnbookmark := false - gtsBookmark := >smodel.StatusBookmark{} - if err := p.db.GetWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsBookmark); err == nil { - // we already have a bookmark for this status - toUnbookmark = true - } - - if toUnbookmark { - if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsBookmark); err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error unfaveing status: %s", err)) - } - } - - // return the apidon representation of the target status - apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) - } - - return apiStatus, nil -} diff --git a/internal/processing/status/unbookmark_test.go b/internal/processing/status/unbookmark_test.go deleted file mode 100644 index 1e75bc726..000000000 --- a/internal/processing/status/unbookmark_test.go +++ /dev/null @@ -1,54 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package status_test - -import ( - "context" - "testing" - - "github.com/stretchr/testify/suite" -) - -type StatusUnbookmarkTestSuite struct { - StatusStandardTestSuite -} - -func (suite *StatusUnbookmarkTestSuite) TestUnbookmark() { - ctx := context.Background() - - // bookmark a status - bookmarkingAccount1 := suite.testAccounts["local_account_1"] - targetStatus1 := suite.testStatuses["admin_account_status_1"] - - bookmark1, err := suite.status.Bookmark(ctx, bookmarkingAccount1, targetStatus1.ID) - suite.NoError(err) - suite.NotNil(bookmark1) - suite.True(bookmark1.Bookmarked) - suite.Equal(targetStatus1.ID, bookmark1.ID) - - bookmark2, err := suite.status.Unbookmark(ctx, bookmarkingAccount1, targetStatus1.ID) - suite.NoError(err) - suite.NotNil(bookmark2) - suite.False(bookmark2.Bookmarked) - suite.Equal(targetStatus1.ID, bookmark1.ID) -} - -func TestStatusUnbookmarkTestSuite(t *testing.T) { - suite.Run(t, new(StatusUnbookmarkTestSuite)) -} diff --git a/internal/processing/status/unboost.go b/internal/processing/status/unboost.go deleted file mode 100644 index 0513e9e81..000000000 --- a/internal/processing/status/unboost.go +++ /dev/null @@ -1,103 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package status - -import ( - "context" - "errors" - "fmt" - - "github.com/superseriousbusiness/gotosocial/internal/ap" - 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/messages" -) - -func (p *processor) Unboost(ctx context.Context, requestingAccount *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) - } - if targetStatus.Account == nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) - } - - visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) - 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 boost for this status - var toUnboost bool - - gtsBoost := >smodel.Status{} - where := []db.Where{ - { - Key: "boost_of_id", - Value: targetStatusID, - }, - { - Key: "account_id", - Value: requestingAccount.ID, - }, - } - err = p.db.GetWhere(ctx, where, gtsBoost) - if err == nil { - // we have a boost - toUnboost = true - } - - if err != nil { - // something went wrong in the db finding the boost - if err != db.ErrNoEntries { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error fetching existing boost from database: %s", err)) - } - // we just don't have a boost - toUnboost = false - } - - if toUnboost { - // pin some stuff onto the boost while we have it out of the db - gtsBoost.Account = requestingAccount - gtsBoost.BoostOf = targetStatus - gtsBoost.BoostOfAccount = targetStatus.Account - gtsBoost.BoostOf.Account = targetStatus.Account - - // send it back to the processor for async processing - p.clientWorker.Queue(messages.FromClientAPI{ - APObjectType: ap.ActivityAnnounce, - APActivityType: ap.ActivityUndo, - GTSModel: gtsBoost, - OriginAccount: requestingAccount, - TargetAccount: targetStatus.Account, - }) - } - - apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) - } - - return apiStatus, nil -} diff --git a/internal/processing/status/unfave.go b/internal/processing/status/unfave.go deleted file mode 100644 index 809c23884..000000000 --- a/internal/processing/status/unfave.go +++ /dev/null @@ -1,91 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package status - -import ( - "context" - "errors" - "fmt" - - "github.com/superseriousbusiness/gotosocial/internal/ap" - 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/messages" -) - -func (p *processor) Unfave(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { - targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) - } - if targetStatus.Account == nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) - } - - visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) - 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(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsFave) - if err == nil { - // we have a fave - toUnfave = true - } - if err != nil { - // something went wrong in the db finding the fave - if err != db.ErrNoEntries { - 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 - if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsFave); err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error unfaveing status: %s", err)) - } - - // send it back to the processor for async processing - p.clientWorker.Queue(messages.FromClientAPI{ - APObjectType: ap.ActivityLike, - APActivityType: ap.ActivityUndo, - GTSModel: gtsFave, - OriginAccount: requestingAccount, - TargetAccount: targetStatus.Account, - }) - } - - apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) - if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) - } - - return apiStatus, nil -} diff --git a/internal/processing/status/util.go b/internal/processing/status/util.go deleted file mode 100644 index 1115219cd..000000000 --- a/internal/processing/status/util.go +++ /dev/null @@ -1,278 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package status - -import ( - "context" - "errors" - "fmt" - - 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/text" -) - -func (p *processor) ProcessVisibility(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountDefaultVis gtsmodel.Visibility, status *gtsmodel.Status) error { - // by default all flags are set to true - federated := true - boostable := true - replyable := true - likeable := true - - // If visibility isn't set on the form, then just take the account default. - // If that's also not set, take the default for the whole instance. - var vis gtsmodel.Visibility - switch { - case form.Visibility != "": - vis = p.tc.APIVisToVis(form.Visibility) - case accountDefaultVis != "": - vis = accountDefaultVis - default: - vis = gtsmodel.VisibilityDefault - } - - switch vis { - 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 { - federated = *form.Federated - } - - if form.Boostable != nil { - boostable = *form.Boostable - } - - if form.Replyable != nil { - replyable = *form.Replyable - } - - if form.Likeable != nil { - 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 - boostable = false - - if form.Federated != nil { - federated = *form.Federated - } - - if form.Replyable != nil { - replyable = *form.Replyable - } - - if form.Likeable != nil { - likeable = *form.Likeable - } - - case gtsmodel.VisibilityDirect: - // direct is pretty easy: there's only one possible setting so return it - federated = true - boostable = false - replyable = true - likeable = true - } - - status.Visibility = vis - status.Federated = &federated - status.Boostable = &boostable - status.Replyable = &replyable - status.Likeable = &likeable - return nil -} - -func (p *processor) ProcessReplyToID(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) gtserror.WithCode { - 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{} - - if err := p.db.GetByID(ctx, form.InReplyToID, repliedStatus); err != nil { - if err == db.ErrNoEntries { - err := fmt.Errorf("status with id %s not replyable because it doesn't exist", form.InReplyToID) - return gtserror.NewErrorBadRequest(err, err.Error()) - } - err := fmt.Errorf("db error fetching status with id %s: %s", form.InReplyToID, err) - return gtserror.NewErrorInternalError(err) - } - if !*repliedStatus.Replyable { - err := fmt.Errorf("status with id %s is marked as not replyable", form.InReplyToID) - return gtserror.NewErrorForbidden(err, err.Error()) - } - - if err := p.db.GetByID(ctx, repliedStatus.AccountID, repliedAccount); err != nil { - if err == db.ErrNoEntries { - err := fmt.Errorf("status with id %s not replyable because account id %s is not known", form.InReplyToID, repliedStatus.AccountID) - return gtserror.NewErrorBadRequest(err, err.Error()) - } - err := fmt.Errorf("db error fetching account with id %s: %s", repliedStatus.AccountID, err) - return gtserror.NewErrorInternalError(err) - } - - if blocked, err := p.db.IsBlocked(ctx, thisAccountID, repliedAccount.ID, true); err != nil { - err := fmt.Errorf("db error checking block: %s", err) - return gtserror.NewErrorInternalError(err) - } else if blocked { - err := fmt.Errorf("status with id %s not replyable", form.InReplyToID) - return gtserror.NewErrorNotFound(err) - } - - status.InReplyToID = repliedStatus.ID - status.InReplyToURI = repliedStatus.URI - status.InReplyToAccountID = repliedAccount.ID - - return nil -} - -func (p *processor) ProcessMediaIDs(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) gtserror.WithCode { - if form.MediaIDs == nil { - return nil - } - - attachments := []*gtsmodel.MediaAttachment{} - attachmentIDs := []string{} - for _, mediaID := range form.MediaIDs { - attachment, err := p.db.GetAttachmentByID(ctx, mediaID) - if err != nil { - if errors.Is(err, db.ErrNoEntries) { - err = fmt.Errorf("ProcessMediaIDs: media not found for media id %s", mediaID) - return gtserror.NewErrorBadRequest(err, err.Error()) - } - err = fmt.Errorf("ProcessMediaIDs: db error for media id %s", mediaID) - return gtserror.NewErrorInternalError(err) - } - - if attachment.AccountID != thisAccountID { - err = fmt.Errorf("ProcessMediaIDs: media with id %s does not belong to account %s", mediaID, thisAccountID) - return gtserror.NewErrorBadRequest(err, err.Error()) - } - - if attachment.StatusID != "" || attachment.ScheduledStatusID != "" { - err = fmt.Errorf("ProcessMediaIDs: media with id %s is already attached to a status", mediaID) - return gtserror.NewErrorBadRequest(err, err.Error()) - } - - minDescriptionChars := config.GetMediaDescriptionMinChars() - if descriptionLength := len([]rune(attachment.Description)); descriptionLength < minDescriptionChars { - err = fmt.Errorf("ProcessMediaIDs: description too short! media description of at least %d chararacters is required but %d was provided for media with id %s", minDescriptionChars, descriptionLength, mediaID) - return gtserror.NewErrorBadRequest(err, err.Error()) - } - - attachments = append(attachments, attachment) - attachmentIDs = append(attachmentIDs, attachment.ID) - } - - status.Attachments = attachments - status.AttachmentIDs = attachmentIDs - return nil -} - -func (p *processor) ProcessLanguage(ctx context.Context, 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) ProcessContent(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error { - // if there's nothing in the status at all we can just return early - if form.Status == "" { - status.Content = "" - return nil - } - - // if format wasn't specified we should try to figure out what format this user prefers - if form.Format == "" { - acct, err := p.db.GetAccountByID(ctx, accountID) - if err != nil { - return fmt.Errorf("error processing new content: couldn't retrieve account from db to check post format: %s", err) - } - - switch acct.StatusFormat { - case "plain": - form.Format = apimodel.StatusFormatPlain - case "markdown": - form.Format = apimodel.StatusFormatMarkdown - default: - form.Format = apimodel.StatusFormatDefault - } - } - - // parse content out of the status depending on what format has been submitted - var f text.FormatFunc - switch form.Format { - case apimodel.StatusFormatPlain: - f = p.formatter.FromPlain - case apimodel.StatusFormatMarkdown: - f = p.formatter.FromMarkdown - default: - return fmt.Errorf("format %s not recognised as a valid status format", form.Format) - } - formatted := f(ctx, p.parseMention, accountID, status.ID, form.Status) - - // add full populated gts {mentions, tags, emojis} to the status for passing them around conveniently - // add just their ids to the status for putting in the db - status.Mentions = formatted.Mentions - status.MentionIDs = make([]string, 0, len(formatted.Mentions)) - for _, gtsmention := range formatted.Mentions { - status.MentionIDs = append(status.MentionIDs, gtsmention.ID) - } - - status.Tags = formatted.Tags - status.TagIDs = make([]string, 0, len(formatted.Tags)) - for _, gtstag := range formatted.Tags { - status.TagIDs = append(status.TagIDs, gtstag.ID) - } - - status.Emojis = formatted.Emojis - status.EmojiIDs = make([]string, 0, len(formatted.Emojis)) - for _, gtsemoji := range formatted.Emojis { - status.EmojiIDs = append(status.EmojiIDs, gtsemoji.ID) - } - - spoilerformatted := p.formatter.FromPlainEmojiOnly(ctx, p.parseMention, accountID, status.ID, form.SpoilerText) - for _, gtsemoji := range spoilerformatted.Emojis { - status.Emojis = append(status.Emojis, gtsemoji) - status.EmojiIDs = append(status.EmojiIDs, gtsemoji.ID) - } - - status.Content = formatted.HTML - return nil -} diff --git a/internal/processing/status/util_test.go b/internal/processing/status/util_test.go deleted file mode 100644 index acd823188..000000000 --- a/internal/processing/status/util_test.go +++ /dev/null @@ -1,155 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package status_test - -import ( - "context" - "fmt" - "testing" - - "github.com/stretchr/testify/suite" - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -const ( - statusText1 = "Another test @foss_satan@fossbros-anonymous.io\n\n#Hashtag\n\nText" - statusText1Expected = "

Another test @foss_satan

#Hashtag

Text

" - statusText2 = "Another test @foss_satan@fossbros-anonymous.io\n\n#Hashtag\n\n#hashTAG" - status2TextExpected = "

Another test @foss_satan

#Hashtag

#hashTAG

" -) - -type UtilTestSuite struct { - StatusStandardTestSuite -} - -func (suite *UtilTestSuite) TestProcessContent1() { - /* - TEST PREPARATION - */ - // we need to partially process the status first since processContent expects a status with some stuff already set on it - creatingAccount := suite.testAccounts["local_account_1"] - mentionedAccount := suite.testAccounts["remote_account_1"] - form := &apimodel.AdvancedStatusCreateForm{ - StatusCreateRequest: apimodel.StatusCreateRequest{ - Status: statusText1, - MediaIDs: []string{}, - Poll: nil, - InReplyToID: "", - Sensitive: false, - SpoilerText: "", - Visibility: apimodel.VisibilityPublic, - ScheduledAt: "", - Language: "en", - Format: apimodel.StatusFormatPlain, - }, - AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{ - Federated: nil, - Boostable: nil, - Replyable: nil, - Likeable: nil, - }, - } - - status := >smodel.Status{ - ID: "01FCTDD78JJMX3K9KPXQ7ZQ8BJ", - } - - /* - ACTUAL TEST - */ - - err := suite.status.ProcessContent(context.Background(), form, creatingAccount.ID, status) - suite.NoError(err) - suite.Equal(statusText1Expected, status.Content) - - suite.Len(status.Mentions, 1) - newMention := status.Mentions[0] - suite.Equal(mentionedAccount.ID, newMention.TargetAccountID) - suite.Equal(creatingAccount.ID, newMention.OriginAccountID) - suite.Equal(creatingAccount.URI, newMention.OriginAccountURI) - suite.Equal(status.ID, newMention.StatusID) - suite.Equal(fmt.Sprintf("@%s@%s", mentionedAccount.Username, mentionedAccount.Domain), newMention.NameString) - suite.Equal(mentionedAccount.URI, newMention.TargetAccountURI) - suite.Equal(mentionedAccount.URL, newMention.TargetAccountURL) - suite.NotNil(newMention.OriginAccount) - - suite.Len(status.MentionIDs, 1) - suite.Equal(newMention.ID, status.MentionIDs[0]) -} - -func (suite *UtilTestSuite) TestProcessContent2() { - /* - TEST PREPARATION - */ - // we need to partially process the status first since processContent expects a status with some stuff already set on it - creatingAccount := suite.testAccounts["local_account_1"] - mentionedAccount := suite.testAccounts["remote_account_1"] - form := &apimodel.AdvancedStatusCreateForm{ - StatusCreateRequest: apimodel.StatusCreateRequest{ - Status: statusText2, - MediaIDs: []string{}, - Poll: nil, - InReplyToID: "", - Sensitive: false, - SpoilerText: "", - Visibility: apimodel.VisibilityPublic, - ScheduledAt: "", - Language: "en", - Format: apimodel.StatusFormatPlain, - }, - AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{ - Federated: nil, - Boostable: nil, - Replyable: nil, - Likeable: nil, - }, - } - - status := >smodel.Status{ - ID: "01FCTDD78JJMX3K9KPXQ7ZQ8BJ", - } - - /* - ACTUAL TEST - */ - - err := suite.status.ProcessContent(context.Background(), form, creatingAccount.ID, status) - suite.NoError(err) - - suite.Equal(status2TextExpected, status.Content) - - suite.Len(status.Mentions, 1) - newMention := status.Mentions[0] - suite.Equal(mentionedAccount.ID, newMention.TargetAccountID) - suite.Equal(creatingAccount.ID, newMention.OriginAccountID) - suite.Equal(creatingAccount.URI, newMention.OriginAccountURI) - suite.Equal(status.ID, newMention.StatusID) - suite.Equal(fmt.Sprintf("@%s@%s", mentionedAccount.Username, mentionedAccount.Domain), newMention.NameString) - suite.Equal(mentionedAccount.URI, newMention.TargetAccountURI) - suite.Equal(mentionedAccount.URL, newMention.TargetAccountURL) - suite.NotNil(newMention.OriginAccount) - - suite.Len(status.MentionIDs, 1) - suite.Equal(newMention.ID, status.MentionIDs[0]) -} - -func TestUtilTestSuite(t *testing.T) { - suite.Run(t, new(UtilTestSuite)) -} diff --git a/internal/processing/statustimeline.go b/internal/processing/statustimeline.go index 45a1c4508..7c9f36f16 100644 --- a/internal/processing/statustimeline.go +++ b/internal/processing/statustimeline.go @@ -137,7 +137,7 @@ func StatusSkipInsertFunction() timeline.SkipInsertFunction { } } -func (p *processor) HomeTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.PageableResponse, gtserror.WithCode) { +func (p *Processor) HomeTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.PageableResponse, gtserror.WithCode) { preparedItems, err := p.statusTimelines.GetTimeline(ctx, authed.Account.ID, maxID, sinceID, minID, limit, local) if err != nil { return nil, gtserror.NewErrorInternalError(err) @@ -172,7 +172,7 @@ func (p *processor) HomeTimelineGet(ctx context.Context, authed *oauth.Auth, max }) } -func (p *processor) PublicTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.PageableResponse, gtserror.WithCode) { +func (p *Processor) PublicTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.PageableResponse, gtserror.WithCode) { statuses, err := p.db.GetPublicTimeline(ctx, maxID, sinceID, minID, limit, local) if err != nil { if err == db.ErrNoEntries { @@ -217,7 +217,7 @@ func (p *processor) PublicTimelineGet(ctx context.Context, authed *oauth.Auth, m }) } -func (p *processor) FavedTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) { +func (p *Processor) FavedTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) { statuses, nextMaxID, prevMinID, err := p.db.GetFavedTimeline(ctx, authed.Account.ID, maxID, minID, limit) if err != nil { if err == db.ErrNoEntries { @@ -251,7 +251,7 @@ func (p *processor) FavedTimelineGet(ctx context.Context, authed *oauth.Auth, ma }) } -func (p *processor) filterPublicStatuses(ctx context.Context, authed *oauth.Auth, statuses []*gtsmodel.Status) ([]*apimodel.Status, error) { +func (p *Processor) filterPublicStatuses(ctx context.Context, authed *oauth.Auth, statuses []*gtsmodel.Status) ([]*apimodel.Status, error) { apiStatuses := []*apimodel.Status{} for _, s := range statuses { targetAccount := >smodel.Account{} @@ -284,7 +284,7 @@ func (p *processor) filterPublicStatuses(ctx context.Context, authed *oauth.Auth return apiStatuses, nil } -func (p *processor) filterFavedStatuses(ctx context.Context, authed *oauth.Auth, statuses []*gtsmodel.Status) ([]*apimodel.Status, error) { +func (p *Processor) filterFavedStatuses(ctx context.Context, authed *oauth.Auth, statuses []*gtsmodel.Status) ([]*apimodel.Status, error) { apiStatuses := []*apimodel.Status{} for _, s := range statuses { targetAccount := >smodel.Account{} diff --git a/internal/processing/streaming/authorize.go b/internal/processing/stream/authorize.go similarity index 88% rename from internal/processing/streaming/authorize.go rename to internal/processing/stream/authorize.go index 1581e7893..5f6811db9 100644 --- a/internal/processing/streaming/authorize.go +++ b/internal/processing/stream/authorize.go @@ -16,7 +16,7 @@ along with this program. If not, see . */ -package streaming +package stream import ( "context" @@ -27,7 +27,8 @@ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) -func (p *processor) AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, gtserror.WithCode) { +// Authorize returns an oauth2 token info in response to an access token query from the streaming API +func (p *Processor) Authorize(ctx context.Context, accessToken string) (*gtsmodel.Account, gtserror.WithCode) { ti, err := p.oauthServer.LoadAccessToken(ctx, accessToken) if err != nil { err := fmt.Errorf("could not load access token: %s", err) diff --git a/internal/processing/streaming/authorize_test.go b/internal/processing/stream/authorize_test.go similarity index 74% rename from internal/processing/streaming/authorize_test.go rename to internal/processing/stream/authorize_test.go index 17ec20424..664c63787 100644 --- a/internal/processing/streaming/authorize_test.go +++ b/internal/processing/stream/authorize_test.go @@ -16,7 +16,7 @@ along with this program. If not, see . */ -package streaming_test +package stream_test import ( "context" @@ -26,19 +26,19 @@ ) type AuthorizeTestSuite struct { - StreamingTestSuite + StreamTestSuite } func (suite *AuthorizeTestSuite) TestAuthorize() { - account1, err := suite.streamingProcessor.AuthorizeStreamingRequest(context.Background(), suite.testTokens["local_account_1"].Access) + account1, err := suite.streamProcessor.Authorize(context.Background(), suite.testTokens["local_account_1"].Access) suite.NoError(err) suite.Equal(suite.testAccounts["local_account_1"].ID, account1.ID) - account2, err := suite.streamingProcessor.AuthorizeStreamingRequest(context.Background(), suite.testTokens["local_account_2"].Access) + account2, err := suite.streamProcessor.Authorize(context.Background(), suite.testTokens["local_account_2"].Access) suite.NoError(err) suite.Equal(suite.testAccounts["local_account_2"].ID, account2.ID) - noAccount, err := suite.streamingProcessor.AuthorizeStreamingRequest(context.Background(), "aaaaaaaaaaaaaaaaaaaaa!!") + noAccount, err := suite.streamProcessor.Authorize(context.Background(), "aaaaaaaaaaaaaaaaaaaaa!!") suite.EqualError(err, "could not load access token: no entries") suite.Nil(noAccount) } diff --git a/internal/processing/streaming/streamdelete.go b/internal/processing/stream/delete.go similarity index 84% rename from internal/processing/streaming/streamdelete.go rename to internal/processing/stream/delete.go index 314e90198..0a25b4a50 100644 --- a/internal/processing/streaming/streamdelete.go +++ b/internal/processing/stream/delete.go @@ -16,7 +16,7 @@ along with this program. If not, see . */ -package streaming +package stream import ( "fmt" @@ -25,7 +25,8 @@ "github.com/superseriousbusiness/gotosocial/internal/stream" ) -func (p *processor) StreamDelete(statusID string) error { +// Delete streams the delete of the given statusID to *ALL* open streams. +func (p *Processor) Delete(statusID string) error { errs := []string{} // get all account IDs with open streams @@ -42,7 +43,7 @@ func (p *processor) StreamDelete(statusID string) error { // stream the delete to every account for _, accountID := range accountIDs { - if err := p.streamToAccount(statusID, stream.EventTypeDelete, stream.AllStatusTimelines, accountID); err != nil { + if err := p.toAccount(statusID, stream.EventTypeDelete, stream.AllStatusTimelines, accountID); err != nil { errs = append(errs, err.Error()) } } diff --git a/internal/processing/streaming/notification.go b/internal/processing/stream/notification.go similarity index 76% rename from internal/processing/streaming/notification.go rename to internal/processing/stream/notification.go index ce3d94394..cf5dbea6a 100644 --- a/internal/processing/streaming/notification.go +++ b/internal/processing/stream/notification.go @@ -16,7 +16,7 @@ along with this program. If not, see . */ -package streaming +package stream import ( "encoding/json" @@ -27,11 +27,12 @@ "github.com/superseriousbusiness/gotosocial/internal/stream" ) -func (p *processor) StreamNotificationToAccount(n *apimodel.Notification, account *gtsmodel.Account) error { +// Notify streams the given notification to any open, appropriate streams belonging to the given account. +func (p *Processor) Notify(n *apimodel.Notification, account *gtsmodel.Account) error { bytes, err := json.Marshal(n) if err != nil { return fmt.Errorf("error marshalling notification to json: %s", err) } - return p.streamToAccount(string(bytes), stream.EventTypeNotification, []string{stream.TimelineNotifications, stream.TimelineHome}, account.ID) + return p.toAccount(string(bytes), stream.EventTypeNotification, []string{stream.TimelineNotifications, stream.TimelineHome}, account.ID) } diff --git a/internal/processing/streaming/notification_test.go b/internal/processing/stream/notification_test.go similarity index 91% rename from internal/processing/streaming/notification_test.go rename to internal/processing/stream/notification_test.go index f31c169e1..56c20b61a 100644 --- a/internal/processing/streaming/notification_test.go +++ b/internal/processing/stream/notification_test.go @@ -16,7 +16,7 @@ along with this program. If not, see . */ -package streaming_test +package stream_test import ( "bytes" @@ -30,13 +30,13 @@ ) type NotificationTestSuite struct { - StreamingTestSuite + StreamTestSuite } func (suite *NotificationTestSuite) TestStreamNotification() { account := suite.testAccounts["local_account_1"] - openStream, errWithCode := suite.streamingProcessor.OpenStreamForAccount(context.Background(), account, "user") + openStream, errWithCode := suite.streamProcessor.Open(context.Background(), account, "user") suite.NoError(errWithCode) followAccount := suite.testAccounts["remote_account_1"] @@ -50,7 +50,7 @@ func (suite *NotificationTestSuite) TestStreamNotification() { Account: followAccountAPIModel, } - err = suite.streamingProcessor.StreamNotificationToAccount(notification, account) + err = suite.streamProcessor.Notify(notification, account) suite.NoError(err) msg := <-openStream.Messages diff --git a/internal/processing/streaming/openstream.go b/internal/processing/stream/open.go similarity index 89% rename from internal/processing/streaming/openstream.go rename to internal/processing/stream/open.go index 7913e6745..10d01a767 100644 --- a/internal/processing/streaming/openstream.go +++ b/internal/processing/stream/open.go @@ -16,7 +16,7 @@ along with this program. If not, see . */ -package streaming +package stream import ( "context" @@ -31,12 +31,12 @@ "github.com/superseriousbusiness/gotosocial/internal/stream" ) -func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamTimeline string) (*stream.Stream, gtserror.WithCode) { - l := log.WithContext(ctx). - WithFields(kv.Fields{ - {"account", account.ID}, - {"streamType", streamTimeline}, - }...) +// Open returns a new Stream for the given account, which will contain a channel for passing messages back to the caller. +func (p *Processor) Open(ctx context.Context, account *gtsmodel.Account, streamTimeline string) (*stream.Stream, gtserror.WithCode) { + l := log.WithContext(ctx).WithFields(kv.Fields{ + {"account", account.ID}, + {"streamType", streamTimeline}, + }...) l.Debug("received open stream request") // each stream needs a unique ID so we know to close it @@ -83,7 +83,7 @@ func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel. // waitToCloseStream waits until the hangup channel is closed for the given stream. // It then iterates through the map of streams stored by the processor, removes the stream from it, // and then closes the messages channel of the stream to indicate that the channel should no longer be read from. -func (p *processor) waitToCloseStream(account *gtsmodel.Account, thisStream *stream.Stream) { +func (p *Processor) waitToCloseStream(account *gtsmodel.Account, thisStream *stream.Stream) { <-thisStream.Hangup // wait for a hangup message // lock the stream to prevent more messages being put in it while we work diff --git a/internal/processing/streaming/openstream_test.go b/internal/processing/stream/open_test.go similarity index 88% rename from internal/processing/streaming/openstream_test.go rename to internal/processing/stream/open_test.go index 13b3c72b3..81b587b58 100644 --- a/internal/processing/streaming/openstream_test.go +++ b/internal/processing/stream/open_test.go @@ -16,7 +16,7 @@ along with this program. If not, see . */ -package streaming_test +package stream_test import ( "context" @@ -26,13 +26,13 @@ ) type OpenStreamTestSuite struct { - StreamingTestSuite + StreamTestSuite } func (suite *OpenStreamTestSuite) TestOpenStream() { account := suite.testAccounts["local_account_1"] - _, errWithCode := suite.streamingProcessor.OpenStreamForAccount(context.Background(), account, "user") + _, errWithCode := suite.streamProcessor.Open(context.Background(), account, "user") suite.NoError(errWithCode) } diff --git a/internal/processing/streaming/streamtoaccount.go b/internal/processing/stream/stream.go similarity index 70% rename from internal/processing/streaming/streamtoaccount.go rename to internal/processing/stream/stream.go index f0159b7eb..3c38e720a 100644 --- a/internal/processing/streaming/streamtoaccount.go +++ b/internal/processing/stream/stream.go @@ -16,16 +16,33 @@ along with this program. If not, see . */ -package streaming +package stream import ( "errors" + "sync" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/stream" ) -// streamToAccount streams the given payload with the given event type to any streams currently open for the given account ID. -func (p *processor) streamToAccount(payload string, event string, timelines []string, accountID string) error { +type Processor struct { + db db.DB + oauthServer oauth.Server + streamMap *sync.Map +} + +func New(db db.DB, oauthServer oauth.Server) Processor { + return Processor{ + db: db, + oauthServer: oauthServer, + streamMap: &sync.Map{}, + } +} + +// toAccount streams the given payload with the given event type to any streams currently open for the given account ID. +func (p *Processor) toAccount(payload string, event string, timelines []string, accountID string) error { v, ok := p.streamMap.Load(accountID) if !ok { // no open connections so nothing to stream diff --git a/internal/processing/streaming/streaming_test.go b/internal/processing/stream/stream_test.go similarity index 85% rename from internal/processing/streaming/streaming_test.go rename to internal/processing/stream/stream_test.go index 21323a051..907c7e1d0 100644 --- a/internal/processing/streaming/streaming_test.go +++ b/internal/processing/stream/stream_test.go @@ -16,28 +16,28 @@ along with this program. If not, see . */ -package streaming_test +package stream_test import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/internal/processing/streaming" + "github.com/superseriousbusiness/gotosocial/internal/processing/stream" "github.com/superseriousbusiness/gotosocial/testrig" ) -type StreamingTestSuite struct { +type StreamTestSuite struct { suite.Suite testAccounts map[string]*gtsmodel.Account testTokens map[string]*gtsmodel.Token db db.DB oauthServer oauth.Server - streamingProcessor streaming.Processor + streamProcessor stream.Processor } -func (suite *StreamingTestSuite) SetupTest() { +func (suite *StreamTestSuite) SetupTest() { testrig.InitTestLog() testrig.InitTestConfig() @@ -45,11 +45,11 @@ func (suite *StreamingTestSuite) SetupTest() { suite.testTokens = testrig.NewTestTokens() suite.db = testrig.NewTestDB() suite.oauthServer = testrig.NewTestOauthServer(suite.db) - suite.streamingProcessor = streaming.New(suite.db, suite.oauthServer) + suite.streamProcessor = stream.New(suite.db, suite.oauthServer) testrig.StandardDBSetup(suite.db, suite.testAccounts) } -func (suite *StreamingTestSuite) TearDownTest() { +func (suite *StreamTestSuite) TearDownTest() { testrig.StandardDBTeardown(suite.db) } diff --git a/internal/processing/streaming/update.go b/internal/processing/stream/update.go similarity index 78% rename from internal/processing/streaming/update.go rename to internal/processing/stream/update.go index e29ad6169..41ce2c4db 100644 --- a/internal/processing/streaming/update.go +++ b/internal/processing/stream/update.go @@ -16,7 +16,7 @@ along with this program. If not, see . */ -package streaming +package stream import ( "encoding/json" @@ -27,11 +27,12 @@ "github.com/superseriousbusiness/gotosocial/internal/stream" ) -func (p *processor) StreamUpdateToAccount(s *apimodel.Status, account *gtsmodel.Account, timeline string) error { +// Update streams the given update to any open, appropriate streams belonging to the given account. +func (p *Processor) Update(s *apimodel.Status, account *gtsmodel.Account, timeline string) error { bytes, err := json.Marshal(s) if err != nil { return fmt.Errorf("error marshalling status to json: %s", err) } - return p.streamToAccount(string(bytes), stream.EventTypeUpdate, []string{timeline}, account.ID) + return p.toAccount(string(bytes), stream.EventTypeUpdate, []string{timeline}, account.ID) } diff --git a/internal/processing/streaming.go b/internal/processing/streaming.go deleted file mode 100644 index acc0c8d5e..000000000 --- a/internal/processing/streaming.go +++ /dev/null @@ -1,35 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package processing - -import ( - "context" - - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/stream" -) - -func (p *processor) AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, gtserror.WithCode) { - return p.streamingProcessor.AuthorizeStreamingRequest(ctx, accessToken) -} - -func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*stream.Stream, gtserror.WithCode) { - return p.streamingProcessor.OpenStreamForAccount(ctx, account, streamType) -} diff --git a/internal/processing/streaming/streaming.go b/internal/processing/streaming/streaming.go deleted file mode 100644 index 4b2a80cc8..000000000 --- a/internal/processing/streaming/streaming.go +++ /dev/null @@ -1,60 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package streaming - -import ( - "context" - "sync" - - 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/stream" -) - -// Processor wraps a bunch of functions for processing streaming. -type Processor interface { - // AuthorizeStreamingRequest returns an oauth2 token info in response to an access token query from the streaming API - AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, gtserror.WithCode) - // OpenStreamForAccount returns a new Stream for the given account, which will contain a channel for passing messages back to the caller. - OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, timeline string) (*stream.Stream, gtserror.WithCode) - // StreamUpdateToAccount streams the given update to any open, appropriate streams belonging to the given account. - StreamUpdateToAccount(s *apimodel.Status, account *gtsmodel.Account, timeline string) error - // StreamNotificationToAccount streams the given notification to any open, appropriate streams belonging to the given account. - StreamNotificationToAccount(n *apimodel.Notification, account *gtsmodel.Account) error - // StreamDelete streams the delete of the given statusID to *ALL* open streams. - StreamDelete(statusID string) error -} - -type processor struct { - db db.DB - oauthServer oauth.Server - streamMap *sync.Map -} - -// New returns a new status processor. -func New(db db.DB, oauthServer oauth.Server) Processor { - return &processor{ - db: db, - oauthServer: oauthServer, - streamMap: &sync.Map{}, - } -} diff --git a/internal/processing/user.go b/internal/processing/user.go deleted file mode 100644 index 5685a9ba9..000000000 --- a/internal/processing/user.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package processing - -import ( - "context" - - 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/oauth" -) - -func (p *processor) UserChangePassword(ctx context.Context, authed *oauth.Auth, form *apimodel.PasswordChangeRequest) gtserror.WithCode { - return p.userProcessor.ChangePassword(ctx, authed.User, form.OldPassword, form.NewPassword) -} - -func (p *processor) UserConfirmEmail(ctx context.Context, token string) (*gtsmodel.User, gtserror.WithCode) { - return p.userProcessor.ConfirmEmail(ctx, token) -} diff --git a/internal/processing/user/emailconfirm.go b/internal/processing/user/email.go similarity index 91% rename from internal/processing/user/emailconfirm.go rename to internal/processing/user/email.go index 3bc889024..349e27f47 100644 --- a/internal/processing/user/emailconfirm.go +++ b/internal/processing/user/email.go @@ -35,7 +35,8 @@ var oneWeek = 168 * time.Hour -func (p *processor) SendConfirmEmail(ctx context.Context, user *gtsmodel.User, username string) error { +// EmailSendConfirmation sends an email address confirmation request email to the given user. +func (p *Processor) EmailSendConfirmation(ctx context.Context, user *gtsmodel.User, username string) error { if user.UnconfirmedEmail == "" || user.UnconfirmedEmail == user.Email { // user has already confirmed this email address, so there's nothing to do return nil @@ -84,7 +85,9 @@ func (p *processor) SendConfirmEmail(ctx context.Context, user *gtsmodel.User, u return nil } -func (p *processor) ConfirmEmail(ctx context.Context, token string) (*gtsmodel.User, gtserror.WithCode) { +// EmailConfirm processes an email confirmation request, usually initiated as a result of clicking on a link +// in a 'confirm your email address' type email. +func (p *Processor) EmailConfirm(ctx context.Context, token string) (*gtsmodel.User, gtserror.WithCode) { if token == "" { return nil, gtserror.NewErrorNotFound(errors.New("no token provided")) } diff --git a/internal/processing/user/emailconfirm_test.go b/internal/processing/user/email_test.go similarity index 95% rename from internal/processing/user/emailconfirm_test.go rename to internal/processing/user/email_test.go index a13a130d0..f66b7987c 100644 --- a/internal/processing/user/emailconfirm_test.go +++ b/internal/processing/user/email_test.go @@ -41,7 +41,7 @@ func (suite *EmailConfirmTestSuite) TestSendConfirmEmail() { user.ConfirmationSentAt = time.Time{} user.ConfirmationToken = "" - err := suite.user.SendConfirmEmail(context.Background(), user, "the_mighty_zork") + err := suite.user.EmailSendConfirmation(context.Background(), user, "the_mighty_zork") suite.NoError(err) // zork should have an email now @@ -78,7 +78,7 @@ func (suite *EmailConfirmTestSuite) TestConfirmEmail() { suite.NoError(err) // confirm with the token set above - updatedUser, errWithCode := suite.user.ConfirmEmail(ctx, "1d1aa44b-afa4-49c8-ac4b-eceb61715cc6") + updatedUser, errWithCode := suite.user.EmailConfirm(ctx, "1d1aa44b-afa4-49c8-ac4b-eceb61715cc6") suite.NoError(errWithCode) // email should now be confirmed and token cleared @@ -106,7 +106,7 @@ func (suite *EmailConfirmTestSuite) TestConfirmEmailOldToken() { suite.NoError(err) // confirm with the token set above - updatedUser, errWithCode := suite.user.ConfirmEmail(ctx, "1d1aa44b-afa4-49c8-ac4b-eceb61715cc6") + updatedUser, errWithCode := suite.user.EmailConfirm(ctx, "1d1aa44b-afa4-49c8-ac4b-eceb61715cc6") suite.Nil(updatedUser) suite.EqualError(errWithCode, "ConfirmEmail: confirmation token expired") } diff --git a/internal/processing/user/changepassword.go b/internal/processing/user/password.go similarity index 92% rename from internal/processing/user/changepassword.go rename to internal/processing/user/password.go index 03b8c4525..3475e005e 100644 --- a/internal/processing/user/changepassword.go +++ b/internal/processing/user/password.go @@ -27,7 +27,8 @@ "golang.org/x/crypto/bcrypt" ) -func (p *processor) ChangePassword(ctx context.Context, user *gtsmodel.User, oldPassword string, newPassword string) gtserror.WithCode { +// PasswordChange processes a password change request for the given user. +func (p *Processor) PasswordChange(ctx context.Context, user *gtsmodel.User, oldPassword string, newPassword string) gtserror.WithCode { if err := bcrypt.CompareHashAndPassword([]byte(user.EncryptedPassword), []byte(oldPassword)); err != nil { return gtserror.NewErrorUnauthorized(err, "old password was incorrect") } diff --git a/internal/processing/user/changepassword_test.go b/internal/processing/user/password_test.go similarity index 94% rename from internal/processing/user/changepassword_test.go rename to internal/processing/user/password_test.go index 74676b323..a02581b5b 100644 --- a/internal/processing/user/changepassword_test.go +++ b/internal/processing/user/password_test.go @@ -35,7 +35,7 @@ type ChangePasswordTestSuite struct { func (suite *ChangePasswordTestSuite) TestChangePasswordOK() { user := suite.testUsers["local_account_1"] - errWithCode := suite.user.ChangePassword(context.Background(), user, "password", "verygoodnewpassword") + errWithCode := suite.user.PasswordChange(context.Background(), user, "password", "verygoodnewpassword") suite.NoError(errWithCode) err := bcrypt.CompareHashAndPassword([]byte(user.EncryptedPassword), []byte("verygoodnewpassword")) @@ -54,7 +54,7 @@ func (suite *ChangePasswordTestSuite) TestChangePasswordOK() { func (suite *ChangePasswordTestSuite) TestChangePasswordIncorrectOld() { user := suite.testUsers["local_account_1"] - errWithCode := suite.user.ChangePassword(context.Background(), user, "ooooopsydoooopsy", "verygoodnewpassword") + errWithCode := suite.user.PasswordChange(context.Background(), user, "ooooopsydoooopsy", "verygoodnewpassword") suite.EqualError(errWithCode, "crypto/bcrypt: hashedPassword is not the hash of the given password") suite.Equal(http.StatusUnauthorized, errWithCode.Code()) suite.Equal("Unauthorized: old password was incorrect", errWithCode.Safe()) @@ -72,7 +72,7 @@ func (suite *ChangePasswordTestSuite) TestChangePasswordIncorrectOld() { func (suite *ChangePasswordTestSuite) TestChangePasswordWeakNew() { user := suite.testUsers["local_account_1"] - errWithCode := suite.user.ChangePassword(context.Background(), user, "password", "1234") + errWithCode := suite.user.PasswordChange(context.Background(), user, "password", "1234") suite.EqualError(errWithCode, "password is only 11% strength, try including more special characters, using lowercase letters, using uppercase letters or using a longer password") suite.Equal(http.StatusBadRequest, errWithCode.Code()) suite.Equal("Bad Request: password is only 11% strength, try including more special characters, using lowercase letters, using uppercase letters or using a longer password", errWithCode.Safe()) diff --git a/internal/processing/user/user.go b/internal/processing/user/user.go index 5ce8cd803..fce628d0c 100644 --- a/internal/processing/user/user.go +++ b/internal/processing/user/user.go @@ -19,33 +19,18 @@ package user import ( - "context" - "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/email" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) -// Processor wraps a bunch of functions for processing user-level actions. -type Processor interface { - // ChangePassword changes the specified user's password from old => new, - // or returns an error if the new password is too weak, or the old password is incorrect. - ChangePassword(ctx context.Context, user *gtsmodel.User, oldPassword string, newPassword string) gtserror.WithCode - // SendConfirmEmail sends a 'confirm-your-email-address' type email to a user. - SendConfirmEmail(ctx context.Context, user *gtsmodel.User, username string) error - // ConfirmEmail confirms an email address using the given token. - ConfirmEmail(ctx context.Context, token string) (*gtsmodel.User, gtserror.WithCode) -} - -type processor struct { +type Processor struct { emailSender email.Sender db db.DB } // New returns a new user processor func New(db db.DB, emailSender email.Sender) Processor { - return &processor{ + return Processor{ emailSender: emailSender, db: db, } diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go index 6a783e190..c63bd8d8c 100644 --- a/internal/typeutils/converter.go +++ b/internal/typeutils/converter.go @@ -100,13 +100,6 @@ type TypeConverter interface { StatusToRSSItem(ctx context.Context, s *gtsmodel.Status) (*feeds.Item, error) - /* - FRONTEND (api) MODEL TO INTERNAL (gts) MODEL - */ - - // APIVisToVis converts an API model visibility into its internal gts equivalent. - APIVisToVis(m apimodel.Visibility) gtsmodel.Visibility - /* ACTIVITYSTREAMS MODEL TO INTERNAL (gts) MODEL */ diff --git a/internal/typeutils/frontendtointernal.go b/internal/typeutils/frontendtointernal.go index 01e6cf5de..80849a9bb 100644 --- a/internal/typeutils/frontendtointernal.go +++ b/internal/typeutils/frontendtointernal.go @@ -23,7 +23,7 @@ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) -func (c *converter) APIVisToVis(m apimodel.Visibility) gtsmodel.Visibility { +func APIVisToVis(m apimodel.Visibility) gtsmodel.Visibility { switch m { case apimodel.VisibilityPublic: return gtsmodel.VisibilityPublic diff --git a/internal/web/confirmemail.go b/internal/web/confirmemail.go index 7fa144543..8efb22cc8 100644 --- a/internal/web/confirmemail.go +++ b/internal/web/confirmemail.go @@ -37,7 +37,7 @@ func (m *Module) confirmEmailGETHandler(c *gin.Context) { return } - user, errWithCode := m.processor.UserConfirmEmail(ctx, token) + user, errWithCode := m.processor.User().EmailConfirm(ctx, token) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/web/customcss.go b/internal/web/customcss.go index 913f3be01..5f41d890b 100644 --- a/internal/web/customcss.go +++ b/internal/web/customcss.go @@ -51,7 +51,7 @@ func (m *Module) customCSSGETHandler(c *gin.Context) { return } - customCSS, errWithCode := m.processor.AccountGetCustomCSSForUsername(c.Request.Context(), username) + customCSS, errWithCode := m.processor.Account().GetCustomCSSForUsername(c.Request.Context(), username) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/web/profile.go b/internal/web/profile.go index 482e5caa7..2319e5dc9 100644 --- a/internal/web/profile.go +++ b/internal/web/profile.go @@ -66,7 +66,7 @@ func (m *Module) profileGETHandler(c *gin.Context) { return instance, nil } - account, errWithCode := m.processor.AccountGetLocalByUsername(ctx, authed, username) + account, errWithCode := m.processor.Account().GetLocalByUsername(ctx, authed.Account, username) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, instanceGet) return @@ -102,7 +102,7 @@ func (m *Module) profileGETHandler(c *gin.Context) { showBackToTop = true } - statusResp, errWithCode := m.processor.AccountWebStatusesGet(ctx, account.ID, maxStatusID) + statusResp, errWithCode := m.processor.Account().WebStatusesGet(ctx, account.ID, maxStatusID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, instanceGet) return @@ -142,7 +142,7 @@ func (m *Module) returnAPProfile(ctx context.Context, c *gin.Context, username s ctx = context.WithValue(ctx, ap.ContextRequestingPublicKeySignature, signature) } - user, errWithCode := m.processor.GetFediUser(ctx, username, c.Request.URL) + user, errWithCode := m.processor.Fedi().UserGet(ctx, username, c.Request.URL) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) //nolint:contextcheck return diff --git a/internal/web/rss.go b/internal/web/rss.go index dccb49542..819494c98 100644 --- a/internal/web/rss.go +++ b/internal/web/rss.go @@ -99,7 +99,7 @@ func (m *Module) rssFeedGETHandler(c *gin.Context) { ifNoneMatch := c.Request.Header.Get(ifNoneMatchHeader) ifModifiedSince := extractIfModifiedSince(c.Request) - getRssFeed, accountLastPostedPublic, errWithCode := m.processor.AccountGetRSSFeedForUsername(ctx, username) + getRssFeed, accountLastPostedPublic, errWithCode := m.processor.Account().GetRSSFeedForUsername(ctx, username) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/web/thread.go b/internal/web/thread.go index af5363fd1..e657aa91b 100644 --- a/internal/web/thread.go +++ b/internal/web/thread.go @@ -72,12 +72,12 @@ func (m *Module) threadGETHandler(c *gin.Context) { // do this check to make sure the status is actually from a local account, // we shouldn't render threads from statuses that don't belong to us! - if _, errWithCode := m.processor.AccountGetLocalByUsername(ctx, authed, username); errWithCode != nil { + if _, errWithCode := m.processor.Account().GetLocalByUsername(ctx, authed.Account, username); errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, instanceGet) return } - status, errWithCode := m.processor.StatusGet(ctx, authed, statusID) + status, errWithCode := m.processor.Status().Get(ctx, authed.Account, statusID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, instanceGet) return @@ -97,7 +97,7 @@ func (m *Module) threadGETHandler(c *gin.Context) { return } - context, errWithCode := m.processor.StatusGetContext(ctx, authed, statusID) + context, errWithCode := m.processor.Status().ContextGet(ctx, authed.Account, statusID) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, instanceGet) return @@ -132,7 +132,7 @@ func (m *Module) returnAPStatus(ctx context.Context, c *gin.Context, username st ctx = context.WithValue(ctx, ap.ContextRequestingPublicKeySignature, signature) } - status, errWithCode := m.processor.GetFediStatus(ctx, username, statusID, c.Request.URL) + status, errWithCode := m.processor.Fedi().StatusGet(ctx, username, statusID, c.Request.URL) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) //nolint:contextcheck return diff --git a/internal/web/web.go b/internal/web/web.go index a6b5a45da..ef0100cae 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -61,12 +61,12 @@ ) type Module struct { - processor processing.Processor + processor *processing.Processor eTagCache cache.Cache[string, eTagCacheEntry] isURIBlocked func(context.Context, *url.URL) (bool, db.Error) } -func New(db db.DB, processor processing.Processor) *Module { +func New(db db.DB, processor *processing.Processor) *Module { return &Module{ processor: processor, eTagCache: newETagCache(), diff --git a/testrig/processor.go b/testrig/processor.go index f86e5c4c7..f451d4ad0 100644 --- a/testrig/processor.go +++ b/testrig/processor.go @@ -30,6 +30,6 @@ ) // NewTestProcessor returns a Processor suitable for testing purposes -func NewTestProcessor(db db.DB, storage *storage.Driver, federator federation.Federator, emailSender email.Sender, mediaManager media.Manager, clientWorker *concurrency.WorkerPool[messages.FromClientAPI], fedWorker *concurrency.WorkerPool[messages.FromFederator]) processing.Processor { +func NewTestProcessor(db db.DB, storage *storage.Driver, federator federation.Federator, emailSender email.Sender, mediaManager media.Manager, clientWorker *concurrency.WorkerPool[messages.FromClientAPI], fedWorker *concurrency.WorkerPool[messages.FromFederator]) *processing.Processor { return processing.NewProcessor(NewTestTypeConverter(db), federator, NewTestOauthServer(db), mediaManager, storage, db, emailSender, clientWorker, fedWorker) }