diff options
Diffstat (limited to 'internal/api/client/account')
22 files changed, 0 insertions, 2783 deletions
diff --git a/internal/api/client/account/account.go b/internal/api/client/account/account.go deleted file mode 100644 index 4205baa2c..000000000 --- a/internal/api/client/account/account.go +++ /dev/null @@ -1,141 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package account - -import ( - "net/http" - "strings" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/processing" - - "github.com/superseriousbusiness/gotosocial/internal/router" -) - -const ( - // LimitKey is for setting the return amount limit for eg., requesting an account's statuses - LimitKey = "limit" - // ExcludeRepliesKey is for specifying whether to exclude replies in a list of returned statuses by an account. - ExcludeRepliesKey = "exclude_replies" - // ExcludeReblogsKey is for specifying whether to exclude reblogs in a list of returned statuses by an account. - ExcludeReblogsKey = "exclude_reblogs" - // PinnedKey is for specifying whether to include pinned statuses in a list of returned statuses by an account. - PinnedKey = "pinned" - // MaxIDKey is for specifying the maximum ID of the status to retrieve. - MaxIDKey = "max_id" - // MinIDKey is for specifying the minimum ID of the status to retrieve. - MinIDKey = "min_id" - // OnlyMediaKey is for specifying that only statuses with media should be returned in a list of returned statuses by an account. - OnlyMediaKey = "only_media" - // OnlyPublicKey is for specifying that only statuses with visibility public should be returned in a list of returned statuses by account. - OnlyPublicKey = "only_public" - - // IDKey is the key to use for retrieving account ID in requests - IDKey = "id" - // BasePath is the base API path for this module - BasePath = "/api/v1/accounts" - // BasePathWithID is the base path for this module with the ID key - BasePathWithID = BasePath + "/:" + IDKey - // VerifyPath is for verifying account credentials - VerifyPath = BasePath + "/verify_credentials" - // UpdateCredentialsPath is for updating account credentials - UpdateCredentialsPath = BasePath + "/update_credentials" - // GetStatusesPath is for showing an account's statuses - GetStatusesPath = BasePathWithID + "/statuses" - // GetFollowersPath is for showing an account's followers - GetFollowersPath = BasePathWithID + "/followers" - // GetFollowingPath is for showing account's that an account follows. - GetFollowingPath = BasePathWithID + "/following" - // GetRelationshipsPath is for showing an account's relationship with other accounts - GetRelationshipsPath = BasePath + "/relationships" - // FollowPath is for POSTing new follows to, and updating existing follows - FollowPath = BasePathWithID + "/follow" - // UnfollowPath is for POSTing an unfollow - UnfollowPath = BasePathWithID + "/unfollow" - // BlockPath is for creating a block of an account - BlockPath = BasePathWithID + "/block" - // UnblockPath is for removing a block of an account - UnblockPath = BasePathWithID + "/unblock" - // DeleteAccountPath is for deleting one's account via the API - DeleteAccountPath = BasePath + "/delete" -) - -// Module implements the ClientAPIModule interface for account-related actions -type Module struct { - processor processing.Processor -} - -// New returns a new account module -func New(processor processing.Processor) api.ClientModule { - return &Module{ - processor: processor, - } -} - -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { - // create account - r.AttachHandler(http.MethodPost, BasePath, m.AccountCreatePOSTHandler) - - // delete account - r.AttachHandler(http.MethodPost, DeleteAccountPath, m.AccountDeletePOSTHandler) - - // get account - r.AttachHandler(http.MethodGet, BasePathWithID, m.muxHandler) - - // modify account - r.AttachHandler(http.MethodPatch, BasePathWithID, m.muxHandler) - - // get account's statuses - r.AttachHandler(http.MethodGet, GetStatusesPath, m.AccountStatusesGETHandler) - - // get following or followers - r.AttachHandler(http.MethodGet, GetFollowersPath, m.AccountFollowersGETHandler) - r.AttachHandler(http.MethodGet, GetFollowingPath, m.AccountFollowingGETHandler) - - // get relationship with account - r.AttachHandler(http.MethodGet, GetRelationshipsPath, m.AccountRelationshipsGETHandler) - - // follow or unfollow account - r.AttachHandler(http.MethodPost, FollowPath, m.AccountFollowPOSTHandler) - r.AttachHandler(http.MethodPost, UnfollowPath, m.AccountUnfollowPOSTHandler) - - // block or unblock account - r.AttachHandler(http.MethodPost, BlockPath, m.AccountBlockPOSTHandler) - r.AttachHandler(http.MethodPost, UnblockPath, m.AccountUnblockPOSTHandler) - - return nil -} - -func (m *Module) muxHandler(c *gin.Context) { - ru := c.Request.RequestURI - switch c.Request.Method { - case http.MethodGet: - if strings.HasPrefix(ru, VerifyPath) { - m.AccountVerifyGETHandler(c) - } else { - m.AccountGETHandler(c) - } - case http.MethodPatch: - if strings.HasPrefix(ru, UpdateCredentialsPath) { - m.AccountUpdateCredentialsPATCHHandler(c) - } - } -} diff --git a/internal/api/client/account/account_test.go b/internal/api/client/account/account_test.go deleted file mode 100644 index 90dbd6249..000000000 --- a/internal/api/client/account/account_test.go +++ /dev/null @@ -1,127 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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_test - -import ( - "bytes" - "fmt" - "net/http" - "net/http/httptest" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/account" - "github.com/superseriousbusiness/gotosocial/internal/concurrency" - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/email" - "github.com/superseriousbusiness/gotosocial/internal/federation" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/media" - "github.com/superseriousbusiness/gotosocial/internal/messages" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/storage" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type AccountStandardTestSuite struct { - // standard suite interfaces - suite.Suite - db db.DB - storage *storage.Driver - mediaManager media.Manager - federator federation.Federator - processor processing.Processor - emailSender email.Sender - sentEmails map[string]string - - // standard suite models - testTokens map[string]*gtsmodel.Token - testClients map[string]*gtsmodel.Client - testApplications map[string]*gtsmodel.Application - testUsers map[string]*gtsmodel.User - testAccounts map[string]*gtsmodel.Account - testAttachments map[string]*gtsmodel.MediaAttachment - testStatuses map[string]*gtsmodel.Status - - // module being tested - accountModule *account.Module -} - -func (suite *AccountStandardTestSuite) SetupSuite() { - suite.testTokens = testrig.NewTestTokens() - suite.testClients = testrig.NewTestClients() - suite.testApplications = testrig.NewTestApplications() - suite.testUsers = testrig.NewTestUsers() - suite.testAccounts = testrig.NewTestAccounts() - suite.testAttachments = testrig.NewTestAttachments() - suite.testStatuses = testrig.NewTestStatuses() -} - -func (suite *AccountStandardTestSuite) SetupTest() { - testrig.InitTestConfig() - testrig.InitTestLog() - - fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1) - clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1) - - suite.db = testrig.NewTestDB() - suite.storage = testrig.NewInMemoryStorage() - suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) - suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker) - suite.sentEmails = make(map[string]string) - suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) - suite.accountModule = account.New(suite.processor).(*account.Module) - testrig.StandardDBSetup(suite.db, nil) - testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") - - suite.NoError(suite.processor.Start()) -} - -func (suite *AccountStandardTestSuite) TearDownTest() { - testrig.StandardDBTeardown(suite.db) - testrig.StandardStorageTeardown(suite.storage) -} - -func (suite *AccountStandardTestSuite) newContext(recorder *httptest.ResponseRecorder, requestMethod string, requestBody []byte, requestPath string, bodyContentType string) *gin.Context { - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"])) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - - protocol := config.GetProtocol() - host := config.GetHost() - - baseURI := fmt.Sprintf("%s://%s", protocol, host) - requestURI := fmt.Sprintf("%s/%s", baseURI, requestPath) - - ctx.Request = httptest.NewRequest(http.MethodPatch, requestURI, bytes.NewReader(requestBody)) // the endpoint we're hitting - - if bodyContentType != "" { - ctx.Request.Header.Set("Content-Type", bodyContentType) - } - - ctx.Request.Header.Set("accept", "application/json") - - return ctx -} diff --git a/internal/api/client/account/accountcreate.go b/internal/api/client/account/accountcreate.go deleted file mode 100644 index e7b6c642d..000000000 --- a/internal/api/client/account/accountcreate.go +++ /dev/null @@ -1,150 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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" - "net" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/internal/validate" -) - -// AccountCreatePOSTHandler swagger:operation POST /api/v1/accounts accountCreate -// -// Create a new account using an application token. -// -// The parameters can also be given in the body of the request, as JSON, if the content-type is set to 'application/json'. -// The parameters can also be given in the body of the request, as XML, if the content-type is set to 'application/xml'. -// -// --- -// tags: -// - accounts -// -// consumes: -// - application/json -// - application/xml -// - application/x-www-form-urlencoded -// -// produces: -// - application/json -// -// security: -// - OAuth2 Application: -// - write:accounts -// -// responses: -// '200': -// description: "An OAuth2 access token for the newly-created account." -// schema: -// "$ref": "#/definitions/oauthToken" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) AccountCreatePOSTHandler(c *gin.Context) { - authed, err := oauth.Authed(c, true, true, false, false) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) - return - } - - if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - form := &model.AccountCreateRequest{} - if err := c.ShouldBind(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - if err := validateCreateAccount(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - clientIP := c.ClientIP() - signUpIP := net.ParseIP(clientIP) - if signUpIP == nil { - err := errors.New("ip address could not be parsed from request") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - form.IP = signUpIP - - ti, errWithCode := m.processor.AccountCreate(c.Request.Context(), authed, form) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, ti) -} - -// validateCreateAccount checks through all the necessary prerequisites for creating a new account, -// according to the provided account create request. If the account isn't eligible, an error will be returned. -func validateCreateAccount(form *model.AccountCreateRequest) error { - if form == nil { - return errors.New("form was nil") - } - - if !config.GetAccountsRegistrationOpen() { - return errors.New("registration is not open for this server") - } - - if err := validate.Username(form.Username); err != nil { - return err - } - - if err := validate.Email(form.Email); err != nil { - return err - } - - if err := validate.NewPassword(form.Password); err != nil { - return err - } - - if !form.Agreement { - return errors.New("agreement to terms and conditions not given") - } - - if err := validate.Language(form.Locale); err != nil { - return err - } - - if err := validate.SignUpReason(form.Reason, config.GetAccountsReasonRequired()); err != nil { - return err - } - - return nil -} diff --git a/internal/api/client/account/accountcreate_test.go b/internal/api/client/account/accountcreate_test.go deleted file mode 100644 index a4fc165bf..000000000 --- a/internal/api/client/account/accountcreate_test.go +++ /dev/null @@ -1,19 +0,0 @@ -// /* -// GoToSocial -// Copyright (C) 2021-2022 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_test diff --git a/internal/api/client/account/accountdelete.go b/internal/api/client/account/accountdelete.go deleted file mode 100644 index 53bdedd0f..000000000 --- a/internal/api/client/account/accountdelete.go +++ /dev/null @@ -1,95 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// AccountDeletePOSTHandler swagger:operation POST /api/v1/accounts/delete accountDelete -// -// Delete your account. -// -// --- -// tags: -// - accounts -// -// consumes: -// - multipart/form-data -// -// parameters: -// - -// name: password -// in: formData -// description: Password of the account user, for confirmation. -// type: string -// required: true -// -// security: -// - OAuth2 Bearer: -// - write:accounts -// -// responses: -// '202': -// description: "The account deletion has been accepted and the account will be deleted." -// '400': -// description: bad request -// '401': -// description: unauthorized -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) AccountDeletePOSTHandler(c *gin.Context) { - authed, err := oauth.Authed(c, true, true, true, true) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) - return - } - - form := &model.AccountDeleteRequest{} - if err := c.ShouldBind(&form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - if form.Password == "" { - err = errors.New("no password provided in account delete request") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - form.DeleteOriginID = authed.Account.ID - - if errWithCode := m.processor.AccountDeleteLocal(c.Request.Context(), authed, form); errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusAccepted, gin.H{"message": "accepted"}) -} diff --git a/internal/api/client/account/accountdelete_test.go b/internal/api/client/account/accountdelete_test.go deleted file mode 100644 index 78348eabc..000000000 --- a/internal/api/client/account/accountdelete_test.go +++ /dev/null @@ -1,101 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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_test - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/account" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type AccountDeleteTestSuite struct { - AccountStandardTestSuite -} - -func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandler() { - // set up the request - // we're deleting zork - requestBody, w, err := testrig.CreateMultipartFormData( - "", "", - map[string]string{ - "password": "password", - }) - if err != nil { - panic(err) - } - bodyBytes := requestBody.Bytes() - recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, account.DeleteAccountPath, w.FormDataContentType()) - - // call the handler - suite.accountModule.AccountDeletePOSTHandler(ctx) - - // 1. we should have Accepted because our request was valid - suite.Equal(http.StatusAccepted, recorder.Code) -} - -func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandlerWrongPassword() { - // set up the request - // we're deleting zork - requestBody, w, err := testrig.CreateMultipartFormData( - "", "", - map[string]string{ - "password": "aaaaaaaaaaaaaaaaaaaaaaaaaaaa", - }) - if err != nil { - panic(err) - } - bodyBytes := requestBody.Bytes() - recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, account.DeleteAccountPath, w.FormDataContentType()) - - // call the handler - suite.accountModule.AccountDeletePOSTHandler(ctx) - - // 1. we should have Forbidden because we supplied the wrong password - suite.Equal(http.StatusForbidden, recorder.Code) -} - -func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandlerNoPassword() { - // set up the request - // we're deleting zork - requestBody, w, err := testrig.CreateMultipartFormData( - "", "", - map[string]string{}) - if err != nil { - panic(err) - } - bodyBytes := requestBody.Bytes() - recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, account.DeleteAccountPath, w.FormDataContentType()) - - // call the handler - suite.accountModule.AccountDeletePOSTHandler(ctx) - - // 1. we should have StatusBadRequest because our request was invalid - suite.Equal(http.StatusBadRequest, recorder.Code) -} - -func TestAccountDeleteTestSuite(t *testing.T) { - suite.Run(t, new(AccountDeleteTestSuite)) -} diff --git a/internal/api/client/account/accountget.go b/internal/api/client/account/accountget.go deleted file mode 100644 index c9aae5b2b..000000000 --- a/internal/api/client/account/accountget.go +++ /dev/null @@ -1,95 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// AccountGETHandler swagger:operation GET /api/v1/accounts/{id} accountGet -// -// Get information about an account with the given ID. -// -// --- -// tags: -// - accounts -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: The id of the requested account. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - read:accounts -// -// responses: -// '200': -// description: The requested account. -// schema: -// "$ref": "#/definitions/account" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) AccountGETHandler(c *gin.Context) { - authed, err := oauth.Authed(c, true, true, true, true) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) - return - } - - if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - targetAcctID := c.Param(IDKey) - if targetAcctID == "" { - err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - acctInfo, errWithCode := m.processor.AccountGet(c.Request.Context(), authed, targetAcctID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, acctInfo) -} diff --git a/internal/api/client/account/accountupdate.go b/internal/api/client/account/accountupdate.go deleted file mode 100644 index f89259a96..000000000 --- a/internal/api/client/account/accountupdate.go +++ /dev/null @@ -1,216 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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" - "net/http" - "strconv" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// AccountUpdateCredentialsPATCHHandler swagger:operation PATCH /api/v1/accounts/update_credentials accountUpdate -// -// Update your account. -// -// --- -// tags: -// - accounts -// -// consumes: -// - multipart/form-data -// -// produces: -// - application/json -// -// parameters: -// - -// name: discoverable -// in: formData -// description: Account should be made discoverable and shown in the profile directory (if enabled). -// type: boolean -// - -// name: bot -// in: formData -// description: Account is flagged as a bot. -// type: boolean -// - -// name: display_name -// in: formData -// description: The display name to use for the account. -// type: string -// allowEmptyValue: true -// - -// name: note -// in: formData -// description: Bio/description of this account. -// type: string -// allowEmptyValue: true -// - -// name: avatar -// in: formData -// description: Avatar of the user. -// type: file -// - -// name: header -// in: formData -// description: Header of the user. -// type: file -// - -// name: locked -// in: formData -// description: Require manual approval of follow requests. -// type: boolean -// - -// name: source[privacy] -// in: formData -// description: Default post privacy for authored statuses. -// type: string -// - -// name: source[sensitive] -// in: formData -// description: Mark authored statuses as sensitive by default. -// type: boolean -// - -// name: source[language] -// in: formData -// description: Default language to use for authored statuses (ISO 6391). -// type: string -// - -// name: source[status_format] -// in: formData -// description: Default format to use for authored statuses (plain or markdown). -// type: string -// - -// name: custom_css -// in: formData -// description: >- -// Custom CSS to use when rendering this account's profile or statuses. -// String must be no more than 5,000 characters (~5kb). -// type: string -// - -// name: enable_rss -// in: formData -// description: Enable RSS feed for this account's Public posts at `/[username]/feed.rss` -// type: boolean -// -// security: -// - OAuth2 Bearer: -// - write:accounts -// -// responses: -// '200': -// description: "The newly updated account." -// schema: -// "$ref": "#/definitions/account" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) AccountUpdateCredentialsPATCHHandler(c *gin.Context) { - authed, err := oauth.Authed(c, true, true, true, true) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) - return - } - - if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - form, err := parseUpdateAccountForm(c) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - acctSensitive, errWithCode := m.processor.AccountUpdate(c.Request.Context(), authed, form) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, acctSensitive) -} - -func parseUpdateAccountForm(c *gin.Context) (*model.UpdateCredentialsRequest, error) { - form := &model.UpdateCredentialsRequest{ - Source: &model.UpdateSource{}, - } - - if err := c.ShouldBind(&form); err != nil { - return nil, fmt.Errorf("could not parse form from request: %s", err) - } - - // parse source field-by-field - sourceMap := c.PostFormMap("source") - - if privacy, ok := sourceMap["privacy"]; ok { - form.Source.Privacy = &privacy - } - - if sensitive, ok := sourceMap["sensitive"]; ok { - sensitiveBool, err := strconv.ParseBool(sensitive) - if err != nil { - return nil, fmt.Errorf("error parsing form source[sensitive]: %s", err) - } - form.Source.Sensitive = &sensitiveBool - } - - if language, ok := sourceMap["language"]; ok { - form.Source.Language = &language - } - - if statusFormat, ok := sourceMap["status_format"]; ok { - form.Source.StatusFormat = &statusFormat - } - - if form == nil || - (form.Discoverable == nil && - form.Bot == nil && - form.DisplayName == nil && - form.Note == nil && - form.Avatar == nil && - form.Header == nil && - form.Locked == nil && - form.Source.Privacy == nil && - form.Source.Sensitive == nil && - form.Source.Language == nil && - form.Source.StatusFormat == nil && - form.FieldsAttributes == nil && - form.CustomCSS == nil && - form.EnableRSS == nil) { - return nil, errors.New("empty form submitted") - } - - return form, nil -} diff --git a/internal/api/client/account/accountupdate_test.go b/internal/api/client/account/accountupdate_test.go deleted file mode 100644 index 259bb69e9..000000000 --- a/internal/api/client/account/accountupdate_test.go +++ /dev/null @@ -1,452 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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_test - -import ( - "context" - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/account" - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type AccountUpdateTestSuite struct { - AccountStandardTestSuite -} - -func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandler() { - // set up the request - // we're updating the note of zork - newBio := "this is my new bio read it and weep" - requestBody, w, err := testrig.CreateMultipartFormData( - "", "", - map[string]string{ - "note": newBio, - }) - if err != nil { - panic(err) - } - bodyBytes := requestBody.Bytes() - recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) - - // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) - - // 1. we should have OK because our request was valid - suite.Equal(http.StatusOK, recorder.Code) - - // 2. we should have no error message in the result body - result := recorder.Result() - defer result.Body.Close() - - // check the response - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - // unmarshal the returned account - apimodelAccount := &apimodel.Account{} - err = json.Unmarshal(b, apimodelAccount) - suite.NoError(err) - - // check the returned api model account - // fields should be updated - suite.Equal("<p>this is my new bio read it and weep</p>", apimodelAccount.Note) - suite.Equal(newBio, apimodelAccount.Source.Note) -} - -func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUnlockLock() { - // set up the first request - requestBody1, w1, err := testrig.CreateMultipartFormData( - "", "", - map[string]string{ - "locked": "false", - }) - if err != nil { - panic(err) - } - bodyBytes1 := requestBody1.Bytes() - recorder1 := httptest.NewRecorder() - ctx1 := suite.newContext(recorder1, http.MethodPatch, bodyBytes1, account.UpdateCredentialsPath, w1.FormDataContentType()) - - // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx1) - - // 1. we should have OK because our request was valid - suite.Equal(http.StatusOK, recorder1.Code) - - // 2. we should have no error message in the result body - result1 := recorder1.Result() - defer result1.Body.Close() - - // check the response - b1, err := ioutil.ReadAll(result1.Body) - suite.NoError(err) - - // unmarshal the returned account - apimodelAccount1 := &apimodel.Account{} - err = json.Unmarshal(b1, apimodelAccount1) - suite.NoError(err) - - // check the returned api model account - // fields should be updated - suite.False(apimodelAccount1.Locked) - - // set up the first request - requestBody2, w2, err := testrig.CreateMultipartFormData( - "", "", - map[string]string{ - "locked": "true", - }) - if err != nil { - panic(err) - } - bodyBytes2 := requestBody2.Bytes() - recorder2 := httptest.NewRecorder() - ctx2 := suite.newContext(recorder2, http.MethodPatch, bodyBytes2, account.UpdateCredentialsPath, w2.FormDataContentType()) - - // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx2) - - // 1. we should have OK because our request was valid - suite.Equal(http.StatusOK, recorder1.Code) - - // 2. we should have no error message in the result body - result2 := recorder2.Result() - defer result2.Body.Close() - - // check the response - b2, err := ioutil.ReadAll(result2.Body) - suite.NoError(err) - - // unmarshal the returned account - apimodelAccount2 := &apimodel.Account{} - err = json.Unmarshal(b2, apimodelAccount2) - suite.NoError(err) - - // check the returned api model account - // fields should be updated - suite.True(apimodelAccount2.Locked) -} - -func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerGetAccountFirst() { - // get the account first to make sure it's in the database cache -- when the account is updated via - // the PATCH handler, it should invalidate the cache and not return the old version - _, err := suite.db.GetAccountByID(context.Background(), suite.testAccounts["local_account_1"].ID) - suite.NoError(err) - - // set up the request - // we're updating the note of zork - newBio := "this is my new bio read it and weep" - requestBody, w, err := testrig.CreateMultipartFormData( - "", "", - map[string]string{ - "note": newBio, - }) - if err != nil { - panic(err) - } - bodyBytes := requestBody.Bytes() - recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) - - // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) - - // 1. we should have OK because our request was valid - suite.Equal(http.StatusOK, recorder.Code) - - // 2. we should have no error message in the result body - result := recorder.Result() - defer result.Body.Close() - - // check the response - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - // unmarshal the returned account - apimodelAccount := &apimodel.Account{} - err = json.Unmarshal(b, apimodelAccount) - suite.NoError(err) - - // check the returned api model account - // fields should be updated - suite.Equal("<p>this is my new bio read it and weep</p>", apimodelAccount.Note) - suite.Equal(newBio, apimodelAccount.Source.Note) -} - -func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerTwoFields() { - // set up the request - // we're updating the note of zork, and setting locked to true - newBio := "this is my new bio read it and weep :rainbow:" - requestBody, w, err := testrig.CreateMultipartFormData( - "", "", - map[string]string{ - "note": newBio, - "locked": "true", - }) - if err != nil { - panic(err) - } - bodyBytes := requestBody.Bytes() - recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) - - // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) - - // 1. we should have OK because our request was valid - suite.Equal(http.StatusOK, recorder.Code) - - // 2. we should have no error message in the result body - result := recorder.Result() - defer result.Body.Close() - - // check the response - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - // unmarshal the returned account - apimodelAccount := &apimodel.Account{} - err = json.Unmarshal(b, apimodelAccount) - suite.NoError(err) - - // check the returned api model account - // fields should be updated - suite.Equal("<p>this is my new bio read it and weep :rainbow:</p>", apimodelAccount.Note) - suite.Equal(newBio, apimodelAccount.Source.Note) - suite.True(apimodelAccount.Locked) - suite.NotEmpty(apimodelAccount.Emojis) - suite.Equal(apimodelAccount.Emojis[0].Shortcode, "rainbow") - - // check the account in the database - dbZork, err := suite.db.GetAccountByID(context.Background(), apimodelAccount.ID) - suite.NoError(err) - suite.Equal(newBio, dbZork.NoteRaw) - suite.Equal("<p>this is my new bio read it and weep :rainbow:</p>", dbZork.Note) - suite.True(*dbZork.Locked) - suite.NotEmpty(dbZork.EmojiIDs) -} - -func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerWithMedia() { - // set up the request - // we're updating the header image, the display name, and the locked status of zork - // we're removing the note/bio - requestBody, w, err := testrig.CreateMultipartFormData( - "header", "../../../../testrig/media/test-jpeg.jpg", - map[string]string{ - "display_name": "updated zork display name!!!", - "note": "", - "locked": "true", - }) - if err != nil { - panic(err) - } - bodyBytes := requestBody.Bytes() - recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) - - // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) - - // 1. we should have OK because our request was valid - suite.Equal(http.StatusOK, recorder.Code) - - // 2. we should have no error message in the result body - result := recorder.Result() - defer result.Body.Close() - - // check the response - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - // unmarshal the returned account - apimodelAccount := &apimodel.Account{} - err = json.Unmarshal(b, apimodelAccount) - suite.NoError(err) - - // check the returned api model account - // fields should be updated - suite.Equal("updated zork display name!!!", apimodelAccount.DisplayName) - suite.True(apimodelAccount.Locked) - suite.Empty(apimodelAccount.Note) - suite.Empty(apimodelAccount.Source.Note) - - // header values... - // should be set - suite.NotEmpty(apimodelAccount.Header) - suite.NotEmpty(apimodelAccount.HeaderStatic) - - // should be different from the values set before - suite.NotEqual("http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg", apimodelAccount.Header) - suite.NotEqual("http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg", apimodelAccount.HeaderStatic) -} - -func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerEmptyForm() { - // set up the request - bodyBytes := []byte{} - recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, "") - - // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) - - // 1. we should have OK because our request was valid - suite.Equal(http.StatusBadRequest, recorder.Code) - - // 2. we should have no error message in the result body - result := recorder.Result() - defer result.Body.Close() - - // check the response - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - suite.Equal(`{"error":"Bad Request: empty form submitted"}`, string(b)) -} - -func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateSource() { - // set up the request - // we're updating the language of zork - newLanguage := "de" - requestBody, w, err := testrig.CreateMultipartFormData( - "", "", - map[string]string{ - "source[privacy]": string(apimodel.VisibilityPrivate), - "source[language]": "de", - "source[sensitive]": "true", - "locked": "true", - }) - if err != nil { - panic(err) - } - bodyBytes := requestBody.Bytes() - recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) - - // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) - - // 1. we should have OK because our request was valid - suite.Equal(http.StatusOK, recorder.Code) - - // 2. we should have no error message in the result body - result := recorder.Result() - defer result.Body.Close() - - // check the response - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - // unmarshal the returned account - apimodelAccount := &apimodel.Account{} - err = json.Unmarshal(b, apimodelAccount) - suite.NoError(err) - - // check the returned api model account - // fields should be updated - suite.Equal(newLanguage, apimodelAccount.Source.Language) - suite.EqualValues(apimodel.VisibilityPrivate, apimodelAccount.Source.Privacy) - suite.True(apimodelAccount.Source.Sensitive) - suite.True(apimodelAccount.Locked) -} - -func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateStatusFormatOK() { - // set up the request - // we're updating the language of zork - requestBody, w, err := testrig.CreateMultipartFormData( - "", "", - map[string]string{ - "source[status_format]": "markdown", - }) - if err != nil { - panic(err) - } - bodyBytes := requestBody.Bytes() - recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) - - // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) - - // 1. we should have OK because our request was valid - suite.Equal(http.StatusOK, recorder.Code) - - // 2. we should have no error message in the result body - result := recorder.Result() - defer result.Body.Close() - - // check the response - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - // unmarshal the returned account - apimodelAccount := &apimodel.Account{} - err = json.Unmarshal(b, apimodelAccount) - suite.NoError(err) - - // check the returned api model account - // fields should be updated - suite.Equal("markdown", apimodelAccount.Source.StatusFormat) - - dbAccount, err := suite.db.GetAccountByID(context.Background(), suite.testAccounts["local_account_1"].ID) - if err != nil { - suite.FailNow(err.Error()) - } - suite.Equal(dbAccount.StatusFormat, "markdown") -} - -func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateStatusFormatBad() { - // set up the request - // we're updating the language of zork - requestBody, w, err := testrig.CreateMultipartFormData( - "", "", - map[string]string{ - "source[status_format]": "peepeepoopoo", - }) - if err != nil { - panic(err) - } - bodyBytes := requestBody.Bytes() - recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) - - // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) - - suite.Equal(http.StatusBadRequest, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - - // check the response - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - suite.Equal(`{"error":"Bad Request: status format 'peepeepoopoo' was not recognized, valid options are 'plain', 'markdown'"}`, string(b)) -} - -func TestAccountUpdateTestSuite(t *testing.T) { - suite.Run(t, new(AccountUpdateTestSuite)) -} diff --git a/internal/api/client/account/accountverify.go b/internal/api/client/account/accountverify.go deleted file mode 100644 index 916d0a322..000000000 --- a/internal/api/client/account/accountverify.go +++ /dev/null @@ -1,78 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package account - -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// AccountVerifyGETHandler swagger:operation GET /api/v1/accounts/verify_credentials accountVerify -// -// Verify a token by returning account details pertaining to it. -// -// --- -// tags: -// - accounts -// -// produces: -// - application/json -// -// security: -// - OAuth2 Bearer: -// - read:accounts -// -// responses: -// '200': -// schema: -// "$ref": "#/definitions/account" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) AccountVerifyGETHandler(c *gin.Context) { - authed, err := oauth.Authed(c, true, true, true, true) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) - return - } - - if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - acctSensitive, errWithCode := m.processor.AccountGet(c.Request.Context(), authed, authed.Account.ID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, acctSensitive) -} diff --git a/internal/api/client/account/accountverify_test.go b/internal/api/client/account/accountverify_test.go deleted file mode 100644 index 886272865..000000000 --- a/internal/api/client/account/accountverify_test.go +++ /dev/null @@ -1,91 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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_test - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/account" - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -type AccountVerifyTestSuite struct { - AccountStandardTestSuite -} - -func (suite *AccountVerifyTestSuite) TestAccountVerifyGet() { - testAccount := suite.testAccounts["local_account_1"] - - // set up the request - recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodGet, nil, account.VerifyPath, "") - - // call the handler - suite.accountModule.AccountVerifyGETHandler(ctx) - - // 1. we should have OK because our request was valid - suite.Equal(http.StatusOK, recorder.Code) - - // 2. we should have no error message in the result body - result := recorder.Result() - defer result.Body.Close() - - // check the response - b, err := ioutil.ReadAll(result.Body) - assert.NoError(suite.T(), err) - - // unmarshal the returned account - apimodelAccount := &apimodel.Account{} - err = json.Unmarshal(b, apimodelAccount) - suite.NoError(err) - - createdAt, err := time.Parse(time.RFC3339, apimodelAccount.CreatedAt) - suite.NoError(err) - - suite.Equal(testAccount.ID, apimodelAccount.ID) - suite.Equal(testAccount.Username, apimodelAccount.Username) - suite.Equal(testAccount.Username, apimodelAccount.Acct) - suite.Equal(testAccount.DisplayName, apimodelAccount.DisplayName) - suite.Equal(*testAccount.Locked, apimodelAccount.Locked) - suite.Equal(*testAccount.Bot, apimodelAccount.Bot) - suite.WithinDuration(testAccount.CreatedAt, createdAt, 30*time.Second) // we lose a bit of accuracy serializing so fuzz this a bit - suite.Equal(testAccount.URL, apimodelAccount.URL) - suite.Equal("http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg", apimodelAccount.Avatar) - suite.Equal("http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpeg", apimodelAccount.AvatarStatic) - suite.Equal("http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg", apimodelAccount.Header) - suite.Equal("http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg", apimodelAccount.HeaderStatic) - suite.Equal(2, apimodelAccount.FollowersCount) - suite.Equal(2, apimodelAccount.FollowingCount) - suite.Equal(5, apimodelAccount.StatusesCount) - suite.EqualValues(gtsmodel.VisibilityPublic, apimodelAccount.Source.Privacy) - suite.Equal(testAccount.Language, apimodelAccount.Source.Language) - suite.Equal(testAccount.NoteRaw, apimodelAccount.Source.Note) -} - -func TestAccountVerifyTestSuite(t *testing.T) { - suite.Run(t, new(AccountVerifyTestSuite)) -} diff --git a/internal/api/client/account/block.go b/internal/api/client/account/block.go deleted file mode 100644 index 9840c96ab..000000000 --- a/internal/api/client/account/block.go +++ /dev/null @@ -1,95 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// AccountBlockPOSTHandler swagger:operation POST /api/v1/accounts/{id}/block accountBlock -// -// Block account with id. -// -// --- -// tags: -// - accounts -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: The id of the account to block. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - write:blocks -// -// responses: -// '200': -// description: Your relationship to the account. -// schema: -// "$ref": "#/definitions/accountRelationship" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) AccountBlockPOSTHandler(c *gin.Context) { - authed, err := oauth.Authed(c, true, true, true, true) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) - return - } - - if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - targetAcctID := c.Param(IDKey) - if targetAcctID == "" { - err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - relationship, errWithCode := m.processor.AccountBlockCreate(c.Request.Context(), authed, targetAcctID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, relationship) -} diff --git a/internal/api/client/account/block_test.go b/internal/api/client/account/block_test.go deleted file mode 100644 index 9c75330aa..000000000 --- a/internal/api/client/account/block_test.go +++ /dev/null @@ -1,74 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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_test - -import ( - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/account" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type BlockTestSuite struct { - AccountStandardTestSuite -} - -func (suite *BlockTestSuite) TestBlockSelf() { - testAcct := suite.testAccounts["local_account_1"] - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedAccount, testAcct) - ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"])) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(account.BlockPath, ":id", testAcct.ID, 1)), nil) - - ctx.Params = gin.Params{ - gin.Param{ - Key: account.IDKey, - Value: testAcct.ID, - }, - } - - suite.accountModule.AccountBlockPOSTHandler(ctx) - - // 1. status should be Not Acceptable due to attempted self-block - suite.Equal(http.StatusNotAcceptable, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - - // check the response - b, err := ioutil.ReadAll(result.Body) - _ = b - assert.NoError(suite.T(), err) -} - -func TestBlockTestSuite(t *testing.T) { - suite.Run(t, new(BlockTestSuite)) -} diff --git a/internal/api/client/account/follow.go b/internal/api/client/account/follow.go deleted file mode 100644 index cc523a7f8..000000000 --- a/internal/api/client/account/follow.go +++ /dev/null @@ -1,124 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// AccountFollowPOSTHandler swagger:operation POST /api/v1/accounts/{id}/follow accountFollow -// -// Follow account with id. -// -// The parameters can also be given in the body of the request, as JSON, if the content-type is set to 'application/json'. -// The parameters can also be given in the body of the request, as XML, if the content-type is set to 'application/xml'. -// -// --- -// tags: -// - accounts -// -// consumes: -// - application/json -// - application/xml -// - application/x-www-form-urlencoded -// -// parameters: -// - -// name: id -// required: true -// in: path -// description: ID of the account to follow. -// type: string -// - -// name: reblogs -// type: boolean -// default: true -// description: Show reblogs from this account. -// in: formData -// - -// default: false -// description: Notify when this account posts. -// in: formData -// name: notify -// type: boolean -// -// produces: -// - application/json -// -// security: -// - OAuth2 Bearer: -// - write:follows -// -// responses: -// '200': -// name: account relationship -// description: Your relationship to this account. -// schema: -// "$ref": "#/definitions/accountRelationship" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) AccountFollowPOSTHandler(c *gin.Context) { - authed, err := oauth.Authed(c, true, true, true, true) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) - return - } - - if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - targetAcctID := c.Param(IDKey) - if targetAcctID == "" { - err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - form := &model.AccountFollowRequest{} - if err := c.ShouldBind(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - form.ID = targetAcctID - - relationship, errWithCode := m.processor.AccountFollowCreate(c.Request.Context(), authed, form) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, relationship) -} diff --git a/internal/api/client/account/follow_test.go b/internal/api/client/account/follow_test.go deleted file mode 100644 index fad67b185..000000000 --- a/internal/api/client/account/follow_test.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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_test - -import ( - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/account" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type FollowTestSuite struct { - AccountStandardTestSuite -} - -func (suite *FollowTestSuite) TestFollowSelf() { - testAcct := suite.testAccounts["local_account_1"] - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedAccount, testAcct) - ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"])) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(account.FollowPath, ":id", testAcct.ID, 1)), nil) - - ctx.Params = gin.Params{ - gin.Param{ - Key: account.IDKey, - Value: testAcct.ID, - }, - } - - // call the handler - suite.accountModule.AccountFollowPOSTHandler(ctx) - - // 1. status should be Not Acceptable due to self-follow attempt - suite.Equal(http.StatusNotAcceptable, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - - // check the response - b, err := ioutil.ReadAll(result.Body) - _ = b - assert.NoError(suite.T(), err) -} - -func TestFollowTestSuite(t *testing.T) { - suite.Run(t, new(FollowTestSuite)) -} diff --git a/internal/api/client/account/followers.go b/internal/api/client/account/followers.go deleted file mode 100644 index cb2f4bfa6..000000000 --- a/internal/api/client/account/followers.go +++ /dev/null @@ -1,98 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// AccountFollowersGETHandler swagger:operation GET /api/v1/accounts/{id}/followers accountFollowers -// -// See followers of account with given id. -// -// --- -// tags: -// - accounts -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: Account ID. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - read:accounts -// -// responses: -// '200': -// name: accounts -// description: Array of accounts that follow this account. -// schema: -// type: array -// items: -// "$ref": "#/definitions/account" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) AccountFollowersGETHandler(c *gin.Context) { - authed, err := oauth.Authed(c, true, true, true, true) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) - return - } - - if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - targetAcctID := c.Param(IDKey) - if targetAcctID == "" { - err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - followers, errWithCode := m.processor.AccountFollowersGet(c.Request.Context(), authed, targetAcctID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, followers) -} diff --git a/internal/api/client/account/following.go b/internal/api/client/account/following.go deleted file mode 100644 index 3d69739c3..000000000 --- a/internal/api/client/account/following.go +++ /dev/null @@ -1,98 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// AccountFollowingGETHandler swagger:operation GET /api/v1/accounts/{id}/following accountFollowing -// -// See accounts followed by given account id. -// -// --- -// tags: -// - accounts -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: Account ID. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - read:accounts -// -// responses: -// '200': -// name: accounts -// description: Array of accounts that are followed by this account. -// schema: -// type: array -// items: -// "$ref": "#/definitions/account" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) AccountFollowingGETHandler(c *gin.Context) { - authed, err := oauth.Authed(c, true, true, true, true) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) - return - } - - if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - targetAcctID := c.Param(IDKey) - if targetAcctID == "" { - err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - following, errWithCode := m.processor.AccountFollowingGet(c.Request.Context(), authed, targetAcctID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, following) -} diff --git a/internal/api/client/account/relationships.go b/internal/api/client/account/relationships.go deleted file mode 100644 index 56159d48e..000000000 --- a/internal/api/client/account/relationships.go +++ /dev/null @@ -1,93 +0,0 @@ -package account - -import ( - "errors" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// AccountRelationshipsGETHandler swagger:operation GET /api/v1/accounts/relationships accountRelationships -// -// See your account's relationships with the given account IDs. -// -// --- -// tags: -// - accounts -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: array -// items: -// type: string -// description: Account IDs. -// in: query -// required: true -// -// security: -// - OAuth2 Bearer: -// - read:accounts -// -// responses: -// '200': -// name: account relationships -// description: Array of account relationships. -// schema: -// type: array -// items: -// "$ref": "#/definitions/accountRelationship" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) AccountRelationshipsGETHandler(c *gin.Context) { - authed, err := oauth.Authed(c, true, true, true, true) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) - return - } - - if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - targetAccountIDs := c.QueryArray("id[]") - if len(targetAccountIDs) == 0 { - // check fallback -- let's be generous and see if maybe it's just set as 'id'? - id := c.Query("id") - if id == "" { - err = errors.New("no account id(s) specified in query") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - targetAccountIDs = append(targetAccountIDs, id) - } - - relationships := []model.Relationship{} - - for _, targetAccountID := range targetAccountIDs { - r, errWithCode := m.processor.AccountRelationshipGet(c.Request.Context(), authed, targetAccountID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - relationships = append(relationships, *r) - } - - c.JSON(http.StatusOK, relationships) -} diff --git a/internal/api/client/account/statuses.go b/internal/api/client/account/statuses.go deleted file mode 100644 index 7ecf3ba9f..000000000 --- a/internal/api/client/account/statuses.go +++ /dev/null @@ -1,246 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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" - "net/http" - "strconv" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// AccountStatusesGETHandler swagger:operation GET /api/v1/accounts/{id}/statuses accountStatuses -// -// See statuses posted by the requested account. -// -// The statuses will be returned in descending chronological order (newest first), with sequential IDs (bigger = newer). -// -// --- -// tags: -// - accounts -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: Account ID. -// in: path -// required: true -// - -// name: limit -// type: integer -// description: Number of statuses to return. -// default: 30 -// in: query -// required: false -// - -// name: exclude_replies -// type: boolean -// description: Exclude statuses that are a reply to another status. -// default: false -// in: query -// required: false -// - -// name: exclude_reblogs -// type: boolean -// description: Exclude statuses that are a reblog/boost of another status. -// default: false -// in: query -// required: false -// - -// name: max_id -// type: string -// description: >- -// Return only statuses *OLDER* than the given max status ID. -// The status with the specified ID will not be included in the response. -// in: query -// - -// name: min_id -// type: string -// description: >- -// Return only statuses *NEWER* than the given min status ID. -// The status with the specified ID will not be included in the response. -// in: query -// required: false -// - -// name: pinned_only -// type: boolean -// description: Show only pinned statuses. In other words, exclude statuses that are not pinned to the given account ID. -// default: false -// in: query -// required: false -// - -// name: only_media -// type: boolean -// description: Show only statuses with media attachments. -// default: false -// in: query -// required: false -// - -// name: only_public -// type: boolean -// description: Show only statuses with a privacy setting of 'public'. -// default: false -// in: query -// required: false -// -// security: -// - OAuth2 Bearer: -// - read:accounts -// -// responses: -// '200': -// name: statuses -// description: Array of statuses. -// schema: -// type: array -// items: -// "$ref": "#/definitions/status" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) AccountStatusesGETHandler(c *gin.Context) { - authed, err := oauth.Authed(c, false, false, false, false) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) - return - } - - if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - targetAcctID := c.Param(IDKey) - if targetAcctID == "" { - err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - limit := 30 - limitString := c.Query(LimitKey) - if limitString != "" { - i, err := strconv.ParseInt(limitString, 10, 32) - if err != nil { - err := fmt.Errorf("error parsing %s: %s", LimitKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - limit = int(i) - } - - excludeReplies := false - excludeRepliesString := c.Query(ExcludeRepliesKey) - if excludeRepliesString != "" { - i, err := strconv.ParseBool(excludeRepliesString) - if err != nil { - err := fmt.Errorf("error parsing %s: %s", ExcludeRepliesKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - excludeReplies = i - } - - excludeReblogs := false - excludeReblogsString := c.Query(ExcludeReblogsKey) - if excludeReblogsString != "" { - i, err := strconv.ParseBool(excludeReblogsString) - if err != nil { - err := fmt.Errorf("error parsing %s: %s", ExcludeReblogsKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - excludeReblogs = i - } - - maxID := "" - maxIDString := c.Query(MaxIDKey) - if maxIDString != "" { - maxID = maxIDString - } - - minID := "" - minIDString := c.Query(MinIDKey) - if minIDString != "" { - minID = minIDString - } - - pinnedOnly := false - pinnedString := c.Query(PinnedKey) - if pinnedString != "" { - i, err := strconv.ParseBool(pinnedString) - if err != nil { - err := fmt.Errorf("error parsing %s: %s", PinnedKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - pinnedOnly = i - } - - mediaOnly := false - mediaOnlyString := c.Query(OnlyMediaKey) - if mediaOnlyString != "" { - i, err := strconv.ParseBool(mediaOnlyString) - if err != nil { - err := fmt.Errorf("error parsing %s: %s", OnlyMediaKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - mediaOnly = i - } - - publicOnly := false - publicOnlyString := c.Query(OnlyPublicKey) - if publicOnlyString != "" { - i, err := strconv.ParseBool(publicOnlyString) - if err != nil { - err := fmt.Errorf("error parsing %s: %s", OnlyPublicKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - publicOnly = i - } - - resp, errWithCode := m.processor.AccountStatusesGet(c.Request.Context(), authed, targetAcctID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - if resp.LinkHeader != "" { - c.Header("Link", resp.LinkHeader) - } - c.JSON(http.StatusOK, resp.Items) -} diff --git a/internal/api/client/account/statuses_test.go b/internal/api/client/account/statuses_test.go deleted file mode 100644 index 1f935896c..000000000 --- a/internal/api/client/account/statuses_test.go +++ /dev/null @@ -1,123 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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_test - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/account" - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" -) - -type AccountStatusesTestSuite struct { - AccountStandardTestSuite -} - -func (suite *AccountStatusesTestSuite) TestGetStatusesPublicOnly() { - // set up the request - // we're getting statuses of admin - targetAccount := suite.testAccounts["admin_account"] - recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodGet, nil, fmt.Sprintf("/api/v1/accounts/%s/statuses?limit=20&only_media=false&only_public=true", targetAccount.ID), "") - ctx.Params = gin.Params{ - gin.Param{ - Key: account.IDKey, - Value: targetAccount.ID, - }, - } - - // call the handler - suite.accountModule.AccountStatusesGETHandler(ctx) - - // 1. we should have OK because our request was valid - suite.Equal(http.StatusOK, recorder.Code) - - // 2. we should have no error message in the result body - result := recorder.Result() - defer result.Body.Close() - - // check the response - b, err := ioutil.ReadAll(result.Body) - assert.NoError(suite.T(), err) - - // unmarshal the returned statuses - apimodelStatuses := []*apimodel.Status{} - err = json.Unmarshal(b, &apimodelStatuses) - suite.NoError(err) - suite.NotEmpty(apimodelStatuses) - - for _, s := range apimodelStatuses { - suite.Equal(apimodel.VisibilityPublic, s.Visibility) - } - - suite.Equal(`<http://localhost:8080/api/v1/accounts/01F8MH17FWEB39HZJ76B6VXSKF/statuses?limit=20&max_id=01F8MH75CBF9JFX4ZAD54N0W0R&exclude_replies=false&exclude_reblogs=false&pinned_only=false&only_media=false&only_public=true>; rel="next", <http://localhost:8080/api/v1/accounts/01F8MH17FWEB39HZJ76B6VXSKF/statuses?limit=20&min_id=01G36SF3V6Y6V5BF9P4R7PQG7G&exclude_replies=false&exclude_reblogs=false&pinned_only=false&only_media=false&only_public=true>; rel="prev"`, result.Header.Get("link")) -} - -func (suite *AccountStatusesTestSuite) TestGetStatusesPublicOnlyMediaOnly() { - // set up the request - // we're getting statuses of admin - targetAccount := suite.testAccounts["admin_account"] - recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodGet, nil, fmt.Sprintf("/api/v1/accounts/%s/statuses?limit=20&only_media=true&only_public=true", targetAccount.ID), "") - ctx.Params = gin.Params{ - gin.Param{ - Key: account.IDKey, - Value: targetAccount.ID, - }, - } - - // call the handler - suite.accountModule.AccountStatusesGETHandler(ctx) - - // 1. we should have OK because our request was valid - suite.Equal(http.StatusOK, recorder.Code) - - // 2. we should have no error message in the result body - result := recorder.Result() - defer result.Body.Close() - - // check the response - b, err := ioutil.ReadAll(result.Body) - assert.NoError(suite.T(), err) - - // unmarshal the returned statuses - apimodelStatuses := []*apimodel.Status{} - err = json.Unmarshal(b, &apimodelStatuses) - suite.NoError(err) - suite.NotEmpty(apimodelStatuses) - - for _, s := range apimodelStatuses { - suite.NotEmpty(s.MediaAttachments) - suite.Equal(apimodel.VisibilityPublic, s.Visibility) - } - - suite.Equal(`<http://localhost:8080/api/v1/accounts/01F8MH17FWEB39HZJ76B6VXSKF/statuses?limit=20&max_id=01F8MH75CBF9JFX4ZAD54N0W0R&exclude_replies=false&exclude_reblogs=false&pinned_only=false&only_media=true&only_public=true>; rel="next", <http://localhost:8080/api/v1/accounts/01F8MH17FWEB39HZJ76B6VXSKF/statuses?limit=20&min_id=01F8MH75CBF9JFX4ZAD54N0W0R&exclude_replies=false&exclude_reblogs=false&pinned_only=false&only_media=true&only_public=true>; rel="prev"`, result.Header.Get("link")) -} - -func TestAccountStatusesTestSuite(t *testing.T) { - suite.Run(t, new(AccountStatusesTestSuite)) -} diff --git a/internal/api/client/account/unblock.go b/internal/api/client/account/unblock.go deleted file mode 100644 index 451b7fd27..000000000 --- a/internal/api/client/account/unblock.go +++ /dev/null @@ -1,96 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// AccountUnblockPOSTHandler swagger:operation POST /api/v1/accounts/{id}/unblock accountUnblock -// -// Unblock account with ID. -// -// --- -// tags: -// - accounts -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: The id of the account to unblock. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - write:blocks -// -// responses: -// '200': -// name: account relationship -// description: Your relationship to this account. -// schema: -// "$ref": "#/definitions/accountRelationship" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) AccountUnblockPOSTHandler(c *gin.Context) { - authed, err := oauth.Authed(c, true, true, true, true) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) - return - } - - if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - targetAcctID := c.Param(IDKey) - if targetAcctID == "" { - err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - relationship, errWithCode := m.processor.AccountBlockRemove(c.Request.Context(), authed, targetAcctID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, relationship) -} diff --git a/internal/api/client/account/unfollow.go b/internal/api/client/account/unfollow.go deleted file mode 100644 index fafba99fd..000000000 --- a/internal/api/client/account/unfollow.go +++ /dev/null @@ -1,96 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// AccountUnfollowPOSTHandler swagger:operation POST /api/v1/accounts/{id}/unfollow accountUnfollow -// -// Unfollow account with id. -// -// --- -// tags: -// - accounts -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: The id of the account to unfollow. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - write:follows -// -// responses: -// '200': -// name: account relationship -// description: Your relationship to this account. -// schema: -// "$ref": "#/definitions/accountRelationship" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) AccountUnfollowPOSTHandler(c *gin.Context) { - authed, err := oauth.Authed(c, true, true, true, true) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) - return - } - - if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - targetAcctID := c.Param(IDKey) - if targetAcctID == "" { - err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - relationship, errWithCode := m.processor.AccountFollowRemove(c.Request.Context(), authed, targetAcctID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, relationship) -} |