diff options
Diffstat (limited to 'internal/api/client/status')
24 files changed, 0 insertions, 2870 deletions
diff --git a/internal/api/client/status/status.go b/internal/api/client/status/status.go deleted file mode 100644 index dc32ae9b5..000000000 --- a/internal/api/client/status/status.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 status - -import ( - "net/http" - "strings" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/log" - "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" -) - -const ( - // IDKey is for status UUIDs - IDKey = "id" - // BasePath is the base path for serving the status API - BasePath = "/api/v1/statuses" - // BasePathWithID is just the base path with the ID key in it. - // Use this anywhere you need to know the ID of the status being queried. - BasePathWithID = BasePath + "/:" + IDKey - - // ContextPath is used for fetching context of posts - ContextPath = BasePathWithID + "/context" - - // FavouritedPath is for seeing who's faved a given status - FavouritedPath = BasePathWithID + "/favourited_by" - // FavouritePath is for posting a fave on a status - FavouritePath = BasePathWithID + "/favourite" - // UnfavouritePath is for removing a fave from a status - UnfavouritePath = BasePathWithID + "/unfavourite" - - // RebloggedPath is for seeing who's boosted a given status - RebloggedPath = BasePathWithID + "/reblogged_by" - // ReblogPath is for boosting/reblogging a given status - ReblogPath = BasePathWithID + "/reblog" - // UnreblogPath is for undoing a boost/reblog of a given status - UnreblogPath = BasePathWithID + "/unreblog" - - // BookmarkPath is for creating a bookmark on a given status - BookmarkPath = BasePathWithID + "/bookmark" - // UnbookmarkPath is for removing a bookmark from a given status - UnbookmarkPath = BasePathWithID + "/unbookmark" - - // MutePath is for muting a given status so that notifications will no longer be received about it. - MutePath = BasePathWithID + "/mute" - // UnmutePath is for undoing an existing mute - UnmutePath = BasePathWithID + "/unmute" - - // PinPath is for pinning a status to an account profile so that it's the first thing people see - PinPath = BasePathWithID + "/pin" - // UnpinPath is for undoing a pin and returning a status to the ever-swirling drain of time and entropy - UnpinPath = BasePathWithID + "/unpin" -) - -// Module implements the ClientAPIModule interface for every related to posting/deleting/interacting with statuses -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 { - r.AttachHandler(http.MethodPost, BasePath, m.StatusCreatePOSTHandler) - r.AttachHandler(http.MethodDelete, BasePathWithID, m.StatusDELETEHandler) - - r.AttachHandler(http.MethodPost, FavouritePath, m.StatusFavePOSTHandler) - r.AttachHandler(http.MethodPost, UnfavouritePath, m.StatusUnfavePOSTHandler) - r.AttachHandler(http.MethodGet, FavouritedPath, m.StatusFavedByGETHandler) - - r.AttachHandler(http.MethodPost, ReblogPath, m.StatusBoostPOSTHandler) - r.AttachHandler(http.MethodPost, UnreblogPath, m.StatusUnboostPOSTHandler) - r.AttachHandler(http.MethodGet, RebloggedPath, m.StatusBoostedByGETHandler) - - r.AttachHandler(http.MethodPost, BookmarkPath, m.StatusBookmarkPOSTHandler) - r.AttachHandler(http.MethodPost, UnbookmarkPath, m.StatusUnbookmarkPOSTHandler) - - r.AttachHandler(http.MethodGet, ContextPath, m.StatusContextGETHandler) - - r.AttachHandler(http.MethodGet, BasePathWithID, m.muxHandler) - return nil -} - -// muxHandler is a little workaround to overcome the limitations of Gin -func (m *Module) muxHandler(c *gin.Context) { - log.Debug("entering mux handler") - ru := c.Request.RequestURI - - if c.Request.Method == http.MethodGet { - switch { - case strings.HasPrefix(ru, ContextPath): - // TODO - case strings.HasPrefix(ru, FavouritedPath): - m.StatusFavedByGETHandler(c) - default: - m.StatusGETHandler(c) - } - } -} diff --git a/internal/api/client/status/status_test.go b/internal/api/client/status/status_test.go deleted file mode 100644 index 7c3f094f2..000000000 --- a/internal/api/client/status/status_test.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 status_test - -import ( - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/status" - "github.com/superseriousbusiness/gotosocial/internal/concurrency" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/email" - "github.com/superseriousbusiness/gotosocial/internal/federation" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/media" - "github.com/superseriousbusiness/gotosocial/internal/messages" - "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/storage" - "github.com/superseriousbusiness/gotosocial/internal/typeutils" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type StatusStandardTestSuite struct { - // standard suite interfaces - suite.Suite - db db.DB - tc typeutils.TypeConverter - mediaManager media.Manager - federator federation.Federator - emailSender email.Sender - processor processing.Processor - storage *storage.Driver - - // 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 - testFollows map[string]*gtsmodel.Follow - - // module being tested - statusModule *status.Module -} - -func (suite *StatusStandardTestSuite) 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() - suite.testFollows = testrig.NewTestFollows() -} - -func (suite *StatusStandardTestSuite) SetupTest() { - testrig.InitTestConfig() - testrig.InitTestLog() - - suite.db = testrig.NewTestDB() - suite.tc = testrig.NewTestTypeConverter(suite.db) - suite.storage = testrig.NewInMemoryStorage() - testrig.StandardDBSetup(suite.db, nil) - testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") - - fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1) - clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1) - - 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.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) - suite.statusModule = status.New(suite.processor).(*status.Module) - - suite.NoError(suite.processor.Start()) -} - -func (suite *StatusStandardTestSuite) TearDownTest() { - testrig.StandardDBTeardown(suite.db) - testrig.StandardStorageTeardown(suite.storage) -} diff --git a/internal/api/client/status/statusbookmark.go b/internal/api/client/status/statusbookmark.go deleted file mode 100644 index 983becd72..000000000 --- a/internal/api/client/status/statusbookmark.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 status - -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" -) - -// StatusBookmarkPOSTHandler swagger:operation POST /api/v1/statuses/{id}/bookmark statusBookmark -// -// Bookmark status with the given ID. -// -// --- -// tags: -// - statuses -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: Target status ID. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - write:statuses -// -// responses: -// '200': -// name: status -// description: The status. -// schema: -// "$ref": "#/definitions/status" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '403': -// description: forbidden -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) StatusBookmarkPOSTHandler(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 - } - - targetStatusID := c.Param(IDKey) - if targetStatusID == "" { - err := errors.New("no status id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - apiStatus, errWithCode := m.processor.StatusBookmark(c.Request.Context(), authed, targetStatusID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, apiStatus) -} diff --git a/internal/api/client/status/statusbookmark_test.go b/internal/api/client/status/statusbookmark_test.go deleted file mode 100644 index d3da4f297..000000000 --- a/internal/api/client/status/statusbookmark_test.go +++ /dev/null @@ -1,83 +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 status_test - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/status" - "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type StatusBookmarkTestSuite struct { - StatusStandardTestSuite -} - -func (suite *StatusBookmarkTestSuite) TestPostBookmark() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - targetStatus := suite.testStatuses["admin_account_status_1"] - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.BookmarkPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - - // normally the router would populate these params from the path values, - // but because we're calling the function directly, we need to set them manually. - ctx.Params = gin.Params{ - gin.Param{ - Key: status.IDKey, - Value: targetStatus.ID, - }, - } - - suite.statusModule.StatusBookmarkPOSTHandler(ctx) - - // check response - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - statusReply := &model.Status{} - err = json.Unmarshal(b, statusReply) - suite.NoError(err) - - suite.True(statusReply.Bookmarked) -} - -func TestStatusBookmarkTestSuite(t *testing.T) { - suite.Run(t, new(StatusBookmarkTestSuite)) -} diff --git a/internal/api/client/status/statusboost.go b/internal/api/client/status/statusboost.go deleted file mode 100644 index d43bedd6c..000000000 --- a/internal/api/client/status/statusboost.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 status - -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" -) - -// StatusBoostPOSTHandler swagger:operation POST /api/v1/statuses/{id}/reblog statusReblog -// -// Reblog/boost status with the given ID. -// -// If the target status is rebloggable/boostable, it will be shared with your followers. -// This is equivalent to an ActivityPub 'Announce' activity. -// -// --- -// tags: -// - statuses -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: Target status ID. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - write:statuses -// -// responses: -// '200': -// name: status -// description: The boost of the status. -// schema: -// "$ref": "#/definitions/status" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '403': -// description: forbidden -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) StatusBoostPOSTHandler(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 - } - - targetStatusID := c.Param(IDKey) - if targetStatusID == "" { - err := errors.New("no status id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - apiStatus, errWithCode := m.processor.StatusBoost(c.Request.Context(), authed, targetStatusID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, apiStatus) -} diff --git a/internal/api/client/status/statusboost_test.go b/internal/api/client/status/statusboost_test.go deleted file mode 100644 index 5b4b1b3cd..000000000 --- a/internal/api/client/status/statusboost_test.go +++ /dev/null @@ -1,247 +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 status_test - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/status" - "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type StatusBoostTestSuite struct { - StatusStandardTestSuite -} - -func (suite *StatusBoostTestSuite) TestPostBoost() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - targetStatus := suite.testStatuses["admin_account_status_1"] - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - - // normally the router would populate these params from the path values, - // but because we're calling the function directly, we need to set them manually. - ctx.Params = gin.Params{ - gin.Param{ - Key: status.IDKey, - Value: targetStatus.ID, - }, - } - - suite.statusModule.StatusBoostPOSTHandler(ctx) - - // check response - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - statusReply := &model.Status{} - err = json.Unmarshal(b, statusReply) - suite.NoError(err) - - suite.False(statusReply.Sensitive) - suite.Equal(model.VisibilityPublic, statusReply.Visibility) - - suite.Equal(targetStatus.ContentWarning, statusReply.SpoilerText) - suite.Equal(targetStatus.Content, statusReply.Content) - suite.Equal("the_mighty_zork", statusReply.Account.Username) - suite.Len(statusReply.MediaAttachments, 0) - suite.Len(statusReply.Mentions, 0) - suite.Len(statusReply.Emojis, 0) - suite.Len(statusReply.Tags, 0) - - suite.NotNil(statusReply.Application) - suite.Equal("really cool gts application", statusReply.Application.Name) - - suite.NotNil(statusReply.Reblog) - suite.Equal(1, statusReply.Reblog.ReblogsCount) - suite.Equal(1, statusReply.Reblog.FavouritesCount) - suite.Equal(targetStatus.Content, statusReply.Reblog.Content) - suite.Equal(targetStatus.ContentWarning, statusReply.Reblog.SpoilerText) - suite.Equal(targetStatus.AccountID, statusReply.Reblog.Account.ID) - suite.Len(statusReply.Reblog.MediaAttachments, 1) - suite.Len(statusReply.Reblog.Tags, 1) - suite.Len(statusReply.Reblog.Emojis, 1) - suite.Equal("superseriousbusiness", statusReply.Reblog.Application.Name) -} - -func (suite *StatusBoostTestSuite) TestPostBoostOwnFollowersOnly() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - testStatus := suite.testStatuses["local_account_1_status_5"] - testAccount := suite.testAccounts["local_account_1"] - testUser := suite.testUsers["local_account_1"] - - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, testUser) - ctx.Set(oauth.SessionAuthorizedAccount, testAccount) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.ReblogPath, ":id", testStatus.ID, 1)), nil) - ctx.Request.Header.Set("accept", "application/json") - - ctx.Params = gin.Params{ - gin.Param{ - Key: status.IDKey, - Value: testStatus.ID, - }, - } - - suite.statusModule.StatusBoostPOSTHandler(ctx) - - // check response - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - responseStatus := &model.Status{} - err = json.Unmarshal(b, responseStatus) - suite.NoError(err) - - suite.False(responseStatus.Sensitive) - suite.Equal(suite.tc.VisToAPIVis(context.Background(), testStatus.Visibility), responseStatus.Visibility) - - suite.Equal(testStatus.ContentWarning, responseStatus.SpoilerText) - suite.Equal(testStatus.Content, responseStatus.Content) - suite.Equal("the_mighty_zork", responseStatus.Account.Username) - suite.Len(responseStatus.MediaAttachments, 0) - suite.Len(responseStatus.Mentions, 0) - suite.Len(responseStatus.Emojis, 0) - suite.Len(responseStatus.Tags, 0) - - suite.NotNil(responseStatus.Application) - suite.Equal("really cool gts application", responseStatus.Application.Name) - - suite.NotNil(responseStatus.Reblog) - suite.Equal(1, responseStatus.Reblog.ReblogsCount) - suite.Equal(0, responseStatus.Reblog.FavouritesCount) - suite.Equal(testStatus.Content, responseStatus.Reblog.Content) - suite.Equal(testStatus.ContentWarning, responseStatus.Reblog.SpoilerText) - suite.Equal(testStatus.AccountID, responseStatus.Reblog.Account.ID) - suite.Equal(suite.tc.VisToAPIVis(context.Background(), testStatus.Visibility), responseStatus.Reblog.Visibility) - suite.Empty(responseStatus.Reblog.MediaAttachments) - suite.Empty(responseStatus.Reblog.Tags) - suite.Empty(responseStatus.Reblog.Emojis) - suite.Equal("really cool gts application", responseStatus.Reblog.Application.Name) -} - -// try to boost a status that's not boostable -func (suite *StatusBoostTestSuite) TestPostUnboostable() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - targetStatus := suite.testStatuses["local_account_2_status_4"] - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - - // normally the router would populate these params from the path values, - // but because we're calling the function directly, we need to set them manually. - ctx.Params = gin.Params{ - gin.Param{ - Key: status.IDKey, - Value: targetStatus.ID, - }, - } - - suite.statusModule.StatusBoostPOSTHandler(ctx) - - // check response - suite.Equal(http.StatusForbidden, recorder.Code) // we 403 unboostable statuses - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - suite.Equal(`{"error":"Forbidden"}`, string(b)) -} - -// try to boost a status that's not visible to the user -func (suite *StatusBoostTestSuite) TestPostNotVisible() { - // stop local_account_2 following zork - err := suite.db.DeleteByID(context.Background(), suite.testFollows["local_account_2_local_account_1"].ID, >smodel.Follow{}) - suite.NoError(err) - - t := suite.testTokens["local_account_2"] - oauthToken := oauth.DBTokenToToken(t) - - targetStatus := suite.testStatuses["local_account_1_status_3"] // this is a mutual only status and these accounts aren't mutuals - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_2"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_2"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - - // normally the router would populate these params from the path values, - // but because we're calling the function directly, we need to set them manually. - ctx.Params = gin.Params{ - gin.Param{ - Key: status.IDKey, - Value: targetStatus.ID, - }, - } - - suite.statusModule.StatusBoostPOSTHandler(ctx) - - // check response - suite.Equal(http.StatusNotFound, recorder.Code) // we 404 statuses that aren't visible -} - -func TestStatusBoostTestSuite(t *testing.T) { - suite.Run(t, new(StatusBoostTestSuite)) -} diff --git a/internal/api/client/status/statusboostedby.go b/internal/api/client/status/statusboostedby.go deleted file mode 100644 index 4a175f6e9..000000000 --- a/internal/api/client/status/statusboostedby.go +++ /dev/null @@ -1,89 +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 status - -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" -) - -// StatusBoostedByGETHandler swagger:operation GET /api/v1/statuses/{id}/reblogged_by statusBoostedBy -// -// View accounts that have reblogged/boosted the target status. -// -// --- -// tags: -// - statuses -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: Target status ID. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - read:accounts -// -// responses: -// '200': -// schema: -// type: array -// items: -// "$ref": "#/definitions/account" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '403': -// description: forbidden -// '404': -// description: not found -func (m *Module) StatusBoostedByGETHandler(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 - } - - targetStatusID := c.Param(IDKey) - if targetStatusID == "" { - err := errors.New("no status id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - apiAccounts, errWithCode := m.processor.StatusBoostedBy(c.Request.Context(), authed, targetStatusID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, apiAccounts) -} diff --git a/internal/api/client/status/statusboostedby_test.go b/internal/api/client/status/statusboostedby_test.go deleted file mode 100644 index 0d7c9f7ab..000000000 --- a/internal/api/client/status/statusboostedby_test.go +++ /dev/null @@ -1,112 +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 status_test - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/status" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type StatusBoostedByTestSuite struct { - StatusStandardTestSuite -} - -func (suite *StatusBoostedByTestSuite) TestRebloggedByOK() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - targetStatus := suite.testStatuses["local_account_1_status_1"] - - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.RebloggedPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - ctx.AddParam("id", targetStatus.ID) - - suite.statusModule.StatusBoostedByGETHandler(ctx) - - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - accounts := []*gtsmodel.Account{} - err = json.Unmarshal(b, &accounts) - suite.NoError(err) - - if !suite.Len(accounts, 1) { - suite.FailNow("should have had 1 account") - } - - suite.Equal(accounts[0].ID, suite.testAccounts["admin_account"].ID) -} - -func (suite *StatusBoostedByTestSuite) TestRebloggedByUseBoostWrapperID() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - targetStatus := suite.testStatuses["admin_account_status_4"] // admin_account_status_4 is a boost of local_account_1_status_1 - - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.RebloggedPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - ctx.AddParam("id", targetStatus.ID) - - suite.statusModule.StatusBoostedByGETHandler(ctx) - - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - accounts := []*gtsmodel.Account{} - err = json.Unmarshal(b, &accounts) - suite.NoError(err) - - if !suite.Len(accounts, 1) { - suite.FailNow("should have had 1 account") - } - - suite.Equal(accounts[0].ID, suite.testAccounts["admin_account"].ID) -} - -func TestStatusBoostedByTestSuite(t *testing.T) { - suite.Run(t, new(StatusBoostedByTestSuite)) -} diff --git a/internal/api/client/status/statuscontext.go b/internal/api/client/status/statuscontext.go deleted file mode 100644 index 632a151d5..000000000 --- a/internal/api/client/status/statuscontext.go +++ /dev/null @@ -1,100 +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 status - -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" -) - -// StatusContextGETHandler swagger:operation GET /api/v1/statuses/{id}/context statusContext -// -// Return ancestors and descendants of the given status. -// -// The returned statuses will be ordered in a thread structure, so they are suitable to be displayed in the order in which they were returned. -// -// --- -// tags: -// - statuses -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: Target status ID. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - read:statuses -// -// responses: -// '200': -// name: statuses -// description: Status context object. -// schema: -// "$ref": "#/definitions/statusContext" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '403': -// description: forbidden -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) StatusContextGETHandler(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 - } - - targetStatusID := c.Param(IDKey) - if targetStatusID == "" { - err := errors.New("no status id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - statusContext, errWithCode := m.processor.StatusGetContext(c.Request.Context(), authed, targetStatusID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, statusContext) -} diff --git a/internal/api/client/status/statuscreate.go b/internal/api/client/status/statuscreate.go deleted file mode 100644 index c1427411d..000000000 --- a/internal/api/client/status/statuscreate.go +++ /dev/null @@ -1,172 +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 status - -import ( - "errors" - "fmt" - "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" -) - -// StatusCreatePOSTHandler swagger:operation POST /api/v1/statuses statusCreate -// -// Create a new status. -// -// 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: -// - statuses -// -// consumes: -// - application/json -// - application/xml -// - application/x-www-form-urlencoded -// -// produces: -// - application/json -// -// security: -// - OAuth2 Bearer: -// - write:statuses -// -// responses: -// '200': -// description: "The newly created status." -// schema: -// "$ref": "#/definitions/status" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '403': -// description: forbidden -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) StatusCreatePOSTHandler(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 := &model.AdvancedStatusCreateForm{} - if err := c.ShouldBind(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - // DO NOT COMMIT THIS UNCOMMENTED, IT WILL CAUSE MASS CHAOS. - // this is being left in as an ode to kim's shitposting. - // - // user := authed.Account.DisplayName - // if user == "" { - // user = authed.Account.Username - // } - // form.Status += "\n\nsent from " + user + "'s iphone\n" - - if err := validateCreateStatus(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - apiStatus, errWithCode := m.processor.StatusCreate(c.Request.Context(), authed, form) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, apiStatus) -} - -func validateCreateStatus(form *model.AdvancedStatusCreateForm) error { - hasStatus := form.Status != "" - hasMedia := len(form.MediaIDs) != 0 - hasPoll := form.Poll != nil - - if !hasStatus && !hasMedia && !hasPoll { - return errors.New("no status, media, or poll provided") - } - - if hasMedia && hasPoll { - return errors.New("can't post media + poll in same status") - } - - maxChars := config.GetStatusesMaxChars() - maxMediaFiles := config.GetStatusesMediaMaxFiles() - maxPollOptions := config.GetStatusesPollMaxOptions() - maxPollChars := config.GetStatusesPollOptionMaxChars() - maxCwChars := config.GetStatusesCWMaxChars() - - if form.Status != "" { - if length := len([]rune(form.Status)); length > maxChars { - return fmt.Errorf("status too long, %d characters provided but limit is %d", length, maxChars) - } - } - - if len(form.MediaIDs) > maxMediaFiles { - return fmt.Errorf("too many media files attached to status, %d attached but limit is %d", len(form.MediaIDs), maxMediaFiles) - } - - if form.Poll != nil { - if form.Poll.Options == nil { - return errors.New("poll with no options") - } - if len(form.Poll.Options) > maxPollOptions { - return fmt.Errorf("too many poll options provided, %d provided but limit is %d", len(form.Poll.Options), maxPollOptions) - } - for _, p := range form.Poll.Options { - if length := len([]rune(p)); length > maxPollChars { - return fmt.Errorf("poll option too long, %d characters provided but limit is %d", length, maxPollChars) - } - } - } - - if form.SpoilerText != "" { - if length := len([]rune(form.SpoilerText)); length > maxCwChars { - return fmt.Errorf("content-warning/spoilertext too long, %d characters provided but limit is %d", length, maxCwChars) - } - } - - if form.Language != "" { - if err := validate.Language(form.Language); err != nil { - return err - } - } - - return nil -} diff --git a/internal/api/client/status/statuscreate_test.go b/internal/api/client/status/statuscreate_test.go deleted file mode 100644 index c143489f3..000000000 --- a/internal/api/client/status/statuscreate_test.go +++ /dev/null @@ -1,398 +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 status_test - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/status" - "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type StatusCreateTestSuite struct { - StatusStandardTestSuite -} - -const ( - statusWithLinksAndTags = "#test alright, should be able to post #links with fragments in them now, let's see........\n\nhttps://docs.gotosocial.org/en/latest/user_guide/posts/#links\n\n#gotosocial\n\n(tobi remember to pull the docker image challenge)" - statusMarkdown = "# Title\n\n## Smaller title\n\nThis is a post written in [markdown](https://www.markdownguide.org/)\n\n<img src=\"https://d33wubrfki0l68.cloudfront.net/f1f475a6fda1c2c4be4cac04033db5c3293032b4/513a4/assets/images/markdown-mark-white.svg\"/>" - statusMarkdownExpected = "<h1>Title</h1><h2>Smaller title</h2><p>This is a post written in <a href=\"https://www.markdownguide.org/\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">markdown</a></p><img src=\"https://d33wubrfki0l68.cloudfront.net/f1f475a6fda1c2c4be4cac04033db5c3293032b4/513a4/assets/images/markdown-mark-white.svg\" crossorigin=\"anonymous\">" -) - -// Post a new status with some custom visibility settings -func (suite *StatusCreateTestSuite) TestPostNewStatus() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", status.BasePath), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - ctx.Request.Form = url.Values{ - "status": {"this is a brand new status! #helloworld"}, - "spoiler_text": {"hello hello"}, - "sensitive": {"true"}, - "visibility": {string(model.VisibilityMutualsOnly)}, - "likeable": {"false"}, - "replyable": {"false"}, - "federated": {"false"}, - } - suite.statusModule.StatusCreatePOSTHandler(ctx) - - // check response - - // 1. we should have OK from our call to the function - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - statusReply := &model.Status{} - err = json.Unmarshal(b, statusReply) - suite.NoError(err) - - suite.Equal("hello hello", statusReply.SpoilerText) - suite.Equal("<p>this is a brand new status! <a href=\"http://localhost:8080/tags/helloworld\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\">#<span>helloworld</span></a></p>", statusReply.Content) - suite.True(statusReply.Sensitive) - suite.Equal(model.VisibilityPrivate, statusReply.Visibility) // even though we set this status to mutuals only, it should serialize to private, because the mastodon api has no idea about mutuals_only - suite.Len(statusReply.Tags, 1) - suite.Equal(model.Tag{ - Name: "helloworld", - URL: "http://localhost:8080/tags/helloworld", - }, statusReply.Tags[0]) - - gtsTag := >smodel.Tag{} - err = suite.db.GetWhere(context.Background(), []db.Where{{Key: "name", Value: "helloworld"}}, gtsTag) - suite.NoError(err) - suite.Equal(statusReply.Account.ID, gtsTag.FirstSeenFromAccountID) -} - -func (suite *StatusCreateTestSuite) TestPostNewStatusMarkdown() { - // set default post language of account 1 to markdown - testAccount := suite.testAccounts["local_account_1"] - testAccount.StatusFormat = "markdown" - a := testAccount - - err := suite.db.UpdateAccount(context.Background(), a) - if err != nil { - suite.FailNow(err.Error()) - } - suite.Equal(a.StatusFormat, "markdown") - - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, a) - - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", status.BasePath), nil) - ctx.Request.Header.Set("accept", "application/json") - ctx.Request.Form = url.Values{ - "status": {statusMarkdown}, - "visibility": {string(model.VisibilityPublic)}, - } - suite.statusModule.StatusCreatePOSTHandler(ctx) - - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - statusReply := &model.Status{} - err = json.Unmarshal(b, statusReply) - suite.NoError(err) - - suite.Equal(statusMarkdownExpected, statusReply.Content) -} - -// mention an account that is not yet known to the instance -- it should be looked up and put in the db -func (suite *StatusCreateTestSuite) TestMentionUnknownAccount() { - // first remove remote account 1 from the database so it gets looked up again - remoteAccount := suite.testAccounts["remote_account_1"] - err := suite.db.DeleteAccount(context.Background(), remoteAccount.ID) - suite.NoError(err) - - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", status.BasePath), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - ctx.Request.Form = url.Values{ - "status": {"hello @brand_new_person@unknown-instance.com"}, - "visibility": {string(model.VisibilityPublic)}, - } - suite.statusModule.StatusCreatePOSTHandler(ctx) - - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - statusReply := &model.Status{} - err = json.Unmarshal(b, statusReply) - suite.NoError(err) - - // if the status is properly formatted, that means the account has been put in the db - suite.Equal(`<p>hello <span class="h-card"><a href="https://unknown-instance.com/@brand_new_person" class="u-url mention" rel="nofollow noreferrer noopener" target="_blank">@<span>brand_new_person</span></a></span></p>`, statusReply.Content) - suite.Equal(model.VisibilityPublic, statusReply.Visibility) -} - -func (suite *StatusCreateTestSuite) TestPostAnotherNewStatus() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", status.BasePath), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - ctx.Request.Form = url.Values{ - "status": {statusWithLinksAndTags}, - } - suite.statusModule.StatusCreatePOSTHandler(ctx) - - // check response - - // 1. we should have OK from our call to the function - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - statusReply := &model.Status{} - err = json.Unmarshal(b, statusReply) - suite.NoError(err) - - suite.Equal("<p><a href=\"http://localhost:8080/tags/test\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\">#<span>test</span></a> alright, should be able to post <a href=\"http://localhost:8080/tags/links\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\">#<span>links</span></a> with fragments in them now, let's see........<br/><br/><a href=\"https://docs.gotosocial.org/en/latest/user_guide/posts/#links\" rel=\"noopener nofollow noreferrer\" target=\"_blank\">docs.gotosocial.org/en/latest/user_guide/posts/#links</a><br/><br/><a href=\"http://localhost:8080/tags/gotosocial\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\">#<span>gotosocial</span></a><br/><br/>(tobi remember to pull the docker image challenge)</p>", statusReply.Content) -} - -func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", status.BasePath), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - ctx.Request.Form = url.Values{ - "status": {"here is a rainbow emoji a few times! :rainbow: :rainbow: :rainbow: \n here's an emoji that isn't in the db: :test_emoji: "}, - } - suite.statusModule.StatusCreatePOSTHandler(ctx) - - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - statusReply := &model.Status{} - err = json.Unmarshal(b, statusReply) - suite.NoError(err) - - suite.Equal("", statusReply.SpoilerText) - suite.Equal("<p>here is a rainbow emoji a few times! :rainbow: :rainbow: :rainbow: <br/> here's an emoji that isn't in the db: :test_emoji:</p>", statusReply.Content) - - suite.Len(statusReply.Emojis, 1) - apiEmoji := statusReply.Emojis[0] - gtsEmoji := testrig.NewTestEmojis()["rainbow"] - - suite.Equal(gtsEmoji.Shortcode, apiEmoji.Shortcode) - suite.Equal(gtsEmoji.ImageURL, apiEmoji.URL) - suite.Equal(gtsEmoji.ImageStaticURL, apiEmoji.StaticURL) -} - -// Try to reply to a status that doesn't exist -func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", status.BasePath), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - ctx.Request.Form = url.Values{ - "status": {"this is a reply to a status that doesn't exist"}, - "spoiler_text": {"don't open cuz it won't work"}, - "in_reply_to_id": {"3759e7ef-8ee1-4c0c-86f6-8b70b9ad3d50"}, - } - suite.statusModule.StatusCreatePOSTHandler(ctx) - - // check response - - suite.EqualValues(http.StatusBadRequest, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - suite.Equal(`{"error":"Bad Request: status with id 3759e7ef-8ee1-4c0c-86f6-8b70b9ad3d50 not replyable because it doesn't exist"}`, string(b)) -} - -// Post a reply to the status of a local user that allows replies. -func (suite *StatusCreateTestSuite) TestReplyToLocalStatus() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", status.BasePath), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - ctx.Request.Form = url.Values{ - "status": {fmt.Sprintf("hello @%s this reply should work!", testrig.NewTestAccounts()["local_account_2"].Username)}, - "in_reply_to_id": {testrig.NewTestStatuses()["local_account_2_status_1"].ID}, - } - suite.statusModule.StatusCreatePOSTHandler(ctx) - - // check response - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - statusReply := &model.Status{} - err = json.Unmarshal(b, statusReply) - suite.NoError(err) - - suite.Equal("", statusReply.SpoilerText) - suite.Equal(fmt.Sprintf("<p>hello <span class=\"h-card\"><a href=\"http://localhost:8080/@%s\" class=\"u-url mention\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">@<span>%s</span></a></span> this reply should work!</p>", testrig.NewTestAccounts()["local_account_2"].Username, testrig.NewTestAccounts()["local_account_2"].Username), statusReply.Content) - suite.False(statusReply.Sensitive) - suite.Equal(model.VisibilityPublic, statusReply.Visibility) - suite.Equal(testrig.NewTestStatuses()["local_account_2_status_1"].ID, *statusReply.InReplyToID) - suite.Equal(testrig.NewTestAccounts()["local_account_2"].ID, *statusReply.InReplyToAccountID) - suite.Len(statusReply.Mentions, 1) -} - -// Take a media file which is currently not associated with a status, and attach it to a new status. -func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - attachment := suite.testAttachments["local_account_1_unattached_1"] - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", status.BasePath), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - ctx.Request.Form = url.Values{ - "status": {"here's an image attachment"}, - "media_ids[]": {attachment.ID}, - } - suite.statusModule.StatusCreatePOSTHandler(ctx) - - // check response - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - statusResponse := &model.Status{} - err = json.Unmarshal(b, statusResponse) - suite.NoError(err) - - suite.Equal("", statusResponse.SpoilerText) - suite.Equal("<p>here's an image attachment</p>", statusResponse.Content) - suite.False(statusResponse.Sensitive) - suite.Equal(model.VisibilityPublic, statusResponse.Visibility) - - // there should be one media attachment - suite.Len(statusResponse.MediaAttachments, 1) - - // get the updated media attachment from the database - gtsAttachment, err := suite.db.GetAttachmentByID(context.Background(), statusResponse.MediaAttachments[0].ID) - suite.NoError(err) - - // convert it to a api attachment - gtsAttachmentAsapi, err := suite.tc.AttachmentToAPIAttachment(context.Background(), gtsAttachment) - suite.NoError(err) - - // compare it with what we have now - suite.EqualValues(statusResponse.MediaAttachments[0], gtsAttachmentAsapi) - - // the status id of the attachment should now be set to the id of the status we just created - suite.Equal(statusResponse.ID, gtsAttachment.StatusID) -} - -func TestStatusCreateTestSuite(t *testing.T) { - suite.Run(t, new(StatusCreateTestSuite)) -} diff --git a/internal/api/client/status/statusdelete.go b/internal/api/client/status/statusdelete.go deleted file mode 100644 index b37dd5f14..000000000 --- a/internal/api/client/status/statusdelete.go +++ /dev/null @@ -1,100 +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 status - -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" -) - -// StatusDELETEHandler swagger:operation DELETE /api/v1/statuses/{id} statusDelete -// -// Delete status with the given ID. The status must belong to you. -// -// The deleted status will be returned in the response. The `text` field will contain the original text of the status as it was submitted. -// This is useful when doing a 'delete and redraft' type operation. -// -// --- -// tags: -// - statuses -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: Target status ID. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - write:statuses -// -// responses: -// '200': -// description: "The status that was just deleted." -// schema: -// "$ref": "#/definitions/status" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '403': -// description: forbidden -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) StatusDELETEHandler(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 - } - - targetStatusID := c.Param(IDKey) - if targetStatusID == "" { - err := errors.New("no status id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - apiStatus, errWithCode := m.processor.StatusDelete(c.Request.Context(), authed, targetStatusID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, apiStatus) -} diff --git a/internal/api/client/status/statusdelete_test.go b/internal/api/client/status/statusdelete_test.go deleted file mode 100644 index f97a13eec..000000000 --- a/internal/api/client/status/statusdelete_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 status_test - -import ( - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/status" - "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type StatusDeleteTestSuite struct { - StatusStandardTestSuite -} - -func (suite *StatusDeleteTestSuite) TestPostDelete() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - targetStatus := suite.testStatuses["local_account_1_status_1"] - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodDelete, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.BasePathWithID, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - - // normally the router would populate these params from the path values, - // but because we're calling the function directly, we need to set them manually. - ctx.Params = gin.Params{ - gin.Param{ - Key: status.IDKey, - Value: targetStatus.ID, - }, - } - - suite.statusModule.StatusDELETEHandler(ctx) - - // check response - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - statusReply := &model.Status{} - err = json.Unmarshal(b, statusReply) - suite.NoError(err) - suite.NotNil(statusReply) - - if !testrig.WaitFor(func() bool { - _, err := suite.db.GetStatusByID(ctx, targetStatus.ID) - return errors.Is(err, db.ErrNoEntries) - }) { - suite.FailNow("time out waiting for status to be deleted") - } - -} - -func TestStatusDeleteTestSuite(t *testing.T) { - suite.Run(t, new(StatusDeleteTestSuite)) -} diff --git a/internal/api/client/status/statusfave.go b/internal/api/client/status/statusfave.go deleted file mode 100644 index 3117e7ef2..000000000 --- a/internal/api/client/status/statusfave.go +++ /dev/null @@ -1,97 +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 status - -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" -) - -// StatusFavePOSTHandler swagger:operation POST /api/v1/statuses/{id}/favourite statusFave -// -// Star/like/favourite the given status, if permitted. -// -// --- -// tags: -// - statuses -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: Target status ID. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - write:statuses -// -// responses: -// '200': -// description: "The newly faved status." -// schema: -// "$ref": "#/definitions/status" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '403': -// description: forbidden -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) StatusFavePOSTHandler(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 - } - - targetStatusID := c.Param(IDKey) - if targetStatusID == "" { - err := errors.New("no status id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - apiStatus, errWithCode := m.processor.StatusFave(c.Request.Context(), authed, targetStatusID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, apiStatus) -} diff --git a/internal/api/client/status/statusfave_test.go b/internal/api/client/status/statusfave_test.go deleted file mode 100644 index da5d2a48a..000000000 --- a/internal/api/client/status/statusfave_test.go +++ /dev/null @@ -1,131 +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 status_test - -import ( - "encoding/json" - "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/status" - "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type StatusFaveTestSuite struct { - StatusStandardTestSuite -} - -// fave a status -func (suite *StatusFaveTestSuite) TestPostFave() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - targetStatus := suite.testStatuses["admin_account_status_2"] - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.FavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - - // normally the router would populate these params from the path values, - // but because we're calling the function directly, we need to set them manually. - ctx.Params = gin.Params{ - gin.Param{ - Key: status.IDKey, - Value: targetStatus.ID, - }, - } - - suite.statusModule.StatusFavePOSTHandler(ctx) - - // check response - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - assert.NoError(suite.T(), err) - - statusReply := &model.Status{} - err = json.Unmarshal(b, statusReply) - assert.NoError(suite.T(), err) - - assert.Equal(suite.T(), targetStatus.ContentWarning, statusReply.SpoilerText) - assert.Equal(suite.T(), targetStatus.Content, statusReply.Content) - assert.True(suite.T(), statusReply.Sensitive) - assert.Equal(suite.T(), model.VisibilityPublic, statusReply.Visibility) - assert.True(suite.T(), statusReply.Favourited) - assert.Equal(suite.T(), 1, statusReply.FavouritesCount) -} - -// try to fave a status that's not faveable -func (suite *StatusFaveTestSuite) TestPostUnfaveable() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - targetStatus := suite.testStatuses["local_account_2_status_3"] // this one is unlikeable and unreplyable - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.FavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - - // normally the router would populate these params from the path values, - // but because we're calling the function directly, we need to set them manually. - ctx.Params = gin.Params{ - gin.Param{ - Key: status.IDKey, - Value: targetStatus.ID, - }, - } - - suite.statusModule.StatusFavePOSTHandler(ctx) - - // check response - suite.EqualValues(http.StatusForbidden, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - assert.NoError(suite.T(), err) - assert.Equal(suite.T(), `{"error":"Forbidden"}`, string(b)) -} - -func TestStatusFaveTestSuite(t *testing.T) { - suite.Run(t, new(StatusFaveTestSuite)) -} diff --git a/internal/api/client/status/statusfavedby.go b/internal/api/client/status/statusfavedby.go deleted file mode 100644 index 20ef86ded..000000000 --- a/internal/api/client/status/statusfavedby.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 status - -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" -) - -// StatusFavedByGETHandler swagger:operation GET /api/v1/statuses/{id}/favourited_by statusFavedBy -// -// View accounts that have faved/starred/liked the target status. -// -// --- -// tags: -// - statuses -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: Target status ID. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - read:accounts -// -// responses: -// '200': -// schema: -// type: array -// items: -// "$ref": "#/definitions/account" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '403': -// description: forbidden -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) StatusFavedByGETHandler(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 - } - - targetStatusID := c.Param(IDKey) - if targetStatusID == "" { - err := errors.New("no status id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - apiAccounts, errWithCode := m.processor.StatusFavedBy(c.Request.Context(), authed, targetStatusID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, apiAccounts) -} diff --git a/internal/api/client/status/statusfavedby_test.go b/internal/api/client/status/statusfavedby_test.go deleted file mode 100644 index e704fa724..000000000 --- a/internal/api/client/status/statusfavedby_test.go +++ /dev/null @@ -1,88 +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 status_test - -import ( - "encoding/json" - "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/status" - "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type StatusFavedByTestSuite struct { - StatusStandardTestSuite -} - -func (suite *StatusFavedByTestSuite) TestGetFavedBy() { - t := suite.testTokens["local_account_2"] - oauthToken := oauth.DBTokenToToken(t) - - targetStatus := suite.testStatuses["admin_account_status_1"] // this status is faved by local_account_1 - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_2"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_2"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_2"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.FavouritedPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - - // normally the router would populate these params from the path values, - // but because we're calling the function directly, we need to set them manually. - ctx.Params = gin.Params{ - gin.Param{ - Key: status.IDKey, - Value: targetStatus.ID, - }, - } - - suite.statusModule.StatusFavedByGETHandler(ctx) - - // check response - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - assert.NoError(suite.T(), err) - - accts := []model.Account{} - err = json.Unmarshal(b, &accts) - assert.NoError(suite.T(), err) - - assert.Len(suite.T(), accts, 1) - assert.Equal(suite.T(), "the_mighty_zork", accts[0].Username) -} - -func TestStatusFavedByTestSuite(t *testing.T) { - suite.Run(t, new(StatusFavedByTestSuite)) -} diff --git a/internal/api/client/status/statusget.go b/internal/api/client/status/statusget.go deleted file mode 100644 index a0d0e913c..000000000 --- a/internal/api/client/status/statusget.go +++ /dev/null @@ -1,97 +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 status - -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" -) - -// StatusGETHandler swagger:operation GET /api/v1/statuses/{id} statusGet -// -// View status with the given ID. -// -// --- -// tags: -// - statuses -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: Target status ID. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - read:statuses -// -// responses: -// '200': -// description: "The requested status." -// schema: -// "$ref": "#/definitions/status" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '403': -// description: forbidden -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) StatusGETHandler(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 - } - - targetStatusID := c.Param(IDKey) - if targetStatusID == "" { - err := errors.New("no status id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - apiStatus, errWithCode := m.processor.StatusGet(c.Request.Context(), authed, targetStatusID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, apiStatus) -} diff --git a/internal/api/client/status/statusget_test.go b/internal/api/client/status/statusget_test.go deleted file mode 100644 index d11c9b587..000000000 --- a/internal/api/client/status/statusget_test.go +++ /dev/null @@ -1,33 +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 status_test - -import ( - "testing" - - "github.com/stretchr/testify/suite" -) - -type StatusGetTestSuite struct { - StatusStandardTestSuite -} - -func TestStatusGetTestSuite(t *testing.T) { - suite.Run(t, new(StatusGetTestSuite)) -} diff --git a/internal/api/client/status/statusunbookmark.go b/internal/api/client/status/statusunbookmark.go deleted file mode 100644 index aa090f8c9..000000000 --- a/internal/api/client/status/statusunbookmark.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 status - -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" -) - -// StatusUnbookmarkPOSTHandler swagger:operation POST /api/v1/statuses/{id}/unbookmark statusUnbookmark -// -// Unbookmark status with the given ID. -// -// --- -// tags: -// - statuses -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: Target status ID. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - write:statuses -// -// responses: -// '200': -// name: status -// description: The status. -// schema: -// "$ref": "#/definitions/status" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '403': -// description: forbidden -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) StatusUnbookmarkPOSTHandler(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 - } - - targetStatusID := c.Param(IDKey) - if targetStatusID == "" { - err := errors.New("no status id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - apiStatus, errWithCode := m.processor.StatusUnbookmark(c.Request.Context(), authed, targetStatusID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, apiStatus) -} diff --git a/internal/api/client/status/statusunbookmark_test.go b/internal/api/client/status/statusunbookmark_test.go deleted file mode 100644 index 09a18ab9b..000000000 --- a/internal/api/client/status/statusunbookmark_test.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 status_test - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/status" - "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type StatusUnbookmarkTestSuite struct { - StatusStandardTestSuite -} - -func (suite *StatusUnbookmarkTestSuite) TestPostUnbookmark() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - targetStatus := suite.testStatuses["admin_account_status_1"] - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.UnbookmarkPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - - ctx.Params = gin.Params{ - gin.Param{ - Key: status.IDKey, - Value: targetStatus.ID, - }, - } - - suite.statusModule.StatusUnbookmarkPOSTHandler(ctx) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - statusReply := &model.Status{} - err = json.Unmarshal(b, statusReply) - suite.NoError(err) - - suite.False(statusReply.Bookmarked) -} - -func TestStatusUnbookmarkTestSuite(t *testing.T) { - suite.Run(t, new(StatusUnbookmarkTestSuite)) -} diff --git a/internal/api/client/status/statusunboost.go b/internal/api/client/status/statusunboost.go deleted file mode 100644 index 45a8e0ece..000000000 --- a/internal/api/client/status/statusunboost.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 status - -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" -) - -// StatusUnboostPOSTHandler swagger:operation POST /api/v1/statuses/{id}/unreblog statusUnreblog -// -// Unreblog/unboost status with the given ID. -// -// --- -// tags: -// - statuses -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: Target status ID. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - write:statuses -// -// responses: -// '200': -// name: status -// description: The unboosted status. -// schema: -// "$ref": "#/definitions/status" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '403': -// description: forbidden -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) StatusUnboostPOSTHandler(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 - } - - targetStatusID := c.Param(IDKey) - if targetStatusID == "" { - err := errors.New("no status id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - apiStatus, errWithCode := m.processor.StatusUnboost(c.Request.Context(), authed, targetStatusID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, apiStatus) -} diff --git a/internal/api/client/status/statusunfave.go b/internal/api/client/status/statusunfave.go deleted file mode 100644 index 19d3da3bd..000000000 --- a/internal/api/client/status/statusunfave.go +++ /dev/null @@ -1,97 +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 status - -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" -) - -// StatusUnfavePOSTHandler swagger:operation POST /api/v1/statuses/{id}/unfavourite statusUnfave -// -// Unstar/unlike/unfavourite the given status. -// -// --- -// tags: -// - statuses -// -// produces: -// - application/json -// -// parameters: -// - -// name: id -// type: string -// description: Target status ID. -// in: path -// required: true -// -// security: -// - OAuth2 Bearer: -// - write:statuses -// -// responses: -// '200': -// description: "The unfaved status." -// schema: -// "$ref": "#/definitions/status" -// '400': -// description: bad request -// '401': -// description: unauthorized -// '403': -// description: forbidden -// '404': -// description: not found -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) StatusUnfavePOSTHandler(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 - } - - targetStatusID := c.Param(IDKey) - if targetStatusID == "" { - err := errors.New("no status id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - apiStatus, errWithCode := m.processor.StatusUnfave(c.Request.Context(), authed, targetStatusID) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, apiStatus) -} diff --git a/internal/api/client/status/statusunfave_test.go b/internal/api/client/status/statusunfave_test.go deleted file mode 100644 index b8448d657..000000000 --- a/internal/api/client/status/statusunfave_test.go +++ /dev/null @@ -1,143 +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 status_test - -import ( - "encoding/json" - "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/status" - "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type StatusUnfaveTestSuite struct { - StatusStandardTestSuite -} - -// unfave a status -func (suite *StatusUnfaveTestSuite) TestPostUnfave() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - // this is the status we wanna unfave: in the testrig it's already faved by this account - targetStatus := suite.testStatuses["admin_account_status_1"] - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.UnfavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - - // normally the router would populate these params from the path values, - // but because we're calling the function directly, we need to set them manually. - ctx.Params = gin.Params{ - gin.Param{ - Key: status.IDKey, - Value: targetStatus.ID, - }, - } - - suite.statusModule.StatusUnfavePOSTHandler(ctx) - - // check response - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - assert.NoError(suite.T(), err) - - statusReply := &model.Status{} - err = json.Unmarshal(b, statusReply) - assert.NoError(suite.T(), err) - - assert.Equal(suite.T(), targetStatus.ContentWarning, statusReply.SpoilerText) - assert.Equal(suite.T(), targetStatus.Content, statusReply.Content) - assert.False(suite.T(), statusReply.Sensitive) - assert.Equal(suite.T(), model.VisibilityPublic, statusReply.Visibility) - assert.False(suite.T(), statusReply.Favourited) - assert.Equal(suite.T(), 0, statusReply.FavouritesCount) -} - -// try to unfave a status that's already not faved -func (suite *StatusUnfaveTestSuite) TestPostAlreadyNotFaved() { - t := suite.testTokens["local_account_1"] - oauthToken := oauth.DBTokenToToken(t) - - // this is the status we wanna unfave: in the testrig it's not faved by this account - targetStatus := suite.testStatuses["admin_account_status_2"] - - // setup - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) - ctx.Set(oauth.SessionAuthorizedToken, oauthToken) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.UnfavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "application/json") - - // normally the router would populate these params from the path values, - // but because we're calling the function directly, we need to set them manually. - ctx.Params = gin.Params{ - gin.Param{ - Key: status.IDKey, - Value: targetStatus.ID, - }, - } - - suite.statusModule.StatusUnfavePOSTHandler(ctx) - - // check response - suite.EqualValues(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - assert.NoError(suite.T(), err) - - statusReply := &model.Status{} - err = json.Unmarshal(b, statusReply) - assert.NoError(suite.T(), err) - - assert.Equal(suite.T(), targetStatus.ContentWarning, statusReply.SpoilerText) - assert.Equal(suite.T(), targetStatus.Content, statusReply.Content) - assert.True(suite.T(), statusReply.Sensitive) - assert.Equal(suite.T(), model.VisibilityPublic, statusReply.Visibility) - assert.False(suite.T(), statusReply.Favourited) - assert.Equal(suite.T(), 0, statusReply.FavouritesCount) -} - -func TestStatusUnfaveTestSuite(t *testing.T) { - suite.Run(t, new(StatusUnfaveTestSuite)) -} |