diff options
Diffstat (limited to 'internal/api')
25 files changed, 169 insertions, 200 deletions
diff --git a/internal/api/client/admin/accountaction.go b/internal/api/client/admin/accountaction.go index 7d74e8530..64e6c39ca 100644 --- a/internal/api/client/admin/accountaction.go +++ b/internal/api/client/admin/accountaction.go @@ -116,10 +116,9 @@ func (m *Module) AccountActionPOSTHandler(c *gin.Context) { return } - targetAcctID := c.Param(IDKey) - if targetAcctID == "" { - err := errors.New("no account id specified") - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + targetAcctID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } form.TargetID = targetAcctID diff --git a/internal/api/client/admin/admin.go b/internal/api/client/admin/admin.go index ef54f0b50..2c55de2f0 100644 --- a/internal/api/client/admin/admin.go +++ b/internal/api/client/admin/admin.go @@ -22,6 +22,7 @@ import ( "codeberg.org/gruf/go-debug" "github.com/gin-gonic/gin" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/processing" "github.com/superseriousbusiness/gotosocial/internal/state" ) @@ -29,48 +30,40 @@ import ( const ( BasePath = "/v1/admin" EmojiPath = BasePath + "/custom_emojis" - EmojiPathWithID = EmojiPath + "/:" + IDKey + EmojiPathWithID = EmojiPath + "/:" + apiutil.IDKey EmojiCategoriesPath = EmojiPath + "/categories" DomainBlocksPath = BasePath + "/domain_blocks" - DomainBlocksPathWithID = DomainBlocksPath + "/:" + IDKey + DomainBlocksPathWithID = DomainBlocksPath + "/:" + apiutil.IDKey DomainAllowsPath = BasePath + "/domain_allows" - DomainAllowsPathWithID = DomainAllowsPath + "/:" + IDKey + DomainAllowsPathWithID = DomainAllowsPath + "/:" + apiutil.IDKey DomainKeysExpirePath = BasePath + "/domain_keys_expire" HeaderAllowsPath = BasePath + "/header_allows" - HeaderAllowsPathWithID = HeaderAllowsPath + "/:" + IDKey + HeaderAllowsPathWithID = HeaderAllowsPath + "/:" + apiutil.IDKey HeaderBlocksPath = BasePath + "/header_blocks" - HeaderBlocksPathWithID = HeaderBlocksPath + "/:" + IDKey + HeaderBlocksPathWithID = HeaderBlocksPath + "/:" + apiutil.IDKey AccountsV1Path = BasePath + "/accounts" AccountsV2Path = "/v2/admin/accounts" - AccountsPathWithID = AccountsV1Path + "/:" + IDKey + AccountsPathWithID = AccountsV1Path + "/:" + apiutil.IDKey AccountsActionPath = AccountsPathWithID + "/action" AccountsApprovePath = AccountsPathWithID + "/approve" AccountsRejectPath = AccountsPathWithID + "/reject" MediaCleanupPath = BasePath + "/media_cleanup" MediaRefetchPath = BasePath + "/media_refetch" ReportsPath = BasePath + "/reports" - ReportsPathWithID = ReportsPath + "/:" + IDKey + ReportsPathWithID = ReportsPath + "/:" + apiutil.IDKey ReportsResolvePath = ReportsPathWithID + "/resolve" EmailPath = BasePath + "/email" EmailTestPath = EmailPath + "/test" InstanceRulesPath = BasePath + "/instance/rules" - InstanceRulesPathWithID = InstanceRulesPath + "/:" + IDKey + InstanceRulesPathWithID = InstanceRulesPath + "/:" + apiutil.IDKey DebugPath = BasePath + "/debug" DebugAPUrlPath = DebugPath + "/apurl" DebugClearCachesPath = DebugPath + "/caches/clear" - IDKey = "id" FilterQueryKey = "filter" MaxShortcodeDomainKey = "max_shortcode_domain" MinShortcodeDomainKey = "min_shortcode_domain" - LimitKey = "limit" DomainQueryKey = "domain" - ResolvedKey = "resolved" - AccountIDKey = "account_id" - TargetAccountIDKey = "target_account_id" - MaxIDKey = "max_id" - SinceIDKey = "since_id" - MinIDKey = "min_id" ) type Module struct { diff --git a/internal/api/client/admin/emojidelete.go b/internal/api/client/admin/emojidelete.go index 47248a1b9..9f9f9d286 100644 --- a/internal/api/client/admin/emojidelete.go +++ b/internal/api/client/admin/emojidelete.go @@ -18,7 +18,6 @@ package admin import ( - "errors" "fmt" "net/http" @@ -97,10 +96,9 @@ func (m *Module) EmojiDELETEHandler(c *gin.Context) { return } - emojiID := c.Param(IDKey) - if emojiID == "" { - err := errors.New("no emoji id specified") - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + emojiID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } diff --git a/internal/api/client/admin/emojidelete_test.go b/internal/api/client/admin/emojidelete_test.go index e27592baf..10cf3fe8d 100644 --- a/internal/api/client/admin/emojidelete_test.go +++ b/internal/api/client/admin/emojidelete_test.go @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/admin" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/db" ) @@ -41,7 +42,7 @@ func (suite *EmojiDeleteTestSuite) TestEmojiDelete1() { path := admin.EmojiPathWithID ctx := suite.newContext(recorder, http.MethodDelete, nil, path, "application/json") - ctx.AddParam(admin.IDKey, testEmoji.ID) + ctx.AddParam(apiutil.IDKey, testEmoji.ID) suite.adminModule.EmojiDELETEHandler(ctx) suite.Equal(http.StatusOK, recorder.Code) @@ -78,7 +79,7 @@ func (suite *EmojiDeleteTestSuite) TestEmojiDelete2() { path := admin.EmojiPathWithID ctx := suite.newContext(recorder, http.MethodDelete, nil, path, "application/json") - ctx.AddParam(admin.IDKey, testEmoji.ID) + ctx.AddParam(apiutil.IDKey, testEmoji.ID) suite.adminModule.EmojiDELETEHandler(ctx) suite.Equal(http.StatusBadRequest, recorder.Code) @@ -100,7 +101,7 @@ func (suite *EmojiDeleteTestSuite) TestEmojiDeleteNotFound() { path := admin.EmojiPathWithID ctx := suite.newContext(recorder, http.MethodDelete, nil, path, "application/json") - ctx.AddParam(admin.IDKey, "01GF8VRXX1R00X7XH8973Z29R1") + ctx.AddParam(apiutil.IDKey, "01GF8VRXX1R00X7XH8973Z29R1") suite.adminModule.EmojiDELETEHandler(ctx) suite.Equal(http.StatusNotFound, recorder.Code) diff --git a/internal/api/client/admin/emojiget.go b/internal/api/client/admin/emojiget.go index 730d92db1..7ecbcfa19 100644 --- a/internal/api/client/admin/emojiget.go +++ b/internal/api/client/admin/emojiget.go @@ -18,7 +18,6 @@ package admin import ( - "errors" "fmt" "net/http" @@ -82,10 +81,9 @@ func (m *Module) EmojiGETHandler(c *gin.Context) { return } - emojiID := c.Param(IDKey) - if emojiID == "" { - err := errors.New("no emoji id specified") - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + emojiID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } diff --git a/internal/api/client/admin/emojiget_test.go b/internal/api/client/admin/emojiget_test.go index 86c847b17..b8bad2536 100644 --- a/internal/api/client/admin/emojiget_test.go +++ b/internal/api/client/admin/emojiget_test.go @@ -27,6 +27,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/admin" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" ) type EmojiGetTestSuite struct { @@ -39,7 +40,7 @@ func (suite *EmojiGetTestSuite) TestEmojiGet1() { path := admin.EmojiPathWithID ctx := suite.newContext(recorder, http.MethodGet, nil, path, "application/json") - ctx.AddParam(admin.IDKey, testEmoji.ID) + ctx.AddParam(apiutil.IDKey, testEmoji.ID) suite.adminModule.EmojiGETHandler(ctx) suite.Equal(http.StatusOK, recorder.Code) @@ -71,7 +72,7 @@ func (suite *EmojiGetTestSuite) TestEmojiGet2() { path := admin.EmojiPathWithID ctx := suite.newContext(recorder, http.MethodGet, nil, path, "application/json") - ctx.AddParam(admin.IDKey, testEmoji.ID) + ctx.AddParam(apiutil.IDKey, testEmoji.ID) suite.adminModule.EmojiGETHandler(ctx) suite.Equal(http.StatusOK, recorder.Code) @@ -102,7 +103,7 @@ func (suite *EmojiGetTestSuite) TestEmojiGetNotFound() { path := admin.EmojiPathWithID ctx := suite.newContext(recorder, http.MethodGet, nil, path, "application/json") - ctx.AddParam(admin.IDKey, "01GF8VRXX1R00X7XH8973Z29R1") + ctx.AddParam(apiutil.IDKey, "01GF8VRXX1R00X7XH8973Z29R1") suite.adminModule.EmojiGETHandler(ctx) suite.Equal(http.StatusNotFound, recorder.Code) diff --git a/internal/api/client/admin/emojisget.go b/internal/api/client/admin/emojisget.go index 4013e1836..d50b553ac 100644 --- a/internal/api/client/admin/emojisget.go +++ b/internal/api/client/admin/emojisget.go @@ -20,7 +20,6 @@ package admin import ( "fmt" "net/http" - "strconv" "strings" "github.com/gin-gonic/gin" @@ -76,6 +75,8 @@ import ( // type: integer // description: Number of emojis to return. Less than 1, or not set, means unlimited (all emojis). // default: 50 +// minimum: 0 +// maximum: 200 // in: query // - // name: max_shortcode_domain @@ -142,19 +143,10 @@ func (m *Module) EmojisGETHandler(c *gin.Context) { maxShortcodeDomain := c.Query(MaxShortcodeDomainKey) minShortcodeDomain := c.Query(MinShortcodeDomainKey) - limit := 50 - limitString := c.Query(LimitKey) - if limitString != "" { - i, err := strconv.ParseInt(limitString, 10, 32) - if err != nil { - err := fmt.Errorf("error parsing %s: %s", LimitKey, err) - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) - return - } - limit = int(i) - } - if limit < 0 { - limit = 0 + limit, errWithCode := apiutil.ParseLimit(c.Query(apiutil.LimitKey), 50, 200, 0) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return } var domain string diff --git a/internal/api/client/admin/emojiupdate.go b/internal/api/client/admin/emojiupdate.go index 1d41dd545..37f67cabd 100644 --- a/internal/api/client/admin/emojiupdate.go +++ b/internal/api/client/admin/emojiupdate.go @@ -147,10 +147,9 @@ func (m *Module) EmojiPATCHHandler(c *gin.Context) { return } - emojiID := c.Param(IDKey) - if emojiID == "" { - err := errors.New("no emoji id specified") - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + emojiID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } diff --git a/internal/api/client/admin/emojiupdate_test.go b/internal/api/client/admin/emojiupdate_test.go index eb9d9d866..676363e39 100644 --- a/internal/api/client/admin/emojiupdate_test.go +++ b/internal/api/client/admin/emojiupdate_test.go @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/admin" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/testrig" ) @@ -53,7 +54,7 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateNewCategory() { bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) - ctx.AddParam(admin.IDKey, testEmoji.ID) + ctx.AddParam(apiutil.IDKey, testEmoji.ID) // call the handler suite.adminModule.EmojiPATCHHandler(ctx) @@ -130,7 +131,7 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateSwitchCategory() { bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) - ctx.AddParam(admin.IDKey, testEmoji.ID) + ctx.AddParam(apiutil.IDKey, testEmoji.ID) // call the handler suite.adminModule.EmojiPATCHHandler(ctx) @@ -208,7 +209,7 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyRemoteToLocal() { bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) - ctx.AddParam(admin.IDKey, testEmoji.ID) + ctx.AddParam(apiutil.IDKey, testEmoji.ID) // call the handler suite.adminModule.EmojiPATCHHandler(ctx) @@ -284,7 +285,7 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateDisableEmoji() { bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) - ctx.AddParam(admin.IDKey, testEmoji.ID) + ctx.AddParam(apiutil.IDKey, testEmoji.ID) // call the handler suite.adminModule.EmojiPATCHHandler(ctx) @@ -325,7 +326,7 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateDisableLocalEmoji() { bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) - ctx.AddParam(admin.IDKey, testEmoji.ID) + ctx.AddParam(apiutil.IDKey, testEmoji.ID) // call the handler suite.adminModule.EmojiPATCHHandler(ctx) @@ -358,7 +359,7 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateModifyRemoteEmoji() { bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) - ctx.AddParam(admin.IDKey, testEmoji.ID) + ctx.AddParam(apiutil.IDKey, testEmoji.ID) // call the handler suite.adminModule.EmojiPATCHHandler(ctx) @@ -391,7 +392,7 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateModifyNoParams() { bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) - ctx.AddParam(admin.IDKey, testEmoji.ID) + ctx.AddParam(apiutil.IDKey, testEmoji.ID) // call the handler suite.adminModule.EmojiPATCHHandler(ctx) @@ -425,7 +426,7 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyLocalToLocal() { bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) - ctx.AddParam(admin.IDKey, testEmoji.ID) + ctx.AddParam(apiutil.IDKey, testEmoji.ID) // call the handler suite.adminModule.EmojiPATCHHandler(ctx) @@ -459,7 +460,7 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyEmptyShortcode() { bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) - ctx.AddParam(admin.IDKey, testEmoji.ID) + ctx.AddParam(apiutil.IDKey, testEmoji.ID) // call the handler suite.adminModule.EmojiPATCHHandler(ctx) @@ -492,7 +493,7 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyNoShortcode() { bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) - ctx.AddParam(admin.IDKey, testEmoji.ID) + ctx.AddParam(apiutil.IDKey, testEmoji.ID) // call the handler suite.adminModule.EmojiPATCHHandler(ctx) @@ -526,7 +527,7 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyShortcodeAlreadyInUse() { bodyBytes := requestBody.Bytes() recorder := httptest.NewRecorder() ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) - ctx.AddParam(admin.IDKey, testEmoji.ID) + ctx.AddParam(apiutil.IDKey, testEmoji.ID) // call the handler suite.adminModule.EmojiPATCHHandler(ctx) diff --git a/internal/api/client/admin/reportget.go b/internal/api/client/admin/reportget.go index f70ae8b54..f2acd214c 100644 --- a/internal/api/client/admin/reportget.go +++ b/internal/api/client/admin/reportget.go @@ -18,7 +18,6 @@ package admin import ( - "errors" "fmt" "net/http" @@ -85,10 +84,9 @@ func (m *Module) ReportGETHandler(c *gin.Context) { return } - reportID := c.Param(IDKey) - if reportID == "" { - err := errors.New("no report id specified") - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + reportID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } diff --git a/internal/api/client/admin/reportresolve.go b/internal/api/client/admin/reportresolve.go index 51c268a2d..f17ae24be 100644 --- a/internal/api/client/admin/reportresolve.go +++ b/internal/api/client/admin/reportresolve.go @@ -18,7 +18,6 @@ package admin import ( - "errors" "fmt" "net/http" @@ -107,10 +106,9 @@ func (m *Module) ReportResolvePOSTHandler(c *gin.Context) { return } - reportID := c.Param(IDKey) - if reportID == "" { - err := errors.New("no report id specified") - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + reportID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } diff --git a/internal/api/client/admin/reportresolve_test.go b/internal/api/client/admin/reportresolve_test.go index 087080a70..561661fe0 100644 --- a/internal/api/client/admin/reportresolve_test.go +++ b/internal/api/client/admin/reportresolve_test.go @@ -29,6 +29,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/admin" 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/gtsmodel" @@ -65,7 +66,7 @@ func (suite *ReportResolveTestSuite) resolveReport( // create the request ctx.Request = httptest.NewRequest(http.MethodPost, requestURI, nil) - ctx.AddParam(admin.IDKey, targetReportID) + ctx.AddParam(apiutil.IDKey, targetReportID) ctx.Request.Header.Set("accept", "application/json") if actionTakenComment != nil { ctx.Request.Form = url.Values{"action_taken_comment": {*actionTakenComment}} diff --git a/internal/api/client/admin/reportsget.go b/internal/api/client/admin/reportsget.go index 58501a6d7..893960e2a 100644 --- a/internal/api/client/admin/reportsget.go +++ b/internal/api/client/admin/reportsget.go @@ -20,12 +20,12 @@ package admin import ( "fmt" "net/http" - "strconv" "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" + "github.com/superseriousbusiness/gotosocial/internal/paging" ) // ReportsGETHandler swagger:operation GET /api/v1/admin/reports adminReports @@ -72,7 +72,7 @@ import ( // name: max_id // type: string // description: >- -// Return only reports *OLDER* than the given max ID. +// Return only reports *OLDER* than the given max ID (for paging downwards). // The report with the specified ID will not be included in the response. // in: query // - @@ -81,23 +81,21 @@ import ( // description: >- // Return only reports *NEWER* than the given since ID. // The report with the specified ID will not be included in the response. -// This parameter is functionally equivalent to min_id. // in: query // - // name: min_id // type: string // description: >- -// Return only reports *NEWER* than the given min ID. +// Return only reports immediately *NEWER* than the given min ID (for paging upwards). // The report with the specified ID will not be included in the response. -// This parameter is functionally equivalent to since_id. // in: query // - // name: limit // type: integer -// description: >- -// Number of reports to return. -// If more than 100 or less than 1, will be clamped to 100. +// description: Number of reports to return. // default: 20 +// minimum: 1 +// maximum: 100 // in: query // // security: @@ -144,34 +142,30 @@ func (m *Module) ReportsGETHandler(c *gin.Context) { return } - var resolved *bool - if resolvedString := c.Query(ResolvedKey); resolvedString != "" { - i, err := strconv.ParseBool(resolvedString) - if err != nil { - err := fmt.Errorf("error parsing %s: %s", ResolvedKey, err) - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) - return - } - resolved = &i + resolved, errWithCode := apiutil.ParseResolved(c.Query(apiutil.ResolvedKey), nil) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return } - limit := 20 - if limitString := c.Query(LimitKey); limitString != "" { - i, err := strconv.Atoi(limitString) - if err != nil { - err := fmt.Errorf("error parsing %s: %s", LimitKey, err) - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) - return - } - - // normalize - if i < 1 || i > 100 { - i = 100 - } - limit = i + page, errWithCode := paging.ParseIDPage(c, + 1, // min limit + 100, // max limit + 20, // default limit + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return } - resp, errWithCode := m.processor.Admin().ReportsGet(c.Request.Context(), authed.Account, resolved, c.Query(AccountIDKey), c.Query(TargetAccountIDKey), c.Query(MaxIDKey), c.Query(SinceIDKey), c.Query(MinIDKey), limit) + resp, errWithCode := m.processor.Admin().ReportsGet( + c.Request.Context(), + authed.Account, + resolved, + c.Query(apiutil.AccountIDKey), + c.Query(apiutil.TargetAccountIDKey), + page, + ) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/admin/reportsget_test.go b/internal/api/client/admin/reportsget_test.go index b20921b36..28efabc00 100644 --- a/internal/api/client/admin/reportsget_test.go +++ b/internal/api/client/admin/reportsget_test.go @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/admin" 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/gtsmodel" @@ -63,24 +64,24 @@ func (suite *ReportsGetTestSuite) getReports( ctx.Set(oauth.SessionAuthorizedUser, user) // create the request URI - requestPath := admin.ReportsPath + "?" + admin.LimitKey + "=" + strconv.Itoa(limit) + requestPath := admin.ReportsPath + "?" + apiutil.LimitKey + "=" + strconv.Itoa(limit) if resolved != nil { - requestPath = requestPath + "&" + admin.ResolvedKey + "=" + strconv.FormatBool(*resolved) + requestPath = requestPath + "&" + apiutil.ResolvedKey + "=" + strconv.FormatBool(*resolved) } if accountID != "" { - requestPath = requestPath + "&" + admin.AccountIDKey + "=" + accountID + requestPath = requestPath + "&" + apiutil.AccountIDKey + "=" + accountID } if targetAccountID != "" { - requestPath = requestPath + "&" + admin.TargetAccountIDKey + "=" + targetAccountID + requestPath = requestPath + "&" + apiutil.TargetAccountIDKey + "=" + targetAccountID } if maxID != "" { - requestPath = requestPath + "&" + admin.MaxIDKey + "=" + maxID + requestPath = requestPath + "&" + apiutil.MaxIDKey + "=" + maxID } if sinceID != "" { - requestPath = requestPath + "&" + admin.SinceIDKey + "=" + sinceID + requestPath = requestPath + "&" + apiutil.SinceIDKey + "=" + sinceID } if minID != "" { - requestPath = requestPath + "&" + admin.MinIDKey + "=" + minID + requestPath = requestPath + "&" + apiutil.MinIDKey + "=" + minID } baseURI := config.GetProtocol() + "://" + config.GetHost() requestURI := baseURI + "/api/" + requestPath @@ -766,7 +767,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetCreatedByAccount() { } ]`, string(b)) - suite.Equal(`<http://localhost:8080/api/v1/admin/reports?limit=20&max_id=01GP3AWY4CRDVRNZKW0TEAMB5R&account_id=01F8MH5NBDF2MV7CTC4Q5128HF>; rel="next", <http://localhost:8080/api/v1/admin/reports?limit=20&min_id=01GP3AWY4CRDVRNZKW0TEAMB5R&account_id=01F8MH5NBDF2MV7CTC4Q5128HF>; rel="prev"`, link) + suite.Equal(`<http://localhost:8080/api/v1/admin/reports?account_id=01F8MH5NBDF2MV7CTC4Q5128HF&limit=20&max_id=01GP3AWY4CRDVRNZKW0TEAMB5R>; rel="next", <http://localhost:8080/api/v1/admin/reports?account_id=01F8MH5NBDF2MV7CTC4Q5128HF&limit=20&min_id=01GP3AWY4CRDVRNZKW0TEAMB5R>; rel="prev"`, link) } func (suite *ReportsGetTestSuite) TestReportsGetTargetAccount() { @@ -1028,8 +1029,8 @@ func (suite *ReportsGetTestSuite) TestReportsGetZeroLimit() { suite.NoError(err) suite.Len(reports, 2) - // Limit in Link header should be set to 100 - suite.Equal(`<http://localhost:8080/api/v1/admin/reports?limit=100&max_id=01GP3AWY4CRDVRNZKW0TEAMB5R>; rel="next", <http://localhost:8080/api/v1/admin/reports?limit=100&min_id=01GP3DFY9XQ1TJMZT5BGAZPXX7>; rel="prev"`, link) + // Limit in Link header should be set to default (20) + suite.Equal(`<http://localhost:8080/api/v1/admin/reports?limit=20&max_id=01GP3AWY4CRDVRNZKW0TEAMB5R>; rel="next", <http://localhost:8080/api/v1/admin/reports?limit=20&min_id=01GP3DFY9XQ1TJMZT5BGAZPXX7>; rel="prev"`, link) } func (suite *ReportsGetTestSuite) TestReportsGetHighLimit() { diff --git a/internal/api/client/admin/ruledelete.go b/internal/api/client/admin/ruledelete.go index ead219e34..7e8fc0037 100644 --- a/internal/api/client/admin/ruledelete.go +++ b/internal/api/client/admin/ruledelete.go @@ -18,7 +18,6 @@ package admin import ( - "errors" "fmt" "net/http" @@ -95,10 +94,9 @@ func (m *Module) RuleDELETEHandler(c *gin.Context) { return } - ruleID := c.Param(IDKey) - if ruleID == "" { - err := errors.New("no rule id specified") - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + ruleID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } diff --git a/internal/api/client/admin/ruleget.go b/internal/api/client/admin/ruleget.go index 8281092fb..28c0599f5 100644 --- a/internal/api/client/admin/ruleget.go +++ b/internal/api/client/admin/ruleget.go @@ -18,7 +18,6 @@ package admin import ( - "errors" "fmt" "net/http" @@ -85,10 +84,9 @@ func (m *Module) RuleGETHandler(c *gin.Context) { return } - ruleID := c.Param(IDKey) - if ruleID == "" { - err := errors.New("no rule id specified") - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + ruleID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } diff --git a/internal/api/client/admin/ruleupdate.go b/internal/api/client/admin/ruleupdate.go index bf838f7ae..d58c30d94 100644 --- a/internal/api/client/admin/ruleupdate.go +++ b/internal/api/client/admin/ruleupdate.go @@ -18,7 +18,6 @@ package admin import ( - "errors" "fmt" "net/http" @@ -87,10 +86,9 @@ func (m *Module) RulePATCHHandler(c *gin.Context) { return } - ruleID := c.Param(IDKey) - if ruleID == "" { - err := errors.New("no rule id specified") - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + ruleID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } diff --git a/internal/api/client/reports/reportget.go b/internal/api/client/reports/reportget.go index 4a9b06664..c9ca0054f 100644 --- a/internal/api/client/reports/reportget.go +++ b/internal/api/client/reports/reportget.go @@ -18,7 +18,6 @@ package reports import ( - "errors" "net/http" "github.com/gin-gonic/gin" @@ -77,10 +76,9 @@ func (m *Module) ReportGETHandler(c *gin.Context) { return } - targetReportID := c.Param(IDKey) - if targetReportID == "" { - err := errors.New("no report id specified") - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + targetReportID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } diff --git a/internal/api/client/reports/reportget_test.go b/internal/api/client/reports/reportget_test.go index 5b6c406f7..4f01101b2 100644 --- a/internal/api/client/reports/reportget_test.go +++ b/internal/api/client/reports/reportget_test.go @@ -145,7 +145,7 @@ func (suite *ReportGetTestSuite) TestGetReport2() { } func (suite *ReportGetTestSuite) TestGetReport3() { - report, err := suite.getReport(http.StatusBadRequest, `{"error":"Bad Request: no report id specified"}`, "") + report, err := suite.getReport(http.StatusBadRequest, `{"error":"Bad Request: required key id was not set or had empty value"}`, "") suite.NoError(err) suite.Nil(report) } diff --git a/internal/api/client/reports/reports.go b/internal/api/client/reports/reports.go index e881474fb..b10697c1f 100644 --- a/internal/api/client/reports/reports.go +++ b/internal/api/client/reports/reports.go @@ -21,19 +21,13 @@ import ( "net/http" "github.com/gin-gonic/gin" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/processing" ) const ( - BasePath = "/v1/reports" - IDKey = "id" - ResolvedKey = "resolved" - TargetAccountIDKey = "target_account_id" - MaxIDKey = "max_id" - SinceIDKey = "since_id" - MinIDKey = "min_id" - LimitKey = "limit" - BasePathWithID = BasePath + "/:" + IDKey + BasePath = "/v1/reports" + BasePathWithID = BasePath + "/:" + apiutil.IDKey ) type Module struct { diff --git a/internal/api/client/reports/reportsget.go b/internal/api/client/reports/reportsget.go index 5f194a589..4c3d4e33a 100644 --- a/internal/api/client/reports/reportsget.go +++ b/internal/api/client/reports/reportsget.go @@ -18,14 +18,13 @@ package reports import ( - "fmt" "net/http" - "strconv" "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" + "github.com/superseriousbusiness/gotosocial/internal/paging" ) // ReportsGETHandler swagger:operation GET /api/v1/reports reports @@ -67,7 +66,7 @@ import ( // name: max_id // type: string // description: >- -// Return only reports *OLDER* than the given max ID. +// Return only reports *OLDER* than the given max ID (for paging downwards). // The report with the specified ID will not be included in the response. // in: query // - @@ -76,24 +75,21 @@ import ( // description: >- // Return only reports *NEWER* than the given since ID. // The report with the specified ID will not be included in the response. -// This parameter is functionally equivalent to min_id. // in: query // - // name: min_id // type: string // description: >- -// Return only reports *NEWER* than the given min ID. +// Return only reports immediately *NEWER* than the given min ID (for paging upwards). // The report with the specified ID will not be included in the response. -// This parameter is functionally equivalent to since_id. // in: query // - // name: limit // type: integer -// description: >- -// Number of reports to return. -// If less than 1, will be clamped to 1. -// If more than 100, will be clamped to 100. +// description: Number of reports to return. // default: 20 +// minimum: 1 +// maximum: 100 // in: query // // security: @@ -134,36 +130,29 @@ func (m *Module) ReportsGETHandler(c *gin.Context) { return } - var resolved *bool - if resolvedString := c.Query(ResolvedKey); resolvedString != "" { - i, err := strconv.ParseBool(resolvedString) - if err != nil { - err := fmt.Errorf("error parsing %s: %s", ResolvedKey, err) - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) - return - } - resolved = &i + resolved, errWithCode := apiutil.ParseResolved(c.Query(apiutil.ResolvedKey), nil) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return } - limit := 20 - if limitString := c.Query(LimitKey); limitString != "" { - i, err := strconv.Atoi(limitString) - if err != nil { - err := fmt.Errorf("error parsing %s: %s", LimitKey, err) - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) - return - } - - // normalize - if i <= 0 { - i = 1 - } else if i >= 100 { - i = 100 - } - limit = i + page, errWithCode := paging.ParseIDPage(c, + 1, // min limit + 100, // max limit + 20, // default limit + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return } - resp, errWithCode := m.processor.Report().GetMultiple(c.Request.Context(), authed.Account, resolved, c.Query(TargetAccountIDKey), c.Query(MaxIDKey), c.Query(SinceIDKey), c.Query(MinIDKey), limit) + resp, errWithCode := m.processor.Report().GetMultiple( + c.Request.Context(), + authed.Account, + resolved, + c.Query(apiutil.TargetAccountIDKey), + page, + ) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/reports/reportsget_test.go b/internal/api/client/reports/reportsget_test.go index c63d6c894..2413292a0 100644 --- a/internal/api/client/reports/reportsget_test.go +++ b/internal/api/client/reports/reportsget_test.go @@ -29,6 +29,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/reports" 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/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -61,21 +62,21 @@ func (suite *ReportsGetTestSuite) getReports( ctx.Set(oauth.SessionAuthorizedUser, user) // create the request URI - requestPath := reports.BasePath + "?" + reports.LimitKey + "=" + strconv.Itoa(limit) + requestPath := reports.BasePath + "?" + apiutil.LimitKey + "=" + strconv.Itoa(limit) if resolved != nil { - requestPath = requestPath + "&" + reports.ResolvedKey + "=" + strconv.FormatBool(*resolved) + requestPath = requestPath + "&" + apiutil.ResolvedKey + "=" + strconv.FormatBool(*resolved) } if targetAccountID != "" { - requestPath = requestPath + "&" + reports.TargetAccountIDKey + "=" + targetAccountID + requestPath = requestPath + "&" + apiutil.TargetAccountIDKey + "=" + targetAccountID } if maxID != "" { - requestPath = requestPath + "&" + reports.MaxIDKey + "=" + maxID + requestPath = requestPath + "&" + apiutil.MaxIDKey + "=" + maxID } if sinceID != "" { - requestPath = requestPath + "&" + reports.SinceIDKey + "=" + sinceID + requestPath = requestPath + "&" + apiutil.SinceIDKey + "=" + sinceID } if minID != "" { - requestPath = requestPath + "&" + reports.MinIDKey + "=" + minID + requestPath = requestPath + "&" + apiutil.MinIDKey + "=" + minID } baseURI := config.GetProtocol() + "://" + config.GetHost() requestURI := baseURI + "/api/" + requestPath diff --git a/internal/api/client/search/searchget.go b/internal/api/client/search/searchget.go index d7ab81388..0f9595efc 100644 --- a/internal/api/client/search/searchget.go +++ b/internal/api/client/search/searchget.go @@ -247,7 +247,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) { Resolve: resolve, Following: following, ExcludeUnreviewed: excludeUnreviewed, - AccountID: c.Query(apiutil.SearchAccountIDKey), + AccountID: c.Query(apiutil.AccountIDKey), APIv1: apiVersion == apiutil.APIv1, } diff --git a/internal/api/client/search/searchget_test.go b/internal/api/client/search/searchget_test.go index a0d0cad0e..27e5f782d 100644 --- a/internal/api/client/search/searchget_test.go +++ b/internal/api/client/search/searchget_test.go @@ -105,7 +105,7 @@ func (suite *SearchGetTestSuite) getSearch( } if fromAccountID != nil { - queryParts = append(queryParts, apiutil.SearchAccountIDKey+"="+url.QueryEscape(*fromAccountID)) + queryParts = append(queryParts, apiutil.AccountIDKey+"="+url.QueryEscape(*fromAccountID)) } requestURL.RawQuery = strings.Join(queryParts, "&") diff --git a/internal/api/util/parsequery.go b/internal/api/util/parsequery.go index 5210735a1..90cc30e6f 100644 --- a/internal/api/util/parsequery.go +++ b/internal/api/util/parsequery.go @@ -34,13 +34,16 @@ const ( /* Common keys */ - IDKey = "id" - LimitKey = "limit" - LocalKey = "local" - MaxIDKey = "max_id" - SinceIDKey = "since_id" - MinIDKey = "min_id" - UsernameKey = "username" + IDKey = "id" + LimitKey = "limit" + LocalKey = "local" + MaxIDKey = "max_id" + SinceIDKey = "since_id" + MinIDKey = "min_id" + UsernameKey = "username" + AccountIDKey = "account_id" + TargetAccountIDKey = "target_account_id" + ResolvedKey = "resolved" /* AP endpoint keys */ @@ -55,7 +58,6 @@ const ( SearchQueryKey = "q" SearchResolveKey = "resolve" SearchTypeKey = "type" - SearchAccountIDKey = "account_id" /* Tag keys */ @@ -132,6 +134,10 @@ func ParseLocal(value string, defaultValue bool) (bool, gtserror.WithCode) { return parseBool(value, defaultValue, LocalKey) } +func ParseResolved(value string, defaultValue *bool) (*bool, gtserror.WithCode) { + return parseBoolPtr(value, defaultValue, ResolvedKey) +} + func ParseSearchExcludeUnreviewed(value string, defaultValue bool) (bool, gtserror.WithCode) { return parseBool(value, defaultValue, SearchExcludeUnreviewedKey) } @@ -289,6 +295,19 @@ func parseBool(value string, defaultValue bool, key string) (bool, gtserror.With return i, nil } +func parseBoolPtr(value string, defaultValue *bool, key string) (*bool, gtserror.WithCode) { + if value == "" { + return defaultValue, nil + } + + i, err := strconv.ParseBool(value) + if err != nil { + return defaultValue, parseError(key, value, defaultValue, err) + } + + return &i, nil +} + func parseInt(value string, defaultValue int, max int, min int, key string) (int, gtserror.WithCode) { if value == "" { return defaultValue, nil |