mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-29 13:22:25 -05:00
[feature] Instance rules (#2125)
* init instance rules database model, admin api * expose instance rules in public instance api * public /api/v1/instance/rules route * GET ruleById * createRule route * createRule auth check * updateRule * deleteRule * list rules on about page * ruleGet auth * add about page ids for anchors * process and store adding violated rules to reports * admin api models for instance rules * instance rule edit frontend * change rule inputs to textareas * database fixes after rebase (#2124) * remove unused imports * fix db migration column name * fix tests * fix more tests * fix postgres error with wrongly used Ident * add some tests, fiddle with rule model a bit, fix postgres migration * swagger docs --------- Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
This commit is contained in:
parent
d5d6ad406f
commit
92de8fb396
49 changed files with 2189 additions and 107 deletions
|
|
@ -25,22 +25,24 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
BasePath = "/v1/admin"
|
||||
EmojiPath = BasePath + "/custom_emojis"
|
||||
EmojiPathWithID = EmojiPath + "/:" + IDKey
|
||||
EmojiCategoriesPath = EmojiPath + "/categories"
|
||||
DomainBlocksPath = BasePath + "/domain_blocks"
|
||||
DomainBlocksPathWithID = DomainBlocksPath + "/:" + IDKey
|
||||
AccountsPath = BasePath + "/accounts"
|
||||
AccountsPathWithID = AccountsPath + "/:" + IDKey
|
||||
AccountsActionPath = AccountsPathWithID + "/action"
|
||||
MediaCleanupPath = BasePath + "/media_cleanup"
|
||||
MediaRefetchPath = BasePath + "/media_refetch"
|
||||
ReportsPath = BasePath + "/reports"
|
||||
ReportsPathWithID = ReportsPath + "/:" + IDKey
|
||||
ReportsResolvePath = ReportsPathWithID + "/resolve"
|
||||
EmailPath = BasePath + "/email"
|
||||
EmailTestPath = EmailPath + "/test"
|
||||
BasePath = "/v1/admin"
|
||||
EmojiPath = BasePath + "/custom_emojis"
|
||||
EmojiPathWithID = EmojiPath + "/:" + IDKey
|
||||
EmojiCategoriesPath = EmojiPath + "/categories"
|
||||
DomainBlocksPath = BasePath + "/domain_blocks"
|
||||
DomainBlocksPathWithID = DomainBlocksPath + "/:" + IDKey
|
||||
AccountsPath = BasePath + "/accounts"
|
||||
AccountsPathWithID = AccountsPath + "/:" + IDKey
|
||||
AccountsActionPath = AccountsPathWithID + "/action"
|
||||
MediaCleanupPath = BasePath + "/media_cleanup"
|
||||
MediaRefetchPath = BasePath + "/media_refetch"
|
||||
ReportsPath = BasePath + "/reports"
|
||||
ReportsPathWithID = ReportsPath + "/:" + IDKey
|
||||
ReportsResolvePath = ReportsPathWithID + "/resolve"
|
||||
EmailPath = BasePath + "/email"
|
||||
EmailTestPath = EmailPath + "/test"
|
||||
InstanceRulesPath = BasePath + "/instance/rules"
|
||||
InstanceRulesPathWithID = InstanceRulesPath + "/:" + IDKey
|
||||
|
||||
IDKey = "id"
|
||||
FilterQueryKey = "filter"
|
||||
|
|
@ -95,4 +97,11 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H
|
|||
|
||||
// email stuff
|
||||
attachHandler(http.MethodPost, EmailTestPath, m.EmailTestPOSTHandler)
|
||||
|
||||
// instance rules stuff
|
||||
attachHandler(http.MethodGet, InstanceRulesPath, m.RulesGETHandler)
|
||||
attachHandler(http.MethodGet, InstanceRulesPathWithID, m.RuleGETHandler)
|
||||
attachHandler(http.MethodPost, InstanceRulesPath, m.RulePOSTHandler)
|
||||
attachHandler(http.MethodPatch, InstanceRulesPathWithID, m.RulePATCHHandler)
|
||||
attachHandler(http.MethodDelete, InstanceRulesPathWithID, m.RuleDELETEHandler)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() {
|
|||
"created_by_application_id": "01F8MGXQRHYF5QPMTMXP78QC2F"
|
||||
},
|
||||
"statuses": [],
|
||||
"rule_ids": [],
|
||||
"rules": [],
|
||||
"action_taken_comment": "user was warned not to be a turtle anymore"
|
||||
},
|
||||
{
|
||||
|
|
@ -528,7 +528,16 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() {
|
|||
"poll": null
|
||||
}
|
||||
],
|
||||
"rule_ids": [],
|
||||
"rules": [
|
||||
{
|
||||
"id": "01GP3AWY4CRDVRNZKW0TEAMB51",
|
||||
"text": "Be gay"
|
||||
},
|
||||
{
|
||||
"id": "01GP3DFY9XQ1TJMZT5BGAZPXX3",
|
||||
"text": "Do crime"
|
||||
}
|
||||
],
|
||||
"action_taken_comment": null
|
||||
}
|
||||
]`, string(b))
|
||||
|
|
@ -740,7 +749,16 @@ func (suite *ReportsGetTestSuite) TestReportsGetCreatedByAccount() {
|
|||
"poll": null
|
||||
}
|
||||
],
|
||||
"rule_ids": [],
|
||||
"rules": [
|
||||
{
|
||||
"id": "01GP3AWY4CRDVRNZKW0TEAMB51",
|
||||
"text": "Be gay"
|
||||
},
|
||||
{
|
||||
"id": "01GP3DFY9XQ1TJMZT5BGAZPXX3",
|
||||
"text": "Do crime"
|
||||
}
|
||||
],
|
||||
"action_taken_comment": null
|
||||
}
|
||||
]`, string(b))
|
||||
|
|
@ -952,7 +970,16 @@ func (suite *ReportsGetTestSuite) TestReportsGetTargetAccount() {
|
|||
"poll": null
|
||||
}
|
||||
],
|
||||
"rule_ids": [],
|
||||
"rules": [
|
||||
{
|
||||
"id": "01GP3AWY4CRDVRNZKW0TEAMB51",
|
||||
"text": "Be gay"
|
||||
},
|
||||
{
|
||||
"id": "01GP3DFY9XQ1TJMZT5BGAZPXX3",
|
||||
"text": "Do crime"
|
||||
}
|
||||
],
|
||||
"action_taken_comment": null
|
||||
}
|
||||
]`, string(b))
|
||||
|
|
|
|||
120
internal/api/client/admin/rulecreate.go
Normal file
120
internal/api/client/admin/rulecreate.go
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 admin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
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"
|
||||
)
|
||||
|
||||
// RulePOSTHandler swagger:operation POST /api/v1/admin/instance/rules ruleCreate
|
||||
//
|
||||
// Create a new instance rule.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// consumes:
|
||||
// - multipart/form-data
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: text
|
||||
// in: formData
|
||||
// description: >-
|
||||
// Text body for the instance rule, plaintext.
|
||||
// type: string
|
||||
// required: true
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: The newly-created instance rule.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/instanceRule"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) RulePOSTHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if !*authed.User.Admin {
|
||||
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
form := &apimodel.InstanceRuleCreateRequest{}
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateCreateRule(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiRule, errWithCode := m.processor.Admin().RuleCreate(c.Request.Context(), form)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, apiRule)
|
||||
}
|
||||
|
||||
func validateCreateRule(form *apimodel.InstanceRuleCreateRequest) error {
|
||||
if form.Text == "" {
|
||||
return errors.New("Instance rule text is empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
107
internal/api/client/admin/ruledelete.go
Normal file
107
internal/api/client/admin/ruledelete.go
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 admin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
// RuleDELETEHandler swagger:operation DELETE /api/v1/admin/instance/rules{id} ruleDelete
|
||||
//
|
||||
// Delete an existing instance rule.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// consumes:
|
||||
// - multipart/form-data
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// in: formData
|
||||
// description: >-
|
||||
// The id of the rule to delete.
|
||||
// type: path
|
||||
// required: true
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: The deleted instance rule.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/instanceRule"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) RuleDELETEHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if !*authed.User.Admin {
|
||||
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
apiRule, errWithCode := m.processor.Admin().RuleDelete(c.Request.Context(), ruleID)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, apiRule)
|
||||
}
|
||||
102
internal/api/client/admin/ruleget.go
Normal file
102
internal/api/client/admin/ruleget.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 admin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
// RuleGETHandler swagger:operation GET /api/v1/admin/rules/{id} adminRuleGet
|
||||
//
|
||||
// View instance rule with the given id.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// type: string
|
||||
// description: The id of the rule.
|
||||
// in: path
|
||||
// required: true
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// name: rule
|
||||
// description: The requested rule.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/instanceRule"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) RuleGETHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if !*authed.User.Admin {
|
||||
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
rule, errWithCode := m.processor.Admin().RuleGet(c.Request.Context(), ruleID)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, rule)
|
||||
}
|
||||
91
internal/api/client/admin/rulesget.go
Normal file
91
internal/api/client/admin/rulesget.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
// rulesGETHandler swagger:operation GET /api/v1/admin/rules rules
|
||||
//
|
||||
// View instance rules, with IDs.
|
||||
//
|
||||
// The rules will be returned in order (sorted by Order ascending).
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: An array with all the rules for the local instance.
|
||||
// schema:
|
||||
// type: array
|
||||
// items:
|
||||
// "$ref": "#/definitions/instanceRule"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) RulesGETHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if !*authed.User.Admin {
|
||||
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
resp, errWithCode := m.processor.Admin().RulesGet(c.Request.Context())
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
127
internal/api/client/admin/ruleupdate.go
Normal file
127
internal/api/client/admin/ruleupdate.go
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 admin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
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"
|
||||
)
|
||||
|
||||
// RulePATCHHandler swagger:operation PATCH /api/v1/admin/instance/rules{id} ruleUpdate
|
||||
//
|
||||
// Update an existing instance rule.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// consumes:
|
||||
// - multipart/form-data
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// in: formData
|
||||
// description: >-
|
||||
// The id of the rule to update.
|
||||
// type: path
|
||||
// required: true
|
||||
// -
|
||||
// name: text
|
||||
// in: formData
|
||||
// description: >-
|
||||
// Text body for the updated instance rule, plaintext.
|
||||
// type: string
|
||||
// required: true
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: The updated instance rule.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/instanceRule"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) RulePATCHHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if !*authed.User.Admin {
|
||||
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
form := &apimodel.InstanceRuleCreateRequest{}
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
// reuses CreateRule validator
|
||||
if err := validateCreateRule(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiRule, errWithCode := m.processor.Admin().RuleUpdate(c.Request.Context(), ruleID, form)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, apiRule)
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ const (
|
|||
InstanceInformationPathV1 = "/v1/instance"
|
||||
InstanceInformationPathV2 = "/v2/instance"
|
||||
InstancePeersPath = InstanceInformationPathV1 + "/peers"
|
||||
InstanceRulesPath = InstanceInformationPathV1 + "/rules"
|
||||
PeersFilterKey = "filter" // PeersFilterKey is used to provide filters to /api/v1/instance/peers
|
||||
)
|
||||
|
||||
|
|
@ -47,4 +48,6 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H
|
|||
|
||||
attachHandler(http.MethodPatch, InstanceInformationPathV1, m.InstanceUpdatePATCHHandler)
|
||||
attachHandler(http.MethodGet, InstancePeersPath, m.InstancePeersGETHandler)
|
||||
|
||||
attachHandler(http.MethodGet, InstanceRulesPath, m.InstanceRulesGETHandler)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,7 +160,17 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() {
|
|||
"name": "admin"
|
||||
}
|
||||
},
|
||||
"max_toot_chars": 5000
|
||||
"max_toot_chars": 5000,
|
||||
"rules": [
|
||||
{
|
||||
"id": "01GP3AWY4CRDVRNZKW0TEAMB51",
|
||||
"text": "Be gay"
|
||||
},
|
||||
{
|
||||
"id": "01GP3DFY9XQ1TJMZT5BGAZPXX3",
|
||||
"text": "Do crime"
|
||||
}
|
||||
]
|
||||
}`, dst.String())
|
||||
}
|
||||
|
||||
|
|
@ -264,7 +274,17 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() {
|
|||
"name": "admin"
|
||||
}
|
||||
},
|
||||
"max_toot_chars": 5000
|
||||
"max_toot_chars": 5000,
|
||||
"rules": [
|
||||
{
|
||||
"id": "01GP3AWY4CRDVRNZKW0TEAMB51",
|
||||
"text": "Be gay"
|
||||
},
|
||||
{
|
||||
"id": "01GP3DFY9XQ1TJMZT5BGAZPXX3",
|
||||
"text": "Do crime"
|
||||
}
|
||||
]
|
||||
}`, dst.String())
|
||||
}
|
||||
|
||||
|
|
@ -368,7 +388,17 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() {
|
|||
"name": "admin"
|
||||
}
|
||||
},
|
||||
"max_toot_chars": 5000
|
||||
"max_toot_chars": 5000,
|
||||
"rules": [
|
||||
{
|
||||
"id": "01GP3AWY4CRDVRNZKW0TEAMB51",
|
||||
"text": "Be gay"
|
||||
},
|
||||
{
|
||||
"id": "01GP3DFY9XQ1TJMZT5BGAZPXX3",
|
||||
"text": "Do crime"
|
||||
}
|
||||
]
|
||||
}`, dst.String())
|
||||
}
|
||||
|
||||
|
|
@ -523,7 +553,17 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() {
|
|||
"name": "admin"
|
||||
}
|
||||
},
|
||||
"max_toot_chars": 5000
|
||||
"max_toot_chars": 5000,
|
||||
"rules": [
|
||||
{
|
||||
"id": "01GP3AWY4CRDVRNZKW0TEAMB51",
|
||||
"text": "Be gay"
|
||||
},
|
||||
{
|
||||
"id": "01GP3DFY9XQ1TJMZT5BGAZPXX3",
|
||||
"text": "Do crime"
|
||||
}
|
||||
]
|
||||
}`, dst.String())
|
||||
}
|
||||
|
||||
|
|
@ -651,7 +691,17 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() {
|
|||
"name": "admin"
|
||||
}
|
||||
},
|
||||
"max_toot_chars": 5000
|
||||
"max_toot_chars": 5000,
|
||||
"rules": [
|
||||
{
|
||||
"id": "01GP3AWY4CRDVRNZKW0TEAMB51",
|
||||
"text": "Be gay"
|
||||
},
|
||||
{
|
||||
"id": "01GP3DFY9XQ1TJMZT5BGAZPXX3",
|
||||
"text": "Do crime"
|
||||
}
|
||||
]
|
||||
}`, dst.String())
|
||||
|
||||
// extra bonus: check the v2 model thumbnail after the patch
|
||||
|
|
@ -790,7 +840,17 @@ func (suite *InstancePatchTestSuite) TestInstancePatch9() {
|
|||
"name": "admin"
|
||||
}
|
||||
},
|
||||
"max_toot_chars": 5000
|
||||
"max_toot_chars": 5000,
|
||||
"rules": [
|
||||
{
|
||||
"id": "01GP3AWY4CRDVRNZKW0TEAMB51",
|
||||
"text": "Be gay"
|
||||
},
|
||||
{
|
||||
"id": "01GP3DFY9XQ1TJMZT5BGAZPXX3",
|
||||
"text": "Do crime"
|
||||
}
|
||||
]
|
||||
}`, dst.String())
|
||||
}
|
||||
|
||||
|
|
|
|||
71
internal/api/client/instance/instancerulesget.go
Normal file
71
internal/api/client/instance/instancerulesget.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 instance
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
)
|
||||
|
||||
// instanceRulesGETHandler swagger:operation GET /api/v1/instance/rules rules
|
||||
//
|
||||
// View instance rules (public).
|
||||
//
|
||||
// The rules will be returned in order (sorted by Order ascending).
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - instance
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: An array with all the rules for the local instance.
|
||||
// schema:
|
||||
// type: array
|
||||
// items:
|
||||
// "$ref": "#/definitions/instanceRule"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) InstanceRulesGETHandler(c *gin.Context) {
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
resp, errWithCode := m.processor.InstanceGetRules(c.Request.Context())
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
|
@ -51,17 +51,13 @@ func (suite *ReportCreateTestSuite) createReport(expectedHTTPStatus int, expecte
|
|||
// create the request
|
||||
ctx.Request = httptest.NewRequest(http.MethodPost, config.GetProtocol()+"://"+config.GetHost()+"/api/"+reports.BasePath, nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
ruleIDs := make([]string, 0, len(form.RuleIDs))
|
||||
for _, r := range form.RuleIDs {
|
||||
ruleIDs = append(ruleIDs, strconv.Itoa(r))
|
||||
}
|
||||
ctx.Request.Form = url.Values{
|
||||
"account_id": {form.AccountID},
|
||||
"status_ids[]": form.StatusIDs,
|
||||
"comment": {form.Comment},
|
||||
"forward": {strconv.FormatBool(form.Forward)},
|
||||
"category": {form.Category},
|
||||
"rule_ids[]": ruleIDs,
|
||||
"rule_ids[]": form.RuleIDs,
|
||||
}
|
||||
|
||||
// trigger the handler
|
||||
|
|
|
|||
|
|
@ -108,7 +108,10 @@ func (suite *ReportGetTestSuite) TestGetReport1() {
|
|||
"status_ids": [
|
||||
"01FVW7JHQFSFK166WWKR8CBA6M"
|
||||
],
|
||||
"rule_ids": [],
|
||||
"rule_ids": [
|
||||
"01GP3AWY4CRDVRNZKW0TEAMB51",
|
||||
"01GP3DFY9XQ1TJMZT5BGAZPXX3"
|
||||
],
|
||||
"target_account": {
|
||||
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
"username": "foss_satan",
|
||||
|
|
|
|||
|
|
@ -133,7 +133,10 @@ func (suite *ReportsGetTestSuite) TestGetReports() {
|
|||
"status_ids": [
|
||||
"01FVW7JHQFSFK166WWKR8CBA6M"
|
||||
],
|
||||
"rule_ids": [],
|
||||
"rule_ids": [
|
||||
"01GP3AWY4CRDVRNZKW0TEAMB51",
|
||||
"01GP3DFY9XQ1TJMZT5BGAZPXX3"
|
||||
],
|
||||
"target_account": {
|
||||
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
"username": "foss_satan",
|
||||
|
|
@ -220,7 +223,10 @@ func (suite *ReportsGetTestSuite) TestGetReports4() {
|
|||
"status_ids": [
|
||||
"01FVW7JHQFSFK166WWKR8CBA6M"
|
||||
],
|
||||
"rule_ids": [],
|
||||
"rule_ids": [
|
||||
"01GP3AWY4CRDVRNZKW0TEAMB51",
|
||||
"01GP3DFY9XQ1TJMZT5BGAZPXX3"
|
||||
],
|
||||
"target_account": {
|
||||
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
"username": "foss_satan",
|
||||
|
|
@ -291,7 +297,10 @@ func (suite *ReportsGetTestSuite) TestGetReports6() {
|
|||
"status_ids": [
|
||||
"01FVW7JHQFSFK166WWKR8CBA6M"
|
||||
],
|
||||
"rule_ids": [],
|
||||
"rule_ids": [
|
||||
"01GP3AWY4CRDVRNZKW0TEAMB51",
|
||||
"01GP3DFY9XQ1TJMZT5BGAZPXX3"
|
||||
],
|
||||
"target_account": {
|
||||
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
"username": "foss_satan",
|
||||
|
|
@ -346,7 +355,10 @@ func (suite *ReportsGetTestSuite) TestGetReports7() {
|
|||
"status_ids": [
|
||||
"01FVW7JHQFSFK166WWKR8CBA6M"
|
||||
],
|
||||
"rule_ids": [],
|
||||
"rule_ids": [
|
||||
"01GP3AWY4CRDVRNZKW0TEAMB51",
|
||||
"01GP3DFY9XQ1TJMZT5BGAZPXX3"
|
||||
],
|
||||
"target_account": {
|
||||
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
"username": "foss_satan",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue