diff options
Diffstat (limited to 'internal/api/client')
-rw-r--r-- | internal/api/client/accounts/account_test.go (renamed from internal/api/client/account/account_test.go) | 8 | ||||
-rw-r--r-- | internal/api/client/accounts/accountcreate.go (renamed from internal/api/client/account/accountcreate.go) | 24 | ||||
-rw-r--r-- | internal/api/client/accounts/accountcreate_test.go (renamed from internal/api/client/account/accountcreate_test.go) | 2 | ||||
-rw-r--r-- | internal/api/client/accounts/accountdelete.go (renamed from internal/api/client/account/accountdelete.go) | 16 | ||||
-rw-r--r-- | internal/api/client/accounts/accountdelete_test.go (renamed from internal/api/client/account/accountdelete_test.go) | 16 | ||||
-rw-r--r-- | internal/api/client/accounts/accountget.go (renamed from internal/api/client/account/accountget.go) | 14 | ||||
-rw-r--r-- | internal/api/client/accounts/accounts.go (renamed from internal/api/client/account/account.go) | 64 | ||||
-rw-r--r-- | internal/api/client/accounts/accountupdate.go (renamed from internal/api/client/account/accountupdate.go) | 22 | ||||
-rw-r--r-- | internal/api/client/accounts/accountupdate_test.go (renamed from internal/api/client/account/accountupdate_test.go) | 44 | ||||
-rw-r--r-- | internal/api/client/accounts/accountverify.go (renamed from internal/api/client/account/accountverify.go) | 12 | ||||
-rw-r--r-- | internal/api/client/accounts/accountverify_test.go (renamed from internal/api/client/account/accountverify_test.go) | 8 | ||||
-rw-r--r-- | internal/api/client/accounts/block.go (renamed from internal/api/client/account/block.go) | 14 | ||||
-rw-r--r-- | internal/api/client/accounts/block_test.go (renamed from internal/api/client/account/block_test.go) | 10 | ||||
-rw-r--r-- | internal/api/client/accounts/follow.go (renamed from internal/api/client/account/follow.go) | 20 | ||||
-rw-r--r-- | internal/api/client/accounts/follow_test.go (renamed from internal/api/client/account/follow_test.go) | 10 | ||||
-rw-r--r-- | internal/api/client/accounts/followers.go (renamed from internal/api/client/account/followers.go) | 14 | ||||
-rw-r--r-- | internal/api/client/accounts/following.go (renamed from internal/api/client/account/following.go) | 14 | ||||
-rw-r--r-- | internal/api/client/accounts/relationships.go (renamed from internal/api/client/account/relationships.go) | 18 | ||||
-rw-r--r-- | internal/api/client/accounts/statuses.go (renamed from internal/api/client/account/statuses.go) | 26 | ||||
-rw-r--r-- | internal/api/client/accounts/statuses_test.go (renamed from internal/api/client/account/statuses_test.go) | 12 | ||||
-rw-r--r-- | internal/api/client/accounts/unblock.go (renamed from internal/api/client/account/unblock.go) | 14 | ||||
-rw-r--r-- | internal/api/client/accounts/unfollow.go (renamed from internal/api/client/account/unfollow.go) | 14 | ||||
-rw-r--r-- | internal/api/client/admin/accountaction.go | 18 | ||||
-rw-r--r-- | internal/api/client/admin/admin.go | 41 | ||||
-rw-r--r-- | internal/api/client/admin/admin_test.go | 2 | ||||
-rw-r--r-- | internal/api/client/admin/domainblockcreate.go | 26 | ||||
-rw-r--r-- | internal/api/client/admin/domainblockdelete.go | 14 | ||||
-rw-r--r-- | internal/api/client/admin/domainblockget.go | 16 | ||||
-rw-r--r-- | internal/api/client/admin/domainblocksget.go | 14 | ||||
-rw-r--r-- | internal/api/client/admin/emojicategoriesget.go | 12 | ||||
-rw-r--r-- | internal/api/client/admin/emojicreate.go | 22 | ||||
-rw-r--r-- | internal/api/client/admin/emojidelete.go | 14 | ||||
-rw-r--r-- | internal/api/client/admin/emojiget.go | 14 | ||||
-rw-r--r-- | internal/api/client/admin/emojisget.go | 16 | ||||
-rw-r--r-- | internal/api/client/admin/emojiupdate.go | 36 | ||||
-rw-r--r-- | internal/api/client/admin/mediacleanup.go | 14 | ||||
-rw-r--r-- | internal/api/client/admin/mediarefetch.go | 8 | ||||
-rw-r--r-- | internal/api/client/app/app_test.go | 21 | ||||
-rw-r--r-- | internal/api/client/apps/appcreate.go (renamed from internal/api/client/app/appcreate.go) | 26 | ||||
-rw-r--r-- | internal/api/client/apps/apps.go (renamed from internal/api/client/app/app.go) | 19 | ||||
-rw-r--r-- | internal/api/client/auth/auth.go | 105 | ||||
-rw-r--r-- | internal/api/client/auth/auth_test.go | 139 | ||||
-rw-r--r-- | internal/api/client/auth/authorize.go | 335 | ||||
-rw-r--r-- | internal/api/client/auth/authorize_test.go | 118 | ||||
-rw-r--r-- | internal/api/client/auth/callback.go | 311 | ||||
-rw-r--r-- | internal/api/client/auth/oob.go | 111 | ||||
-rw-r--r-- | internal/api/client/auth/signin.go | 145 | ||||
-rw-r--r-- | internal/api/client/auth/token.go | 115 | ||||
-rw-r--r-- | internal/api/client/auth/token_test.go | 215 | ||||
-rw-r--r-- | internal/api/client/auth/util.go | 31 | ||||
-rw-r--r-- | internal/api/client/blocks/blocks.go | 17 | ||||
-rw-r--r-- | internal/api/client/blocks/blocksget.go | 12 | ||||
-rw-r--r-- | internal/api/client/bookmarks/bookmarks.go | 13 | ||||
-rw-r--r-- | internal/api/client/bookmarks/bookmarks_test.go | 10 | ||||
-rw-r--r-- | internal/api/client/bookmarks/bookmarksget.go | 14 | ||||
-rw-r--r-- | internal/api/client/customemojis/customemojis.go (renamed from internal/api/client/emoji/emoji.go) | 19 | ||||
-rw-r--r-- | internal/api/client/customemojis/customemojisget.go | 76 | ||||
-rw-r--r-- | internal/api/client/emoji/emojisget.go | 58 | ||||
-rw-r--r-- | internal/api/client/favourites/favourites.go | 17 | ||||
-rw-r--r-- | internal/api/client/favourites/favourites_test.go | 2 | ||||
-rw-r--r-- | internal/api/client/favourites/favouritesget.go | 12 | ||||
-rw-r--r-- | internal/api/client/fileserver/fileserver.go | 64 | ||||
-rw-r--r-- | internal/api/client/fileserver/fileserver_test.go | 109 | ||||
-rw-r--r-- | internal/api/client/fileserver/servefile.go | 135 | ||||
-rw-r--r-- | internal/api/client/fileserver/servefile_test.go | 272 | ||||
-rw-r--r-- | internal/api/client/filters/filter.go (renamed from internal/api/client/filter/filter.go) | 17 | ||||
-rw-r--r-- | internal/api/client/filters/filtersget.go (renamed from internal/api/client/filter/filtersget.go) | 8 | ||||
-rw-r--r-- | internal/api/client/followrequests/authorize.go (renamed from internal/api/client/followrequest/authorize.go) | 14 | ||||
-rw-r--r-- | internal/api/client/followrequests/authorize_test.go (renamed from internal/api/client/followrequest/authorize_test.go) | 8 | ||||
-rw-r--r-- | internal/api/client/followrequests/followrequest.go (renamed from internal/api/client/followrequest/followrequest.go) | 23 | ||||
-rw-r--r-- | internal/api/client/followrequests/followrequest_test.go (renamed from internal/api/client/followrequest/followrequest_test.go) | 8 | ||||
-rw-r--r-- | internal/api/client/followrequests/get.go (renamed from internal/api/client/followrequest/get.go) | 12 | ||||
-rw-r--r-- | internal/api/client/followrequests/get_test.go (renamed from internal/api/client/followrequest/get_test.go) | 2 | ||||
-rw-r--r-- | internal/api/client/followrequests/reject.go (renamed from internal/api/client/followrequest/reject.go) | 14 | ||||
-rw-r--r-- | internal/api/client/followrequests/reject_test.go (renamed from internal/api/client/followrequest/reject_test.go) | 6 | ||||
-rw-r--r-- | internal/api/client/instance/instance.go | 21 | ||||
-rw-r--r-- | internal/api/client/instance/instance_test.go | 2 | ||||
-rw-r--r-- | internal/api/client/instance/instanceget.go | 8 | ||||
-rw-r--r-- | internal/api/client/instance/instancepatch.go | 22 | ||||
-rw-r--r-- | internal/api/client/instance/instancepeersget.go | 12 | ||||
-rw-r--r-- | internal/api/client/list/listsgets.go | 25 | ||||
-rw-r--r-- | internal/api/client/lists/list.go (renamed from internal/api/client/list/list.go) | 19 | ||||
-rw-r--r-- | internal/api/client/lists/listsgets.go | 44 | ||||
-rw-r--r-- | internal/api/client/media/media.go | 27 | ||||
-rw-r--r-- | internal/api/client/media/mediacreate.go | 34 | ||||
-rw-r--r-- | internal/api/client/media/mediacreate_test.go | 53 | ||||
-rw-r--r-- | internal/api/client/media/mediaget.go | 18 | ||||
-rw-r--r-- | internal/api/client/media/mediaupdate.go | 28 | ||||
-rw-r--r-- | internal/api/client/media/mediaupdate_test.go | 31 | ||||
-rw-r--r-- | internal/api/client/notifications/notifications.go (renamed from internal/api/client/notification/notification.go) | 21 | ||||
-rw-r--r-- | internal/api/client/notifications/notificationsclear.go (renamed from internal/api/client/notification/notificationsclear.go) | 12 | ||||
-rw-r--r-- | internal/api/client/notifications/notificationsget.go (renamed from internal/api/client/notification/notificationsget.go) | 14 | ||||
-rw-r--r-- | internal/api/client/search/search.go | 23 | ||||
-rw-r--r-- | internal/api/client/search/search_test.go | 2 | ||||
-rw-r--r-- | internal/api/client/search/searchget.go | 26 | ||||
-rw-r--r-- | internal/api/client/statuses/status.go (renamed from internal/api/client/status/status.go) | 77 | ||||
-rw-r--r-- | internal/api/client/statuses/status_test.go (renamed from internal/api/client/status/status_test.go) | 8 | ||||
-rw-r--r-- | internal/api/client/statuses/statusbookmark.go (renamed from internal/api/client/status/statusbookmark.go) | 14 | ||||
-rw-r--r-- | internal/api/client/statuses/statusbookmark_test.go (renamed from internal/api/client/status/statusbookmark_test.go) | 8 | ||||
-rw-r--r-- | internal/api/client/statuses/statusboost.go (renamed from internal/api/client/status/statusboost.go) | 14 | ||||
-rw-r--r-- | internal/api/client/statuses/statusboost_test.go (renamed from internal/api/client/status/statusboost_test.go) | 28 | ||||
-rw-r--r-- | internal/api/client/statuses/statusboostedby.go (renamed from internal/api/client/status/statusboostedby.go) | 10 | ||||
-rw-r--r-- | internal/api/client/statuses/statusboostedby_test.go (renamed from internal/api/client/status/statusboostedby_test.go) | 8 | ||||
-rw-r--r-- | internal/api/client/statuses/statuscontext.go (renamed from internal/api/client/status/statuscontext.go) | 14 | ||||
-rw-r--r-- | internal/api/client/statuses/statuscreate.go (renamed from internal/api/client/status/statuscreate.go) | 22 | ||||
-rw-r--r-- | internal/api/client/statuses/statuscreate_test.go (renamed from internal/api/client/status/statuscreate_test.go) | 52 | ||||
-rw-r--r-- | internal/api/client/statuses/statusdelete.go (renamed from internal/api/client/status/statusdelete.go) | 14 | ||||
-rw-r--r-- | internal/api/client/statuses/statusdelete_test.go (renamed from internal/api/client/status/statusdelete_test.go) | 12 | ||||
-rw-r--r-- | internal/api/client/statuses/statusfave.go (renamed from internal/api/client/status/statusfave.go) | 14 | ||||
-rw-r--r-- | internal/api/client/statuses/statusfave_test.go (renamed from internal/api/client/status/statusfave_test.go) | 19 | ||||
-rw-r--r-- | internal/api/client/statuses/statusfavedby.go (renamed from internal/api/client/status/statusfavedby.go) | 14 | ||||
-rw-r--r-- | internal/api/client/statuses/statusfavedby_test.go (renamed from internal/api/client/status/statusfavedby_test.go) | 12 | ||||
-rw-r--r-- | internal/api/client/statuses/statusget.go (renamed from internal/api/client/status/statusget.go) | 14 | ||||
-rw-r--r-- | internal/api/client/statuses/statusget_test.go (renamed from internal/api/client/status/statusget_test.go) | 2 | ||||
-rw-r--r-- | internal/api/client/statuses/statusunbookmark.go (renamed from internal/api/client/status/statusunbookmark.go) | 14 | ||||
-rw-r--r-- | internal/api/client/statuses/statusunbookmark_test.go (renamed from internal/api/client/status/statusunbookmark_test.go) | 8 | ||||
-rw-r--r-- | internal/api/client/statuses/statusunboost.go (renamed from internal/api/client/status/statusunboost.go) | 14 | ||||
-rw-r--r-- | internal/api/client/statuses/statusunfave.go (renamed from internal/api/client/status/statusunfave.go) | 14 | ||||
-rw-r--r-- | internal/api/client/statuses/statusunfave_test.go (renamed from internal/api/client/status/statusunfave_test.go) | 22 | ||||
-rw-r--r-- | internal/api/client/streaming/stream.go | 60 | ||||
-rw-r--r-- | internal/api/client/streaming/streaming.go | 19 | ||||
-rw-r--r-- | internal/api/client/streaming/streaming_test.go | 2 | ||||
-rw-r--r-- | internal/api/client/timelines/home.go (renamed from internal/api/client/timeline/home.go) | 16 | ||||
-rw-r--r-- | internal/api/client/timelines/public.go (renamed from internal/api/client/timeline/public.go) | 16 | ||||
-rw-r--r-- | internal/api/client/timelines/timeline.go (renamed from internal/api/client/timeline/timeline.go) | 21 | ||||
-rw-r--r-- | internal/api/client/user/passwordchange.go | 20 | ||||
-rw-r--r-- | internal/api/client/user/user.go | 17 | ||||
-rw-r--r-- | internal/api/client/user/user_test.go | 2 |
128 files changed, 1018 insertions, 3321 deletions
diff --git a/internal/api/client/account/account_test.go b/internal/api/client/accounts/account_test.go index 90dbd6249..57d1e6c04 100644 --- a/internal/api/client/account/account_test.go +++ b/internal/api/client/accounts/account_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account_test +package accounts_test import ( "bytes" @@ -26,7 +26,7 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/account" + "github.com/superseriousbusiness/gotosocial/internal/api/client/accounts" "github.com/superseriousbusiness/gotosocial/internal/concurrency" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -62,7 +62,7 @@ type AccountStandardTestSuite struct { testStatuses map[string]*gtsmodel.Status // module being tested - accountModule *account.Module + accountsModule *accounts.Module } func (suite *AccountStandardTestSuite) SetupSuite() { @@ -89,7 +89,7 @@ func (suite *AccountStandardTestSuite) SetupTest() { suite.sentEmails = make(map[string]string) suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails) suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) - suite.accountModule = account.New(suite.processor).(*account.Module) + suite.accountsModule = accounts.New(suite.processor) testrig.StandardDBSetup(suite.db, nil) testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") diff --git a/internal/api/client/account/accountcreate.go b/internal/api/client/accounts/accountcreate.go index e7b6c642d..041ca7fc4 100644 --- a/internal/api/client/account/accountcreate.go +++ b/internal/api/client/accounts/accountcreate.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account +package accounts import ( "errors" @@ -24,8 +24,8 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -73,23 +73,23 @@ import ( func (m *Module) AccountCreatePOSTHandler(c *gin.Context) { authed, err := oauth.Authed(c, true, true, false, false) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } - form := &model.AccountCreateRequest{} + form := &apimodel.AccountCreateRequest{} if err := c.ShouldBind(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if err := validateCreateAccount(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } @@ -97,14 +97,14 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) { signUpIP := net.ParseIP(clientIP) if signUpIP == nil { err := errors.New("ip address could not be parsed from request") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } form.IP = signUpIP ti, errWithCode := m.processor.AccountCreate(c.Request.Context(), authed, form) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } @@ -113,7 +113,7 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) { // validateCreateAccount checks through all the necessary prerequisites for creating a new account, // according to the provided account create request. If the account isn't eligible, an error will be returned. -func validateCreateAccount(form *model.AccountCreateRequest) error { +func validateCreateAccount(form *apimodel.AccountCreateRequest) error { if form == nil { return errors.New("form was nil") } diff --git a/internal/api/client/account/accountcreate_test.go b/internal/api/client/accounts/accountcreate_test.go index a4fc165bf..b2b8c715f 100644 --- a/internal/api/client/account/accountcreate_test.go +++ b/internal/api/client/accounts/accountcreate_test.go @@ -16,4 +16,4 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // */ -package account_test +package accounts_test diff --git a/internal/api/client/account/accountdelete.go b/internal/api/client/accounts/accountdelete.go index 53bdedd0f..f1b95e95a 100644 --- a/internal/api/client/account/accountdelete.go +++ b/internal/api/client/accounts/accountdelete.go @@ -16,15 +16,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account +package accounts import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -68,26 +68,26 @@ import ( func (m *Module) AccountDeletePOSTHandler(c *gin.Context) { authed, err := oauth.Authed(c, true, true, true, true) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } - form := &model.AccountDeleteRequest{} + form := &apimodel.AccountDeleteRequest{} if err := c.ShouldBind(&form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if form.Password == "" { err = errors.New("no password provided in account delete request") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } form.DeleteOriginID = authed.Account.ID if errWithCode := m.processor.AccountDeleteLocal(c.Request.Context(), authed, form); errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/account/accountdelete_test.go b/internal/api/client/accounts/accountdelete_test.go index 78348eabc..31559d59a 100644 --- a/internal/api/client/account/accountdelete_test.go +++ b/internal/api/client/accounts/accountdelete_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account_test +package accounts_test import ( "net/http" @@ -24,7 +24,7 @@ import ( "testing" "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/account" + "github.com/superseriousbusiness/gotosocial/internal/api/client/accounts" "github.com/superseriousbusiness/gotosocial/testrig" ) @@ -45,10 +45,10 @@ func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandler() { } bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, account.DeleteAccountPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, accounts.DeleteAccountPath, w.FormDataContentType()) // call the handler - suite.accountModule.AccountDeletePOSTHandler(ctx) + suite.accountsModule.AccountDeletePOSTHandler(ctx) // 1. we should have Accepted because our request was valid suite.Equal(http.StatusAccepted, recorder.Code) @@ -67,10 +67,10 @@ func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandlerWrongPassword() } bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, account.DeleteAccountPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, accounts.DeleteAccountPath, w.FormDataContentType()) // call the handler - suite.accountModule.AccountDeletePOSTHandler(ctx) + suite.accountsModule.AccountDeletePOSTHandler(ctx) // 1. we should have Forbidden because we supplied the wrong password suite.Equal(http.StatusForbidden, recorder.Code) @@ -87,10 +87,10 @@ func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandlerNoPassword() { } bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, account.DeleteAccountPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, accounts.DeleteAccountPath, w.FormDataContentType()) // call the handler - suite.accountModule.AccountDeletePOSTHandler(ctx) + suite.accountsModule.AccountDeletePOSTHandler(ctx) // 1. we should have StatusBadRequest because our request was invalid suite.Equal(http.StatusBadRequest, recorder.Code) diff --git a/internal/api/client/account/accountget.go b/internal/api/client/accounts/accountget.go index c9aae5b2b..1a6354490 100644 --- a/internal/api/client/account/accountget.go +++ b/internal/api/client/accounts/accountget.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account +package accounts import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -69,25 +69,25 @@ import ( func (m *Module) AccountGETHandler(c *gin.Context) { authed, err := oauth.Authed(c, true, true, true, true) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } targetAcctID := c.Param(IDKey) if targetAcctID == "" { err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } acctInfo, errWithCode := m.processor.AccountGet(c.Request.Context(), authed, targetAcctID) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/account/account.go b/internal/api/client/accounts/accounts.go index 4205baa2c..54c6c5f22 100644 --- a/internal/api/client/account/account.go +++ b/internal/api/client/accounts/accounts.go @@ -16,17 +16,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account +package accounts import ( "net/http" - "strings" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" "github.com/superseriousbusiness/gotosocial/internal/processing" - - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( @@ -49,8 +45,8 @@ const ( // IDKey is the key to use for retrieving account ID in requests IDKey = "id" - // BasePath is the base API path for this module - BasePath = "/api/v1/accounts" + // BasePath is the base API path for this module, excluding the 'api' prefix + BasePath = "/v1/accounts" // BasePathWithID is the base path for this module with the ID key BasePathWithID = BasePath + "/:" + IDKey // VerifyPath is for verifying account credentials @@ -77,65 +73,47 @@ const ( DeleteAccountPath = BasePath + "/delete" ) -// Module implements the ClientAPIModule interface for account-related actions type Module struct { processor processing.Processor } -// New returns a new account module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { return &Module{ processor: processor, } } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { // create account - r.AttachHandler(http.MethodPost, BasePath, m.AccountCreatePOSTHandler) + attachHandler(http.MethodPost, BasePath, m.AccountCreatePOSTHandler) + + // get account + attachHandler(http.MethodGet, BasePathWithID, m.AccountGETHandler) // delete account - r.AttachHandler(http.MethodPost, DeleteAccountPath, m.AccountDeletePOSTHandler) + attachHandler(http.MethodPost, DeleteAccountPath, m.AccountDeletePOSTHandler) - // get account - r.AttachHandler(http.MethodGet, BasePathWithID, m.muxHandler) + // verify account + attachHandler(http.MethodGet, VerifyPath, m.AccountVerifyGETHandler) // modify account - r.AttachHandler(http.MethodPatch, BasePathWithID, m.muxHandler) + attachHandler(http.MethodPatch, UpdateCredentialsPath, m.AccountUpdateCredentialsPATCHHandler) // get account's statuses - r.AttachHandler(http.MethodGet, GetStatusesPath, m.AccountStatusesGETHandler) + attachHandler(http.MethodGet, GetStatusesPath, m.AccountStatusesGETHandler) // get following or followers - r.AttachHandler(http.MethodGet, GetFollowersPath, m.AccountFollowersGETHandler) - r.AttachHandler(http.MethodGet, GetFollowingPath, m.AccountFollowingGETHandler) + attachHandler(http.MethodGet, GetFollowersPath, m.AccountFollowersGETHandler) + attachHandler(http.MethodGet, GetFollowingPath, m.AccountFollowingGETHandler) // get relationship with account - r.AttachHandler(http.MethodGet, GetRelationshipsPath, m.AccountRelationshipsGETHandler) + attachHandler(http.MethodGet, GetRelationshipsPath, m.AccountRelationshipsGETHandler) // follow or unfollow account - r.AttachHandler(http.MethodPost, FollowPath, m.AccountFollowPOSTHandler) - r.AttachHandler(http.MethodPost, UnfollowPath, m.AccountUnfollowPOSTHandler) + attachHandler(http.MethodPost, FollowPath, m.AccountFollowPOSTHandler) + attachHandler(http.MethodPost, UnfollowPath, m.AccountUnfollowPOSTHandler) // block or unblock account - r.AttachHandler(http.MethodPost, BlockPath, m.AccountBlockPOSTHandler) - r.AttachHandler(http.MethodPost, UnblockPath, m.AccountUnblockPOSTHandler) - - return nil -} - -func (m *Module) muxHandler(c *gin.Context) { - ru := c.Request.RequestURI - switch c.Request.Method { - case http.MethodGet: - if strings.HasPrefix(ru, VerifyPath) { - m.AccountVerifyGETHandler(c) - } else { - m.AccountGETHandler(c) - } - case http.MethodPatch: - if strings.HasPrefix(ru, UpdateCredentialsPath) { - m.AccountUpdateCredentialsPATCHHandler(c) - } - } + attachHandler(http.MethodPost, BlockPath, m.AccountBlockPOSTHandler) + attachHandler(http.MethodPost, UnblockPath, m.AccountUnblockPOSTHandler) } diff --git a/internal/api/client/account/accountupdate.go b/internal/api/client/accounts/accountupdate.go index f89259a96..5dbf0ce46 100644 --- a/internal/api/client/account/accountupdate.go +++ b/internal/api/client/accounts/accountupdate.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account +package accounts import ( "errors" @@ -25,8 +25,8 @@ import ( "strconv" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -138,33 +138,33 @@ import ( func (m *Module) AccountUpdateCredentialsPATCHHandler(c *gin.Context) { authed, err := oauth.Authed(c, true, true, true, true) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } form, err := parseUpdateAccountForm(c) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } acctSensitive, errWithCode := m.processor.AccountUpdate(c.Request.Context(), authed, form) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } c.JSON(http.StatusOK, acctSensitive) } -func parseUpdateAccountForm(c *gin.Context) (*model.UpdateCredentialsRequest, error) { - form := &model.UpdateCredentialsRequest{ - Source: &model.UpdateSource{}, +func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest, error) { + form := &apimodel.UpdateCredentialsRequest{ + Source: &apimodel.UpdateSource{}, } if err := c.ShouldBind(&form); err != nil { diff --git a/internal/api/client/account/accountupdate_test.go b/internal/api/client/accounts/accountupdate_test.go index 259bb69e9..45a287ec8 100644 --- a/internal/api/client/account/accountupdate_test.go +++ b/internal/api/client/accounts/accountupdate_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account_test +package accounts_test import ( "context" @@ -27,7 +27,7 @@ import ( "testing" "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/account" + "github.com/superseriousbusiness/gotosocial/internal/api/client/accounts" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/testrig" ) @@ -50,10 +50,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandler() } bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, w.FormDataContentType()) // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) + suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx) // 1. we should have OK because our request was valid suite.Equal(http.StatusOK, recorder.Code) @@ -89,10 +89,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUnl } bodyBytes1 := requestBody1.Bytes() recorder1 := httptest.NewRecorder() - ctx1 := suite.newContext(recorder1, http.MethodPatch, bodyBytes1, account.UpdateCredentialsPath, w1.FormDataContentType()) + ctx1 := suite.newContext(recorder1, http.MethodPatch, bodyBytes1, accounts.UpdateCredentialsPath, w1.FormDataContentType()) // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx1) + suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx1) // 1. we should have OK because our request was valid suite.Equal(http.StatusOK, recorder1.Code) @@ -125,10 +125,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUnl } bodyBytes2 := requestBody2.Bytes() recorder2 := httptest.NewRecorder() - ctx2 := suite.newContext(recorder2, http.MethodPatch, bodyBytes2, account.UpdateCredentialsPath, w2.FormDataContentType()) + ctx2 := suite.newContext(recorder2, http.MethodPatch, bodyBytes2, accounts.UpdateCredentialsPath, w2.FormDataContentType()) // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx2) + suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx2) // 1. we should have OK because our request was valid suite.Equal(http.StatusOK, recorder1.Code) @@ -170,10 +170,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerGet } bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, w.FormDataContentType()) // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) + suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx) // 1. we should have OK because our request was valid suite.Equal(http.StatusOK, recorder.Code) @@ -212,10 +212,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerTwo } bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, w.FormDataContentType()) // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) + suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx) // 1. we should have OK because our request was valid suite.Equal(http.StatusOK, recorder.Code) @@ -266,10 +266,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerWit } bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, w.FormDataContentType()) // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) + suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx) // 1. we should have OK because our request was valid suite.Equal(http.StatusOK, recorder.Code) @@ -308,10 +308,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerEmp // set up the request bodyBytes := []byte{} recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, "") + ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, "") // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) + suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx) // 1. we should have OK because our request was valid suite.Equal(http.StatusBadRequest, recorder.Code) @@ -343,10 +343,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpd } bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, w.FormDataContentType()) // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) + suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx) // 1. we should have OK because our request was valid suite.Equal(http.StatusOK, recorder.Code) @@ -385,10 +385,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpd } bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, w.FormDataContentType()) // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) + suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx) // 1. we should have OK because our request was valid suite.Equal(http.StatusOK, recorder.Code) @@ -430,10 +430,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpd } bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, w.FormDataContentType()) // call the handler - suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) + suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx) suite.Equal(http.StatusBadRequest, recorder.Code) diff --git a/internal/api/client/account/accountverify.go b/internal/api/client/accounts/accountverify.go index 916d0a322..2b39d5ab2 100644 --- a/internal/api/client/account/accountverify.go +++ b/internal/api/client/accounts/accountverify.go @@ -16,13 +16,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account +package accounts import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -59,18 +59,18 @@ import ( func (m *Module) AccountVerifyGETHandler(c *gin.Context) { authed, err := oauth.Authed(c, true, true, true, true) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } acctSensitive, errWithCode := m.processor.AccountGet(c.Request.Context(), authed, authed.Account.ID) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/account/accountverify_test.go b/internal/api/client/accounts/accountverify_test.go index 886272865..e74c30aba 100644 --- a/internal/api/client/account/accountverify_test.go +++ b/internal/api/client/accounts/accountverify_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account_test +package accounts_test import ( "encoding/json" @@ -28,7 +28,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/account" + "github.com/superseriousbusiness/gotosocial/internal/api/client/accounts" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) @@ -42,10 +42,10 @@ func (suite *AccountVerifyTestSuite) TestAccountVerifyGet() { // set up the request recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodGet, nil, account.VerifyPath, "") + ctx := suite.newContext(recorder, http.MethodGet, nil, accounts.VerifyPath, "") // call the handler - suite.accountModule.AccountVerifyGETHandler(ctx) + suite.accountsModule.AccountVerifyGETHandler(ctx) // 1. we should have OK because our request was valid suite.Equal(http.StatusOK, recorder.Code) diff --git a/internal/api/client/account/block.go b/internal/api/client/accounts/block.go index 9840c96ab..9e14ecb6e 100644 --- a/internal/api/client/account/block.go +++ b/internal/api/client/accounts/block.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account +package accounts import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -69,25 +69,25 @@ import ( func (m *Module) AccountBlockPOSTHandler(c *gin.Context) { authed, err := oauth.Authed(c, true, true, true, true) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } targetAcctID := c.Param(IDKey) if targetAcctID == "" { err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } relationship, errWithCode := m.processor.AccountBlockCreate(c.Request.Context(), authed, targetAcctID) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/account/block_test.go b/internal/api/client/accounts/block_test.go index 9c75330aa..474a53eb8 100644 --- a/internal/api/client/account/block_test.go +++ b/internal/api/client/accounts/block_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account_test +package accounts_test import ( "fmt" @@ -29,7 +29,7 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/account" + "github.com/superseriousbusiness/gotosocial/internal/api/client/accounts" "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/testrig" ) @@ -46,16 +46,16 @@ func (suite *BlockTestSuite) TestBlockSelf() { ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"])) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(account.BlockPath, ":id", testAcct.ID, 1)), nil) + ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(accounts.BlockPath, ":id", testAcct.ID, 1)), nil) ctx.Params = gin.Params{ gin.Param{ - Key: account.IDKey, + Key: accounts.IDKey, Value: testAcct.ID, }, } - suite.accountModule.AccountBlockPOSTHandler(ctx) + suite.accountsModule.AccountBlockPOSTHandler(ctx) // 1. status should be Not Acceptable due to attempted self-block suite.Equal(http.StatusNotAcceptable, recorder.Code) diff --git a/internal/api/client/account/follow.go b/internal/api/client/accounts/follow.go index cc523a7f8..d2a8af886 100644 --- a/internal/api/client/account/follow.go +++ b/internal/api/client/accounts/follow.go @@ -16,15 +16,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account +package accounts import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -91,32 +91,32 @@ import ( func (m *Module) AccountFollowPOSTHandler(c *gin.Context) { authed, err := oauth.Authed(c, true, true, true, true) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } targetAcctID := c.Param(IDKey) if targetAcctID == "" { err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } - form := &model.AccountFollowRequest{} + form := &apimodel.AccountFollowRequest{} if err := c.ShouldBind(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } form.ID = targetAcctID relationship, errWithCode := m.processor.AccountFollowCreate(c.Request.Context(), authed, form) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/account/follow_test.go b/internal/api/client/accounts/follow_test.go index fad67b185..fd15c3734 100644 --- a/internal/api/client/account/follow_test.go +++ b/internal/api/client/accounts/follow_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account_test +package accounts_test import ( "fmt" @@ -29,7 +29,7 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/account" + "github.com/superseriousbusiness/gotosocial/internal/api/client/accounts" "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/testrig" ) @@ -46,17 +46,17 @@ func (suite *FollowTestSuite) TestFollowSelf() { ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"])) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(account.FollowPath, ":id", testAcct.ID, 1)), nil) + ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(accounts.FollowPath, ":id", testAcct.ID, 1)), nil) ctx.Params = gin.Params{ gin.Param{ - Key: account.IDKey, + Key: accounts.IDKey, Value: testAcct.ID, }, } // call the handler - suite.accountModule.AccountFollowPOSTHandler(ctx) + suite.accountsModule.AccountFollowPOSTHandler(ctx) // 1. status should be Not Acceptable due to self-follow attempt suite.Equal(http.StatusNotAcceptable, recorder.Code) diff --git a/internal/api/client/account/followers.go b/internal/api/client/accounts/followers.go index cb2f4bfa6..b464a5ad6 100644 --- a/internal/api/client/account/followers.go +++ b/internal/api/client/accounts/followers.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account +package accounts import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -72,25 +72,25 @@ import ( func (m *Module) AccountFollowersGETHandler(c *gin.Context) { authed, err := oauth.Authed(c, true, true, true, true) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } targetAcctID := c.Param(IDKey) if targetAcctID == "" { err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } followers, errWithCode := m.processor.AccountFollowersGet(c.Request.Context(), authed, targetAcctID) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/account/following.go b/internal/api/client/accounts/following.go index 3d69739c3..4589ad07a 100644 --- a/internal/api/client/account/following.go +++ b/internal/api/client/accounts/following.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account +package accounts import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -72,25 +72,25 @@ import ( func (m *Module) AccountFollowingGETHandler(c *gin.Context) { authed, err := oauth.Authed(c, true, true, true, true) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } targetAcctID := c.Param(IDKey) if targetAcctID == "" { err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } following, errWithCode := m.processor.AccountFollowingGet(c.Request.Context(), authed, targetAcctID) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/account/relationships.go b/internal/api/client/accounts/relationships.go index 56159d48e..60e7b517c 100644 --- a/internal/api/client/account/relationships.go +++ b/internal/api/client/accounts/relationships.go @@ -1,12 +1,12 @@ -package account +package accounts import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -57,12 +57,12 @@ import ( func (m *Module) AccountRelationshipsGETHandler(c *gin.Context) { authed, err := oauth.Authed(c, true, true, true, true) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } @@ -72,18 +72,18 @@ func (m *Module) AccountRelationshipsGETHandler(c *gin.Context) { id := c.Query("id") if id == "" { err = errors.New("no account id(s) specified in query") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } targetAccountIDs = append(targetAccountIDs, id) } - relationships := []model.Relationship{} + relationships := []apimodel.Relationship{} for _, targetAccountID := range targetAccountIDs { r, errWithCode := m.processor.AccountRelationshipGet(c.Request.Context(), authed, targetAccountID) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } relationships = append(relationships, *r) diff --git a/internal/api/client/account/statuses.go b/internal/api/client/accounts/statuses.go index 7ecf3ba9f..a04517feb 100644 --- a/internal/api/client/account/statuses.go +++ b/internal/api/client/accounts/statuses.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account +package accounts import ( "errors" @@ -25,7 +25,7 @@ import ( "strconv" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -133,19 +133,19 @@ import ( func (m *Module) AccountStatusesGETHandler(c *gin.Context) { authed, err := oauth.Authed(c, false, false, false, false) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } targetAcctID := c.Param(IDKey) if targetAcctID == "" { err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } @@ -155,7 +155,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) { i, err := strconv.ParseInt(limitString, 10, 32) if err != nil { err := fmt.Errorf("error parsing %s: %s", LimitKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } limit = int(i) @@ -167,7 +167,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) { i, err := strconv.ParseBool(excludeRepliesString) if err != nil { err := fmt.Errorf("error parsing %s: %s", ExcludeRepliesKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } excludeReplies = i @@ -179,7 +179,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) { i, err := strconv.ParseBool(excludeReblogsString) if err != nil { err := fmt.Errorf("error parsing %s: %s", ExcludeReblogsKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } excludeReblogs = i @@ -203,7 +203,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) { i, err := strconv.ParseBool(pinnedString) if err != nil { err := fmt.Errorf("error parsing %s: %s", PinnedKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } pinnedOnly = i @@ -215,7 +215,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) { i, err := strconv.ParseBool(mediaOnlyString) if err != nil { err := fmt.Errorf("error parsing %s: %s", OnlyMediaKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } mediaOnly = i @@ -227,7 +227,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) { i, err := strconv.ParseBool(publicOnlyString) if err != nil { err := fmt.Errorf("error parsing %s: %s", OnlyPublicKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } publicOnly = i @@ -235,7 +235,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) { resp, errWithCode := m.processor.AccountStatusesGet(c.Request.Context(), authed, targetAcctID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/account/statuses_test.go b/internal/api/client/accounts/statuses_test.go index 1f935896c..92ca9d925 100644 --- a/internal/api/client/account/statuses_test.go +++ b/internal/api/client/accounts/statuses_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account_test +package accounts_test import ( "encoding/json" @@ -29,7 +29,7 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/account" + "github.com/superseriousbusiness/gotosocial/internal/api/client/accounts" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" ) @@ -45,13 +45,13 @@ func (suite *AccountStatusesTestSuite) TestGetStatusesPublicOnly() { ctx := suite.newContext(recorder, http.MethodGet, nil, fmt.Sprintf("/api/v1/accounts/%s/statuses?limit=20&only_media=false&only_public=true", targetAccount.ID), "") ctx.Params = gin.Params{ gin.Param{ - Key: account.IDKey, + Key: accounts.IDKey, Value: targetAccount.ID, }, } // call the handler - suite.accountModule.AccountStatusesGETHandler(ctx) + suite.accountsModule.AccountStatusesGETHandler(ctx) // 1. we should have OK because our request was valid suite.Equal(http.StatusOK, recorder.Code) @@ -85,13 +85,13 @@ func (suite *AccountStatusesTestSuite) TestGetStatusesPublicOnlyMediaOnly() { ctx := suite.newContext(recorder, http.MethodGet, nil, fmt.Sprintf("/api/v1/accounts/%s/statuses?limit=20&only_media=true&only_public=true", targetAccount.ID), "") ctx.Params = gin.Params{ gin.Param{ - Key: account.IDKey, + Key: accounts.IDKey, Value: targetAccount.ID, }, } // call the handler - suite.accountModule.AccountStatusesGETHandler(ctx) + suite.accountsModule.AccountStatusesGETHandler(ctx) // 1. we should have OK because our request was valid suite.Equal(http.StatusOK, recorder.Code) diff --git a/internal/api/client/account/unblock.go b/internal/api/client/accounts/unblock.go index 451b7fd27..e0a0a978e 100644 --- a/internal/api/client/account/unblock.go +++ b/internal/api/client/accounts/unblock.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account +package accounts import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -70,25 +70,25 @@ import ( func (m *Module) AccountUnblockPOSTHandler(c *gin.Context) { authed, err := oauth.Authed(c, true, true, true, true) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } targetAcctID := c.Param(IDKey) if targetAcctID == "" { err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } relationship, errWithCode := m.processor.AccountBlockRemove(c.Request.Context(), authed, targetAcctID) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/account/unfollow.go b/internal/api/client/accounts/unfollow.go index fafba99fd..95c819903 100644 --- a/internal/api/client/account/unfollow.go +++ b/internal/api/client/accounts/unfollow.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package account +package accounts import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -70,25 +70,25 @@ import ( func (m *Module) AccountUnfollowPOSTHandler(c *gin.Context) { authed, err := oauth.Authed(c, true, true, true, true) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } targetAcctID := c.Param(IDKey) if targetAcctID == "" { err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } relationship, errWithCode := m.processor.AccountFollowRemove(c.Request.Context(), authed, targetAcctID) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/admin/accountaction.go b/internal/api/client/admin/accountaction.go index 2dc84a2d0..d40404b15 100644 --- a/internal/api/client/admin/accountaction.go +++ b/internal/api/client/admin/accountaction.go @@ -24,8 +24,8 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -85,38 +85,38 @@ import ( func (m *Module) AccountActionPOSTHandler(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) + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } if !*authed.User.Admin { err := fmt.Errorf("user %s not an admin", authed.User.ID) - api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) return } - form := &model.AdminAccountActionRequest{} + form := &apimodel.AdminAccountActionRequest{} if err := c.ShouldBind(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if form.Type == "" { err := errors.New("no type specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } targetAcctID := c.Param(IDKey) if targetAcctID == "" { err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } form.TargetAccountID = targetAcctID if errWithCode := m.processor.AdminAccountAction(c.Request.Context(), authed, form); errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/admin/admin.go b/internal/api/client/admin/admin.go index 569354c96..b4fb4d6d1 100644 --- a/internal/api/client/admin/admin.go +++ b/internal/api/client/admin/admin.go @@ -21,14 +21,13 @@ package admin import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( - // BasePath is the base API path for this module. - BasePath = "/api/v1/admin" + // BasePath is the base API path for this module, excluding the api prefix + BasePath = "/v1/admin" // EmojiPath is used for posting/deleting custom emojis. EmojiPath = BasePath + "/custom_emojis" // EmojiPathWithID is used for interacting with a single emoji. @@ -68,32 +67,28 @@ const ( DomainQueryKey = "domain" ) -// Module implements the ClientAPIModule interface for admin-related actions (reports, emojis, etc) type Module struct { processor processing.Processor } -// New returns a new admin module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { 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, EmojiPath, m.EmojiCreatePOSTHandler) - r.AttachHandler(http.MethodGet, EmojiPath, m.EmojisGETHandler) - r.AttachHandler(http.MethodDelete, EmojiPathWithID, m.EmojiDELETEHandler) - r.AttachHandler(http.MethodGet, EmojiPathWithID, m.EmojiGETHandler) - r.AttachHandler(http.MethodPatch, EmojiPathWithID, m.EmojiPATCHHandler) - r.AttachHandler(http.MethodPost, DomainBlocksPath, m.DomainBlocksPOSTHandler) - r.AttachHandler(http.MethodGet, DomainBlocksPath, m.DomainBlocksGETHandler) - r.AttachHandler(http.MethodGet, DomainBlocksPathWithID, m.DomainBlockGETHandler) - r.AttachHandler(http.MethodDelete, DomainBlocksPathWithID, m.DomainBlockDELETEHandler) - r.AttachHandler(http.MethodPost, AccountsActionPath, m.AccountActionPOSTHandler) - r.AttachHandler(http.MethodPost, MediaCleanupPath, m.MediaCleanupPOSTHandler) - r.AttachHandler(http.MethodPost, MediaRefetchPath, m.MediaRefetchPOSTHandler) - r.AttachHandler(http.MethodGet, EmojiCategoriesPath, m.EmojiCategoriesGETHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodPost, EmojiPath, m.EmojiCreatePOSTHandler) + attachHandler(http.MethodGet, EmojiPath, m.EmojisGETHandler) + attachHandler(http.MethodDelete, EmojiPathWithID, m.EmojiDELETEHandler) + attachHandler(http.MethodGet, EmojiPathWithID, m.EmojiGETHandler) + attachHandler(http.MethodPatch, EmojiPathWithID, m.EmojiPATCHHandler) + attachHandler(http.MethodPost, DomainBlocksPath, m.DomainBlocksPOSTHandler) + attachHandler(http.MethodGet, DomainBlocksPath, m.DomainBlocksGETHandler) + attachHandler(http.MethodGet, DomainBlocksPathWithID, m.DomainBlockGETHandler) + attachHandler(http.MethodDelete, DomainBlocksPathWithID, m.DomainBlockDELETEHandler) + attachHandler(http.MethodPost, AccountsActionPath, m.AccountActionPOSTHandler) + attachHandler(http.MethodPost, MediaCleanupPath, m.MediaCleanupPOSTHandler) + attachHandler(http.MethodPost, MediaRefetchPath, m.MediaRefetchPOSTHandler) + attachHandler(http.MethodGet, EmojiCategoriesPath, m.EmojiCategoriesGETHandler) } diff --git a/internal/api/client/admin/admin_test.go b/internal/api/client/admin/admin_test.go index 52c2630d9..ac3bbcb98 100644 --- a/internal/api/client/admin/admin_test.go +++ b/internal/api/client/admin/admin_test.go @@ -93,7 +93,7 @@ func (suite *AdminStandardTestSuite) SetupTest() { suite.sentEmails = make(map[string]string) suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails) suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) - suite.adminModule = admin.New(suite.processor).(*admin.Module) + suite.adminModule = admin.New(suite.processor) testrig.StandardDBSetup(suite.db, nil) testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") } diff --git a/internal/api/client/admin/domainblockcreate.go b/internal/api/client/admin/domainblockcreate.go index 034ea8682..44410abe3 100644 --- a/internal/api/client/admin/domainblockcreate.go +++ b/internal/api/client/admin/domainblockcreate.go @@ -25,8 +25,8 @@ import ( "strconv" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -126,18 +126,18 @@ import ( func (m *Module) DomainBlocksPOSTHandler(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) + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } if !*authed.User.Admin { err := fmt.Errorf("user %s not an admin", authed.User.ID) - api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } @@ -147,21 +147,21 @@ func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) { i, err := strconv.ParseBool(importString) if err != nil { err := fmt.Errorf("error parsing %s: %s", ImportQueryKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } imp = i } - form := &model.DomainBlockCreateRequest{} + form := &apimodel.DomainBlockCreateRequest{} if err := c.ShouldBind(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if err := validateCreateDomainBlock(form, imp); err != nil { err := fmt.Errorf("error validating form: %s", err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } @@ -169,7 +169,7 @@ func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) { // we're importing multiple blocks domainBlocks, errWithCode := m.processor.AdminDomainBlocksImport(c.Request.Context(), authed, form) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } c.JSON(http.StatusOK, domainBlocks) @@ -179,13 +179,13 @@ func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) { // we're just creating one block domainBlock, errWithCode := m.processor.AdminDomainBlockCreate(c.Request.Context(), authed, form) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } c.JSON(http.StatusOK, domainBlock) } -func validateCreateDomainBlock(form *model.DomainBlockCreateRequest, imp bool) error { +func validateCreateDomainBlock(form *apimodel.DomainBlockCreateRequest, imp bool) error { if imp { if form.Domains.Size == 0 { return errors.New("import was specified but list of domains is empty") diff --git a/internal/api/client/admin/domainblockdelete.go b/internal/api/client/admin/domainblockdelete.go index 6f3684418..ddb07e6f6 100644 --- a/internal/api/client/admin/domainblockdelete.go +++ b/internal/api/client/admin/domainblockdelete.go @@ -24,7 +24,7 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -72,31 +72,31 @@ import ( func (m *Module) DomainBlockDELETEHandler(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) + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } if !*authed.User.Admin { err := fmt.Errorf("user %s not an admin", authed.User.ID) - api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } domainBlockID := c.Param(IDKey) if domainBlockID == "" { err := errors.New("no domain block id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } domainBlock, errWithCode := m.processor.AdminDomainBlockDelete(c.Request.Context(), authed, domainBlockID) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/admin/domainblockget.go b/internal/api/client/admin/domainblockget.go index 3d27b585e..b9d365caa 100644 --- a/internal/api/client/admin/domainblockget.go +++ b/internal/api/client/admin/domainblockget.go @@ -25,7 +25,7 @@ import ( "strconv" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -73,25 +73,25 @@ import ( func (m *Module) DomainBlockGETHandler(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) + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } if !*authed.User.Admin { err := fmt.Errorf("user %s not an admin", authed.User.ID) - api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } domainBlockID := c.Param(IDKey) if domainBlockID == "" { err := errors.New("no domain block id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } @@ -101,7 +101,7 @@ func (m *Module) DomainBlockGETHandler(c *gin.Context) { i, err := strconv.ParseBool(exportString) if err != nil { err := fmt.Errorf("error parsing %s: %s", ExportQueryKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } export = i @@ -109,7 +109,7 @@ func (m *Module) DomainBlockGETHandler(c *gin.Context) { domainBlock, errWithCode := m.processor.AdminDomainBlockGet(c.Request.Context(), authed, domainBlockID, export) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/admin/domainblocksget.go b/internal/api/client/admin/domainblocksget.go index a4ab4ac1c..fea0ca35e 100644 --- a/internal/api/client/admin/domainblocksget.go +++ b/internal/api/client/admin/domainblocksget.go @@ -24,7 +24,7 @@ import ( "strconv" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -78,18 +78,18 @@ import ( func (m *Module) DomainBlocksGETHandler(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) + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } if !*authed.User.Admin { err := fmt.Errorf("user %s not an admin", authed.User.ID) - api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } @@ -99,7 +99,7 @@ func (m *Module) DomainBlocksGETHandler(c *gin.Context) { i, err := strconv.ParseBool(exportString) if err != nil { err := fmt.Errorf("error parsing %s: %s", ExportQueryKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } export = i @@ -107,7 +107,7 @@ func (m *Module) DomainBlocksGETHandler(c *gin.Context) { domainBlocks, errWithCode := m.processor.AdminDomainBlocksGet(c.Request.Context(), authed, export) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/admin/emojicategoriesget.go b/internal/api/client/admin/emojicategoriesget.go index d8b379674..e69506413 100644 --- a/internal/api/client/admin/emojicategoriesget.go +++ b/internal/api/client/admin/emojicategoriesget.go @@ -23,7 +23,7 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -69,24 +69,24 @@ import ( func (m *Module) EmojiCategoriesGETHandler(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) + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } if !*authed.User.Admin { err := fmt.Errorf("user %s not an admin", authed.User.ID) - api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } categories, errWithCode := m.processor.AdminEmojiCategoriesGet(c.Request.Context()) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/admin/emojicreate.go b/internal/api/client/admin/emojicreate.go index 2a075708f..8368a12b0 100644 --- a/internal/api/client/admin/emojicreate.go +++ b/internal/api/client/admin/emojicreate.go @@ -24,8 +24,8 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -100,42 +100,42 @@ import ( func (m *Module) EmojiCreatePOSTHandler(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) + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } if !*authed.User.Admin { err := fmt.Errorf("user %s not an admin", authed.User.ID) - api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } - form := &model.EmojiCreateRequest{} + form := &apimodel.EmojiCreateRequest{} if err := c.ShouldBind(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if err := validateCreateEmoji(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } apiEmoji, errWithCode := m.processor.AdminEmojiCreate(c.Request.Context(), authed, form) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } c.JSON(http.StatusOK, apiEmoji) } -func validateCreateEmoji(form *model.EmojiCreateRequest) error { +func validateCreateEmoji(form *apimodel.EmojiCreateRequest) error { if form.Image == nil || form.Image.Size == 0 { return errors.New("no emoji given") } diff --git a/internal/api/client/admin/emojidelete.go b/internal/api/client/admin/emojidelete.go index 14f3c70ff..b66116b6d 100644 --- a/internal/api/client/admin/emojidelete.go +++ b/internal/api/client/admin/emojidelete.go @@ -24,7 +24,7 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -78,31 +78,31 @@ import ( func (m *Module) EmojiDELETEHandler(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) + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } if !*authed.User.Admin { err := fmt.Errorf("user %s not an admin", authed.User.ID) - api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } emojiID := c.Param(IDKey) if emojiID == "" { err := errors.New("no emoji id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } emoji, errWithCode := m.processor.AdminEmojiDelete(c.Request.Context(), authed, emojiID) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/admin/emojiget.go b/internal/api/client/admin/emojiget.go index 60f7d5948..49d586756 100644 --- a/internal/api/client/admin/emojiget.go +++ b/internal/api/client/admin/emojiget.go @@ -24,7 +24,7 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -68,31 +68,31 @@ import ( func (m *Module) EmojiGETHandler(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) + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } if !*authed.User.Admin { err := fmt.Errorf("user %s not an admin", authed.User.ID) - api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } emojiID := c.Param(IDKey) if emojiID == "" { err := errors.New("no emoji id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } emoji, errWithCode := m.processor.AdminEmojiGet(c.Request.Context(), authed, emojiID) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/admin/emojisget.go b/internal/api/client/admin/emojisget.go index 0b7cfe059..e8b3c0e49 100644 --- a/internal/api/client/admin/emojisget.go +++ b/internal/api/client/admin/emojisget.go @@ -25,7 +25,7 @@ import ( "strings" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtserror" @@ -125,18 +125,18 @@ import ( func (m *Module) EmojisGETHandler(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) + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } if !*authed.User.Admin { err := fmt.Errorf("user %s not an admin", authed.User.ID) - api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } @@ -149,7 +149,7 @@ func (m *Module) EmojisGETHandler(c *gin.Context) { i, err := strconv.ParseInt(limitString, 10, 32) if err != nil { err := fmt.Errorf("error parsing %s: %s", LimitKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } limit = int(i) @@ -177,7 +177,7 @@ func (m *Module) EmojisGETHandler(c *gin.Context) { shortcode = strings.Trim(filter[10:], ":") // remove any errant ":" default: err := fmt.Errorf("filter %s not recognized; accepted values are 'domain:[domain]', 'disabled', 'enabled', 'shortcode:[shortcode]'", filter) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } } @@ -200,7 +200,7 @@ func (m *Module) EmojisGETHandler(c *gin.Context) { resp, errWithCode := m.processor.AdminEmojisGet(c.Request.Context(), authed, domain, includeDisabled, includeEnabled, shortcode, maxShortcodeDomain, minShortcodeDomain, limit) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/admin/emojiupdate.go b/internal/api/client/admin/emojiupdate.go index 695c6bcde..8402b30e9 100644 --- a/internal/api/client/admin/emojiupdate.go +++ b/internal/api/client/admin/emojiupdate.go @@ -25,8 +25,8 @@ import ( "strings" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -123,42 +123,42 @@ import ( func (m *Module) EmojiPATCHHandler(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) + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } if !*authed.User.Admin { err := fmt.Errorf("user %s not an admin", authed.User.ID) - api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } emojiID := c.Param(IDKey) if emojiID == "" { err := errors.New("no emoji id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } - form := &model.EmojiUpdateRequest{} + form := &apimodel.EmojiUpdateRequest{} if err := c.ShouldBind(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if err := validateUpdateEmoji(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } emoji, errWithCode := m.processor.AdminEmojiUpdate(c.Request.Context(), emojiID, form) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } @@ -166,14 +166,14 @@ func (m *Module) EmojiPATCHHandler(c *gin.Context) { } // do a first pass on the form here -func validateUpdateEmoji(form *model.EmojiUpdateRequest) error { +func validateUpdateEmoji(form *apimodel.EmojiUpdateRequest) error { // check + normalize update type so we don't need // to do this trimming + lowercasing again later switch strings.TrimSpace(strings.ToLower(string(form.Type))) { - case string(model.EmojiUpdateDisable): + case string(apimodel.EmojiUpdateDisable): // no params required for this one, so don't bother checking - form.Type = model.EmojiUpdateDisable - case string(model.EmojiUpdateCopy): + form.Type = apimodel.EmojiUpdateDisable + case string(apimodel.EmojiUpdateCopy): // need at least a valid shortcode when doing a copy if form.Shortcode == nil { return errors.New("emoji action type was 'copy' but no shortcode was provided") @@ -190,8 +190,8 @@ func validateUpdateEmoji(form *model.EmojiUpdateRequest) error { } } - form.Type = model.EmojiUpdateCopy - case string(model.EmojiUpdateModify): + form.Type = apimodel.EmojiUpdateCopy + case string(apimodel.EmojiUpdateModify): // need either image or category name for modify hasImage := form.Image != nil && form.Image.Size != 0 hasCategoryName := form.CategoryName != nil @@ -212,7 +212,7 @@ func validateUpdateEmoji(form *model.EmojiUpdateRequest) error { } } - form.Type = model.EmojiUpdateModify + form.Type = apimodel.EmojiUpdateModify default: return errors.New("emoji action type must be one of 'disable', 'copy', 'modify'") } diff --git a/internal/api/client/admin/mediacleanup.go b/internal/api/client/admin/mediacleanup.go index 157f35ab0..7f3fc11d5 100644 --- a/internal/api/client/admin/mediacleanup.go +++ b/internal/api/client/admin/mediacleanup.go @@ -23,8 +23,8 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -71,19 +71,19 @@ import ( func (m *Module) MediaCleanupPOSTHandler(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) + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } if !*authed.User.Admin { err := fmt.Errorf("user %s not an admin", authed.User.ID) - api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) return } - form := &model.MediaCleanupRequest{} + form := &apimodel.MediaCleanupRequest{} if err := c.ShouldBind(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } @@ -98,7 +98,7 @@ func (m *Module) MediaCleanupPOSTHandler(c *gin.Context) { } if errWithCode := m.processor.AdminMediaPrune(c.Request.Context(), remoteCacheDays); errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/admin/mediarefetch.go b/internal/api/client/admin/mediarefetch.go index 9c8a30c1b..5618843e5 100644 --- a/internal/api/client/admin/mediarefetch.go +++ b/internal/api/client/admin/mediarefetch.go @@ -23,7 +23,7 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -74,18 +74,18 @@ import ( func (m *Module) MediaRefetchPOSTHandler(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) + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } if !*authed.User.Admin { err := fmt.Errorf("user %s not an admin", authed.User.ID) - api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) return } if errWithCode := m.processor.AdminMediaRefetch(c.Request.Context(), authed, c.Query(DomainQueryKey)); errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/app/app_test.go b/internal/api/client/app/app_test.go deleted file mode 100644 index 5c1981ba1..000000000 --- a/internal/api/client/app/app_test.go +++ /dev/null @@ -1,21 +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 app_test - -// TODO: write tests diff --git a/internal/api/client/app/appcreate.go b/internal/api/client/apps/appcreate.go index 6060c9480..f381e9954 100644 --- a/internal/api/client/app/appcreate.go +++ b/internal/api/client/apps/appcreate.go @@ -16,15 +16,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package app +package apps import ( "fmt" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -77,48 +77,48 @@ const ( func (m *Module) AppsPOSTHandler(c *gin.Context) { authed, err := oauth.Authed(c, false, false, false, false) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } - form := &model.ApplicationCreateRequest{} + form := &apimodel.ApplicationCreateRequest{} if err := c.ShouldBind(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if len([]rune(form.ClientName)) > formFieldLen { err := fmt.Errorf("client_name must be less than %d characters", formFieldLen) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if len([]rune(form.RedirectURIs)) > formRedirectLen { err := fmt.Errorf("redirect_uris must be less than %d characters", formRedirectLen) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if len([]rune(form.Scopes)) > formFieldLen { err := fmt.Errorf("scopes must be less than %d characters", formFieldLen) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if len([]rune(form.Website)) > formFieldLen { err := fmt.Errorf("website must be less than %d characters", formFieldLen) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } apiApp, errWithCode := m.processor.AppCreate(c.Request.Context(), authed, form) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/app/app.go b/internal/api/client/apps/apps.go index 0bbeb6cc9..264a76f6f 100644 --- a/internal/api/client/app/app.go +++ b/internal/api/client/apps/apps.go @@ -16,33 +16,28 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package app +package apps import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) -// BasePath is the base path for this api module -const BasePath = "/api/v1/apps" +// BasePath is the base path for this api module, excluding the api prefix +const BasePath = "/v1/apps" -// Module implements the ClientAPIModule interface for requests relating to registering/removing applications type Module struct { processor processing.Processor } -// New returns a new auth module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { return &Module{ processor: processor, } } -// Route satisfies the RESTAPIModule interface -func (m *Module) Route(s router.Router) error { - s.AttachHandler(http.MethodPost, BasePath, m.AppsPOSTHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodPost, BasePath, m.AppsPOSTHandler) } diff --git a/internal/api/client/auth/auth.go b/internal/api/client/auth/auth.go deleted file mode 100644 index 8a1d9d483..000000000 --- a/internal/api/client/auth/auth.go +++ /dev/null @@ -1,105 +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 auth - -import ( - "net/http" - - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/internal/oidc" - "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" -) - -/* #nosec G101 */ -const ( - // AuthSignInPath is the API path for users to sign in through - AuthSignInPath = "/auth/sign_in" - - // CheckYourEmailPath users land here after registering a new account, instructs them to confirm thier email - CheckYourEmailPath = "/check_your_email" - - // WaitForApprovalPath users land here after confirming thier email but before an admin approves thier account - // (if such is required) - WaitForApprovalPath = "/wait_for_approval" - - // AccountDisabledPath users land here when thier account is suspended by an admin - AccountDisabledPath = "/account_disabled" - - // OauthTokenPath is the API path to use for granting token requests to users with valid credentials - OauthTokenPath = "/oauth/token" - - // OauthAuthorizePath is the API path for authorization requests (eg., authorize this app to act on my behalf as a user) - OauthAuthorizePath = "/oauth/authorize" - - // OauthFinalizePath is the API path for completing user registration with additional user details - OauthFinalizePath = "/oauth/finalize" - - // CallbackPath is the API path for receiving callback tokens from external OIDC providers - CallbackPath = oidc.CallbackPath - - callbackStateParam = "state" - callbackCodeParam = "code" - - sessionUserID = "userid" - sessionClientID = "client_id" - sessionRedirectURI = "redirect_uri" - sessionForceLogin = "force_login" - sessionResponseType = "response_type" - sessionScope = "scope" - sessionInternalState = "internal_state" - sessionClientState = "client_state" - sessionClaims = "claims" - sessionAppID = "app_id" -) - -// Module implements the ClientAPIModule interface for -type Module struct { - db db.DB - idp oidc.IDP - processor processing.Processor -} - -// New returns a new auth module -func New(db db.DB, idp oidc.IDP, processor processing.Processor) api.ClientModule { - return &Module{ - db: db, - idp: idp, - processor: processor, - } -} - -// Route satisfies the RESTAPIModule interface -func (m *Module) Route(s router.Router) error { - s.AttachHandler(http.MethodGet, AuthSignInPath, m.SignInGETHandler) - s.AttachHandler(http.MethodPost, AuthSignInPath, m.SignInPOSTHandler) - - s.AttachHandler(http.MethodPost, OauthTokenPath, m.TokenPOSTHandler) - - s.AttachHandler(http.MethodGet, OauthAuthorizePath, m.AuthorizeGETHandler) - s.AttachHandler(http.MethodPost, OauthAuthorizePath, m.AuthorizePOSTHandler) - - s.AttachHandler(http.MethodGet, CallbackPath, m.CallbackGETHandler) - s.AttachHandler(http.MethodPost, OauthFinalizePath, m.FinalizePOSTHandler) - - s.AttachHandler(http.MethodGet, oauth.OOBTokenPath, m.OobHandler) - return nil -} diff --git a/internal/api/client/auth/auth_test.go b/internal/api/client/auth/auth_test.go deleted file mode 100644 index 75e958418..000000000 --- a/internal/api/client/auth/auth_test.go +++ /dev/null @@ -1,139 +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 auth_test - -import ( - "bytes" - "context" - "fmt" - "net/http/httptest" - - "github.com/gin-contrib/sessions" - "github.com/gin-contrib/sessions/memstore" - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/auth" - "github.com/superseriousbusiness/gotosocial/internal/concurrency" - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/email" - "github.com/superseriousbusiness/gotosocial/internal/federation" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/media" - "github.com/superseriousbusiness/gotosocial/internal/messages" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/internal/oidc" - "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" - "github.com/superseriousbusiness/gotosocial/internal/storage" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type AuthStandardTestSuite struct { - suite.Suite - db db.DB - storage *storage.Driver - mediaManager media.Manager - federator federation.Federator - processor processing.Processor - emailSender email.Sender - idp oidc.IDP - oauthServer oauth.Server - - // 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 - - // module being tested - authModule *auth.Module -} - -const ( - sessionUserID = "userid" - sessionClientID = "client_id" -) - -func (suite *AuthStandardTestSuite) SetupSuite() { - suite.testTokens = testrig.NewTestTokens() - suite.testClients = testrig.NewTestClients() - suite.testApplications = testrig.NewTestApplications() - suite.testUsers = testrig.NewTestUsers() - suite.testAccounts = testrig.NewTestAccounts() -} - -func (suite *AuthStandardTestSuite) SetupTest() { - testrig.InitTestConfig() - testrig.InitTestLog() - - fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1) - clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1) - - suite.db = testrig.NewTestDB() - suite.storage = testrig.NewInMemoryStorage() - suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) - suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker) - suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) - - suite.oauthServer = testrig.NewTestOauthServer(suite.db) - var err error - suite.idp, err = oidc.NewIDP(context.Background()) - if err != nil { - panic(err) - } - suite.authModule = auth.New(suite.db, suite.idp, suite.processor).(*auth.Module) - testrig.StandardDBSetup(suite.db, suite.testAccounts) -} - -func (suite *AuthStandardTestSuite) TearDownTest() { - testrig.StandardDBTeardown(suite.db) -} - -func (suite *AuthStandardTestSuite) newContext(requestMethod string, requestPath string, requestBody []byte, bodyContentType string) (*gin.Context, *httptest.ResponseRecorder) { - // create the recorder and gin test context - recorder := httptest.NewRecorder() - ctx, engine := testrig.CreateGinTestContext(recorder, nil) - - // load templates into the engine - testrig.ConfigureTemplatesWithGin(engine, "../../../../web/template") - - // create the request - protocol := config.GetProtocol() - host := config.GetHost() - baseURI := fmt.Sprintf("%s://%s", protocol, host) - requestURI := fmt.Sprintf("%s/%s", baseURI, requestPath) - - ctx.Request = httptest.NewRequest(requestMethod, requestURI, bytes.NewReader(requestBody)) // the endpoint we're hitting - ctx.Request.Header.Set("accept", "text/html") - - if bodyContentType != "" { - ctx.Request.Header.Set("Content-Type", bodyContentType) - } - - // trigger the session middleware on the context - store := memstore.NewStore(make([]byte, 32), make([]byte, 32)) - store.Options(router.SessionOptions()) - sessionMiddleware := sessions.Sessions("gotosocial-localhost", store) - sessionMiddleware(ctx) - - return ctx, recorder -} diff --git a/internal/api/client/auth/authorize.go b/internal/api/client/auth/authorize.go deleted file mode 100644 index f28d1dfc9..000000000 --- a/internal/api/client/auth/authorize.go +++ /dev/null @@ -1,335 +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 auth - -import ( - "errors" - "fmt" - "net/http" - "net/url" - - "github.com/gin-contrib/sessions" - "github.com/gin-gonic/gin" - "github.com/google/uuid" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// AuthorizeGETHandler should be served as GET at https://example.org/oauth/authorize -// The idea here is to present an oauth authorize page to the user, with a button -// that they have to click to accept. -func (m *Module) AuthorizeGETHandler(c *gin.Context) { - s := sessions.Default(c) - - if _, err := api.NegotiateAccept(c, api.HTMLAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - // UserID will be set in the session by AuthorizePOSTHandler if the caller has already gone through the authentication flow - // If it's not set, then we don't know yet who the user is, so we need to redirect them to the sign in page. - userID, ok := s.Get(sessionUserID).(string) - if !ok || userID == "" { - form := &model.OAuthAuthorize{} - if err := c.ShouldBind(form); err != nil { - m.clearSession(s) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) - return - } - - if errWithCode := saveAuthFormToSession(s, form); errWithCode != nil { - m.clearSession(s) - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.Redirect(http.StatusSeeOther, AuthSignInPath) - return - } - - // use session information to validate app, user, and account for this request - clientID, ok := s.Get(sessionClientID).(string) - if !ok || clientID == "" { - m.clearSession(s) - err := fmt.Errorf("key %s was not found in session", sessionClientID) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) - return - } - - app := >smodel.Application{} - if err := m.db.GetWhere(c.Request.Context(), []db.Where{{Key: sessionClientID, Value: clientID}}, app); err != nil { - m.clearSession(s) - safe := fmt.Sprintf("application for %s %s could not be retrieved", sessionClientID, clientID) - var errWithCode gtserror.WithCode - if err == db.ErrNoEntries { - errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice) - } else { - errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice) - } - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - user, err := m.db.GetUserByID(c.Request.Context(), userID) - if err != nil { - m.clearSession(s) - safe := fmt.Sprintf("user with id %s could not be retrieved", userID) - var errWithCode gtserror.WithCode - if err == db.ErrNoEntries { - errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice) - } else { - errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice) - } - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - acct, err := m.db.GetAccountByID(c.Request.Context(), user.AccountID) - if err != nil { - m.clearSession(s) - safe := fmt.Sprintf("account with id %s could not be retrieved", user.AccountID) - var errWithCode gtserror.WithCode - if err == db.ErrNoEntries { - errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice) - } else { - errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice) - } - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - if ensureUserIsAuthorizedOrRedirect(c, user, acct) { - return - } - - // Finally we should also get the redirect and scope of this particular request, as stored in the session. - redirect, ok := s.Get(sessionRedirectURI).(string) - if !ok || redirect == "" { - m.clearSession(s) - err := fmt.Errorf("key %s was not found in session", sessionRedirectURI) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) - return - } - - scope, ok := s.Get(sessionScope).(string) - if !ok || scope == "" { - m.clearSession(s) - err := fmt.Errorf("key %s was not found in session", sessionScope) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) - return - } - - instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), config.GetHost()) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - // the authorize template will display a form to the user where they can get some information - // about the app that's trying to authorize, and the scope of the request. - // They can then approve it if it looks OK to them, which will POST to the AuthorizePOSTHandler - c.HTML(http.StatusOK, "authorize.tmpl", gin.H{ - "appname": app.Name, - "appwebsite": app.Website, - "redirect": redirect, - "scope": scope, - "user": acct.Username, - "instance": instance, - }) -} - -// AuthorizePOSTHandler should be served as POST at https://example.org/oauth/authorize -// At this point we assume that the user has A) logged in and B) accepted that the app should act for them, -// so we should proceed with the authentication flow and generate an oauth token for them if we can. -func (m *Module) AuthorizePOSTHandler(c *gin.Context) { - s := sessions.Default(c) - - // We need to retrieve the original form submitted to the authorizeGEThandler, and - // recreate it on the request so that it can be used further by the oauth2 library. - errs := []string{} - - forceLogin, ok := s.Get(sessionForceLogin).(string) - if !ok { - forceLogin = "false" - } - - responseType, ok := s.Get(sessionResponseType).(string) - if !ok || responseType == "" { - errs = append(errs, fmt.Sprintf("key %s was not found in session", sessionResponseType)) - } - - clientID, ok := s.Get(sessionClientID).(string) - if !ok || clientID == "" { - errs = append(errs, fmt.Sprintf("key %s was not found in session", sessionClientID)) - } - - redirectURI, ok := s.Get(sessionRedirectURI).(string) - if !ok || redirectURI == "" { - errs = append(errs, fmt.Sprintf("key %s was not found in session", sessionRedirectURI)) - } - - scope, ok := s.Get(sessionScope).(string) - if !ok { - errs = append(errs, fmt.Sprintf("key %s was not found in session", sessionScope)) - } - - var clientState string - if s, ok := s.Get(sessionClientState).(string); ok { - clientState = s - } - - userID, ok := s.Get(sessionUserID).(string) - if !ok { - errs = append(errs, fmt.Sprintf("key %s was not found in session", sessionUserID)) - } - - if len(errs) != 0 { - errs = append(errs, oauth.HelpfulAdvice) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(errors.New("one or more missing keys on session during AuthorizePOSTHandler"), errs...), m.processor.InstanceGet) - return - } - - user, err := m.db.GetUserByID(c.Request.Context(), userID) - if err != nil { - m.clearSession(s) - safe := fmt.Sprintf("user with id %s could not be retrieved", userID) - var errWithCode gtserror.WithCode - if err == db.ErrNoEntries { - errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice) - } else { - errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice) - } - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - acct, err := m.db.GetAccountByID(c.Request.Context(), user.AccountID) - if err != nil { - m.clearSession(s) - safe := fmt.Sprintf("account with id %s could not be retrieved", user.AccountID) - var errWithCode gtserror.WithCode - if err == db.ErrNoEntries { - errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice) - } else { - errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice) - } - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - if ensureUserIsAuthorizedOrRedirect(c, user, acct) { - return - } - - if redirectURI != oauth.OOBURI { - // we're done with the session now, so just clear it out - m.clearSession(s) - } - - // we have to set the values on the request form - // so that they're picked up by the oauth server - c.Request.Form = url.Values{ - sessionForceLogin: {forceLogin}, - sessionResponseType: {responseType}, - sessionClientID: {clientID}, - sessionRedirectURI: {redirectURI}, - sessionScope: {scope}, - sessionUserID: {userID}, - } - - if clientState != "" { - c.Request.Form.Set("state", clientState) - } - - if errWithCode := m.processor.OAuthHandleAuthorizeRequest(c.Writer, c.Request); errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - } -} - -// saveAuthFormToSession checks the given OAuthAuthorize form, -// and stores the values in the form into the session. -func saveAuthFormToSession(s sessions.Session, form *model.OAuthAuthorize) gtserror.WithCode { - if form == nil { - err := errors.New("OAuthAuthorize form was nil") - return gtserror.NewErrorBadRequest(err, err.Error(), oauth.HelpfulAdvice) - } - - if form.ResponseType == "" { - err := errors.New("field response_type was not set on OAuthAuthorize form") - return gtserror.NewErrorBadRequest(err, err.Error(), oauth.HelpfulAdvice) - } - - if form.ClientID == "" { - err := errors.New("field client_id was not set on OAuthAuthorize form") - return gtserror.NewErrorBadRequest(err, err.Error(), oauth.HelpfulAdvice) - } - - if form.RedirectURI == "" { - err := errors.New("field redirect_uri was not set on OAuthAuthorize form") - return gtserror.NewErrorBadRequest(err, err.Error(), oauth.HelpfulAdvice) - } - - // set default scope to read - if form.Scope == "" { - form.Scope = "read" - } - - // save these values from the form so we can use them elsewhere in the session - s.Set(sessionForceLogin, form.ForceLogin) - s.Set(sessionResponseType, form.ResponseType) - s.Set(sessionClientID, form.ClientID) - s.Set(sessionRedirectURI, form.RedirectURI) - s.Set(sessionScope, form.Scope) - s.Set(sessionInternalState, uuid.NewString()) - s.Set(sessionClientState, form.State) - - if err := s.Save(); err != nil { - err := fmt.Errorf("error saving form values onto session: %s", err) - return gtserror.NewErrorInternalError(err, oauth.HelpfulAdvice) - } - - return nil -} - -func ensureUserIsAuthorizedOrRedirect(ctx *gin.Context, user *gtsmodel.User, account *gtsmodel.Account) (redirected bool) { - if user.ConfirmedAt.IsZero() { - ctx.Redirect(http.StatusSeeOther, CheckYourEmailPath) - redirected = true - return - } - - if !*user.Approved { - ctx.Redirect(http.StatusSeeOther, WaitForApprovalPath) - redirected = true - return - } - - if *user.Disabled || !account.SuspendedAt.IsZero() { - ctx.Redirect(http.StatusSeeOther, AccountDisabledPath) - redirected = true - return - } - - return -} diff --git a/internal/api/client/auth/authorize_test.go b/internal/api/client/auth/authorize_test.go deleted file mode 100644 index 738b3b910..000000000 --- a/internal/api/client/auth/authorize_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package auth_test - -import ( - "context" - "fmt" - "net/http" - "testing" - "time" - - "github.com/gin-contrib/sessions" - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/auth" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type AuthAuthorizeTestSuite struct { - AuthStandardTestSuite -} - -type authorizeHandlerTestCase struct { - description string - mutateUserAccount func(*gtsmodel.User, *gtsmodel.Account) []string - expectedStatusCode int - expectedLocationHeader string -} - -func (suite *AuthAuthorizeTestSuite) TestAccountAuthorizeHandler() { - tests := []authorizeHandlerTestCase{ - { - description: "user has their email unconfirmed", - mutateUserAccount: func(user *gtsmodel.User, account *gtsmodel.Account) []string { - user.ConfirmedAt = time.Time{} - return []string{"confirmed_at"} - }, - expectedStatusCode: http.StatusSeeOther, - expectedLocationHeader: auth.CheckYourEmailPath, - }, - { - description: "user has their email confirmed but is not approved", - mutateUserAccount: func(user *gtsmodel.User, account *gtsmodel.Account) []string { - user.ConfirmedAt = time.Now() - user.Email = user.UnconfirmedEmail - return []string{"confirmed_at", "email"} - }, - expectedStatusCode: http.StatusSeeOther, - expectedLocationHeader: auth.WaitForApprovalPath, - }, - { - description: "user has their email confirmed and is approved, but User entity has been disabled", - mutateUserAccount: func(user *gtsmodel.User, account *gtsmodel.Account) []string { - user.ConfirmedAt = time.Now() - user.Email = user.UnconfirmedEmail - user.Approved = testrig.TrueBool() - user.Disabled = testrig.TrueBool() - return []string{"confirmed_at", "email", "approved", "disabled"} - }, - expectedStatusCode: http.StatusSeeOther, - expectedLocationHeader: auth.AccountDisabledPath, - }, - { - description: "user has their email confirmed and is approved, but Account entity has been suspended", - mutateUserAccount: func(user *gtsmodel.User, account *gtsmodel.Account) []string { - user.ConfirmedAt = time.Now() - user.Email = user.UnconfirmedEmail - user.Approved = testrig.TrueBool() - user.Disabled = testrig.FalseBool() - account.SuspendedAt = time.Now() - return []string{"confirmed_at", "email", "approved", "disabled"} - }, - expectedStatusCode: http.StatusSeeOther, - expectedLocationHeader: auth.AccountDisabledPath, - }, - } - - doTest := func(testCase authorizeHandlerTestCase) { - ctx, recorder := suite.newContext(http.MethodGet, auth.OauthAuthorizePath, nil, "") - - user := >smodel.User{} - account := >smodel.Account{} - - *user = *suite.testUsers["unconfirmed_account"] - *account = *suite.testAccounts["unconfirmed_account"] - - testSession := sessions.Default(ctx) - testSession.Set(sessionUserID, user.ID) - testSession.Set(sessionClientID, suite.testApplications["application_1"].ClientID) - if err := testSession.Save(); err != nil { - panic(fmt.Errorf("failed on case %s: %w", testCase.description, err)) - } - - columns := testCase.mutateUserAccount(user, account) - - testCase.description = fmt.Sprintf("%s, %t, %s", user.Email, *user.Disabled, account.SuspendedAt) - - err := suite.db.UpdateUser(context.Background(), user, columns...) - suite.NoError(err) - err = suite.db.UpdateAccount(context.Background(), account) - suite.NoError(err) - - // call the handler - suite.authModule.AuthorizeGETHandler(ctx) - - // 1. we should have a redirect - suite.Equal(testCase.expectedStatusCode, recorder.Code, fmt.Sprintf("failed on case: %s", testCase.description)) - - // 2. we should have a redirect to the check your email path, as this user has not confirmed their email yet. - suite.Equal(testCase.expectedLocationHeader, recorder.Header().Get("Location"), fmt.Sprintf("failed on case: %s", testCase.description)) - } - - for _, testCase := range tests { - doTest(testCase) - } -} - -func TestAccountUpdateTestSuite(t *testing.T) { - suite.Run(t, new(AuthAuthorizeTestSuite)) -} diff --git a/internal/api/client/auth/callback.go b/internal/api/client/auth/callback.go deleted file mode 100644 index c97abf7aa..000000000 --- a/internal/api/client/auth/callback.go +++ /dev/null @@ -1,311 +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 auth - -import ( - "context" - "errors" - "fmt" - "net" - "net/http" - "strings" - - "github.com/gin-contrib/sessions" - "github.com/gin-gonic/gin" - "github.com/google/uuid" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/internal/oidc" - "github.com/superseriousbusiness/gotosocial/internal/validate" -) - -// extraInfo wraps a form-submitted username and transmitted name -type extraInfo struct { - Username string `form:"username"` - Name string `form:"name"` // note that this is only used for re-rendering the page in case of an error -} - -// CallbackGETHandler parses a token from an external auth provider. -func (m *Module) CallbackGETHandler(c *gin.Context) { - s := sessions.Default(c) - - // check the query vs session state parameter to mitigate csrf - // https://auth0.com/docs/secure/attack-protection/state-parameters - - returnedInternalState := c.Query(callbackStateParam) - if returnedInternalState == "" { - m.clearSession(s) - err := fmt.Errorf("%s parameter not found on callback query", callbackStateParam) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - savedInternalStateI := s.Get(sessionInternalState) - savedInternalState, ok := savedInternalStateI.(string) - if !ok { - m.clearSession(s) - err := fmt.Errorf("key %s was not found in session", sessionInternalState) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - if returnedInternalState != savedInternalState { - m.clearSession(s) - err := errors.New("mismatch between callback state and saved state") - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) - return - } - - // retrieve stored claims using code - code := c.Query(callbackCodeParam) - if code == "" { - m.clearSession(s) - err := fmt.Errorf("%s parameter not found on callback query", callbackCodeParam) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - claims, errWithCode := m.idp.HandleCallback(c.Request.Context(), code) - if errWithCode != nil { - m.clearSession(s) - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - // We can use the client_id on the session to retrieve - // info about the app associated with the client_id - clientID, ok := s.Get(sessionClientID).(string) - if !ok || clientID == "" { - m.clearSession(s) - err := fmt.Errorf("key %s was not found in session", sessionClientID) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) - return - } - - app := >smodel.Application{} - if err := m.db.GetWhere(c.Request.Context(), []db.Where{{Key: sessionClientID, Value: clientID}}, app); err != nil { - m.clearSession(s) - safe := fmt.Sprintf("application for %s %s could not be retrieved", sessionClientID, clientID) - var errWithCode gtserror.WithCode - if err == db.ErrNoEntries { - errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice) - } else { - errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice) - } - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - user, errWithCode := m.fetchUserForClaims(c.Request.Context(), claims, net.IP(c.ClientIP()), app.ID) - if errWithCode != nil { - m.clearSession(s) - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - if user == nil { - // no user exists yet - let's ask them for their preferred username - instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), config.GetHost()) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - // store the claims in the session - that way we know the user is authenticated when processing the form later - s.Set(sessionClaims, claims) - s.Set(sessionAppID, app.ID) - if err := s.Save(); err != nil { - m.clearSession(s) - api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) - return - } - c.HTML(http.StatusOK, "finalize.tmpl", gin.H{ - "instance": instance, - "name": claims.Name, - "preferredUsername": claims.PreferredUsername, - }) - return - } - s.Set(sessionUserID, user.ID) - if err := s.Save(); err != nil { - m.clearSession(s) - api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) - return - } - c.Redirect(http.StatusFound, OauthAuthorizePath) -} - -// FinalizePOSTHandler registers the user after additional data has been provided -func (m *Module) FinalizePOSTHandler(c *gin.Context) { - s := sessions.Default(c) - - form := &extraInfo{} - if err := c.ShouldBind(form); err != nil { - m.clearSession(s) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) - return - } - - // since we have multiple possible validation error, `validationError` is a shorthand for rendering them - validationError := func(err error) { - instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), config.GetHost()) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - c.HTML(http.StatusOK, "finalize.tmpl", gin.H{ - "instance": instance, - "name": form.Name, - "preferredUsername": form.Username, - "error": err, - }) - } - - // check if the username conforms to the spec - if err := validate.Username(form.Username); err != nil { - validationError(err) - return - } - - // see if the username is still available - usernameAvailable, err := m.db.IsUsernameAvailable(c.Request.Context(), form.Username) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) - return - } - if !usernameAvailable { - validationError(fmt.Errorf("Username %s is already taken", form.Username)) - return - } - - // retrieve the information previously set by the oidc logic - appID, ok := s.Get(sessionAppID).(string) - if !ok { - err := fmt.Errorf("key %s was not found in session", sessionAppID) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) - return - } - - // retrieve the claims returned by the IDP. Having this present means that we previously already verified these claims - claims, ok := s.Get(sessionClaims).(*oidc.Claims) - if !ok { - err := fmt.Errorf("key %s was not found in session", sessionClaims) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) - return - } - - // we're now ready to actually create the user - user, errWithCode := m.createUserFromOIDC(c.Request.Context(), claims, form, net.IP(c.ClientIP()), appID) - if errWithCode != nil { - m.clearSession(s) - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - s.Delete(sessionClaims) - s.Delete(sessionAppID) - s.Set(sessionUserID, user.ID) - if err := s.Save(); err != nil { - m.clearSession(s) - api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) - return - } - c.Redirect(http.StatusFound, OauthAuthorizePath) -} - -func (m *Module) fetchUserForClaims(ctx context.Context, claims *oidc.Claims, ip net.IP, appID string) (*gtsmodel.User, gtserror.WithCode) { - if claims.Sub == "" { - err := errors.New("no sub claim found - is your provider OIDC compliant?") - return nil, gtserror.NewErrorBadRequest(err, err.Error()) - } - user, err := m.db.GetUserByExternalID(ctx, claims.Sub) - if err == nil { - return user, nil - } - if err != db.ErrNoEntries { - err := fmt.Errorf("error checking database for externalID %s: %s", claims.Sub, err) - return nil, gtserror.NewErrorInternalError(err) - } - if !config.GetOIDCLinkExisting() { - return nil, nil - } - // fallback to email if we want to link existing users - user, err = m.db.GetUserByEmailAddress(ctx, claims.Email) - if err == db.ErrNoEntries { - return nil, nil - } else if err != nil { - err := fmt.Errorf("error checking database for email %s: %s", claims.Email, err) - return nil, gtserror.NewErrorInternalError(err) - } - // at this point we have found a matching user but still need to link the newly received external ID - - user.ExternalID = claims.Sub - err = m.db.UpdateUser(ctx, user, "external_id") - if err != nil { - err := fmt.Errorf("error linking existing user %s: %s", claims.Email, err) - return nil, gtserror.NewErrorInternalError(err) - } - return user, nil -} - -func (m *Module) createUserFromOIDC(ctx context.Context, claims *oidc.Claims, extraInfo *extraInfo, ip net.IP, appID string) (*gtsmodel.User, gtserror.WithCode) { - // check if the email address is available for use; if it's not there's nothing we can so - emailAvailable, err := m.db.IsEmailAvailable(ctx, claims.Email) - if err != nil { - return nil, gtserror.NewErrorBadRequest(err) - } - if !emailAvailable { - help := "The email address given to us by your authentication provider already exists in our records and the server administrator has not enabled account migration" - return nil, gtserror.NewErrorConflict(fmt.Errorf("email address %s is not available", claims.Email), help) - } - - // check if the user is in any recognised admin groups - var admin bool - for _, g := range claims.Groups { - if strings.EqualFold(g, "admin") || strings.EqualFold(g, "admins") { - admin = true - } - } - - // We still need to set *a* password even if it's not a password the user will end up using, so set something random. - // We'll just set two uuids on top of each other, which should be long + random enough to baffle any attempts to crack. - // - // If the user ever wants to log in using gts password rather than oidc flow, they'll have to request a password reset, which is fine - password := uuid.NewString() + uuid.NewString() - - // Since this user is created via oidc, which has been set up by the admin, we can assume that the account is already - // implicitly approved, and that the email address has already been verified: otherwise, we end up in situations where - // the admin first approves the user in OIDC, and then has to approve them again in GoToSocial, which doesn't make sense. - // - // In other words, if a user logs in via OIDC, they should be able to use their account straight away. - // - // See: https://github.com/superseriousbusiness/gotosocial/issues/357 - requireApproval := false - emailVerified := true - - // create the user! this will also create an account and store it in the database so we don't need to do that here - user, err := m.db.NewSignup(ctx, extraInfo.Username, "", requireApproval, claims.Email, password, ip, "", appID, emailVerified, claims.Sub, admin) - if err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - - return user, nil -} diff --git a/internal/api/client/auth/oob.go b/internal/api/client/auth/oob.go deleted file mode 100644 index 92e49d328..000000000 --- a/internal/api/client/auth/oob.go +++ /dev/null @@ -1,111 +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 auth - -import ( - "context" - "errors" - "fmt" - "net/http" - - "github.com/gin-contrib/sessions" - "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/db" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -func (m *Module) OobHandler(c *gin.Context) { - host := config.GetHost() - instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), host) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - instanceGet := func(ctx context.Context, domain string) (*model.Instance, gtserror.WithCode) { return instance, nil } - - oobToken := c.Query("code") - if oobToken == "" { - err := errors.New("no 'code' query value provided in callback redirect") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error(), oauth.HelpfulAdvice), instanceGet) - return - } - - s := sessions.Default(c) - - errs := []string{} - - scope, ok := s.Get(sessionScope).(string) - if !ok { - errs = append(errs, fmt.Sprintf("key %s was not found in session", sessionScope)) - } - - userID, ok := s.Get(sessionUserID).(string) - if !ok { - errs = append(errs, fmt.Sprintf("key %s was not found in session", sessionUserID)) - } - - if len(errs) != 0 { - errs = append(errs, oauth.HelpfulAdvice) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(errors.New("one or more missing keys on session during OobHandler"), errs...), m.processor.InstanceGet) - return - } - - user, err := m.db.GetUserByID(c.Request.Context(), userID) - if err != nil { - m.clearSession(s) - safe := fmt.Sprintf("user with id %s could not be retrieved", userID) - var errWithCode gtserror.WithCode - if err == db.ErrNoEntries { - errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice) - } else { - errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice) - } - api.ErrorHandler(c, errWithCode, instanceGet) - return - } - - acct, err := m.db.GetAccountByID(c.Request.Context(), user.AccountID) - if err != nil { - m.clearSession(s) - safe := fmt.Sprintf("account with id %s could not be retrieved", user.AccountID) - var errWithCode gtserror.WithCode - if err == db.ErrNoEntries { - errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice) - } else { - errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice) - } - api.ErrorHandler(c, errWithCode, instanceGet) - return - } - - // we're done with the session now, so just clear it out - m.clearSession(s) - - c.HTML(http.StatusOK, "oob.tmpl", gin.H{ - "instance": instance, - "user": acct.Username, - "oobToken": oobToken, - "scope": scope, - }) -} diff --git a/internal/api/client/auth/signin.go b/internal/api/client/auth/signin.go deleted file mode 100644 index 73a5de398..000000000 --- a/internal/api/client/auth/signin.go +++ /dev/null @@ -1,145 +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 auth - -import ( - "context" - "errors" - "fmt" - "net/http" - - "github.com/gin-contrib/sessions" - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "golang.org/x/crypto/bcrypt" -) - -// login just wraps a form-submitted username (we want an email) and password -type login struct { - Email string `form:"username"` - Password string `form:"password"` -} - -// SignInGETHandler should be served at https://example.org/auth/sign_in. -// The idea is to present a sign in page to the user, where they can enter their username and password. -// The form will then POST to the sign in page, which will be handled by SignInPOSTHandler. -// If an idp provider is set, then the user will be redirected to that to do their sign in. -func (m *Module) SignInGETHandler(c *gin.Context) { - if _, err := api.NegotiateAccept(c, api.HTMLAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - if m.idp == nil { - instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), config.GetHost()) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - // no idp provider, use our own funky little sign in page - c.HTML(http.StatusOK, "sign-in.tmpl", gin.H{ - "instance": instance, - }) - return - } - - // idp provider is in use, so redirect to it - s := sessions.Default(c) - - internalStateI := s.Get(sessionInternalState) - internalState, ok := internalStateI.(string) - if !ok { - m.clearSession(s) - err := fmt.Errorf("key %s was not found in session", sessionInternalState) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) - return - } - - c.Redirect(http.StatusSeeOther, m.idp.AuthCodeURL(internalState)) -} - -// SignInPOSTHandler should be served at https://example.org/auth/sign_in. -// The idea is to present a sign in page to the user, where they can enter their username and password. -// The handler will then redirect to the auth handler served at /auth -func (m *Module) SignInPOSTHandler(c *gin.Context) { - s := sessions.Default(c) - - form := &login{} - if err := c.ShouldBind(form); err != nil { - m.clearSession(s) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) - return - } - - userid, errWithCode := m.ValidatePassword(c.Request.Context(), form.Email, form.Password) - if errWithCode != nil { - // don't clear session here, so the user can just press back and try again - // if they accidentally gave the wrong password or something - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - s.Set(sessionUserID, userid) - if err := s.Save(); err != nil { - err := fmt.Errorf("error saving user id onto session: %s", err) - api.ErrorHandler(c, gtserror.NewErrorInternalError(err, oauth.HelpfulAdvice), m.processor.InstanceGet) - } - - c.Redirect(http.StatusFound, OauthAuthorizePath) -} - -// ValidatePassword takes an email address and a password. -// The goal is to authenticate the password against the one for that email -// address stored in the database. If OK, we return the userid (a ulid) for that user, -// so that it can be used in further Oauth flows to generate a token/retreieve an oauth client from the db. -func (m *Module) ValidatePassword(ctx context.Context, email string, password string) (string, gtserror.WithCode) { - if email == "" || password == "" { - err := errors.New("email or password was not provided") - return incorrectPassword(err) - } - - user, err := m.db.GetUserByEmailAddress(ctx, email) - if err != nil { - err := fmt.Errorf("user %s was not retrievable from db during oauth authorization attempt: %s", email, err) - return incorrectPassword(err) - } - - if user.EncryptedPassword == "" { - err := fmt.Errorf("encrypted password for user %s was empty for some reason", user.Email) - return incorrectPassword(err) - } - - if err := bcrypt.CompareHashAndPassword([]byte(user.EncryptedPassword), []byte(password)); err != nil { - err := fmt.Errorf("password hash didn't match for user %s during login attempt: %s", user.Email, err) - return incorrectPassword(err) - } - - return user.ID, nil -} - -// incorrectPassword wraps the given error in a gtserror.WithCode, and returns -// only a generic 'safe' error message to the user, to not give any info away. -func incorrectPassword(err error) (string, gtserror.WithCode) { - safeErr := fmt.Errorf("password/email combination was incorrect") - return "", gtserror.NewErrorUnauthorized(err, safeErr.Error(), oauth.HelpfulAdvice) -} diff --git a/internal/api/client/auth/token.go b/internal/api/client/auth/token.go deleted file mode 100644 index fbbd08404..000000000 --- a/internal/api/client/auth/token.go +++ /dev/null @@ -1,115 +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 auth - -import ( - "net/http" - "net/url" - - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - - "github.com/gin-gonic/gin" -) - -type tokenRequestForm struct { - GrantType *string `form:"grant_type" json:"grant_type" xml:"grant_type"` - Code *string `form:"code" json:"code" xml:"code"` - RedirectURI *string `form:"redirect_uri" json:"redirect_uri" xml:"redirect_uri"` - ClientID *string `form:"client_id" json:"client_id" xml:"client_id"` - ClientSecret *string `form:"client_secret" json:"client_secret" xml:"client_secret"` - Scope *string `form:"scope" json:"scope" xml:"scope"` -} - -// TokenPOSTHandler should be served as a POST at https://example.org/oauth/token -// The idea here is to serve an oauth access token to a user, which can be used for authorizing against non-public APIs. -func (m *Module) TokenPOSTHandler(c *gin.Context) { - if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - help := []string{} - - form := &tokenRequestForm{} - if err := c.ShouldBind(form); err != nil { - api.OAuthErrorHandler(c, gtserror.NewErrorBadRequest(oauth.InvalidRequest(), err.Error())) - return - } - - c.Request.Form = url.Values{} - - var grantType string - if form.GrantType != nil { - grantType = *form.GrantType - c.Request.Form.Set("grant_type", grantType) - } else { - help = append(help, "grant_type was not set in the token request form, but must be set to authorization_code or client_credentials") - } - - if form.ClientID != nil { - c.Request.Form.Set("client_id", *form.ClientID) - } else { - help = append(help, "client_id was not set in the token request form") - } - - if form.ClientSecret != nil { - c.Request.Form.Set("client_secret", *form.ClientSecret) - } else { - help = append(help, "client_secret was not set in the token request form") - } - - if form.RedirectURI != nil { - c.Request.Form.Set("redirect_uri", *form.RedirectURI) - } else { - help = append(help, "redirect_uri was not set in the token request form") - } - - var code string - if form.Code != nil { - if grantType != "authorization_code" { - help = append(help, "a code was provided in the token request form, but grant_type was not set to authorization_code") - } else { - code = *form.Code - c.Request.Form.Set("code", code) - } - } else if grantType == "authorization_code" { - help = append(help, "code was not set in the token request form, but must be set since grant_type is authorization_code") - } - - if form.Scope != nil { - c.Request.Form.Set("scope", *form.Scope) - } - - if len(help) != 0 { - api.OAuthErrorHandler(c, gtserror.NewErrorBadRequest(oauth.InvalidRequest(), help...)) - return - } - - token, errWithCode := m.processor.OAuthHandleTokenRequest(c.Request) - if errWithCode != nil { - api.OAuthErrorHandler(c, errWithCode) - return - } - - c.Header("Cache-Control", "no-store") - c.Header("Pragma", "no-cache") - c.JSON(http.StatusOK, token) -} diff --git a/internal/api/client/auth/token_test.go b/internal/api/client/auth/token_test.go deleted file mode 100644 index 50bbd6918..000000000 --- a/internal/api/client/auth/token_test.go +++ /dev/null @@ -1,215 +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 auth_test - -import ( - "context" - "encoding/json" - "io/ioutil" - "net/http" - "testing" - "time" - - "github.com/stretchr/testify/suite" - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type TokenTestSuite struct { - AuthStandardTestSuite -} - -func (suite *TokenTestSuite) TestPOSTTokenEmptyForm() { - ctx, recorder := suite.newContext(http.MethodPost, "oauth/token", []byte{}, "") - ctx.Request.Header.Set("accept", "application/json") - - suite.authModule.TokenPOSTHandler(ctx) - - suite.Equal(http.StatusBadRequest, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - suite.Equal(`{"error":"invalid_request","error_description":"Bad Request: grant_type was not set in the token request form, but must be set to authorization_code or client_credentials: client_id was not set in the token request form: client_secret was not set in the token request form: redirect_uri was not set in the token request form"}`, string(b)) -} - -func (suite *TokenTestSuite) TestRetrieveClientCredentialsOK() { - testClient := suite.testClients["local_account_1"] - - requestBody, w, err := testrig.CreateMultipartFormData( - "", "", - map[string]string{ - "grant_type": "client_credentials", - "client_id": testClient.ID, - "client_secret": testClient.Secret, - "redirect_uri": "http://localhost:8080", - }) - if err != nil { - panic(err) - } - bodyBytes := requestBody.Bytes() - - ctx, recorder := suite.newContext(http.MethodPost, "oauth/token", bodyBytes, w.FormDataContentType()) - ctx.Request.Header.Set("accept", "application/json") - - suite.authModule.TokenPOSTHandler(ctx) - - suite.Equal(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - t := &apimodel.Token{} - err = json.Unmarshal(b, t) - suite.NoError(err) - - suite.Equal("Bearer", t.TokenType) - suite.NotEmpty(t.AccessToken) - suite.NotEmpty(t.CreatedAt) - suite.WithinDuration(time.Now(), time.Unix(t.CreatedAt, 0), 1*time.Minute) - - // there should be a token in the database now too - dbToken := >smodel.Token{} - err = suite.db.GetWhere(context.Background(), []db.Where{{Key: "access", Value: t.AccessToken}}, dbToken) - suite.NoError(err) - suite.NotNil(dbToken) -} - -func (suite *TokenTestSuite) TestRetrieveAuthorizationCodeOK() { - testClient := suite.testClients["local_account_1"] - testUserAuthorizationToken := suite.testTokens["local_account_1_user_authorization_token"] - - requestBody, w, err := testrig.CreateMultipartFormData( - "", "", - map[string]string{ - "grant_type": "authorization_code", - "client_id": testClient.ID, - "client_secret": testClient.Secret, - "redirect_uri": "http://localhost:8080", - "code": testUserAuthorizationToken.Code, - }) - if err != nil { - panic(err) - } - bodyBytes := requestBody.Bytes() - - ctx, recorder := suite.newContext(http.MethodPost, "oauth/token", bodyBytes, w.FormDataContentType()) - ctx.Request.Header.Set("accept", "application/json") - - suite.authModule.TokenPOSTHandler(ctx) - - suite.Equal(http.StatusOK, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - t := &apimodel.Token{} - err = json.Unmarshal(b, t) - suite.NoError(err) - - suite.Equal("Bearer", t.TokenType) - suite.NotEmpty(t.AccessToken) - suite.NotEmpty(t.CreatedAt) - suite.WithinDuration(time.Now(), time.Unix(t.CreatedAt, 0), 1*time.Minute) - - dbToken := >smodel.Token{} - err = suite.db.GetWhere(context.Background(), []db.Where{{Key: "access", Value: t.AccessToken}}, dbToken) - suite.NoError(err) - suite.NotNil(dbToken) -} - -func (suite *TokenTestSuite) TestRetrieveAuthorizationCodeNoCode() { - testClient := suite.testClients["local_account_1"] - - requestBody, w, err := testrig.CreateMultipartFormData( - "", "", - map[string]string{ - "grant_type": "authorization_code", - "client_id": testClient.ID, - "client_secret": testClient.Secret, - "redirect_uri": "http://localhost:8080", - }) - if err != nil { - panic(err) - } - bodyBytes := requestBody.Bytes() - - ctx, recorder := suite.newContext(http.MethodPost, "oauth/token", bodyBytes, w.FormDataContentType()) - ctx.Request.Header.Set("accept", "application/json") - - suite.authModule.TokenPOSTHandler(ctx) - - suite.Equal(http.StatusBadRequest, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - suite.Equal(`{"error":"invalid_request","error_description":"Bad Request: code was not set in the token request form, but must be set since grant_type is authorization_code"}`, string(b)) -} - -func (suite *TokenTestSuite) TestRetrieveAuthorizationCodeWrongGrantType() { - testClient := suite.testClients["local_account_1"] - - requestBody, w, err := testrig.CreateMultipartFormData( - "", "", - map[string]string{ - "grant_type": "client_credentials", - "client_id": testClient.ID, - "client_secret": testClient.Secret, - "redirect_uri": "http://localhost:8080", - "code": "peepeepoopoo", - }) - if err != nil { - panic(err) - } - bodyBytes := requestBody.Bytes() - - ctx, recorder := suite.newContext(http.MethodPost, "oauth/token", bodyBytes, w.FormDataContentType()) - ctx.Request.Header.Set("accept", "application/json") - - suite.authModule.TokenPOSTHandler(ctx) - - suite.Equal(http.StatusBadRequest, recorder.Code) - - result := recorder.Result() - defer result.Body.Close() - - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - - suite.Equal(`{"error":"invalid_request","error_description":"Bad Request: a code was provided in the token request form, but grant_type was not set to authorization_code"}`, string(b)) -} - -func TestTokenTestSuite(t *testing.T) { - suite.Run(t, &TokenTestSuite{}) -} diff --git a/internal/api/client/auth/util.go b/internal/api/client/auth/util.go deleted file mode 100644 index d59983c55..000000000 --- a/internal/api/client/auth/util.go +++ /dev/null @@ -1,31 +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 auth - -import ( - "github.com/gin-contrib/sessions" -) - -func (m *Module) clearSession(s sessions.Session) { - s.Clear() - - if err := s.Save(); err != nil { - panic(err) - } -} diff --git a/internal/api/client/blocks/blocks.go b/internal/api/client/blocks/blocks.go index 2211a8076..df2ee65bb 100644 --- a/internal/api/client/blocks/blocks.go +++ b/internal/api/client/blocks/blocks.go @@ -21,14 +21,13 @@ package blocks import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( - // BasePath is the base URI path for serving favourites - BasePath = "/api/v1/blocks" + // BasePath is the base URI path for serving blocks, minus the api prefix. + BasePath = "/v1/blocks" // MaxIDKey is the url query for setting a max ID to return MaxIDKey = "max_id" @@ -38,20 +37,16 @@ const ( LimitKey = "limit" ) -// Module implements the ClientAPIModule interface for everything relating to viewing blocks type Module struct { processor processing.Processor } -// New returns a new blocks module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { 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.MethodGet, BasePath, m.BlocksGETHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodGet, BasePath, m.BlocksGETHandler) } diff --git a/internal/api/client/blocks/blocksget.go b/internal/api/client/blocks/blocksget.go index 98f5ce6ea..290ea6617 100644 --- a/internal/api/client/blocks/blocksget.go +++ b/internal/api/client/blocks/blocksget.go @@ -24,7 +24,7 @@ import ( "strconv" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -96,12 +96,12 @@ import ( func (m *Module) BlocksGETHandler(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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } @@ -123,7 +123,7 @@ func (m *Module) BlocksGETHandler(c *gin.Context) { i, err := strconv.ParseInt(limitString, 10, 32) if err != nil { err := fmt.Errorf("error parsing %s: %s", LimitKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } limit = int(i) @@ -131,7 +131,7 @@ func (m *Module) BlocksGETHandler(c *gin.Context) { resp, errWithCode := m.processor.BlocksGet(c.Request.Context(), authed, maxID, sinceID, limit) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/bookmarks/bookmarks.go b/internal/api/client/bookmarks/bookmarks.go index 492b7364c..d0273321c 100644 --- a/internal/api/client/bookmarks/bookmarks.go +++ b/internal/api/client/bookmarks/bookmarks.go @@ -21,9 +21,8 @@ package bookmarks import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( @@ -31,20 +30,16 @@ const ( BasePath = "/api/v1/bookmarks" ) -// Module implements the ClientAPIModule interface for everything related to bookmarks type Module struct { processor processing.Processor } -// New returns a new emoji module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { 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.MethodGet, BasePath, m.BookmarksGETHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodGet, BasePath, m.BookmarksGETHandler) } diff --git a/internal/api/client/bookmarks/bookmarks_test.go b/internal/api/client/bookmarks/bookmarks_test.go index b4a4bdfb1..3bd12aee1 100644 --- a/internal/api/client/bookmarks/bookmarks_test.go +++ b/internal/api/client/bookmarks/bookmarks_test.go @@ -29,7 +29,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/bookmarks" - "github.com/superseriousbusiness/gotosocial/internal/api/client/status" + "github.com/superseriousbusiness/gotosocial/internal/api/client/statuses" "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/concurrency" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -67,7 +67,7 @@ type BookmarkTestSuite struct { testFollows map[string]*gtsmodel.Follow // module being tested - statusModule *status.Module + statusModule *statuses.Module bookmarkModule *bookmarks.Module } @@ -99,8 +99,8 @@ func (suite *BookmarkTestSuite) SetupTest() { 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.bookmarkModule = bookmarks.New(suite.processor).(*bookmarks.Module) + suite.statusModule = statuses.New(suite.processor) + suite.bookmarkModule = bookmarks.New(suite.processor) suite.NoError(suite.processor.Start()) } @@ -123,7 +123,7 @@ func (suite *BookmarkTestSuite) TestGetBookmark() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.BookmarkPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/json") suite.bookmarkModule.BookmarksGETHandler(ctx) diff --git a/internal/api/client/bookmarks/bookmarksget.go b/internal/api/client/bookmarks/bookmarksget.go index dafc896ef..8f587f13d 100644 --- a/internal/api/client/bookmarks/bookmarksget.go +++ b/internal/api/client/bookmarks/bookmarksget.go @@ -6,7 +6,7 @@ import ( "strconv" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -56,12 +56,12 @@ const ( func (m *Module) BookmarksGETHandler(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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } @@ -71,7 +71,7 @@ func (m *Module) BookmarksGETHandler(c *gin.Context) { i, err := strconv.ParseInt(limitString, 10, 64) if err != nil { err := fmt.Errorf("error parsing %s: %s", LimitKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } limit = int(i) @@ -91,12 +91,12 @@ func (m *Module) BookmarksGETHandler(c *gin.Context) { resp, errWithCode := m.processor.BookmarksGet(c.Request.Context(), authed, maxID, minID, limit) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/emoji/emoji.go b/internal/api/client/customemojis/customemojis.go index 871a12854..ab89415d0 100644 --- a/internal/api/client/emoji/emoji.go +++ b/internal/api/client/customemojis/customemojis.go @@ -16,35 +16,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package emoji +package customemojis import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( - // BasePath is the base path for serving the emoji API - BasePath = "/api/v1/custom_emojis" + // BasePath is the base path for serving custom emojis, minus the 'api' prefix + BasePath = "/v1/custom_emojis" ) -// Module implements the ClientAPIModule interface for everything related to emoji type Module struct { processor processing.Processor } -// New returns a new emoji module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { 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.MethodGet, BasePath, m.EmojisGETHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodGet, BasePath, m.CustomEmojisGETHandler) } diff --git a/internal/api/client/customemojis/customemojisget.go b/internal/api/client/customemojis/customemojisget.go new file mode 100644 index 000000000..3428071d0 --- /dev/null +++ b/internal/api/client/customemojis/customemojisget.go @@ -0,0 +1,76 @@ +/* + 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 customemojis + +import ( + "net/http" + + "github.com/gin-gonic/gin" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/oauth" +) + +// CustomEmojisGETHandler swagger:operation GET /api/v1/custom_emojis customEmojisGet +// +// Get an array of custom emojis available on the instance. +// +// --- +// tags: +// - custom_emojis +// +// produces: +// - application/json +// +// security: +// - OAuth2 Bearer: +// - read:custom_emojis +// +// responses: +// '200': +// description: Array of custom emojis. +// schema: +// type: array +// items: +// "$ref": "#/definitions/emoji" +// '401': +// description: unauthorized +// '406': +// description: not acceptable +// '500': +// description: internal server error +func (m *Module) CustomEmojisGETHandler(c *gin.Context) { + if _, err := oauth.Authed(c, true, true, true, true); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + return + } + + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) + return + } + + emojis, errWithCode := m.processor.CustomEmojisGet(c) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + return + } + + c.JSON(http.StatusOK, emojis) +} diff --git a/internal/api/client/emoji/emojisget.go b/internal/api/client/emoji/emojisget.go deleted file mode 100644 index d41e5e7df..000000000 --- a/internal/api/client/emoji/emojisget.go +++ /dev/null @@ -1,58 +0,0 @@ -package emoji - -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// EmojisGETHandler swagger:operation GET /api/v1/custom_emojis customEmojisGet -// -// Get an array of custom emojis available on the instance. -// -// --- -// tags: -// - custom_emojis -// -// produces: -// - application/json -// -// security: -// - OAuth2 Bearer: -// - read:custom_emojis -// -// responses: -// '200': -// description: Array of custom emojis. -// schema: -// type: array -// items: -// "$ref": "#/definitions/emoji" -// '401': -// description: unauthorized -// '406': -// description: not acceptable -// '500': -// description: internal server error -func (m *Module) EmojisGETHandler(c *gin.Context) { - if _, err := oauth.Authed(c, true, true, true, true); 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 - } - - emojis, errWithCode := m.processor.CustomEmojisGet(c) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - c.JSON(http.StatusOK, emojis) -} diff --git a/internal/api/client/favourites/favourites.go b/internal/api/client/favourites/favourites.go index f310d6873..5abc85a27 100644 --- a/internal/api/client/favourites/favourites.go +++ b/internal/api/client/favourites/favourites.go @@ -21,14 +21,13 @@ package favourites import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( - // BasePath is the base URI path for serving favourites - BasePath = "/api/v1/favourites" + // BasePath is the base URI path for serving favourites, minus the 'api' prefix + BasePath = "/v1/favourites" // MaxIDKey is the url query for setting a max status ID to return MaxIDKey = "max_id" @@ -42,20 +41,16 @@ const ( LocalKey = "local" ) -// Module implements the ClientAPIModule interface for everything relating to viewing favourites type Module struct { processor processing.Processor } -// New returns a new favourites module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { 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.MethodGet, BasePath, m.FavouritesGETHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodGet, BasePath, m.FavouritesGETHandler) } diff --git a/internal/api/client/favourites/favourites_test.go b/internal/api/client/favourites/favourites_test.go index c84da6b32..050b72536 100644 --- a/internal/api/client/favourites/favourites_test.go +++ b/internal/api/client/favourites/favourites_test.go @@ -87,7 +87,7 @@ func (suite *FavouritesStandardTestSuite) SetupTest() { 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.favModule = favourites.New(suite.processor).(*favourites.Module) + suite.favModule = favourites.New(suite.processor) suite.NoError(suite.processor.Start()) } diff --git a/internal/api/client/favourites/favouritesget.go b/internal/api/client/favourites/favouritesget.go index 5ff032b9a..9b6bb715e 100644 --- a/internal/api/client/favourites/favouritesget.go +++ b/internal/api/client/favourites/favouritesget.go @@ -6,7 +6,7 @@ import ( "strconv" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -78,12 +78,12 @@ import ( func (m *Module) FavouritesGETHandler(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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } @@ -105,7 +105,7 @@ func (m *Module) FavouritesGETHandler(c *gin.Context) { i, err := strconv.ParseInt(limitString, 10, 32) if err != nil { err := fmt.Errorf("error parsing %s: %s", LimitKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } limit = int(i) @@ -113,7 +113,7 @@ func (m *Module) FavouritesGETHandler(c *gin.Context) { resp, errWithCode := m.processor.FavedTimelineGet(c.Request.Context(), authed, maxID, minID, limit) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/fileserver/fileserver.go b/internal/api/client/fileserver/fileserver.go deleted file mode 100644 index dcb54f986..000000000 --- a/internal/api/client/fileserver/fileserver.go +++ /dev/null @@ -1,64 +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 fileserver - -import ( - "fmt" - "net/http" - - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" - "github.com/superseriousbusiness/gotosocial/internal/uris" -) - -const ( - // FileServeBasePath forms the first part of the fileserver path. - FileServeBasePath = "/" + uris.FileserverPath - // AccountIDKey is the url key for account id (an account ulid) - AccountIDKey = "account_id" - // MediaTypeKey is the url key for media type (usually something like attachment or header etc) - MediaTypeKey = "media_type" - // MediaSizeKey is the url key for the desired media size--original/small/static - MediaSizeKey = "media_size" - // FileNameKey is the actual filename being sought. Will usually be a UUID then something like .jpeg - FileNameKey = "file_name" -) - -// FileServer implements the RESTAPIModule interface. -// The goal here is to serve requested media files if the gotosocial server is configured to use local storage. -type FileServer struct { - processor processing.Processor -} - -// New returns a new fileServer module -func New(processor processing.Processor) api.ClientModule { - return &FileServer{ - processor: processor, - } -} - -// Route satisfies the RESTAPIModule interface -func (m *FileServer) Route(s router.Router) error { - // something like "/fileserver/:account_id/:media_type/:media_size/:file_name" - fileServePath := fmt.Sprintf("%s/:%s/:%s/:%s/:%s", FileServeBasePath, AccountIDKey, MediaTypeKey, MediaSizeKey, FileNameKey) - s.AttachHandler(http.MethodGet, fileServePath, m.ServeFile) - s.AttachHandler(http.MethodHead, fileServePath, m.ServeFile) - return nil -} diff --git a/internal/api/client/fileserver/fileserver_test.go b/internal/api/client/fileserver/fileserver_test.go deleted file mode 100644 index f1fab5672..000000000 --- a/internal/api/client/fileserver/fileserver_test.go +++ /dev/null @@ -1,109 +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 fileserver_test - -import ( - "context" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/fileserver" - "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/log" - "github.com/superseriousbusiness/gotosocial/internal/media" - "github.com/superseriousbusiness/gotosocial/internal/messages" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/storage" - "github.com/superseriousbusiness/gotosocial/internal/typeutils" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type FileserverTestSuite struct { - // standard suite interfaces - suite.Suite - db db.DB - storage *storage.Driver - federator federation.Federator - tc typeutils.TypeConverter - processor processing.Processor - mediaManager media.Manager - oauthServer oauth.Server - emailSender email.Sender - - // 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 - - // item being tested - fileServer *fileserver.FileServer -} - -/* - TEST INFRASTRUCTURE -*/ - -func (suite *FileserverTestSuite) SetupSuite() { - testrig.InitTestConfig() - testrig.InitTestLog() - - fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1) - clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1) - - suite.db = testrig.NewTestDB() - suite.storage = testrig.NewInMemoryStorage() - suite.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, testrig.NewTestMediaManager(suite.db, suite.storage), clientWorker, fedWorker) - suite.tc = testrig.NewTestTypeConverter(suite.db) - suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) - suite.oauthServer = testrig.NewTestOauthServer(suite.db) - - suite.fileServer = fileserver.New(suite.processor).(*fileserver.FileServer) -} - -func (suite *FileserverTestSuite) SetupTest() { - testrig.StandardDBSetup(suite.db, nil) - testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") - suite.testTokens = testrig.NewTestTokens() - suite.testClients = testrig.NewTestClients() - suite.testApplications = testrig.NewTestApplications() - suite.testUsers = testrig.NewTestUsers() - suite.testAccounts = testrig.NewTestAccounts() - suite.testAttachments = testrig.NewTestAttachments() -} - -func (suite *FileserverTestSuite) TearDownSuite() { - if err := suite.db.Stop(context.Background()); err != nil { - log.Panicf("error closing db connection: %s", err) - } -} - -func (suite *FileserverTestSuite) TearDownTest() { - testrig.StandardDBTeardown(suite.db) - testrig.StandardStorageTeardown(suite.storage) -} diff --git a/internal/api/client/fileserver/servefile.go b/internal/api/client/fileserver/servefile.go deleted file mode 100644 index d2328a5fc..000000000 --- a/internal/api/client/fileserver/servefile.go +++ /dev/null @@ -1,135 +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 fileserver - -import ( - "bytes" - "fmt" - "io" - "net/http" - "strconv" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/log" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// ServeFile is for serving attachments, headers, and avatars to the requester from instance storage. -// -// Note: to mitigate scraping attempts, no information should be given out on a bad request except "404 page not found". -// Don't give away account ids or media ids or anything like that; callers shouldn't be able to infer anything. -func (m *FileServer) ServeFile(c *gin.Context) { - authed, err := oauth.Authed(c, false, false, false, false) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet) - return - } - - // We use request params to check what to pull out of the database/storage so check everything. A request URL should be formatted as follows: - // "https://example.org/fileserver/[ACCOUNT_ID]/[MEDIA_TYPE]/[MEDIA_SIZE]/[FILE_NAME]" - // "FILE_NAME" consists of two parts, the attachment's database id, a period, and the file extension. - accountID := c.Param(AccountIDKey) - if accountID == "" { - err := fmt.Errorf("missing %s from request", AccountIDKey) - api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet) - return - } - - mediaType := c.Param(MediaTypeKey) - if mediaType == "" { - err := fmt.Errorf("missing %s from request", MediaTypeKey) - api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet) - return - } - - mediaSize := c.Param(MediaSizeKey) - if mediaSize == "" { - err := fmt.Errorf("missing %s from request", MediaSizeKey) - api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet) - return - } - - fileName := c.Param(FileNameKey) - if fileName == "" { - err := fmt.Errorf("missing %s from request", FileNameKey) - api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet) - return - } - - content, errWithCode := m.processor.FileGet(c.Request.Context(), authed, &model.GetContentRequestForm{ - AccountID: accountID, - MediaType: mediaType, - MediaSize: mediaSize, - FileName: fileName, - }) - if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) - return - } - - defer func() { - // close content when we're done - if content.Content != nil { - if err := content.Content.Close(); err != nil { - log.Errorf("ServeFile: error closing readcloser: %s", err) - } - } - }() - - if content.URL != nil { - c.Redirect(http.StatusFound, content.URL.String()) - return - } - - // TODO: if the requester only accepts text/html we should try to serve them *something*. - // This is mostly needed because when sharing a link to a gts-hosted file on something like mastodon, the masto servers will - // attempt to look up the content to provide a preview of the link, and they ask for text/html. - format, err := api.NegotiateAccept(c, api.MIME(content.ContentType)) - if err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) - return - } - - // since we'll never host different files at the same - // URL (bc the ULIDs are generated per piece of media), - // it's sensible and safe to use a long cache here, so - // that clients don't keep fetching files over + over again - c.Header("Cache-Control", "max-age=604800") - - if c.Request.Method == http.MethodHead { - c.Header("Content-Type", format) - c.Header("Content-Length", strconv.FormatInt(content.ContentLength, 10)) - c.Status(http.StatusOK) - return - } - - // try to slurp the first few bytes to make sure we have something - b := bytes.NewBuffer(make([]byte, 0, 64)) - if _, err := io.CopyN(b, content.Content, 64); err != nil { - err = fmt.Errorf("ServeFile: error reading from content: %w", err) - api.ErrorHandler(c, gtserror.NewErrorNotFound(err, err.Error()), m.processor.InstanceGet) - return - } - - // we're good, return the slurped bytes + the rest of the content - c.DataFromReader(http.StatusOK, content.ContentLength, format, io.MultiReader(b, content.Content), nil) -} diff --git a/internal/api/client/fileserver/servefile_test.go b/internal/api/client/fileserver/servefile_test.go deleted file mode 100644 index 1ca0c60d6..000000000 --- a/internal/api/client/fileserver/servefile_test.go +++ /dev/null @@ -1,272 +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 fileserver_test - -import ( - "context" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/fileserver" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/media" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type ServeFileTestSuite struct { - FileserverTestSuite -} - -// GetFile is just a convenience function to save repetition in this test suite. -// It takes the required params to serve a file, calls the handler, and returns -// the http status code, the response headers, and the parsed body bytes. -func (suite *ServeFileTestSuite) GetFile( - accountID string, - mediaType media.Type, - mediaSize media.Size, - filename string, -) (code int, headers http.Header, body []byte) { - recorder := httptest.NewRecorder() - - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Request = httptest.NewRequest(http.MethodGet, "http://localhost:8080/whatever", nil) - ctx.Request.Header.Set("accept", "*/*") - ctx.AddParam(fileserver.AccountIDKey, accountID) - ctx.AddParam(fileserver.MediaTypeKey, string(mediaType)) - ctx.AddParam(fileserver.MediaSizeKey, string(mediaSize)) - ctx.AddParam(fileserver.FileNameKey, filename) - - suite.fileServer.ServeFile(ctx) - code = recorder.Code - headers = recorder.Result().Header - - var err error - body, err = ioutil.ReadAll(recorder.Body) - if err != nil { - suite.FailNow(err.Error()) - } - - return -} - -// UncacheAttachment is a convenience function that uncaches the targetAttachment by -// removing its associated files from storage, and updating the database. -func (suite *ServeFileTestSuite) UncacheAttachment(targetAttachment *gtsmodel.MediaAttachment) { - ctx := context.Background() - - cached := false - targetAttachment.Cached = &cached - - if err := suite.db.UpdateByID(ctx, targetAttachment, targetAttachment.ID, "cached"); err != nil { - suite.FailNow(err.Error()) - } - if err := suite.storage.Delete(ctx, targetAttachment.File.Path); err != nil { - suite.FailNow(err.Error()) - } - if err := suite.storage.Delete(ctx, targetAttachment.Thumbnail.Path); err != nil { - suite.FailNow(err.Error()) - } -} - -func (suite *ServeFileTestSuite) TestServeOriginalLocalFileOK() { - targetAttachment := >smodel.MediaAttachment{} - *targetAttachment = *suite.testAttachments["admin_account_status_1_attachment_1"] - fileInStorage, err := suite.storage.Get(context.Background(), targetAttachment.File.Path) - if err != nil { - suite.FailNow(err.Error()) - } - - code, headers, body := suite.GetFile( - targetAttachment.AccountID, - media.TypeAttachment, - media.SizeOriginal, - targetAttachment.ID+".jpeg", - ) - - suite.Equal(http.StatusOK, code) - suite.Equal("image/jpeg", headers.Get("content-type")) - suite.Equal(fileInStorage, body) -} - -func (suite *ServeFileTestSuite) TestServeSmallLocalFileOK() { - targetAttachment := >smodel.MediaAttachment{} - *targetAttachment = *suite.testAttachments["admin_account_status_1_attachment_1"] - fileInStorage, err := suite.storage.Get(context.Background(), targetAttachment.Thumbnail.Path) - if err != nil { - suite.FailNow(err.Error()) - } - - code, headers, body := suite.GetFile( - targetAttachment.AccountID, - media.TypeAttachment, - media.SizeSmall, - targetAttachment.ID+".jpeg", - ) - - suite.Equal(http.StatusOK, code) - suite.Equal("image/jpeg", headers.Get("content-type")) - suite.Equal(fileInStorage, body) -} - -func (suite *ServeFileTestSuite) TestServeOriginalRemoteFileOK() { - targetAttachment := >smodel.MediaAttachment{} - *targetAttachment = *suite.testAttachments["remote_account_1_status_1_attachment_1"] - fileInStorage, err := suite.storage.Get(context.Background(), targetAttachment.File.Path) - if err != nil { - suite.FailNow(err.Error()) - } - - code, headers, body := suite.GetFile( - targetAttachment.AccountID, - media.TypeAttachment, - media.SizeOriginal, - targetAttachment.ID+".jpeg", - ) - - suite.Equal(http.StatusOK, code) - suite.Equal("image/jpeg", headers.Get("content-type")) - suite.Equal(fileInStorage, body) -} - -func (suite *ServeFileTestSuite) TestServeSmallRemoteFileOK() { - targetAttachment := >smodel.MediaAttachment{} - *targetAttachment = *suite.testAttachments["remote_account_1_status_1_attachment_1"] - fileInStorage, err := suite.storage.Get(context.Background(), targetAttachment.Thumbnail.Path) - if err != nil { - suite.FailNow(err.Error()) - } - - code, headers, body := suite.GetFile( - targetAttachment.AccountID, - media.TypeAttachment, - media.SizeSmall, - targetAttachment.ID+".jpeg", - ) - - suite.Equal(http.StatusOK, code) - suite.Equal("image/jpeg", headers.Get("content-type")) - suite.Equal(fileInStorage, body) -} - -func (suite *ServeFileTestSuite) TestServeOriginalRemoteFileRecache() { - targetAttachment := >smodel.MediaAttachment{} - *targetAttachment = *suite.testAttachments["remote_account_1_status_1_attachment_1"] - fileInStorage, err := suite.storage.Get(context.Background(), targetAttachment.File.Path) - if err != nil { - suite.FailNow(err.Error()) - } - - // uncache the attachment so we'll have to refetch it from the 'remote' instance - suite.UncacheAttachment(targetAttachment) - - code, headers, body := suite.GetFile( - targetAttachment.AccountID, - media.TypeAttachment, - media.SizeOriginal, - targetAttachment.ID+".jpeg", - ) - - suite.Equal(http.StatusOK, code) - suite.Equal("image/jpeg", headers.Get("content-type")) - suite.Equal(fileInStorage, body) -} - -func (suite *ServeFileTestSuite) TestServeSmallRemoteFileRecache() { - targetAttachment := >smodel.MediaAttachment{} - *targetAttachment = *suite.testAttachments["remote_account_1_status_1_attachment_1"] - fileInStorage, err := suite.storage.Get(context.Background(), targetAttachment.Thumbnail.Path) - if err != nil { - suite.FailNow(err.Error()) - } - - // uncache the attachment so we'll have to refetch it from the 'remote' instance - suite.UncacheAttachment(targetAttachment) - - code, headers, body := suite.GetFile( - targetAttachment.AccountID, - media.TypeAttachment, - media.SizeSmall, - targetAttachment.ID+".jpeg", - ) - - suite.Equal(http.StatusOK, code) - suite.Equal("image/jpeg", headers.Get("content-type")) - suite.Equal(fileInStorage, body) -} - -func (suite *ServeFileTestSuite) TestServeOriginalRemoteFileRecacheNotFound() { - targetAttachment := >smodel.MediaAttachment{} - *targetAttachment = *suite.testAttachments["remote_account_1_status_1_attachment_1"] - - // uncache the attachment *and* set the remote URL to something that will return a 404 - suite.UncacheAttachment(targetAttachment) - targetAttachment.RemoteURL = "http://nothing.at.this.url/weeeeeeeee" - if err := suite.db.UpdateByID(context.Background(), targetAttachment, targetAttachment.ID, "remote_url"); err != nil { - suite.FailNow(err.Error()) - } - - code, _, _ := suite.GetFile( - targetAttachment.AccountID, - media.TypeAttachment, - media.SizeOriginal, - targetAttachment.ID+".jpeg", - ) - - suite.Equal(http.StatusNotFound, code) -} - -func (suite *ServeFileTestSuite) TestServeSmallRemoteFileRecacheNotFound() { - targetAttachment := >smodel.MediaAttachment{} - *targetAttachment = *suite.testAttachments["remote_account_1_status_1_attachment_1"] - - // uncache the attachment *and* set the remote URL to something that will return a 404 - suite.UncacheAttachment(targetAttachment) - targetAttachment.RemoteURL = "http://nothing.at.this.url/weeeeeeeee" - if err := suite.db.UpdateByID(context.Background(), targetAttachment, targetAttachment.ID, "remote_url"); err != nil { - suite.FailNow(err.Error()) - } - - code, _, _ := suite.GetFile( - targetAttachment.AccountID, - media.TypeAttachment, - media.SizeSmall, - targetAttachment.ID+".jpeg", - ) - - suite.Equal(http.StatusNotFound, code) -} - -// Callers trying to get some random-ass file that doesn't exist should just get a 404 -func (suite *ServeFileTestSuite) TestServeFileNotFound() { - code, _, _ := suite.GetFile( - "01GMMY4G9B0QEG0PQK5Q5JGJWZ", - media.TypeAttachment, - media.SizeOriginal, - "01GMMY68Y7E5DJ3CA3Y9SS8524.jpeg", - ) - - suite.Equal(http.StatusNotFound, code) -} - -func TestServeFileTestSuite(t *testing.T) { - suite.Run(t, new(ServeFileTestSuite)) -} diff --git a/internal/api/client/filter/filter.go b/internal/api/client/filters/filter.go index cf801e0a5..bdfd89ffe 100644 --- a/internal/api/client/filter/filter.go +++ b/internal/api/client/filters/filter.go @@ -21,30 +21,25 @@ package filter import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( - // BasePath is the base path for serving the filter API - BasePath = "/api/v1/filters" + // BasePath is the base path for serving the filters API, minus the 'api' prefix + BasePath = "/v1/filters" ) -// Module implements the ClientAPIModule interface for every related to filters type Module struct { processor processing.Processor } -// New returns a new filter module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { 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.MethodGet, BasePath, m.FiltersGETHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodGet, BasePath, m.FiltersGETHandler) } diff --git a/internal/api/client/filter/filtersget.go b/internal/api/client/filters/filtersget.go index 8e0a0bb34..71d6cac3e 100644 --- a/internal/api/client/filter/filtersget.go +++ b/internal/api/client/filters/filtersget.go @@ -4,7 +4,7 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -12,12 +12,12 @@ import ( // FiltersGETHandler returns a list of filters set by/for the authed account func (m *Module) FiltersGETHandler(c *gin.Context) { if _, err := oauth.Authed(c, true, true, true, true); err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } diff --git a/internal/api/client/followrequest/authorize.go b/internal/api/client/followrequests/authorize.go index a5a392f76..d30bb979f 100644 --- a/internal/api/client/followrequest/authorize.go +++ b/internal/api/client/followrequests/authorize.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package followrequest +package followrequests import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -72,25 +72,25 @@ import ( func (m *Module) FollowRequestAuthorizePOSTHandler(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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } originAccountID := c.Param(IDKey) if originAccountID == "" { err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } relationship, errWithCode := m.processor.FollowRequestAccept(c.Request.Context(), authed, originAccountID) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/followrequest/authorize_test.go b/internal/api/client/followrequests/authorize_test.go index 693380d91..048c462c7 100644 --- a/internal/api/client/followrequest/authorize_test.go +++ b/internal/api/client/followrequests/authorize_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package followrequest_test +package followrequests_test import ( "context" @@ -30,7 +30,7 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest" + "github.com/superseriousbusiness/gotosocial/internal/api/client/followrequests" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) @@ -60,7 +60,7 @@ func (suite *AuthorizeTestSuite) TestAuthorize() { ctx.Params = gin.Params{ gin.Param{ - Key: followrequest.IDKey, + Key: followrequests.IDKey, Value: requestingAccount.ID, }, } @@ -90,7 +90,7 @@ func (suite *AuthorizeTestSuite) TestAuthorizeNoFR() { ctx.Params = gin.Params{ gin.Param{ - Key: followrequest.IDKey, + Key: followrequests.IDKey, Value: requestingAccount.ID, }, } diff --git a/internal/api/client/followrequest/followrequest.go b/internal/api/client/followrequests/followrequest.go index a511d7226..d9d241e63 100644 --- a/internal/api/client/followrequest/followrequest.go +++ b/internal/api/client/followrequests/followrequest.go @@ -16,21 +16,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package followrequest +package followrequests import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( // IDKey is for account IDs IDKey = "id" - // BasePath is the base path for serving the follow request API - BasePath = "/api/v1/follow_requests" + // BasePath is the base path for serving the follow request API, minus the 'api' prefix + BasePath = "/v1/follow_requests" // BasePathWithID is just the base path with the ID key in it. // Use this anywhere you need to know the ID of the account that owns the follow request being queried. BasePathWithID = BasePath + "/:" + IDKey @@ -40,22 +39,18 @@ const ( RejectPath = BasePathWithID + "/reject" ) -// Module implements the ClientAPIModule interface type Module struct { processor processing.Processor } -// New returns a new follow request module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { 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.MethodGet, BasePath, m.FollowRequestGETHandler) - r.AttachHandler(http.MethodPost, AuthorizePath, m.FollowRequestAuthorizePOSTHandler) - r.AttachHandler(http.MethodPost, RejectPath, m.FollowRequestRejectPOSTHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodGet, BasePath, m.FollowRequestGETHandler) + attachHandler(http.MethodPost, AuthorizePath, m.FollowRequestAuthorizePOSTHandler) + attachHandler(http.MethodPost, RejectPath, m.FollowRequestRejectPOSTHandler) } diff --git a/internal/api/client/followrequest/followrequest_test.go b/internal/api/client/followrequests/followrequest_test.go index ca00ea054..c8036cd24 100644 --- a/internal/api/client/followrequest/followrequest_test.go +++ b/internal/api/client/followrequests/followrequest_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package followrequest_test +package followrequests_test import ( "bytes" @@ -25,7 +25,7 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest" + "github.com/superseriousbusiness/gotosocial/internal/api/client/followrequests" "github.com/superseriousbusiness/gotosocial/internal/concurrency" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -59,7 +59,7 @@ type FollowRequestStandardTestSuite struct { testStatuses map[string]*gtsmodel.Status // module being tested - followRequestModule *followrequest.Module + followRequestModule *followrequests.Module } func (suite *FollowRequestStandardTestSuite) SetupSuite() { @@ -85,7 +85,7 @@ func (suite *FollowRequestStandardTestSuite) SetupTest() { 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.followRequestModule = followrequest.New(suite.processor).(*followrequest.Module) + suite.followRequestModule = followrequests.New(suite.processor) testrig.StandardDBSetup(suite.db, nil) testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") diff --git a/internal/api/client/followrequest/get.go b/internal/api/client/followrequests/get.go index 8a2be3686..1153f0f4b 100644 --- a/internal/api/client/followrequest/get.go +++ b/internal/api/client/followrequests/get.go @@ -16,13 +16,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package followrequest +package followrequests import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -74,18 +74,18 @@ import ( func (m *Module) FollowRequestGETHandler(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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } accts, errWithCode := m.processor.FollowRequestsGet(c.Request.Context(), authed) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/followrequest/get_test.go b/internal/api/client/followrequests/get_test.go index c9b72a35b..d4c9da0a1 100644 --- a/internal/api/client/followrequest/get_test.go +++ b/internal/api/client/followrequests/get_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package followrequest_test +package followrequests_test import ( "context" diff --git a/internal/api/client/followrequest/reject.go b/internal/api/client/followrequests/reject.go index 717dbf4dd..782f932cd 100644 --- a/internal/api/client/followrequest/reject.go +++ b/internal/api/client/followrequests/reject.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package followrequest +package followrequests import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -70,25 +70,25 @@ import ( func (m *Module) FollowRequestRejectPOSTHandler(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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } originAccountID := c.Param(IDKey) if originAccountID == "" { err := errors.New("no account id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } relationship, errWithCode := m.processor.FollowRequestReject(c.Request.Context(), authed, originAccountID) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/followrequest/reject_test.go b/internal/api/client/followrequests/reject_test.go index 94c646ddc..cea42829d 100644 --- a/internal/api/client/followrequest/reject_test.go +++ b/internal/api/client/followrequests/reject_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package followrequest_test +package followrequests_test import ( "context" @@ -30,7 +30,7 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest" + "github.com/superseriousbusiness/gotosocial/internal/api/client/followrequests" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) @@ -60,7 +60,7 @@ func (suite *RejectTestSuite) TestReject() { ctx.Params = gin.Params{ gin.Param{ - Key: followrequest.IDKey, + Key: followrequests.IDKey, Value: requestingAccount.ID, }, } diff --git a/internal/api/client/instance/instance.go b/internal/api/client/instance/instance.go index 16ff7c9f9..101e8cea4 100644 --- a/internal/api/client/instance/instance.go +++ b/internal/api/client/instance/instance.go @@ -21,36 +21,31 @@ package instance import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( - // InstanceInformationPath is for serving instance info requests - InstanceInformationPath = "api/v1/instance" + // InstanceInformationPath is for serving instance info requests, minus the 'api' prefix. + InstanceInformationPath = "/v1/instance" // InstancePeersPath is for serving instance peers requests. InstancePeersPath = InstanceInformationPath + "/peers" // PeersFilterKey is used to provide filters to /api/v1/instance/peers PeersFilterKey = "filter" ) -// Module implements the ClientModule interface type Module struct { processor processing.Processor } -// New returns a new instance information module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { return &Module{ processor: processor, } } -// Route satisfies the ClientModule interface -func (m *Module) Route(s router.Router) error { - s.AttachHandler(http.MethodGet, InstanceInformationPath, m.InstanceInformationGETHandler) - s.AttachHandler(http.MethodPatch, InstanceInformationPath, m.InstanceUpdatePATCHHandler) - s.AttachHandler(http.MethodGet, InstancePeersPath, m.InstancePeersGETHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodGet, InstanceInformationPath, m.InstanceInformationGETHandler) + attachHandler(http.MethodPatch, InstanceInformationPath, m.InstanceUpdatePATCHHandler) + attachHandler(http.MethodGet, InstancePeersPath, m.InstancePeersGETHandler) } diff --git a/internal/api/client/instance/instance_test.go b/internal/api/client/instance/instance_test.go index 26f29027d..33efbc847 100644 --- a/internal/api/client/instance/instance_test.go +++ b/internal/api/client/instance/instance_test.go @@ -88,7 +88,7 @@ func (suite *InstanceStandardTestSuite) SetupTest() { suite.sentEmails = make(map[string]string) suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails) suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) - suite.instanceModule = instance.New(suite.processor).(*instance.Module) + suite.instanceModule = instance.New(suite.processor) testrig.StandardDBSetup(suite.db, nil) testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") } diff --git a/internal/api/client/instance/instanceget.go b/internal/api/client/instance/instanceget.go index bcedf398b..dfb8330ff 100644 --- a/internal/api/client/instance/instanceget.go +++ b/internal/api/client/instance/instanceget.go @@ -21,7 +21,7 @@ package instance import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/gtserror" @@ -49,14 +49,14 @@ import ( // '500': // description: internal error func (m *Module) InstanceInformationGETHandler(c *gin.Context) { - if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { - api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), config.GetHost()) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/instance/instancepatch.go b/internal/api/client/instance/instancepatch.go index d4fa8ca5d..891ce8e38 100644 --- a/internal/api/client/instance/instancepatch.go +++ b/internal/api/client/instance/instancepatch.go @@ -24,8 +24,8 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -130,42 +130,42 @@ import ( func (m *Module) InstanceUpdatePATCHHandler(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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } if !*authed.User.Admin { err := errors.New("user is not an admin so cannot update instance settings") - api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) return } - form := &model.InstanceSettingsUpdateRequest{} + form := &apimodel.InstanceSettingsUpdateRequest{} if err := c.ShouldBind(&form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if err := validateInstanceUpdate(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } i, errWithCode := m.processor.InstancePatch(c.Request.Context(), form) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } c.JSON(http.StatusOK, i) } -func validateInstanceUpdate(form *model.InstanceSettingsUpdateRequest) error { +func validateInstanceUpdate(form *apimodel.InstanceSettingsUpdateRequest) error { if form.Title == nil && form.ContactUsername == nil && form.ContactEmail == nil && diff --git a/internal/api/client/instance/instancepeersget.go b/internal/api/client/instance/instancepeersget.go index f7d05acdc..de6e40e7c 100644 --- a/internal/api/client/instance/instancepeersget.go +++ b/internal/api/client/instance/instancepeersget.go @@ -23,7 +23,7 @@ import ( "net/http" "strings" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -101,12 +101,12 @@ import ( func (m *Module) InstancePeersGETHandler(c *gin.Context) { authed, err := oauth.Authed(c, false, false, false, false) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } @@ -124,7 +124,7 @@ func (m *Module) InstancePeersGETHandler(c *gin.Context) { includeOpen = true default: err := fmt.Errorf("filter %s not recognized; accepted values are 'open', 'suspended'", trimmed) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } } @@ -138,7 +138,7 @@ func (m *Module) InstancePeersGETHandler(c *gin.Context) { data, errWithCode := m.processor.InstancePeersGet(c.Request.Context(), authed, includeSuspended, includeOpen, flat) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/list/listsgets.go b/internal/api/client/list/listsgets.go deleted file mode 100644 index 246a1216a..000000000 --- a/internal/api/client/list/listsgets.go +++ /dev/null @@ -1,25 +0,0 @@ -package list - -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// ListsGETHandler returns a list of lists created by/for the authed account -func (m *Module) ListsGETHandler(c *gin.Context) { - if _, err := oauth.Authed(c, true, true, true, true); 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 - } - - c.JSON(http.StatusOK, []string{}) -} diff --git a/internal/api/client/list/list.go b/internal/api/client/lists/list.go index c64ada43e..c14917b98 100644 --- a/internal/api/client/list/list.go +++ b/internal/api/client/lists/list.go @@ -16,35 +16,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package list +package lists import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( - // BasePath is the base path for serving the lists API - BasePath = "/api/v1/lists" + // BasePath is the base path for serving the lists API, minus the 'api' prefix + BasePath = "/v1/lists" ) -// Module implements the ClientAPIModule interface for everything related to lists type Module struct { processor processing.Processor } -// New returns a new list module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { 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.MethodGet, BasePath, m.ListsGETHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodGet, BasePath, m.ListsGETHandler) } diff --git a/internal/api/client/lists/listsgets.go b/internal/api/client/lists/listsgets.go new file mode 100644 index 000000000..a4e5cbefa --- /dev/null +++ b/internal/api/client/lists/listsgets.go @@ -0,0 +1,44 @@ +/* + 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 lists + +import ( + "net/http" + + "github.com/gin-gonic/gin" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/oauth" +) + +// ListsGETHandler returns a list of lists created by/for the authed account +func (m *Module) ListsGETHandler(c *gin.Context) { + if _, err := oauth.Authed(c, true, true, true, true); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + return + } + + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) + return + } + + // todo: implement this; currently it's a no-op + c.JSON(http.StatusOK, []string{}) +} diff --git a/internal/api/client/media/media.go b/internal/api/client/media/media.go index 87cc2f091..889a4f3df 100644 --- a/internal/api/client/media/media.go +++ b/internal/api/client/media/media.go @@ -21,34 +21,31 @@ package media import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( - IDKey = "id" // IDKey is the key for media attachment IDs - APIVersionKey = "api_version" // APIVersionKey is the key for which version of the API to use (v1 or v2) - BasePathWithAPIVersion = "/api/:" + APIVersionKey + "/media" // BasePathWithAPIVersion is the base API path for making media requests through v1 or v2 of the api (for mastodon API compatibility) - BasePathWithIDV1 = "/api/v1/media/:" + IDKey // BasePathWithID corresponds to a media attachment with the given ID + IDKey = "id" // IDKey is the key for media attachment IDs + APIVersionKey = "api_version" // APIVersionKey is the key for which version of the API to use (v1 or v2) + APIv1 = "v1" // APIV1 corresponds to version 1 of the api + APIv2 = "v2" // APIV2 corresponds to version 2 of the api + BasePath = "/:" + APIVersionKey + "/media" // BasePath is the base API path for making media requests through v1 or v2 of the api (for mastodon API compatibility) + AttachmentWithID = BasePath + "/:" + IDKey // BasePathWithID corresponds to a media attachment with the given ID ) -// Module implements the ClientAPIModule interface for media type Module struct { processor processing.Processor } -// New returns a new auth module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { return &Module{ processor: processor, } } -// Route satisfies the RESTAPIModule interface -func (m *Module) Route(s router.Router) error { - s.AttachHandler(http.MethodPost, BasePathWithAPIVersion, m.MediaCreatePOSTHandler) - s.AttachHandler(http.MethodGet, BasePathWithIDV1, m.MediaGETHandler) - s.AttachHandler(http.MethodPut, BasePathWithIDV1, m.MediaPUTHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodPost, BasePath, m.MediaCreatePOSTHandler) + attachHandler(http.MethodGet, AttachmentWithID, m.MediaGETHandler) + attachHandler(http.MethodPut, AttachmentWithID, m.MediaPUTHandler) } diff --git a/internal/api/client/media/mediacreate.go b/internal/api/client/media/mediacreate.go index db8b2ea56..7e29b2bb3 100644 --- a/internal/api/client/media/mediacreate.go +++ b/internal/api/client/media/mediacreate.go @@ -24,8 +24,8 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -94,42 +94,42 @@ import ( // '500': // description: internal server error func (m *Module) MediaCreatePOSTHandler(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) + apiVersion := c.Param(APIVersionKey) + if apiVersion != APIv1 && apiVersion != APIv2 { + err := errors.New("api version must be one of v1 or v2 for this path") + apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(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) + authed, err := oauth.Authed(c, true, true, true, true) + if err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } - apiVersion := c.Param(APIVersionKey) - if apiVersion != "v1" && apiVersion != "v2" { - err := errors.New("api version must be one of v1 or v2") - api.ErrorHandler(c, gtserror.NewErrorNotFound(err, err.Error()), m.processor.InstanceGet) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } - form := &model.AttachmentRequest{} + form := &apimodel.AttachmentRequest{} if err := c.ShouldBind(&form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if err := validateCreateMedia(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } apiAttachment, errWithCode := m.processor.MediaCreate(c.Request.Context(), authed, form) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } - if apiVersion == "v2" { + if apiVersion == APIv2 { // the mastodon v2 media API specifies that the URL should be null // and that the client should call /api/v1/media/:id to get the URL // @@ -141,7 +141,7 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) { c.JSON(http.StatusOK, apiAttachment) } -func validateCreateMedia(form *model.AttachmentRequest) error { +func validateCreateMedia(form *apimodel.AttachmentRequest) error { // check there actually is a file attached and it's not size 0 if form.File == nil { return errors.New("no attachment given") diff --git a/internal/api/client/media/mediacreate_test.go b/internal/api/client/media/mediacreate_test.go index 2f6fb12a4..9e787b4b9 100644 --- a/internal/api/client/media/mediacreate_test.go +++ b/internal/api/client/media/mediacreate_test.go @@ -30,10 +30,9 @@ import ( "net/http/httptest" "testing" - "github.com/gin-gonic/gin" "github.com/stretchr/testify/suite" mediamodule "github.com/superseriousbusiness/gotosocial/internal/api/client/media" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/concurrency" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -96,7 +95,7 @@ func (suite *MediaCreateTestSuite) SetupSuite() { suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) // setup module being tested - suite.mediaModule = mediamodule.New(suite.processor).(*mediamodule.Module) + suite.mediaModule = mediamodule.New(suite.processor) } func (suite *MediaCreateTestSuite) TearDownSuite() { @@ -158,12 +157,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() { ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v1/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting ctx.Request.Header.Set("Content-Type", w.FormDataContentType()) ctx.Request.Header.Set("accept", "application/json") - ctx.Params = gin.Params{ - gin.Param{ - Key: mediamodule.APIVersionKey, - Value: "v1", - }, - } + ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv1) // do the actual request suite.mediaModule.MediaCreatePOSTHandler(ctx) @@ -188,26 +182,26 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() { suite.NoError(err) fmt.Println(string(b)) - attachmentReply := &model.Attachment{} + attachmentReply := &apimodel.Attachment{} err = json.Unmarshal(b, attachmentReply) suite.NoError(err) suite.Equal("this is a test image -- a cool background from somewhere", *attachmentReply.Description) suite.Equal("image", attachmentReply.Type) - suite.EqualValues(model.MediaMeta{ - Original: model.MediaDimensions{ + suite.EqualValues(apimodel.MediaMeta{ + Original: apimodel.MediaDimensions{ Width: 1920, Height: 1080, Size: "1920x1080", Aspect: 1.7777778, }, - Small: model.MediaDimensions{ + Small: apimodel.MediaDimensions{ Width: 512, Height: 288, Size: "512x288", Aspect: 1.7777778, }, - Focus: model.MediaFocus{ + Focus: apimodel.MediaFocus{ X: -0.5, Y: 0.5, }, @@ -252,12 +246,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessfulV2() { ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v2/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting ctx.Request.Header.Set("Content-Type", w.FormDataContentType()) ctx.Request.Header.Set("accept", "application/json") - ctx.Params = gin.Params{ - gin.Param{ - Key: mediamodule.APIVersionKey, - Value: "v2", - }, - } + ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv2) // do the actual request suite.mediaModule.MediaCreatePOSTHandler(ctx) @@ -282,26 +271,26 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessfulV2() { suite.NoError(err) fmt.Println(string(b)) - attachmentReply := &model.Attachment{} + attachmentReply := &apimodel.Attachment{} err = json.Unmarshal(b, attachmentReply) suite.NoError(err) suite.Equal("this is a test image -- a cool background from somewhere", *attachmentReply.Description) suite.Equal("image", attachmentReply.Type) - suite.EqualValues(model.MediaMeta{ - Original: model.MediaDimensions{ + suite.EqualValues(apimodel.MediaMeta{ + Original: apimodel.MediaDimensions{ Width: 1920, Height: 1080, Size: "1920x1080", Aspect: 1.7777778, }, - Small: model.MediaDimensions{ + Small: apimodel.MediaDimensions{ Width: 512, Height: 288, Size: "512x288", Aspect: 1.7777778, }, - Focus: model.MediaFocus{ + Focus: apimodel.MediaFocus{ X: -0.5, Y: 0.5, }, @@ -342,12 +331,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateLongDescription() { ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v1/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting ctx.Request.Header.Set("Content-Type", w.FormDataContentType()) ctx.Request.Header.Set("accept", "application/json") - ctx.Params = gin.Params{ - gin.Param{ - Key: mediamodule.APIVersionKey, - Value: "v1", - }, - } + ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv1) // do the actual request suite.mediaModule.MediaCreatePOSTHandler(ctx) @@ -388,12 +372,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateTooShortDescription() { ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v1/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting ctx.Request.Header.Set("Content-Type", w.FormDataContentType()) ctx.Request.Header.Set("accept", "application/json") - ctx.Params = gin.Params{ - gin.Param{ - Key: mediamodule.APIVersionKey, - Value: "v1", - }, - } + ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv1) // do the actual request suite.mediaModule.MediaCreatePOSTHandler(ctx) diff --git a/internal/api/client/media/mediaget.go b/internal/api/client/media/mediaget.go index fd232c4c7..b22c8e79c 100644 --- a/internal/api/client/media/mediaget.go +++ b/internal/api/client/media/mediaget.go @@ -23,7 +23,7 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -67,27 +67,33 @@ import ( // '500': // description: internal server error func (m *Module) MediaGETHandler(c *gin.Context) { + if apiVersion := c.Param(APIVersionKey); apiVersion != APIv1 { + err := errors.New("api version must be one v1 for this path") + apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(err, err.Error()), m.processor.InstanceGet) + return + } + authed, err := oauth.Authed(c, true, true, true, true) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } attachmentID := c.Param(IDKey) if attachmentID == "" { err := errors.New("no attachment id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } attachment, errWithCode := m.processor.MediaGet(c.Request.Context(), authed, attachmentID) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/media/mediaupdate.go b/internal/api/client/media/mediaupdate.go index 438eaca23..9cfd8a5f1 100644 --- a/internal/api/client/media/mediaupdate.go +++ b/internal/api/client/media/mediaupdate.go @@ -24,8 +24,8 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -99,45 +99,51 @@ import ( // '500': // description: internal server error func (m *Module) MediaPUTHandler(c *gin.Context) { + if apiVersion := c.Param(APIVersionKey); apiVersion != APIv1 { + err := errors.New("api version must be one v1 for this path") + apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(err, err.Error()), m.processor.InstanceGet) + return + } + authed, err := oauth.Authed(c, true, true, true, true) if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } attachmentID := c.Param(IDKey) if attachmentID == "" { err := errors.New("no attachment id specified") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } - form := &model.AttachmentUpdateRequest{} + form := &apimodel.AttachmentUpdateRequest{} if err := c.ShouldBind(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if err := validateUpdateMedia(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } attachment, errWithCode := m.processor.MediaUpdate(c.Request.Context(), authed, attachmentID, form) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } c.JSON(http.StatusOK, attachment) } -func validateUpdateMedia(form *model.AttachmentUpdateRequest) error { +func validateUpdateMedia(form *apimodel.AttachmentUpdateRequest) error { minDescriptionChars := config.GetMediaDescriptionMinChars() maxDescriptionChars := config.GetMediaDescriptionMaxChars() diff --git a/internal/api/client/media/mediaupdate_test.go b/internal/api/client/media/mediaupdate_test.go index e5abb0a91..bcf9a4dfe 100644 --- a/internal/api/client/media/mediaupdate_test.go +++ b/internal/api/client/media/mediaupdate_test.go @@ -28,10 +28,9 @@ import ( "net/http/httptest" "testing" - "github.com/gin-gonic/gin" "github.com/stretchr/testify/suite" mediamodule "github.com/superseriousbusiness/gotosocial/internal/api/client/media" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/concurrency" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -94,7 +93,7 @@ func (suite *MediaUpdateTestSuite) SetupSuite() { suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) // setup module being tested - suite.mediaModule = mediamodule.New(suite.processor).(*mediamodule.Module) + suite.mediaModule = mediamodule.New(suite.processor) } func (suite *MediaUpdateTestSuite) TearDownSuite() { @@ -148,12 +147,8 @@ func (suite *MediaUpdateTestSuite) TestUpdateImage() { ctx.Request = httptest.NewRequest(http.MethodPut, fmt.Sprintf("http://localhost:8080/api/v1/media/%s", toUpdate.ID), bytes.NewReader(buf.Bytes())) // the endpoint we're hitting ctx.Request.Header.Set("Content-Type", w.FormDataContentType()) ctx.Request.Header.Set("accept", "application/json") - ctx.Params = gin.Params{ - gin.Param{ - Key: mediamodule.IDKey, - Value: toUpdate.ID, - }, - } + ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv1) + ctx.AddParam(mediamodule.IDKey, toUpdate.ID) // do the actual request suite.mediaModule.MediaPUTHandler(ctx) @@ -167,17 +162,17 @@ func (suite *MediaUpdateTestSuite) TestUpdateImage() { suite.NoError(err) // reply should be an attachment - attachmentReply := &model.Attachment{} + attachmentReply := &apimodel.Attachment{} err = json.Unmarshal(b, attachmentReply) suite.NoError(err) // the reply should contain the updated fields suite.Equal("new description!", *attachmentReply.Description) suite.EqualValues("image", attachmentReply.Type) - suite.EqualValues(model.MediaMeta{ - Original: model.MediaDimensions{Width: 800, Height: 450, FrameRate: "", Duration: 0, Bitrate: 0, Size: "800x450", Aspect: 1.7777778}, - Small: model.MediaDimensions{Width: 256, Height: 144, FrameRate: "", Duration: 0, Bitrate: 0, Size: "256x144", Aspect: 1.7777778}, - Focus: model.MediaFocus{X: -0.1, Y: 0.3}, + suite.EqualValues(apimodel.MediaMeta{ + Original: apimodel.MediaDimensions{Width: 800, Height: 450, FrameRate: "", Duration: 0, Bitrate: 0, Size: "800x450", Aspect: 1.7777778}, + Small: apimodel.MediaDimensions{Width: 256, Height: 144, FrameRate: "", Duration: 0, Bitrate: 0, Size: "256x144", Aspect: 1.7777778}, + Focus: apimodel.MediaFocus{X: -0.1, Y: 0.3}, }, attachmentReply.Meta) suite.Equal(toUpdate.Blurhash, attachmentReply.Blurhash) suite.Equal(toUpdate.ID, attachmentReply.ID) @@ -213,12 +208,8 @@ func (suite *MediaUpdateTestSuite) TestUpdateImageShortDescription() { ctx.Request = httptest.NewRequest(http.MethodPut, fmt.Sprintf("http://localhost:8080/api/v1/media/%s", toUpdate.ID), bytes.NewReader(buf.Bytes())) // the endpoint we're hitting ctx.Request.Header.Set("Content-Type", w.FormDataContentType()) ctx.Request.Header.Set("accept", "application/json") - ctx.Params = gin.Params{ - gin.Param{ - Key: mediamodule.IDKey, - Value: toUpdate.ID, - }, - } + ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv1) + ctx.AddParam(mediamodule.IDKey, toUpdate.ID) // do the actual request suite.mediaModule.MediaPUTHandler(ctx) diff --git a/internal/api/client/notification/notification.go b/internal/api/client/notifications/notifications.go index 6ade0b02f..235f0a678 100644 --- a/internal/api/client/notification/notification.go +++ b/internal/api/client/notifications/notifications.go @@ -16,21 +16,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package notification +package notifications import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( // IDKey is for notification UUIDs IDKey = "id" - // BasePath is the base path for serving the notification API - BasePath = "/api/v1/notifications" + // BasePath is the base path for serving the notification API, minus the 'api' prefix. + BasePath = "/v1/notifications" // BasePathWithID is just the base path with the ID key in it. // Use this anywhere you need to know the ID of the notification being queried. BasePathWithID = BasePath + "/:" + IDKey @@ -46,21 +45,17 @@ const ( SinceIDKey = "since_id" ) -// Module implements the ClientAPIModule interface for every related to posting/deleting/interacting with notifications type Module struct { processor processing.Processor } -// New returns a new notification module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { 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.MethodGet, BasePath, m.NotificationsGETHandler) - r.AttachHandler(http.MethodPost, BasePathWithClear, m.NotificationsClearPOSTHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodGet, BasePath, m.NotificationsGETHandler) + attachHandler(http.MethodPost, BasePathWithClear, m.NotificationsClearPOSTHandler) } diff --git a/internal/api/client/notification/notificationsclear.go b/internal/api/client/notifications/notificationsclear.go index b97371638..48c074504 100644 --- a/internal/api/client/notification/notificationsclear.go +++ b/internal/api/client/notifications/notificationsclear.go @@ -16,13 +16,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package notification +package notifications import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -61,18 +61,18 @@ import ( func (m *Module) NotificationsClearPOSTHandler(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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } errWithCode := m.processor.NotificationsClear(c.Request.Context(), authed) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/notification/notificationsget.go b/internal/api/client/notifications/notificationsget.go index d6b3f5162..09000d02a 100644 --- a/internal/api/client/notification/notificationsget.go +++ b/internal/api/client/notifications/notificationsget.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package notification +package notifications import ( "fmt" @@ -24,7 +24,7 @@ import ( "strconv" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -111,12 +111,12 @@ import ( func (m *Module) NotificationsGETHandler(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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } @@ -126,7 +126,7 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) { i, err := strconv.ParseInt(limitString, 10, 32) if err != nil { err := fmt.Errorf("error parsing %s: %s", LimitKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } limit = int(i) @@ -148,7 +148,7 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) { resp, errWithCode := m.processor.NotificationsGet(c.Request.Context(), authed, excludeTypes, limit, maxID, sinceID) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/search/search.go b/internal/api/client/search/search.go index 71370a6d5..bebe0bd61 100644 --- a/internal/api/client/search/search.go +++ b/internal/api/client/search/search.go @@ -21,17 +21,16 @@ package search import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( - // BasePathV1 is the base path for serving v1 of the search API - BasePathV1 = "/api/v1/search" + // BasePathV1 is the base path for serving v1 of the search API, minus the 'api' prefix + BasePathV1 = "/v1/search" - // BasePathV2 is the base path for serving v2 of the search API - BasePathV2 = "/api/v2/search" + // BasePathV2 is the base path for serving v2 of the search API, minus the 'api' prefix + BasePathV2 = "/v2/search" // AccountIDKey -- If provided, statuses returned will be authored only by this account AccountIDKey = "account_id" @@ -62,21 +61,17 @@ const ( TypeStatuses = "statuses" ) -// Module implements the ClientAPIModule interface for everything related to searching type Module struct { processor processing.Processor } -// New returns a new search module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { 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.MethodGet, BasePathV1, m.SearchGETHandler) - r.AttachHandler(http.MethodGet, BasePathV2, m.SearchGETHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodGet, BasePathV1, m.SearchGETHandler) + attachHandler(http.MethodGet, BasePathV2, m.SearchGETHandler) } diff --git a/internal/api/client/search/search_test.go b/internal/api/client/search/search_test.go index 11b5b80b2..3cb5e8377 100644 --- a/internal/api/client/search/search_test.go +++ b/internal/api/client/search/search_test.go @@ -84,7 +84,7 @@ func (suite *SearchStandardTestSuite) SetupTest() { suite.sentEmails = make(map[string]string) suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails) suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) - suite.searchModule = search.New(suite.processor).(*search.Module) + suite.searchModule = search.New(suite.processor) testrig.StandardDBSetup(suite.db, nil) testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") diff --git a/internal/api/client/search/searchget.go b/internal/api/client/search/searchget.go index 7026213ac..15786e6e3 100644 --- a/internal/api/client/search/searchget.go +++ b/internal/api/client/search/searchget.go @@ -25,8 +25,8 @@ import ( "strconv" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -66,12 +66,12 @@ import ( func (m *Module) SearchGETHandler(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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } @@ -82,7 +82,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) { excludeUnreviewed, err = strconv.ParseBool(excludeUnreviewedString) if err != nil { err := fmt.Errorf("error parsing %s: %s", ExcludeUnreviewedKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } } @@ -90,7 +90,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) { query := c.Query(QueryKey) if query == "" { err := errors.New("query parameter q was empty") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } @@ -101,7 +101,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) { resolve, err = strconv.ParseBool(resolveString) if err != nil { err := fmt.Errorf("error parsing %s: %s", ResolveKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } } @@ -112,7 +112,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) { i, err := strconv.ParseInt(limitString, 10, 32) if err != nil { err := fmt.Errorf("error parsing %s: %s", LimitKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } limit = int(i) @@ -130,7 +130,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) { i, err := strconv.ParseInt(offsetString, 10, 32) if err != nil { err := fmt.Errorf("error parsing %s: %s", OffsetKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } offset = int(i) @@ -143,12 +143,12 @@ func (m *Module) SearchGETHandler(c *gin.Context) { following, err = strconv.ParseBool(followingString) if err != nil { err := fmt.Errorf("error parsing %s: %s", FollowingKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } } - searchQuery := &model.SearchQuery{ + searchQuery := &apimodel.SearchQuery{ AccountID: c.Query(AccountIDKey), MaxID: c.Query(MaxIDKey), MinID: c.Query(MinIDKey), @@ -163,7 +163,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) { results, errWithCode := m.processor.SearchGet(c.Request.Context(), authed, searchQuery) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/status/status.go b/internal/api/client/statuses/status.go index dc32ae9b5..7f58e8c9d 100644 --- a/internal/api/client/status/status.go +++ b/internal/api/client/statuses/status.go @@ -16,31 +16,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status +package statuses 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" + // BasePath is the base path for serving the statuses API, minus the 'api' prefix + BasePath = "/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 @@ -69,55 +62,39 @@ const ( 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" + + // ContextPath is used for fetching context of posts + ContextPath = BasePathWithID + "/context" ) -// 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 { +func New(processor processing.Processor) *Module { 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) - } - } +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + // create / get / delete status + attachHandler(http.MethodPost, BasePath, m.StatusCreatePOSTHandler) + attachHandler(http.MethodGet, BasePathWithID, m.StatusGETHandler) + attachHandler(http.MethodDelete, BasePathWithID, m.StatusDELETEHandler) + + // fave stuff + attachHandler(http.MethodPost, FavouritePath, m.StatusFavePOSTHandler) + attachHandler(http.MethodPost, UnfavouritePath, m.StatusUnfavePOSTHandler) + attachHandler(http.MethodGet, FavouritedPath, m.StatusFavedByGETHandler) + + // reblog stuff + attachHandler(http.MethodPost, ReblogPath, m.StatusBoostPOSTHandler) + attachHandler(http.MethodPost, UnreblogPath, m.StatusUnboostPOSTHandler) + attachHandler(http.MethodGet, RebloggedPath, m.StatusBoostedByGETHandler) + attachHandler(http.MethodPost, BookmarkPath, m.StatusBookmarkPOSTHandler) + attachHandler(http.MethodPost, UnbookmarkPath, m.StatusUnbookmarkPOSTHandler) + + // context / status thread + attachHandler(http.MethodGet, ContextPath, m.StatusContextGETHandler) } diff --git a/internal/api/client/status/status_test.go b/internal/api/client/statuses/status_test.go index 7c3f094f2..0bf824fdb 100644 --- a/internal/api/client/status/status_test.go +++ b/internal/api/client/statuses/status_test.go @@ -16,11 +16,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status_test +package statuses_test import ( "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/status" + "github.com/superseriousbusiness/gotosocial/internal/api/client/statuses" "github.com/superseriousbusiness/gotosocial/internal/concurrency" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/email" @@ -56,7 +56,7 @@ type StatusStandardTestSuite struct { testFollows map[string]*gtsmodel.Follow // module being tested - statusModule *status.Module + statusModule *statuses.Module } func (suite *StatusStandardTestSuite) SetupSuite() { @@ -87,7 +87,7 @@ func (suite *StatusStandardTestSuite) SetupTest() { 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.statusModule = statuses.New(suite.processor) suite.NoError(suite.processor.Start()) } diff --git a/internal/api/client/status/statusbookmark.go b/internal/api/client/statuses/statusbookmark.go index 983becd72..4efa53528 100644 --- a/internal/api/client/status/statusbookmark.go +++ b/internal/api/client/statuses/statusbookmark.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status +package statuses import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -72,25 +72,25 @@ import ( 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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.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) + apiutil.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) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/status/statusbookmark_test.go b/internal/api/client/statuses/statusbookmark_test.go index d3da4f297..ba2de78e1 100644 --- a/internal/api/client/status/statusbookmark_test.go +++ b/internal/api/client/statuses/statusbookmark_test.go @@ -13,7 +13,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status_test +package statuses_test import ( "encoding/json" @@ -26,7 +26,7 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/status" + "github.com/superseriousbusiness/gotosocial/internal/api/client/statuses" "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/testrig" @@ -49,14 +49,14 @@ func (suite *StatusBookmarkTestSuite) TestPostBookmark() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.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, + Key: statuses.IDKey, Value: targetStatus.ID, }, } diff --git a/internal/api/client/status/statusboost.go b/internal/api/client/statuses/statusboost.go index d43bedd6c..c8921b1b6 100644 --- a/internal/api/client/status/statusboost.go +++ b/internal/api/client/statuses/statusboost.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status +package statuses import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -75,25 +75,25 @@ import ( 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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.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) + apiutil.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) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/status/statusboost_test.go b/internal/api/client/statuses/statusboost_test.go index 5b4b1b3cd..13ca2acf2 100644 --- a/internal/api/client/status/statusboost_test.go +++ b/internal/api/client/statuses/statusboost_test.go @@ -13,7 +13,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status_test +package statuses_test import ( "context" @@ -27,8 +27,8 @@ import ( "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/api/client/statuses" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/testrig" @@ -51,14 +51,14 @@ func (suite *StatusBoostTestSuite) TestPostBoost() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.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, + Key: statuses.IDKey, Value: targetStatus.ID, }, } @@ -73,12 +73,12 @@ func (suite *StatusBoostTestSuite) TestPostBoost() { b, err := ioutil.ReadAll(result.Body) suite.NoError(err) - statusReply := &model.Status{} + statusReply := &apimodel.Status{} err = json.Unmarshal(b, statusReply) suite.NoError(err) suite.False(statusReply.Sensitive) - suite.Equal(model.VisibilityPublic, statusReply.Visibility) + suite.Equal(apimodel.VisibilityPublic, statusReply.Visibility) suite.Equal(targetStatus.ContentWarning, statusReply.SpoilerText) suite.Equal(targetStatus.Content, statusReply.Content) @@ -117,12 +117,12 @@ func (suite *StatusBoostTestSuite) TestPostBoostOwnFollowersOnly() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.ReblogPath, ":id", testStatus.ID, 1)), nil) ctx.Request.Header.Set("accept", "application/json") ctx.Params = gin.Params{ gin.Param{ - Key: status.IDKey, + Key: statuses.IDKey, Value: testStatus.ID, }, } @@ -137,7 +137,7 @@ func (suite *StatusBoostTestSuite) TestPostBoostOwnFollowersOnly() { b, err := ioutil.ReadAll(result.Body) suite.NoError(err) - responseStatus := &model.Status{} + responseStatus := &apimodel.Status{} err = json.Unmarshal(b, responseStatus) suite.NoError(err) @@ -182,14 +182,14 @@ func (suite *StatusBoostTestSuite) TestPostUnboostable() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.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, + Key: statuses.IDKey, Value: targetStatus.ID, }, } @@ -224,14 +224,14 @@ func (suite *StatusBoostTestSuite) TestPostNotVisible() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.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, + Key: statuses.IDKey, Value: targetStatus.ID, }, } diff --git a/internal/api/client/status/statusboostedby.go b/internal/api/client/statuses/statusboostedby.go index 4a175f6e9..dc1567dba 100644 --- a/internal/api/client/status/statusboostedby.go +++ b/internal/api/client/statuses/statusboostedby.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status +package statuses import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -68,20 +68,20 @@ import ( 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) + apiutil.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) + apiutil.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) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/status/statusboostedby_test.go b/internal/api/client/statuses/statusboostedby_test.go index 0d7c9f7ab..576dee369 100644 --- a/internal/api/client/status/statusboostedby_test.go +++ b/internal/api/client/statuses/statusboostedby_test.go @@ -13,7 +13,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status_test +package statuses_test import ( "encoding/json" @@ -25,7 +25,7 @@ import ( "testing" "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/status" + "github.com/superseriousbusiness/gotosocial/internal/api/client/statuses" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/testrig" @@ -46,7 +46,7 @@ func (suite *StatusBoostedByTestSuite) TestRebloggedByOK() { 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 = httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.RebloggedPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/json") ctx.AddParam("id", targetStatus.ID) @@ -82,7 +82,7 @@ func (suite *StatusBoostedByTestSuite) TestRebloggedByUseBoostWrapperID() { 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 = httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.RebloggedPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/json") ctx.AddParam("id", targetStatus.ID) diff --git a/internal/api/client/status/statuscontext.go b/internal/api/client/statuses/statuscontext.go index 632a151d5..9a6ac9f7f 100644 --- a/internal/api/client/status/statuscontext.go +++ b/internal/api/client/statuses/statuscontext.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status +package statuses import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -74,25 +74,25 @@ import ( 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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.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) + apiutil.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) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/status/statuscreate.go b/internal/api/client/statuses/statuscreate.go index c1427411d..d36c93e77 100644 --- a/internal/api/client/status/statuscreate.go +++ b/internal/api/client/statuses/statuscreate.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status +package statuses import ( "errors" @@ -24,8 +24,8 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -75,18 +75,18 @@ import ( 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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } - form := &model.AdvancedStatusCreateForm{} + form := &apimodel.AdvancedStatusCreateForm{} if err := c.ShouldBind(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } @@ -100,20 +100,20 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) { // 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) + apiutil.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) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } c.JSON(http.StatusOK, apiStatus) } -func validateCreateStatus(form *model.AdvancedStatusCreateForm) error { +func validateCreateStatus(form *apimodel.AdvancedStatusCreateForm) error { hasStatus := form.Status != "" hasMedia := len(form.MediaIDs) != 0 hasPoll := form.Poll != nil diff --git a/internal/api/client/status/statuscreate_test.go b/internal/api/client/statuses/statuscreate_test.go index c143489f3..3648d7520 100644 --- a/internal/api/client/status/statuscreate_test.go +++ b/internal/api/client/statuses/statuscreate_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status_test +package statuses_test import ( "context" @@ -29,8 +29,8 @@ import ( "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/api/client/statuses" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -59,13 +59,13 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.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)}, + "visibility": {string(apimodel.VisibilityMutualsOnly)}, "likeable": {"false"}, "replyable": {"false"}, "federated": {"false"}, @@ -82,16 +82,16 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() { b, err := ioutil.ReadAll(result.Body) suite.NoError(err) - statusReply := &model.Status{} + statusReply := &apimodel.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.Equal(apimodel.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{ + suite.Equal(apimodel.Tag{ Name: "helloworld", URL: "http://localhost:8080/tags/helloworld", }, statusReply.Tags[0]) @@ -124,11 +124,11 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusMarkdown() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) ctx.Request.Header.Set("accept", "application/json") ctx.Request.Form = url.Values{ "status": {statusMarkdown}, - "visibility": {string(model.VisibilityPublic)}, + "visibility": {string(apimodel.VisibilityPublic)}, } suite.statusModule.StatusCreatePOSTHandler(ctx) @@ -139,7 +139,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusMarkdown() { b, err := ioutil.ReadAll(result.Body) suite.NoError(err) - statusReply := &model.Status{} + statusReply := &apimodel.Status{} err = json.Unmarshal(b, statusReply) suite.NoError(err) @@ -163,11 +163,11 @@ func (suite *StatusCreateTestSuite) TestMentionUnknownAccount() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.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)}, + "visibility": {string(apimodel.VisibilityPublic)}, } suite.statusModule.StatusCreatePOSTHandler(ctx) @@ -178,13 +178,13 @@ func (suite *StatusCreateTestSuite) TestMentionUnknownAccount() { b, err := ioutil.ReadAll(result.Body) suite.NoError(err) - statusReply := &model.Status{} + statusReply := &apimodel.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) + suite.Equal(apimodel.VisibilityPublic, statusReply.Visibility) } func (suite *StatusCreateTestSuite) TestPostAnotherNewStatus() { @@ -198,7 +198,7 @@ func (suite *StatusCreateTestSuite) TestPostAnotherNewStatus() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/json") ctx.Request.Form = url.Values{ "status": {statusWithLinksAndTags}, @@ -215,7 +215,7 @@ func (suite *StatusCreateTestSuite) TestPostAnotherNewStatus() { b, err := ioutil.ReadAll(result.Body) suite.NoError(err) - statusReply := &model.Status{} + statusReply := &apimodel.Status{} err = json.Unmarshal(b, statusReply) suite.NoError(err) @@ -233,7 +233,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.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: "}, @@ -247,7 +247,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() { b, err := ioutil.ReadAll(result.Body) suite.NoError(err) - statusReply := &model.Status{} + statusReply := &apimodel.Status{} err = json.Unmarshal(b, statusReply) suite.NoError(err) @@ -275,7 +275,7 @@ func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.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"}, @@ -307,7 +307,7 @@ func (suite *StatusCreateTestSuite) TestReplyToLocalStatus() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.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)}, @@ -323,14 +323,14 @@ func (suite *StatusCreateTestSuite) TestReplyToLocalStatus() { b, err := ioutil.ReadAll(result.Body) suite.NoError(err) - statusReply := &model.Status{} + statusReply := &apimodel.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(apimodel.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) @@ -350,7 +350,7 @@ func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.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"}, @@ -366,14 +366,14 @@ func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() { b, err := ioutil.ReadAll(result.Body) suite.NoError(err) - statusResponse := &model.Status{} + statusResponse := &apimodel.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) + suite.Equal(apimodel.VisibilityPublic, statusResponse.Visibility) // there should be one media attachment suite.Len(statusResponse.MediaAttachments, 1) diff --git a/internal/api/client/status/statusdelete.go b/internal/api/client/statuses/statusdelete.go index b37dd5f14..3db7397db 100644 --- a/internal/api/client/status/statusdelete.go +++ b/internal/api/client/statuses/statusdelete.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status +package statuses import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -74,25 +74,25 @@ import ( 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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.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) + apiutil.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) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/status/statusdelete_test.go b/internal/api/client/statuses/statusdelete_test.go index f97a13eec..9a9ceef8f 100644 --- a/internal/api/client/status/statusdelete_test.go +++ b/internal/api/client/statuses/statusdelete_test.go @@ -13,7 +13,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status_test +package statuses_test import ( "encoding/json" @@ -27,8 +27,8 @@ import ( "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/api/client/statuses" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/testrig" @@ -50,14 +50,14 @@ func (suite *StatusDeleteTestSuite) TestPostDelete() { 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 = httptest.NewRequest(http.MethodDelete, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.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, + Key: statuses.IDKey, Value: targetStatus.ID, }, } @@ -72,7 +72,7 @@ func (suite *StatusDeleteTestSuite) TestPostDelete() { b, err := ioutil.ReadAll(result.Body) suite.NoError(err) - statusReply := &model.Status{} + statusReply := &apimodel.Status{} err = json.Unmarshal(b, statusReply) suite.NoError(err) suite.NotNil(statusReply) diff --git a/internal/api/client/status/statusfave.go b/internal/api/client/statuses/statusfave.go index 3117e7ef2..bd9ded147 100644 --- a/internal/api/client/status/statusfave.go +++ b/internal/api/client/statuses/statusfave.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status +package statuses import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -71,25 +71,25 @@ import ( 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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.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) + apiutil.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) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/status/statusfave_test.go b/internal/api/client/statuses/statusfave_test.go index da5d2a48a..20805d87c 100644 --- a/internal/api/client/status/statusfave_test.go +++ b/internal/api/client/statuses/statusfave_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status_test +package statuses_test import ( "encoding/json" @@ -30,8 +30,9 @@ import ( "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/api/client/statuses" + + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/testrig" ) @@ -54,14 +55,14 @@ func (suite *StatusFaveTestSuite) TestPostFave() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.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, + Key: statuses.IDKey, Value: targetStatus.ID, }, } @@ -76,14 +77,14 @@ func (suite *StatusFaveTestSuite) TestPostFave() { b, err := ioutil.ReadAll(result.Body) assert.NoError(suite.T(), err) - statusReply := &model.Status{} + statusReply := &apimodel.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.Equal(suite.T(), apimodel.VisibilityPublic, statusReply.Visibility) assert.True(suite.T(), statusReply.Favourited) assert.Equal(suite.T(), 1, statusReply.FavouritesCount) } @@ -102,14 +103,14 @@ func (suite *StatusFaveTestSuite) TestPostUnfaveable() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.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, + Key: statuses.IDKey, Value: targetStatus.ID, }, } diff --git a/internal/api/client/status/statusfavedby.go b/internal/api/client/statuses/statusfavedby.go index 20ef86ded..aa0f1f8d6 100644 --- a/internal/api/client/status/statusfavedby.go +++ b/internal/api/client/statuses/statusfavedby.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status +package statuses import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -72,25 +72,25 @@ import ( 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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.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) + apiutil.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) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/status/statusfavedby_test.go b/internal/api/client/statuses/statusfavedby_test.go index e704fa724..fc04c490e 100644 --- a/internal/api/client/status/statusfavedby_test.go +++ b/internal/api/client/statuses/statusfavedby_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status_test +package statuses_test import ( "encoding/json" @@ -30,8 +30,8 @@ import ( "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/api/client/statuses" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/testrig" ) @@ -53,14 +53,14 @@ func (suite *StatusFavedByTestSuite) TestGetFavedBy() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.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, + Key: statuses.IDKey, Value: targetStatus.ID, }, } @@ -75,7 +75,7 @@ func (suite *StatusFavedByTestSuite) TestGetFavedBy() { b, err := ioutil.ReadAll(result.Body) assert.NoError(suite.T(), err) - accts := []model.Account{} + accts := []apimodel.Account{} err = json.Unmarshal(b, &accts) assert.NoError(suite.T(), err) diff --git a/internal/api/client/status/statusget.go b/internal/api/client/statuses/statusget.go index a0d0e913c..5e7a59027 100644 --- a/internal/api/client/status/statusget.go +++ b/internal/api/client/statuses/statusget.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status +package statuses import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -71,25 +71,25 @@ import ( 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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.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) + apiutil.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) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/status/statusget_test.go b/internal/api/client/statuses/statusget_test.go index d11c9b587..e8e1fd8f4 100644 --- a/internal/api/client/status/statusget_test.go +++ b/internal/api/client/statuses/statusget_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status_test +package statuses_test import ( "testing" diff --git a/internal/api/client/status/statusunbookmark.go b/internal/api/client/statuses/statusunbookmark.go index aa090f8c9..117ef833b 100644 --- a/internal/api/client/status/statusunbookmark.go +++ b/internal/api/client/statuses/statusunbookmark.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status +package statuses import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -72,25 +72,25 @@ import ( 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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.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) + apiutil.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) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/status/statusunbookmark_test.go b/internal/api/client/statuses/statusunbookmark_test.go index 09a18ab9b..9c4667ad8 100644 --- a/internal/api/client/status/statusunbookmark_test.go +++ b/internal/api/client/statuses/statusunbookmark_test.go @@ -13,7 +13,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status_test +package statuses_test import ( "encoding/json" @@ -26,7 +26,7 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/status" + "github.com/superseriousbusiness/gotosocial/internal/api/client/statuses" "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/testrig" @@ -49,12 +49,12 @@ func (suite *StatusUnbookmarkTestSuite) TestPostUnbookmark() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.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, + Key: statuses.IDKey, Value: targetStatus.ID, }, } diff --git a/internal/api/client/status/statusunboost.go b/internal/api/client/statuses/statusunboost.go index 45a8e0ece..e91081195 100644 --- a/internal/api/client/status/statusunboost.go +++ b/internal/api/client/statuses/statusunboost.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status +package statuses import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -72,25 +72,25 @@ import ( 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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.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) + apiutil.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) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/status/statusunfave.go b/internal/api/client/statuses/statusunfave.go index 19d3da3bd..57ae88e1e 100644 --- a/internal/api/client/status/statusunfave.go +++ b/internal/api/client/statuses/statusunfave.go @@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status +package statuses import ( "errors" "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -71,25 +71,25 @@ import ( 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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.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) + apiutil.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) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/status/statusunfave_test.go b/internal/api/client/statuses/statusunfave_test.go index b8448d657..2ca3450a4 100644 --- a/internal/api/client/status/statusunfave_test.go +++ b/internal/api/client/statuses/statusunfave_test.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package status_test +package statuses_test import ( "encoding/json" @@ -30,8 +30,8 @@ import ( "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/api/client/statuses" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/testrig" ) @@ -55,14 +55,14 @@ func (suite *StatusUnfaveTestSuite) TestPostUnfave() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.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, + Key: statuses.IDKey, Value: targetStatus.ID, }, } @@ -77,14 +77,14 @@ func (suite *StatusUnfaveTestSuite) TestPostUnfave() { b, err := ioutil.ReadAll(result.Body) assert.NoError(suite.T(), err) - statusReply := &model.Status{} + statusReply := &apimodel.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.Equal(suite.T(), apimodel.VisibilityPublic, statusReply.Visibility) assert.False(suite.T(), statusReply.Favourited) assert.Equal(suite.T(), 0, statusReply.FavouritesCount) } @@ -104,14 +104,14 @@ func (suite *StatusUnfaveTestSuite) TestPostAlreadyNotFaved() { 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 = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.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, + Key: statuses.IDKey, Value: targetStatus.ID, }, } @@ -126,14 +126,14 @@ func (suite *StatusUnfaveTestSuite) TestPostAlreadyNotFaved() { b, err := ioutil.ReadAll(result.Body) assert.NoError(suite.T(), err) - statusReply := &model.Status{} + statusReply := &apimodel.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.Equal(suite.T(), apimodel.VisibilityPublic, statusReply.Visibility) assert.False(suite.T(), statusReply.Favourited) assert.Equal(suite.T(), 0, statusReply.FavouritesCount) } diff --git a/internal/api/client/streaming/stream.go b/internal/api/client/streaming/stream.go index a9cb62732..de98719c2 100644 --- a/internal/api/client/streaming/stream.go +++ b/internal/api/client/streaming/stream.go @@ -1,3 +1,21 @@ +/* + 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 streaming import ( @@ -6,7 +24,7 @@ import ( "time" "codeberg.org/gruf/go-kv" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/log" @@ -14,12 +32,15 @@ import ( "github.com/gorilla/websocket" ) -var wsUpgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - // we expect cors requests (via eg., pinafore.social) so be lenient - CheckOrigin: func(r *http.Request) bool { return true }, -} +var ( + wsUpgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + // we expect cors requests (via eg., pinafore.social) so be lenient + CheckOrigin: func(r *http.Request) bool { return true }, + } + errNoToken = fmt.Errorf("no access token provided under query key %s or under header %s", AccessTokenQueryKey, AccessTokenHeader) +) // StreamGETHandler swagger:operation GET /api/v1/streaming streamGet // @@ -125,29 +146,33 @@ func (m *Module) StreamGETHandler(c *gin.Context) { streamType := c.Query(StreamQueryKey) if streamType == "" { err := fmt.Errorf("no stream type provided under query key %s", StreamQueryKey) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } - accessToken := c.Query(AccessTokenQueryKey) - if accessToken == "" { - accessToken = c.GetHeader(AccessTokenHeader) - } - if accessToken == "" { - err := fmt.Errorf("no access token provided under query key %s or under header %s", AccessTokenQueryKey, AccessTokenHeader) - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + var accessToken string + if t := c.Query(AccessTokenQueryKey); t != "" { + // try query param first + accessToken = t + } else if t := c.GetHeader(AccessTokenHeader); t != "" { + // fall back to Sec-Websocket-Protocol + accessToken = t + } else { + // no token + err := errNoToken + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) return } account, errWithCode := m.processor.AuthorizeStreamingRequest(c.Request.Context(), accessToken) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } stream, errWithCode := m.processor.OpenStreamForAccount(c.Request.Context(), account, streamType) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } @@ -175,6 +200,7 @@ func (m *Module) StreamGETHandler(c *gin.Context) { }() streamTicker := time.NewTicker(m.tickDuration) + defer streamTicker.Stop() // We want to stay in the loop as long as possible while the client is connected. // The only thing that should break the loop is if the client leaves or the connection becomes unhealthy. diff --git a/internal/api/client/streaming/streaming.go b/internal/api/client/streaming/streaming.go index b15dfbdbd..f9d9fdf36 100644 --- a/internal/api/client/streaming/streaming.go +++ b/internal/api/client/streaming/streaming.go @@ -22,14 +22,13 @@ import ( "net/http" "time" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( - // BasePath is the path for the streaming api - BasePath = "/api/v1/streaming" + // BasePath is the path for the streaming api, minus the 'api' prefix + BasePath = "/v1/streaming" // StreamQueryKey is the query key for the type of stream being requested StreamQueryKey = "stream" @@ -41,29 +40,25 @@ const ( AccessTokenHeader = "Sec-Websocket-Protocol" ) -// Module implements the api.ClientModule interface for everything related to streaming type Module struct { processor processing.Processor tickDuration time.Duration } -// New returns a new streaming module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { return &Module{ processor: processor, tickDuration: 30 * time.Second, } } -func NewWithTickDuration(processor processing.Processor, tickDuration time.Duration) api.ClientModule { +func NewWithTickDuration(processor processing.Processor, tickDuration time.Duration) *Module { return &Module{ processor: processor, tickDuration: tickDuration, } } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { - r.AttachHandler(http.MethodGet, BasePath, m.StreamGETHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodGet, BasePath, m.StreamGETHandler) } diff --git a/internal/api/client/streaming/streaming_test.go b/internal/api/client/streaming/streaming_test.go index 49c983fff..2f2d850c1 100644 --- a/internal/api/client/streaming/streaming_test.go +++ b/internal/api/client/streaming/streaming_test.go @@ -99,7 +99,7 @@ func (suite *StreamingTestSuite) SetupTest() { 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.streamingModule = streaming.NewWithTickDuration(suite.processor, 1).(*streaming.Module) + suite.streamingModule = streaming.NewWithTickDuration(suite.processor, 1) suite.NoError(suite.processor.Start()) } diff --git a/internal/api/client/timeline/home.go b/internal/api/client/timelines/home.go index e6135dd63..33af8fe5e 100644 --- a/internal/api/client/timeline/home.go +++ b/internal/api/client/timelines/home.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package timeline +package timelines import ( "fmt" @@ -24,7 +24,7 @@ import ( "strconv" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -112,12 +112,12 @@ import ( func (m *Module) HomeTimelineGETHandler(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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } @@ -145,7 +145,7 @@ func (m *Module) HomeTimelineGETHandler(c *gin.Context) { i, err := strconv.ParseInt(limitString, 10, 32) if err != nil { err := fmt.Errorf("error parsing %s: %s", LimitKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } limit = int(i) @@ -157,7 +157,7 @@ func (m *Module) HomeTimelineGETHandler(c *gin.Context) { i, err := strconv.ParseBool(localString) if err != nil { err := fmt.Errorf("error parsing %s: %s", LocalKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } local = i @@ -165,7 +165,7 @@ func (m *Module) HomeTimelineGETHandler(c *gin.Context) { resp, errWithCode := m.processor.HomeTimelineGet(c.Request.Context(), authed, maxID, sinceID, minID, limit, local) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/timeline/public.go b/internal/api/client/timelines/public.go index fda23438b..efe351a37 100644 --- a/internal/api/client/timeline/public.go +++ b/internal/api/client/timelines/public.go @@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package timeline +package timelines import ( "fmt" @@ -24,7 +24,7 @@ import ( "strconv" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -123,12 +123,12 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) { } if err != nil { - api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } @@ -156,7 +156,7 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) { i, err := strconv.ParseInt(limitString, 10, 32) if err != nil { err := fmt.Errorf("error parsing %s: %s", LimitKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } limit = int(i) @@ -168,7 +168,7 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) { i, err := strconv.ParseBool(localString) if err != nil { err := fmt.Errorf("error parsing %s: %s", LocalKey, err) - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } local = i @@ -176,7 +176,7 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) { resp, errWithCode := m.processor.PublicTimelineGet(c.Request.Context(), authed, maxID, sinceID, minID, limit, local) if errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/timeline/timeline.go b/internal/api/client/timelines/timeline.go index 3604a1fc2..609e1855e 100644 --- a/internal/api/client/timeline/timeline.go +++ b/internal/api/client/timelines/timeline.go @@ -16,19 +16,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package timeline +package timelines import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( - // BasePath is the base URI path for serving timelines - BasePath = "/api/v1/timelines" + // BasePath is the base URI path for serving timelines, minus the 'api' prefix. + BasePath = "/v1/timelines" // HomeTimeline is the path for the home timeline HomeTimeline = BasePath + "/home" // PublicTimeline is the path for the public (and public local) timeline @@ -45,21 +44,17 @@ const ( LocalKey = "local" ) -// Module implements the ClientAPIModule interface for everything relating to viewing timelines type Module struct { processor processing.Processor } -// New returns a new timeline module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { 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.MethodGet, HomeTimeline, m.HomeTimelineGETHandler) - r.AttachHandler(http.MethodGet, PublicTimeline, m.PublicTimelineGETHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodGet, HomeTimeline, m.HomeTimelineGETHandler) + attachHandler(http.MethodGet, PublicTimeline, m.PublicTimelineGETHandler) } diff --git a/internal/api/client/user/passwordchange.go b/internal/api/client/user/passwordchange.go index a900af897..c766d915c 100644 --- a/internal/api/client/user/passwordchange.go +++ b/internal/api/client/user/passwordchange.go @@ -23,8 +23,8 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/superseriousbusiness/gotosocial/internal/api" - "github.com/superseriousbusiness/gotosocial/internal/api/model" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -68,35 +68,35 @@ import ( func (m *Module) PasswordChangePOSTHandler(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) + apiutil.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) + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) return } - form := &model.PasswordChangeRequest{} + form := &apimodel.PasswordChangeRequest{} if err := c.ShouldBind(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if form.OldPassword == "" { err := errors.New("password change request missing field old_password") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if form.NewPassword == "" { err := errors.New("password change request missing field new_password") - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) return } if errWithCode := m.processor.UserChangePassword(c.Request.Context(), authed, form); errWithCode != nil { - api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) return } diff --git a/internal/api/client/user/user.go b/internal/api/client/user/user.go index 86a0096e0..5e6002b40 100644 --- a/internal/api/client/user/user.go +++ b/internal/api/client/user/user.go @@ -21,32 +21,27 @@ package user import ( "net/http" - "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/router" ) const ( - // BasePath is the base URI path for this module - BasePath = "/api/v1/user" + // BasePath is the base URI path for this module, minus the 'api' prefix + BasePath = "/v1/user" // PasswordChangePath is the path for POSTing a password change request. PasswordChangePath = BasePath + "/password_change" ) -// Module implements the ClientAPIModule interface type Module struct { processor processing.Processor } -// New returns a new user module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module { 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, PasswordChangePath, m.PasswordChangePOSTHandler) - return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodPost, PasswordChangePath, m.PasswordChangePOSTHandler) } diff --git a/internal/api/client/user/user_test.go b/internal/api/client/user/user_test.go index cc4fafca9..055b1f7a4 100644 --- a/internal/api/client/user/user_test.go +++ b/internal/api/client/user/user_test.go @@ -73,7 +73,7 @@ func (suite *UserStandardTestSuite) SetupTest() { suite.sentEmails = make(map[string]string) suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails) suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) - suite.userModule = user.New(suite.processor).(*user.Module) + suite.userModule = user.New(suite.processor) testrig.StandardDBSetup(suite.db, suite.testAccounts) testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") |