[chore] The Big Middleware and API Refactor (tm) (#1250)

* interim commit: start refactoring middlewares into package under router

* another interim commit, this is becoming a big job

* another fucking massive interim commit

* refactor bookmarks to new style

* ambassador, wiz zeze commits you are spoiling uz

* she compiles, we're getting there

* we're just normal men; we're just innocent men

* apiutil

* whoopsie

* i'm glad noone reads commit msgs haha :blob_sweat:

* use that weirdo go-bytesize library for maxMultipartMemory

* fix media module paths
This commit is contained in:
tobi
2023-01-02 13:10:50 +01:00
committed by GitHub
parent 560ff1209d
commit 941893a774
228 changed files with 3188 additions and 3047 deletions

View File

@@ -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")

View File

@@ -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")
}

View File

@@ -16,4 +16,4 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// */
package account_test
package accounts_test

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)
// delete account
r.AttachHandler(http.MethodPost, DeleteAccountPath, m.AccountDeletePOSTHandler)
attachHandler(http.MethodPost, BasePath, m.AccountCreatePOSTHandler)
// get account
r.AttachHandler(http.MethodGet, BasePathWithID, m.muxHandler)
attachHandler(http.MethodGet, BasePathWithID, m.AccountGETHandler)
// delete account
attachHandler(http.MethodPost, DeleteAccountPath, m.AccountDeletePOSTHandler)
// 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)
}

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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")
}

View File

@@ -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")

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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'")
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 := &gtsmodel.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
}

View File

@@ -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 := &gtsmodel.User{}
account := &gtsmodel.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))
}

View File

@@ -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 := &gtsmodel.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
}

View File

@@ -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,
})
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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 := &gtsmodel.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 := &gtsmodel.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{})
}

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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())
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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))
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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,
},
}

View File

@@ -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)
}

View File

@@ -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")

View File

@@ -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
}

View File

@@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package followrequest_test
package followrequests_test
import (
"context"

View File

@@ -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
}

View File

@@ -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,
},
}

View File

@@ -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)
}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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 &&

View File

@@ -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
}

View File

@@ -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{})
}

View File

@@ -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)
}

View File

@@ -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{})
}

View File

@@ -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)
}

View File

@@ -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) {
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
}
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
}
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)
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")

View File

@@ -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)

View File

@@ -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) {
authed, err := oauth.Authed(c, true, true, true, true)
if err != nil {
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
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
}
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
}
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
}

View File

@@ -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) {
authed, err := oauth.Authed(c, true, true, true, true)
if err != nil {
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
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
}
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
}
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()

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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")

View File

@@ -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
}

View File

@@ -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)
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)
r.AttachHandler(http.MethodPost, FavouritePath, m.StatusFavePOSTHandler)
r.AttachHandler(http.MethodPost, UnfavouritePath, m.StatusUnfavePOSTHandler)
r.AttachHandler(http.MethodGet, FavouritedPath, m.StatusFavedByGETHandler)
// fave stuff
attachHandler(http.MethodPost, FavouritePath, m.StatusFavePOSTHandler)
attachHandler(http.MethodPost, UnfavouritePath, m.StatusUnfavePOSTHandler)
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)
// 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)
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)
}
}
// context / status thread
attachHandler(http.MethodGet, ContextPath, m.StatusContextGETHandler)
}

View File

@@ -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())
}

View File

@@ -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
}

View File

@@ -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,
},
}

View File

@@ -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
}

Some files were not shown because too many files have changed in this diff Show More