summaryrefslogtreecommitdiff
path: root/internal/processing/account
diff options
context:
space:
mode:
Diffstat (limited to 'internal/processing/account')
-rw-r--r--internal/processing/account/account.go96
-rw-r--r--internal/processing/account/create.go64
-rw-r--r--internal/processing/account/createfollow.go116
-rw-r--r--internal/processing/account/delete.go270
-rw-r--r--internal/processing/account/get.go59
-rw-r--r--internal/processing/account/getfollowers.go79
-rw-r--r--internal/processing/account/getfollowing.go79
-rw-r--r--internal/processing/account/getrelationship.go46
-rw-r--r--internal/processing/account/getstatuses.go63
-rw-r--r--internal/processing/account/removefollow.go110
-rw-r--r--internal/processing/account/update.go199
11 files changed, 1181 insertions, 0 deletions
diff --git a/internal/processing/account/account.go b/internal/processing/account/account.go
new file mode 100644
index 000000000..442b6fa9f
--- /dev/null
+++ b/internal/processing/account/account.go
@@ -0,0 +1,96 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package account
+
+import (
+ "mime/multipart"
+
+ "github.com/sirupsen/logrus"
+ apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/config"
+ "github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/federation"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/media"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+ "github.com/superseriousbusiness/gotosocial/internal/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(applicationToken oauth2.TokenInfo, application *gtsmodel.Application, form *apimodel.AccountCreateRequest) (*apimodel.Token, error)
+ // Delete deletes an account, and all of that account's statuses, media, follows, notifications, etc etc etc.
+ Delete(account *gtsmodel.Account, deletedBy string) error
+ // Get processes the given request for account information.
+ Get(requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Account, error)
+ // Update processes the update of an account with the given form
+ Update(account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error)
+ // StatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for
+ // the account given in authed.
+ StatusesGet(requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, maxID string, pinned bool, mediaOnly bool) ([]apimodel.Status, gtserror.WithCode)
+ // FollowersGet fetches a list of the target account's followers.
+ FollowersGet(requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode)
+ // FollowingGet fetches a list of the accounts that target account is following.
+ FollowingGet(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(requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode)
+ // FollowCreate handles a follow request to an account, either remote or local.
+ FollowCreate(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(requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode)
+ // 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.
+ UpdateAvatar(avatar *multipart.FileHeader, accountID string) (*gtsmodel.MediaAttachment, error)
+ // 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.
+ UpdateHeader(header *multipart.FileHeader, accountID string) (*gtsmodel.MediaAttachment, error)
+}
+
+type processor struct {
+ tc typeutils.TypeConverter
+ config *config.Config
+ mediaHandler media.Handler
+ fromClientAPI chan gtsmodel.FromClientAPI
+ oauthServer oauth.Server
+ filter visibility.Filter
+ db db.DB
+ federator federation.Federator
+ log *logrus.Logger
+}
+
+// New returns a new account processor.
+func New(db db.DB, tc typeutils.TypeConverter, mediaHandler media.Handler, oauthServer oauth.Server, fromClientAPI chan gtsmodel.FromClientAPI, federator federation.Federator, config *config.Config, log *logrus.Logger) Processor {
+ return &processor{
+ tc: tc,
+ config: config,
+ mediaHandler: mediaHandler,
+ fromClientAPI: fromClientAPI,
+ oauthServer: oauthServer,
+ filter: visibility.NewFilter(db, log),
+ db: db,
+ federator: federator,
+ log: log,
+ }
+}
diff --git a/internal/processing/account/create.go b/internal/processing/account/create.go
new file mode 100644
index 000000000..a6bfb8a60
--- /dev/null
+++ b/internal/processing/account/create.go
@@ -0,0 +1,64 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package account
+
+import (
+ "fmt"
+
+ apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/oauth2/v4"
+)
+
+func (p *processor) Create(applicationToken oauth2.TokenInfo, application *gtsmodel.Application, form *apimodel.AccountCreateRequest) (*apimodel.Token, error) {
+ l := p.log.WithField("func", "accountCreate")
+
+ if err := p.db.IsEmailAvailable(form.Email); err != nil {
+ return nil, err
+ }
+
+ if err := p.db.IsUsernameAvailable(form.Username); err != nil {
+ return nil, err
+ }
+
+ // don't store a reason if we don't require one
+ reason := form.Reason
+ if !p.config.AccountsConfig.ReasonRequired {
+ reason = ""
+ }
+
+ l.Trace("creating new username and account")
+ user, err := p.db.NewSignup(form.Username, reason, p.config.AccountsConfig.RequireApproval, form.Email, form.Password, form.IP, form.Locale, application.ID)
+ if err != nil {
+ return nil, fmt.Errorf("error creating new signup in the database: %s", err)
+ }
+
+ l.Tracef("generating a token for user %s with account %s and application %s", user.ID, user.AccountID, application.ID)
+ accessToken, err := p.oauthServer.GenerateUserAccessToken(applicationToken, application.ClientSecret, user.ID)
+ if err != nil {
+ return nil, fmt.Errorf("error creating new access token for user %s: %s", user.ID, err)
+ }
+
+ return &apimodel.Token{
+ AccessToken: accessToken.GetAccess(),
+ TokenType: "Bearer",
+ Scope: accessToken.GetScope(),
+ CreatedAt: accessToken.GetAccessCreateAt().Unix(),
+ }, nil
+}
diff --git a/internal/processing/account/createfollow.go b/internal/processing/account/createfollow.go
new file mode 100644
index 000000000..50e575f19
--- /dev/null
+++ b/internal/processing/account/createfollow.go
@@ -0,0 +1,116 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package account
+
+import (
+ "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"
+ "github.com/superseriousbusiness/gotosocial/internal/id"
+ "github.com/superseriousbusiness/gotosocial/internal/util"
+)
+
+func (p *processor) FollowCreate(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
+ blocked, err := p.db.Blocked(requestingAccount.ID, form.TargetAccountID)
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+ if blocked {
+ return nil, gtserror.NewErrorNotFound(fmt.Errorf("accountfollowcreate: block exists between accounts"))
+ }
+
+ // make sure the target account actually exists in our db
+ targetAcct := &gtsmodel.Account{}
+ if err := p.db.GetByID(form.TargetAccountID, targetAcct); err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ return nil, gtserror.NewErrorNotFound(fmt.Errorf("accountfollowcreate: account %s not found in the db: %s", form.TargetAccountID, err))
+ }
+ }
+
+ // check if a follow exists already
+ follows, err := p.db.Follows(requestingAccount, targetAcct)
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("accountfollowcreate: error checking follow in db: %s", err))
+ }
+ if follows {
+ // already follows so just return the relationship
+ return p.RelationshipGet(requestingAccount, form.TargetAccountID)
+ }
+
+ // check if a follow exists already
+ followRequested, err := p.db.FollowRequested(requestingAccount, targetAcct)
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("accountfollowcreate: error checking follow request in db: %s", err))
+ }
+ if followRequested {
+ // already follow requested so just return the relationship
+ return p.RelationshipGet(requestingAccount, form.TargetAccountID)
+ }
+
+ // make the follow request
+ newFollowID, err := id.NewRandomULID()
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ fr := &gtsmodel.FollowRequest{
+ ID: newFollowID,
+ AccountID: requestingAccount.ID,
+ TargetAccountID: form.TargetAccountID,
+ ShowReblogs: true,
+ URI: util.GenerateURIForFollow(requestingAccount.Username, p.config.Protocol, p.config.Host, newFollowID),
+ Notify: false,
+ }
+ if form.Reblogs != nil {
+ fr.ShowReblogs = *form.Reblogs
+ }
+ if form.Notify != nil {
+ fr.Notify = *form.Notify
+ }
+
+ // whack it in the database
+ if err := p.db.Put(fr); err != nil {
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("accountfollowcreate: error creating follow request in db: %s", err))
+ }
+
+ // if it's a local account that's not locked we can just straight up accept the follow request
+ if !targetAcct.Locked && targetAcct.Domain == "" {
+ if _, err := p.db.AcceptFollowRequest(requestingAccount.ID, form.TargetAccountID); err != nil {
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("accountfollowcreate: error accepting folow request for local unlocked account: %s", err))
+ }
+ // return the new relationship
+ return p.RelationshipGet(requestingAccount, form.TargetAccountID)
+ }
+
+ // otherwise we leave the follow request as it is and we handle the rest of the process asynchronously
+ p.fromClientAPI <- gtsmodel.FromClientAPI{
+ APObjectType: gtsmodel.ActivityStreamsFollow,
+ APActivityType: gtsmodel.ActivityStreamsCreate,
+ GTSModel: fr,
+ OriginAccount: requestingAccount,
+ TargetAccount: targetAcct,
+ }
+
+ // return whatever relationship results from this
+ return p.RelationshipGet(requestingAccount, form.TargetAccountID)
+}
diff --git a/internal/processing/account/delete.go b/internal/processing/account/delete.go
new file mode 100644
index 000000000..c31b67352
--- /dev/null
+++ b/internal/processing/account/delete.go
@@ -0,0 +1,270 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package account
+
+import (
+ "time"
+
+ "github.com/sirupsen/logrus"
+ "github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+)
+
+// Delete handles the complete deletion of an account.
+//
+// TODO 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(account *gtsmodel.Account, deletedBy string) error {
+ l := p.log.WithFields(logrus.Fields{
+ "func": "Delete",
+ "username": account.Username,
+ })
+
+ l.Debugf("beginning account delete process for username %s", account.Username)
+
+ // 1. Delete account's application(s), clients, and oauth tokens
+ // we only need to do this step for local account since remote ones won't have any tokens or applications on our server
+ if account.Domain == "" {
+ // see if we can get a user for this account
+ u := &gtsmodel.User{}
+ if err := p.db.GetWhere([]db.Where{{Key: "account_id", Value: account.ID}}, u); err == nil {
+ // we got one! select all tokens with the user's ID
+ tokens := []*oauth.Token{}
+ if err := p.db.GetWhere([]db.Where{{Key: "user_id", Value: u.ID}}, &tokens); err == nil {
+ // we have some tokens to delete
+ for _, t := range tokens {
+ // delete client(s) associated with this token
+ if err := p.db.DeleteByID(t.ClientID, &oauth.Client{}); err != nil {
+ l.Errorf("error deleting oauth client: %s", err)
+ }
+ // delete application(s) associated with this token
+ if err := p.db.DeleteWhere([]db.Where{{Key: "client_id", Value: t.ClientID}}, &gtsmodel.Application{}); err != nil {
+ l.Errorf("error deleting application: %s", err)
+ }
+ // delete the token itself
+ if err := p.db.DeleteByID(t.ID, t); err != nil {
+ l.Errorf("error deleting oauth token: %s", err)
+ }
+ }
+ }
+ }
+ }
+
+ // 2. Delete account's blocks
+ l.Debug("deleting account blocks")
+ // first delete any blocks that this account created
+ if err := p.db.DeleteWhere([]db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.Block{}); err != nil {
+ l.Errorf("error deleting blocks created by account: %s", err)
+ }
+
+ // now delete any blocks that target this account
+ if err := p.db.DeleteWhere([]db.Where{{Key: "target_account_id", Value: account.ID}}, &[]*gtsmodel.Block{}); err != nil {
+ l.Errorf("error deleting blocks targeting account: %s", err)
+ }
+
+ // 3. Delete account's emoji
+ // nothing to do here
+
+ // 4. Delete account's follow requests
+ l.Debug("deleting account follow requests")
+ // first delete any follow requests that this account created
+ if err := p.db.DeleteWhere([]db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.FollowRequest{}); err != nil {
+ l.Errorf("error deleting follow requests created by account: %s", err)
+ }
+
+ // now delete any follow requests that target this account
+ if err := p.db.DeleteWhere([]db.Where{{Key: "target_account_id", Value: account.ID}}, &[]*gtsmodel.FollowRequest{}); err != nil {
+ l.Errorf("error deleting follow requests targeting account: %s", err)
+ }
+
+ // 5. Delete account's follows
+ l.Debug("deleting account follows")
+ // first delete any follows that this account created
+ if err := p.db.DeleteWhere([]db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.Follow{}); err != nil {
+ l.Errorf("error deleting follows created by account: %s", err)
+ }
+
+ // now delete any follows that target this account
+ if err := p.db.DeleteWhere([]db.Where{{Key: "target_account_id", Value: account.ID}}, &[]*gtsmodel.Follow{}); err != nil {
+ l.Errorf("error deleting follows targeting account: %s", err)
+ }
+
+ // 6. Delete account's statuses
+ l.Debug("deleting account statuses")
+ // we'll select statuses 20 at a time so we don't wreck the db, and pass them through to the client api channel
+ // Deleting the statuses in this way also handles 7. Delete account's media attachments, 8. Delete account's mentions, and 9. Delete account's polls,
+ // since these are all attached to statuses.
+ var maxID string
+selectStatusesLoop:
+ for {
+ statuses, err := p.db.GetStatusesForAccount(account.ID, 20, false, maxID, false, false)
+ if err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ // no statuses left for this instance so we're done
+ l.Infof("Delete: done iterating through statuses for account %s", account.Username)
+ break selectStatusesLoop
+ }
+ // an actual error has occurred
+ l.Errorf("Delete: db error selecting statuses for account %s: %s", account.Username, err)
+ break selectStatusesLoop
+ }
+
+ for i, s := range statuses {
+ // pass the status delete through the client api channel for processing
+ s.GTSAuthorAccount = account
+ l.Debug("putting status in the client api channel")
+ p.fromClientAPI <- gtsmodel.FromClientAPI{
+ APObjectType: gtsmodel.ActivityStreamsNote,
+ APActivityType: gtsmodel.ActivityStreamsDelete,
+ GTSModel: s,
+ OriginAccount: account,
+ TargetAccount: account,
+ }
+
+ if err := p.db.DeleteByID(s.ID, s); err != nil {
+ if _, ok := err.(db.ErrNoEntries); !ok {
+ // actual error has occurred
+ l.Errorf("Delete: db error status %s for account %s: %s", s.ID, account.Username, err)
+ break selectStatusesLoop
+ }
+ }
+
+ // if there are any boosts of this status, delete them as well
+ boosts := []*gtsmodel.Status{}
+ if err := p.db.GetWhere([]db.Where{{Key: "boost_of_id", Value: s.ID}}, &boosts); err != nil {
+ if _, ok := err.(db.ErrNoEntries); !ok {
+ // an actual error has occurred
+ l.Errorf("Delete: db error selecting boosts of status %s for account %s: %s", s.ID, account.Username, err)
+ break selectStatusesLoop
+ }
+ }
+
+ for _, b := range boosts {
+ oa := &gtsmodel.Account{}
+ if err := p.db.GetByID(b.AccountID, oa); err == nil {
+
+ l.Debug("putting boost undo in the client api channel")
+ p.fromClientAPI <- gtsmodel.FromClientAPI{
+ APObjectType: gtsmodel.ActivityStreamsAnnounce,
+ APActivityType: gtsmodel.ActivityStreamsUndo,
+ GTSModel: s,
+ OriginAccount: oa,
+ TargetAccount: account,
+ }
+ }
+
+ if err := p.db.DeleteByID(b.ID, b); err != nil {
+ if _, ok := err.(db.ErrNoEntries); !ok {
+ // actual error has occurred
+ l.Errorf("Delete: db error deleting boost with id %s: %s", b.ID, err)
+ break selectStatusesLoop
+ }
+ }
+ }
+
+ // if this is the last status in the slice, set the maxID appropriately for the next query
+ if i == len(statuses)-1 {
+ maxID = s.ID
+ }
+ }
+ }
+ l.Debug("done deleting statuses")
+
+ // 10. Delete account's notifications
+ l.Debug("deleting account notifications")
+ if err := p.db.DeleteWhere([]db.Where{{Key: "origin_account_id", Value: account.ID}}, &[]*gtsmodel.Notification{}); err != nil {
+ l.Errorf("error deleting notifications created by account: %s", err)
+ }
+
+ // 11. Delete account's bookmarks
+ l.Debug("deleting account bookmarks")
+ if err := p.db.DeleteWhere([]db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.StatusBookmark{}); err != nil {
+ l.Errorf("error deleting bookmarks created by account: %s", err)
+ }
+
+ // 12. Delete account's faves
+ l.Debug("deleting account faves")
+ if err := p.db.DeleteWhere([]db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.StatusFave{}); err != nil {
+ l.Errorf("error deleting faves created by account: %s", err)
+ }
+
+ // 13. Delete account's mutes
+ l.Debug("deleting account mutes")
+ if err := p.db.DeleteWhere([]db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.StatusMute{}); err != nil {
+ l.Errorf("error deleting status mutes created by account: %s", err)
+ }
+
+ // 14. Delete account's streams
+
+ // 15. Delete account's tags
+ // TODO
+
+ // 16. Delete account's user
+ l.Debug("deleting account user")
+ if err := p.db.DeleteWhere([]db.Where{{Key: "account_id", Value: account.ID}}, &gtsmodel.User{}); err != nil {
+ return err
+ }
+
+ // 17. Delete account's timeline
+
+ // 18. Delete account itself
+ // to prevent the account being created again, set all these fields and update it in the db
+ // the account won't actually be *removed* from the database but it will be set to just a stub
+
+ account.Note = ""
+ account.DisplayName = ""
+ account.AvatarMediaAttachmentID = ""
+ account.AvatarRemoteURL = ""
+ account.HeaderMediaAttachmentID = ""
+ account.HeaderRemoteURL = ""
+ account.Reason = ""
+ account.Fields = []gtsmodel.Field{}
+ account.HideCollections = true
+ account.Discoverable = false
+
+ account.UpdatedAt = time.Now()
+
+ account.SuspendedAt = time.Now()
+ account.SuspensionOrigin = deletedBy
+
+ if err := p.db.UpdateByID(account.ID, account); err != nil {
+ return err
+ }
+
+ l.Infof("deleted account with username %s from domain %s", account.Username, account.Domain)
+ return nil
+}
diff --git a/internal/processing/account/get.go b/internal/processing/account/get.go
new file mode 100644
index 000000000..aba1ed14a
--- /dev/null
+++ b/internal/processing/account/get.go
@@ -0,0 +1,59 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package account
+
+import (
+ "errors"
+ "fmt"
+
+ apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+func (p *processor) Get(requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Account, error) {
+ targetAccount := &gtsmodel.Account{}
+ if err := p.db.GetByID(targetAccountID, targetAccount); err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ return nil, errors.New("account not found")
+ }
+ return nil, fmt.Errorf("db error: %s", err)
+ }
+
+ // lazily dereference things on the account if it hasn't been done yet
+ var requestingUsername string
+ if requestingAccount != nil {
+ requestingUsername = requestingAccount.Username
+ }
+ if err := p.federator.DereferenceAccountFields(targetAccount, requestingUsername, false); err != nil {
+ p.log.WithField("func", "AccountGet").Debugf("dereferencing account: %s", err)
+ }
+
+ var mastoAccount *apimodel.Account
+ var err error
+ if requestingAccount != nil && targetAccount.ID == requestingAccount.ID {
+ mastoAccount, err = p.tc.AccountToMastoSensitive(targetAccount)
+ } else {
+ mastoAccount, err = p.tc.AccountToMastoPublic(targetAccount)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("error converting account: %s", err)
+ }
+ return mastoAccount, nil
+}
diff --git a/internal/processing/account/getfollowers.go b/internal/processing/account/getfollowers.go
new file mode 100644
index 000000000..bfc463d3f
--- /dev/null
+++ b/internal/processing/account/getfollowers.go
@@ -0,0 +1,79 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package account
+
+import (
+ "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(requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) {
+ blocked, err := p.db.Blocked(requestingAccount.ID, targetAccountID)
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ if blocked {
+ return nil, gtserror.NewErrorNotFound(fmt.Errorf("block exists between accounts"))
+ }
+
+ followers := []gtsmodel.Follow{}
+ accounts := []apimodel.Account{}
+ if err := p.db.GetFollowersByAccountID(targetAccountID, &followers, false); err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ return accounts, nil
+ }
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ for _, f := range followers {
+ blocked, err := p.db.Blocked(requestingAccount.ID, f.AccountID)
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+ if blocked {
+ continue
+ }
+
+ a := &gtsmodel.Account{}
+ if err := p.db.GetByID(f.AccountID, a); err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ continue
+ }
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ // derefence account fields in case we haven't done it already
+ if err := p.federator.DereferenceAccountFields(a, requestingAccount.Username, false); err != nil {
+ // don't bail if we can't fetch them, we'll try another time
+ p.log.WithField("func", "AccountFollowersGet").Debugf("error dereferencing account fields: %s", err)
+ }
+
+ account, err := p.tc.AccountToMastoPublic(a)
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+ accounts = append(accounts, *account)
+ }
+ return accounts, nil
+}
diff --git a/internal/processing/account/getfollowing.go b/internal/processing/account/getfollowing.go
new file mode 100644
index 000000000..bb6a905f4
--- /dev/null
+++ b/internal/processing/account/getfollowing.go
@@ -0,0 +1,79 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package account
+
+import (
+ "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) FollowingGet(requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) {
+ blocked, err := p.db.Blocked(requestingAccount.ID, targetAccountID)
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ if blocked {
+ return nil, gtserror.NewErrorNotFound(fmt.Errorf("block exists between accounts"))
+ }
+
+ following := []gtsmodel.Follow{}
+ accounts := []apimodel.Account{}
+ if err := p.db.GetFollowingByAccountID(targetAccountID, &following); err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ return accounts, nil
+ }
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ for _, f := range following {
+ blocked, err := p.db.Blocked(requestingAccount.ID, f.AccountID)
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+ if blocked {
+ continue
+ }
+
+ a := &gtsmodel.Account{}
+ if err := p.db.GetByID(f.TargetAccountID, a); err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ continue
+ }
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ // derefence account fields in case we haven't done it already
+ if err := p.federator.DereferenceAccountFields(a, requestingAccount.Username, false); err != nil {
+ // don't bail if we can't fetch them, we'll try another time
+ p.log.WithField("func", "AccountFollowingGet").Debugf("error dereferencing account fields: %s", err)
+ }
+
+ account, err := p.tc.AccountToMastoPublic(a)
+ 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
new file mode 100644
index 000000000..a0a93a4c2
--- /dev/null
+++ b/internal/processing/account/getrelationship.go
@@ -0,0 +1,46 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package account
+
+import (
+ "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(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(requestingAccount.ID, targetAccountID)
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("error getting relationship: %s", err))
+ }
+
+ r, err := p.tc.RelationshipToMasto(gtsR)
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting relationship: %s", err))
+ }
+
+ return r, nil
+}
diff --git a/internal/processing/account/getstatuses.go b/internal/processing/account/getstatuses.go
new file mode 100644
index 000000000..b8ccbc528
--- /dev/null
+++ b/internal/processing/account/getstatuses.go
@@ -0,0 +1,63 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package account
+
+import (
+ "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) StatusesGet(requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, maxID string, pinnedOnly bool, mediaOnly bool) ([]apimodel.Status, gtserror.WithCode) {
+ targetAccount := &gtsmodel.Account{}
+ if err := p.db.GetByID(targetAccountID, targetAccount); err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ return nil, gtserror.NewErrorNotFound(fmt.Errorf("no entry found for account id %s", targetAccountID))
+ }
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ apiStatuses := []apimodel.Status{}
+ statuses, err := p.db.GetStatusesForAccount(targetAccountID, limit, excludeReplies, maxID, pinnedOnly, mediaOnly)
+ if err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ return apiStatuses, nil
+ }
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ for _, s := range statuses {
+ visible, err := p.filter.StatusVisible(s, requestingAccount)
+ if err != nil || !visible {
+ continue
+ }
+
+ apiStatus, err := p.tc.StatusToMasto(s, requestingAccount)
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status to masto: %s", err))
+ }
+
+ apiStatuses = append(apiStatuses, *apiStatus)
+ }
+
+ return apiStatuses, nil
+}
diff --git a/internal/processing/account/removefollow.go b/internal/processing/account/removefollow.go
new file mode 100644
index 000000000..ef8994893
--- /dev/null
+++ b/internal/processing/account/removefollow.go
@@ -0,0 +1,110 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package account
+
+import (
+ "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) FollowRemove(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.Blocked(requestingAccount.ID, targetAccountID)
+ 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 := &gtsmodel.Account{}
+ if err := p.db.GetByID(targetAccountID, targetAcct); err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ 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 := &gtsmodel.FollowRequest{}
+ if err := p.db.GetWhere([]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(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 := &gtsmodel.Follow{}
+ if err := p.db.GetWhere([]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(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.fromClientAPI <- gtsmodel.FromClientAPI{
+ APObjectType: gtsmodel.ActivityStreamsFollow,
+ APActivityType: gtsmodel.ActivityStreamsUndo,
+ GTSModel: &gtsmodel.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.fromClientAPI <- gtsmodel.FromClientAPI{
+ APObjectType: gtsmodel.ActivityStreamsFollow,
+ APActivityType: gtsmodel.ActivityStreamsUndo,
+ GTSModel: &gtsmodel.Follow{
+ AccountID: requestingAccount.ID,
+ TargetAccountID: targetAccountID,
+ URI: fURI,
+ },
+ OriginAccount: requestingAccount,
+ TargetAccount: targetAcct,
+ }
+ }
+
+ // return whatever relationship results from all this
+ return p.RelationshipGet(requestingAccount, targetAccountID)
+}
diff --git a/internal/processing/account/update.go b/internal/processing/account/update.go
new file mode 100644
index 000000000..830fec60a
--- /dev/null
+++ b/internal/processing/account/update.go
@@ -0,0 +1,199 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package account
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "mime/multipart"
+
+ apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/media"
+ "github.com/superseriousbusiness/gotosocial/internal/util"
+)
+
+func (p *processor) Update(account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error) {
+ l := p.log.WithField("func", "AccountUpdate")
+
+ if form.Discoverable != nil {
+ if err := p.db.UpdateOneByID(account.ID, "discoverable", *form.Discoverable, &gtsmodel.Account{}); err != nil {
+ return nil, fmt.Errorf("error updating discoverable: %s", err)
+ }
+ }
+
+ if form.Bot != nil {
+ if err := p.db.UpdateOneByID(account.ID, "bot", *form.Bot, &gtsmodel.Account{}); err != nil {
+ return nil, fmt.Errorf("error updating bot: %s", err)
+ }
+ }
+
+ if form.DisplayName != nil {
+ if err := util.ValidateDisplayName(*form.DisplayName); err != nil {
+ return nil, err
+ }
+ if err := p.db.UpdateOneByID(account.ID, "display_name", *form.DisplayName, &gtsmodel.Account{}); err != nil {
+ return nil, err
+ }
+ }
+
+ if form.Note != nil {
+ if err := util.ValidateNote(*form.Note); err != nil {
+ return nil, err
+ }
+ if err := p.db.UpdateOneByID(account.ID, "note", *form.Note, &gtsmodel.Account{}); err != nil {
+ return nil, err
+ }
+ }
+
+ if form.Avatar != nil && form.Avatar.Size != 0 {
+ avatarInfo, err := p.UpdateAvatar(form.Avatar, account.ID)
+ if err != nil {
+ return nil, err
+ }
+ l.Tracef("new avatar info for account %s is %+v", account.ID, avatarInfo)
+ }
+
+ if form.Header != nil && form.Header.Size != 0 {
+ headerInfo, err := p.UpdateHeader(form.Header, account.ID)
+ if err != nil {
+ return nil, err
+ }
+ l.Tracef("new header info for account %s is %+v", account.ID, headerInfo)
+ }
+
+ if form.Locked != nil {
+ if err := p.db.UpdateOneByID(account.ID, "locked", *form.Locked, &gtsmodel.Account{}); err != nil {
+ return nil, err
+ }
+ }
+
+ if form.Source != nil {
+ if form.Source.Language != nil {
+ if err := util.ValidateLanguage(*form.Source.Language); err != nil {
+ return nil, err
+ }
+ if err := p.db.UpdateOneByID(account.ID, "language", *form.Source.Language, &gtsmodel.Account{}); err != nil {
+ return nil, err
+ }
+ }
+
+ if form.Source.Sensitive != nil {
+ if err := p.db.UpdateOneByID(account.ID, "locked", *form.Locked, &gtsmodel.Account{}); err != nil {
+ return nil, err
+ }
+ }
+
+ if form.Source.Privacy != nil {
+ if err := util.ValidatePrivacy(*form.Source.Privacy); err != nil {
+ return nil, err
+ }
+ if err := p.db.UpdateOneByID(account.ID, "privacy", *form.Source.Privacy, &gtsmodel.Account{}); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ // fetch the account with all updated values set
+ updatedAccount := &gtsmodel.Account{}
+ if err := p.db.GetByID(account.ID, updatedAccount); err != nil {
+ return nil, fmt.Errorf("could not fetch updated account %s: %s", account.ID, err)
+ }
+
+ p.fromClientAPI <- gtsmodel.FromClientAPI{
+ APObjectType: gtsmodel.ActivityStreamsProfile,
+ APActivityType: gtsmodel.ActivityStreamsUpdate,
+ GTSModel: updatedAccount,
+ OriginAccount: updatedAccount,
+ }
+
+ acctSensitive, err := p.tc.AccountToMastoSensitive(updatedAccount)
+ if err != nil {
+ return nil, fmt.Errorf("could not convert account into mastosensitive account: %s", err)
+ }
+ return acctSensitive, nil
+}
+
+// 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(avatar *multipart.FileHeader, accountID string) (*gtsmodel.MediaAttachment, error) {
+ var err error
+ if int(avatar.Size) > p.config.MediaConfig.MaxImageSize {
+ err = fmt.Errorf("avatar with size %d exceeded max image size of %d bytes", avatar.Size, p.config.MediaConfig.MaxImageSize)
+ return nil, err
+ }
+ f, err := avatar.Open()
+ if err != nil {
+ return nil, fmt.Errorf("could not read provided avatar: %s", err)
+ }
+
+ // extract the bytes
+ buf := new(bytes.Buffer)
+ size, err := io.Copy(buf, f)
+ if err != nil {
+ return nil, fmt.Errorf("could not read provided avatar: %s", err)
+ }
+ if size == 0 {
+ return nil, errors.New("could not read provided avatar: size 0 bytes")
+ }
+
+ // do the setting
+ avatarInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Avatar, "")
+ if err != nil {
+ return nil, fmt.Errorf("error processing avatar: %s", err)
+ }
+
+ return avatarInfo, f.Close()
+}
+
+// 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(header *multipart.FileHeader, accountID string) (*gtsmodel.MediaAttachment, error) {
+ var err error
+ if int(header.Size) > p.config.MediaConfig.MaxImageSize {
+ err = fmt.Errorf("header with size %d exceeded max image size of %d bytes", header.Size, p.config.MediaConfig.MaxImageSize)
+ return nil, err
+ }
+ f, err := header.Open()
+ if err != nil {
+ return nil, fmt.Errorf("could not read provided header: %s", err)
+ }
+
+ // extract the bytes
+ buf := new(bytes.Buffer)
+ size, err := io.Copy(buf, f)
+ if err != nil {
+ return nil, fmt.Errorf("could not read provided header: %s", err)
+ }
+ if size == 0 {
+ return nil, errors.New("could not read provided header: size 0 bytes")
+ }
+
+ // do the setting
+ headerInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Header, "")
+ if err != nil {
+ return nil, fmt.Errorf("error processing header: %s", err)
+ }
+
+ return headerInfo, f.Close()
+}