summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorLibravatar f0x52 <f0x@cthu.lu>2023-08-19 14:33:15 +0200
committerLibravatar GitHub <noreply@github.com>2023-08-19 14:33:15 +0200
commit92de8fb396265d057f18aab4de0bc8aff4b90188 (patch)
tree46438b9ff550261f56aa58d038cdf2f1e15493e3 /internal
parent[bugfix] fix double firing bun.DB query hooks (#2124) (diff)
downloadgotosocial-92de8fb396265d057f18aab4de0bc8aff4b90188.tar.xz
[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>
Diffstat (limited to 'internal')
-rw-r--r--internal/api/client/admin/admin.go41
-rw-r--r--internal/api/client/admin/reportsget_test.go35
-rw-r--r--internal/api/client/admin/rulecreate.go120
-rw-r--r--internal/api/client/admin/ruledelete.go107
-rw-r--r--internal/api/client/admin/ruleget.go102
-rw-r--r--internal/api/client/admin/rulesget.go91
-rw-r--r--internal/api/client/admin/ruleupdate.go127
-rw-r--r--internal/api/client/instance/instance.go3
-rw-r--r--internal/api/client/instance/instancepatch_test.go72
-rw-r--r--internal/api/client/instance/instancerulesget.go71
-rw-r--r--internal/api/client/reports/reportcreate_test.go6
-rw-r--r--internal/api/client/reports/reportget_test.go5
-rw-r--r--internal/api/client/reports/reportsget_test.go20
-rw-r--r--internal/api/model/admin.go13
-rw-r--r--internal/api/model/instancev1.go2
-rw-r--r--internal/api/model/instancev2.go5
-rw-r--r--internal/api/model/report.go9
-rw-r--r--internal/api/model/rule.go41
-rw-r--r--internal/db/bundb/bundb.go5
-rw-r--r--internal/db/bundb/bundb_test.go2
-rw-r--r--internal/db/bundb/instance.go10
-rw-r--r--internal/db/bundb/migrations/20230815164500_rules_model.go47
-rw-r--r--internal/db/bundb/migrations/20230817174700_add_report_rule_ids.go53
-rw-r--r--internal/db/bundb/report.go13
-rw-r--r--internal/db/bundb/rule.go149
-rw-r--r--internal/db/bundb/rule_test.go122
-rw-r--r--internal/db/db.go1
-rw-r--r--internal/db/rule.go42
-rw-r--r--internal/gtsmodel/instance.go1
-rw-r--r--internal/gtsmodel/report.go2
-rw-r--r--internal/gtsmodel/rule.go30
-rw-r--r--internal/processing/admin/rule.go127
-rw-r--r--internal/processing/instance.go9
-rw-r--r--internal/processing/report/create.go9
-rw-r--r--internal/typeutils/converter.go4
-rw-r--r--internal/typeutils/internaltofrontend.go47
-rw-r--r--internal/typeutils/internaltofrontend_test.go24
37 files changed, 1512 insertions, 55 deletions
diff --git a/internal/api/client/admin/admin.go b/internal/api/client/admin/admin.go
index a6c825b2b..ce6604c29 100644
--- a/internal/api/client/admin/admin.go
+++ b/internal/api/client/admin/admin.go
@@ -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)
}
diff --git a/internal/api/client/admin/reportsget_test.go b/internal/api/client/admin/reportsget_test.go
index 943e9711a..4c714a9e0 100644
--- a/internal/api/client/admin/reportsget_test.go
+++ b/internal/api/client/admin/reportsget_test.go
@@ -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))
diff --git a/internal/api/client/admin/rulecreate.go b/internal/api/client/admin/rulecreate.go
new file mode 100644
index 000000000..7792233f6
--- /dev/null
+++ b/internal/api/client/admin/rulecreate.go
@@ -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
+}
diff --git a/internal/api/client/admin/ruledelete.go b/internal/api/client/admin/ruledelete.go
new file mode 100644
index 000000000..7281ed62e
--- /dev/null
+++ b/internal/api/client/admin/ruledelete.go
@@ -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)
+}
diff --git a/internal/api/client/admin/ruleget.go b/internal/api/client/admin/ruleget.go
new file mode 100644
index 000000000..444820a3f
--- /dev/null
+++ b/internal/api/client/admin/ruleget.go
@@ -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)
+}
diff --git a/internal/api/client/admin/rulesget.go b/internal/api/client/admin/rulesget.go
new file mode 100644
index 000000000..56f83866f
--- /dev/null
+++ b/internal/api/client/admin/rulesget.go
@@ -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)
+}
diff --git a/internal/api/client/admin/ruleupdate.go b/internal/api/client/admin/ruleupdate.go
new file mode 100644
index 000000000..82ed41190
--- /dev/null
+++ b/internal/api/client/admin/ruleupdate.go
@@ -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)
+}
diff --git a/internal/api/client/instance/instance.go b/internal/api/client/instance/instance.go
index 8c58b62aa..82f6a4714 100644
--- a/internal/api/client/instance/instance.go
+++ b/internal/api/client/instance/instance.go
@@ -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)
}
diff --git a/internal/api/client/instance/instancepatch_test.go b/internal/api/client/instance/instancepatch_test.go
index 11382f83a..a402f8347 100644
--- a/internal/api/client/instance/instancepatch_test.go
+++ b/internal/api/client/instance/instancepatch_test.go
@@ -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())
}
diff --git a/internal/api/client/instance/instancerulesget.go b/internal/api/client/instance/instancerulesget.go
new file mode 100644
index 000000000..5cc99ba41
--- /dev/null
+++ b/internal/api/client/instance/instancerulesget.go
@@ -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)
+}
diff --git a/internal/api/client/reports/reportcreate_test.go b/internal/api/client/reports/reportcreate_test.go
index e17695cb9..35dc3d015 100644
--- a/internal/api/client/reports/reportcreate_test.go
+++ b/internal/api/client/reports/reportcreate_test.go
@@ -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
diff --git a/internal/api/client/reports/reportget_test.go b/internal/api/client/reports/reportget_test.go
index e29836b6a..1bdb7557c 100644
--- a/internal/api/client/reports/reportget_test.go
+++ b/internal/api/client/reports/reportget_test.go
@@ -108,7 +108,10 @@ func (suite *ReportGetTestSuite) TestGetReport1() {
"status_ids": [
"01FVW7JHQFSFK166WWKR8CBA6M"
],
- "rule_ids": [],
+ "rule_ids": [
+ "01GP3AWY4CRDVRNZKW0TEAMB51",
+ "01GP3DFY9XQ1TJMZT5BGAZPXX3"
+ ],
"target_account": {
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
"username": "foss_satan",
diff --git a/internal/api/client/reports/reportsget_test.go b/internal/api/client/reports/reportsget_test.go
index d220dc94d..e58a622db 100644
--- a/internal/api/client/reports/reportsget_test.go
+++ b/internal/api/client/reports/reportsget_test.go
@@ -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",
diff --git a/internal/api/model/admin.go b/internal/api/model/admin.go
index cc449ab82..860cb8926 100644
--- a/internal/api/model/admin.go
+++ b/internal/api/model/admin.go
@@ -117,9 +117,9 @@ type AdminReport struct {
// Array of statuses that were submitted along with this report.
// Will be empty if no status IDs were submitted with the report.
Statuses []*Status `json:"statuses"`
- // Array of rule IDs that were submitted along with this report.
- // NOT IMPLEMENTED, will always be empty array.
- Rules []interface{} `json:"rule_ids"`
+ // Array of rules that were broken according to this report.
+ // Will be empty if no rule IDs were submitted with the report.
+ Rules []*InstanceRule `json:"rules"`
// If an action was taken, what comment was made by the admin on the taken action?
// Will be null if not set / no action yet taken.
// example: Account was suspended.
@@ -189,3 +189,10 @@ type AdminSendTestEmailRequest struct {
// Email address to send the test email to.
Email string `form:"email" json:"email" xml:"email"`
}
+
+type AdminInstanceRule struct {
+ ID string `json:"id"` // id of this item in the database
+ CreatedAt string `json:"created_at"` // when was item created
+ UpdatedAt string `json:"updated_at"` // when was item last updated
+ Text string `json:"text"` // text content of the rule
+}
diff --git a/internal/api/model/instancev1.go b/internal/api/model/instancev1.go
index 19682c1f1..3b3d215b0 100644
--- a/internal/api/model/instancev1.go
+++ b/internal/api/model/instancev1.go
@@ -88,6 +88,8 @@ type InstanceV1 struct {
//
// example: 5000
MaxTootChars uint `json:"max_toot_chars"`
+ // An itemized list of rules for this instance.
+ Rules []InstanceRule `json:"rules"`
}
// InstanceV1URLs models instance-relevant URLs for client application consumption.
diff --git a/internal/api/model/instancev2.go b/internal/api/model/instancev2.go
index 25d9c790d..3099b36c4 100644
--- a/internal/api/model/instancev2.go
+++ b/internal/api/model/instancev2.go
@@ -62,9 +62,8 @@ type InstanceV2 struct {
Registrations InstanceV2Registrations `json:"registrations"`
// Hints related to contacting a representative of the instance.
Contact InstanceV2Contact `json:"contact"`
- // An itemized list of rules for this website.
- // Currently not implemented (will always be empty array).
- Rules []interface{} `json:"rules"`
+ // An itemized list of rules for this instance.
+ Rules []InstanceRule `json:"rules"`
}
// Usage data for this instance.
diff --git a/internal/api/model/report.go b/internal/api/model/report.go
index eb68e7911..b9b8c77d2 100644
--- a/internal/api/model/report.go
+++ b/internal/api/model/report.go
@@ -54,8 +54,8 @@ type Report struct {
StatusIDs []string `json:"status_ids"`
// Array of rule IDs that were submitted along with this report.
// Will be empty if no rule IDs were submitted.
- // example: [1, 2]
- RuleIDs []int `json:"rule_ids"`
+ // example: ["01GPBN5YDY6JKBWE44H7YQBDCQ","01GPBN65PDWSBPWVDD0SQCFFY3"]
+ RuleIDs []string `json:"rule_ids"`
// Account that was reported.
TargetAccount *Account `json:"target_account"`
}
@@ -89,8 +89,7 @@ type ReportCreateRequest struct {
// in: formData
Category string `form:"category" json:"category" xml:"category"`
// IDs of rules on this instance which have been broken according to the reporter.
- // This is currently not supported, provided only for API compatibility.
- // example: [1, 2, 3]
+ // example: ["01GPBN5YDY6JKBWE44H7YQBDCQ","01GPBN65PDWSBPWVDD0SQCFFY3"]
// in: formData
- RuleIDs []int `form:"rule_ids[]" json:"rule_ids" xml:"rule_ids"`
+ RuleIDs []string `form:"rule_ids[]" json:"rule_ids" xml:"rule_ids"`
}
diff --git a/internal/api/model/rule.go b/internal/api/model/rule.go
new file mode 100644
index 000000000..f4caf7dd0
--- /dev/null
+++ b/internal/api/model/rule.go
@@ -0,0 +1,41 @@
+// 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 model
+
+// InstanceRule represents a single instance rule.
+//
+// swagger:model instanceRule
+type InstanceRule struct {
+ ID string `json:"id"`
+ Text string `json:"text"`
+}
+
+// InstanceRuleCreateRequest represents a request to create a new instance rule, made through the admin API.
+//
+// swagger:model instanceRuleCreateRequest
+type InstanceRuleCreateRequest struct {
+ Text string `form:"text" validation:"required"`
+}
+
+// InstanceRuleUpdateRequest represents a request to update the text of an instance rule, made through the admin API.
+//
+// swagger:model instanceRuleUpdateRequest
+type InstanceRuleUpdateRequest struct {
+ ID string `form:"id"`
+ Text string `form:"text"`
+}
diff --git a/internal/db/bundb/bundb.go b/internal/db/bundb/bundb.go
index ad9053e6e..e92234f81 100644
--- a/internal/db/bundb/bundb.go
+++ b/internal/db/bundb/bundb.go
@@ -72,6 +72,7 @@ type DBService struct {
db.Notification
db.Relationship
db.Report
+ db.Rule
db.Search
db.Session
db.Status
@@ -216,6 +217,10 @@ func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) {
db: db,
state: state,
},
+ Rule: &ruleDB{
+ db: db,
+ state: state,
+ },
Search: &searchDB{
db: db,
state: state,
diff --git a/internal/db/bundb/bundb_test.go b/internal/db/bundb/bundb_test.go
index 0cdbb5cce..f3640cf59 100644
--- a/internal/db/bundb/bundb_test.go
+++ b/internal/db/bundb/bundb_test.go
@@ -51,6 +51,7 @@ type BunDBStandardTestSuite struct {
testListEntries map[string]*gtsmodel.ListEntry
testAccountNotes map[string]*gtsmodel.AccountNote
testMarkers map[string]*gtsmodel.Marker
+ testRules map[string]*gtsmodel.Rule
}
func (suite *BunDBStandardTestSuite) SetupSuite() {
@@ -72,6 +73,7 @@ func (suite *BunDBStandardTestSuite) SetupSuite() {
suite.testListEntries = testrig.NewTestListEntries()
suite.testAccountNotes = testrig.NewTestAccountNotes()
suite.testMarkers = testrig.NewTestMarkers()
+ suite.testRules = testrig.NewTestRules()
}
func (suite *BunDBStandardTestSuite) SetupTest() {
diff --git a/internal/db/bundb/instance.go b/internal/db/bundb/instance.go
index 7f0e92634..6fec3f2fe 100644
--- a/internal/db/bundb/instance.go
+++ b/internal/db/bundb/instance.go
@@ -151,6 +151,16 @@ func (i *instanceDB) getInstance(ctx context.Context, lookup string, dbQuery fun
return nil, err
}
+ if instance.Domain == config.GetHost() {
+ // also populate Rules
+ rules, err := i.state.DB.GetActiveRules(ctx)
+ if err != nil {
+ log.Error(ctx, err)
+ } else {
+ instance.Rules = rules
+ }
+ }
+
return &instance, nil
}, keyParts...)
if err != nil {
diff --git a/internal/db/bundb/migrations/20230815164500_rules_model.go b/internal/db/bundb/migrations/20230815164500_rules_model.go
new file mode 100644
index 000000000..9b202ede9
--- /dev/null
+++ b/internal/db/bundb/migrations/20230815164500_rules_model.go
@@ -0,0 +1,47 @@
+// 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 migrations
+
+import (
+ "context"
+
+ gtsmodel "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/uptrace/bun"
+)
+
+func init() {
+ up := func(ctx context.Context, db *bun.DB) error {
+ return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
+ if _, err := tx.NewCreateTable().Model(&gtsmodel.Rule{}).IfNotExists().Exec(ctx); err != nil {
+ return err
+ }
+
+ return nil
+ })
+ }
+
+ down := func(ctx context.Context, db *bun.DB) error {
+ return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
+ return nil
+ })
+ }
+
+ if err := Migrations.Register(up, down); err != nil {
+ panic(err)
+ }
+}
diff --git a/internal/db/bundb/migrations/20230817174700_add_report_rule_ids.go b/internal/db/bundb/migrations/20230817174700_add_report_rule_ids.go
new file mode 100644
index 000000000..a66739e4c
--- /dev/null
+++ b/internal/db/bundb/migrations/20230817174700_add_report_rule_ids.go
@@ -0,0 +1,53 @@
+// 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 migrations
+
+import (
+ "context"
+ "strings"
+
+ "github.com/uptrace/bun"
+ "github.com/uptrace/bun/dialect"
+)
+
+func init() {
+ up := func(ctx context.Context, db *bun.DB) error {
+ if db.Dialect().Name() == dialect.SQLite { // sqlite does not have an array type
+ _, err := db.ExecContext(ctx, "ALTER TABLE ? ADD COLUMN ? VARCHAR", bun.Ident("reports"), bun.Ident("rules"))
+ if err != nil && !(strings.Contains(err.Error(), "already exists") || strings.Contains(err.Error(), "duplicate column name") || strings.Contains(err.Error(), "SQLSTATE 42701")) {
+ return err
+ }
+ } else {
+ _, err := db.ExecContext(ctx, "ALTER TABLE ? ADD COLUMN ? VARCHAR[]", bun.Ident("reports"), bun.Ident("rules"))
+ if err != nil && !(strings.Contains(err.Error(), "already exists") || strings.Contains(err.Error(), "duplicate column name") || strings.Contains(err.Error(), "SQLSTATE 42701")) {
+ return err
+ }
+ }
+ return nil
+ }
+
+ down := func(ctx context.Context, db *bun.DB) error {
+ return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
+ return nil
+ })
+ }
+
+ if err := Migrations.Register(up, down); err != nil {
+ panic(err)
+ }
+}
diff --git a/internal/db/bundb/report.go b/internal/db/bundb/report.go
index 7c1dd16e7..9e4ba5b29 100644
--- a/internal/db/bundb/report.go
+++ b/internal/db/bundb/report.go
@@ -186,6 +186,19 @@ func (r *reportDB) PopulateReport(ctx context.Context, report *gtsmodel.Report)
}
}
+ if l := len(report.RuleIDs); l > 0 && l != len(report.Rules) {
+ // Report target rules not set, fetch from the database.
+
+ for _, v := range report.RuleIDs {
+ rule, err := r.state.DB.GetRuleByID(ctx, v)
+ if err != nil {
+ errs.Appendf("error populating report rules: %w", err)
+ } else {
+ report.Rules = append(report.Rules, rule)
+ }
+ }
+ }
+
if report.ActionTakenByAccountID != "" &&
report.ActionTakenByAccount == nil {
// Report action account is not set, fetch from the database.
diff --git a/internal/db/bundb/rule.go b/internal/db/bundb/rule.go
new file mode 100644
index 000000000..79825923b
--- /dev/null
+++ b/internal/db/bundb/rule.go
@@ -0,0 +1,149 @@
+// 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 bundb
+
+import (
+ "context"
+ "errors"
+ "time"
+
+ "github.com/superseriousbusiness/gotosocial/internal/config"
+ "github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
+ "github.com/superseriousbusiness/gotosocial/internal/util"
+ "github.com/uptrace/bun"
+)
+
+type ruleDB struct {
+ db *DB
+ state *state.State
+}
+
+func (r *ruleDB) GetRuleByID(ctx context.Context, id string) (*gtsmodel.Rule, error) {
+ var rule gtsmodel.Rule
+
+ q := r.db.
+ NewSelect().
+ Model(&rule).
+ Where("? = ?", bun.Ident("rule.id"), id)
+
+ if err := q.Scan(ctx); err != nil {
+ return nil, err
+ }
+
+ return &rule, nil
+}
+
+func (r *ruleDB) GetRulesByIDs(ctx context.Context, ids []string) ([]*gtsmodel.Rule, error) {
+ rules := make([]*gtsmodel.Rule, 0, len(ids))
+
+ for _, id := range ids {
+ // Attempt to fetch status from DB.
+ rule, err := r.GetRuleByID(ctx, id)
+ if err != nil {
+ log.Errorf(ctx, "error getting rule %q: %v", id, err)
+ continue
+ }
+
+ // Append status to return slice.
+ rules = append(rules, rule)
+ }
+
+ return rules, nil
+}
+
+func (r *ruleDB) GetActiveRules(ctx context.Context) ([]gtsmodel.Rule, error) {
+ rules := make([]gtsmodel.Rule, 0)
+
+ q := r.db.
+ NewSelect().
+ Model(&rules).
+ // Ignore deleted (ie., inactive) rules.
+ Where("? = ?", bun.Ident("rule.deleted"), false).
+ Order("rule.order ASC")
+
+ if err := q.Scan(ctx); err != nil {
+ return nil, err
+ }
+
+ return rules, nil
+}
+
+func (r *ruleDB) PutRule(ctx context.Context, rule *gtsmodel.Rule) error {
+ var lastRuleOrder uint
+
+ // Select highest existing rule order.
+ err := r.db.
+ NewSelect().
+ TableExpr("? AS ?", bun.Ident("rules"), bun.Ident("rule")).
+ Column("rule.order").
+ Order("rule.order DESC").
+ Limit(1).
+ Scan(ctx, &lastRuleOrder)
+
+ switch {
+ case errors.Is(err, db.ErrNoEntries):
+ // No rules set yet, index from 0.
+ rule.Order = util.Ptr(uint(0))
+
+ case err != nil:
+ // Real db error.
+ return err
+
+ default:
+ // No error means previous rule(s)
+ // existed. New rule order should
+ // be 1 higher than previous rule.
+ rule.Order = func() *uint {
+ o := lastRuleOrder + 1
+ return &o
+ }()
+ }
+
+ if _, err := r.db.
+ NewInsert().
+ Model(rule).
+ Exec(ctx); err != nil {
+ return err
+ }
+
+ // invalidate cached local instance response, so it gets updated with the new rules
+ r.state.Caches.GTS.Instance().Invalidate("Domain", config.GetHost())
+
+ return nil
+}
+
+func (r *ruleDB) UpdateRule(ctx context.Context, rule *gtsmodel.Rule) (*gtsmodel.Rule, error) {
+ // Update the rule's last-updated
+ rule.UpdatedAt = time.Now()
+
+ if _, err := r.db.
+ NewUpdate().
+ Model(rule).
+ WherePK().
+ Exec(ctx); err != nil {
+ return nil, err
+ }
+
+ // invalidate cached local instance response, so it gets updated with the new rules
+ r.state.Caches.GTS.Instance().Invalidate("Domain", config.GetHost())
+
+ return rule, nil
+}
diff --git a/internal/db/bundb/rule_test.go b/internal/db/bundb/rule_test.go
new file mode 100644
index 000000000..822f92fca
--- /dev/null
+++ b/internal/db/bundb/rule_test.go
@@ -0,0 +1,122 @@
+// 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 bundb_test
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/suite"
+ "github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/id"
+)
+
+type RuleTestSuite struct {
+ BunDBStandardTestSuite
+}
+
+func (suite *RuleTestSuite) TestPutRuleWithExisting() {
+ r := &gtsmodel.Rule{
+ ID: id.NewULID(),
+ Text: "Pee pee poo poo",
+ }
+
+ if err := suite.state.DB.PutRule(context.Background(), r); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.Equal(uint(len(suite.testRules)), *r.Order)
+}
+
+func (suite *RuleTestSuite) TestPutRuleNoExisting() {
+ var (
+ ctx = context.Background()
+ whereAny = []db.Where{{Key: "id", Value: "", Not: true}}
+ )
+
+ // Wipe all existing rules from the DB.
+ if err := suite.state.DB.DeleteWhere(
+ ctx,
+ whereAny,
+ &[]*gtsmodel.Rule{},
+ ); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ r := &gtsmodel.Rule{
+ ID: id.NewULID(),
+ Text: "Pee pee poo poo",
+ }
+
+ if err := suite.state.DB.PutRule(ctx, r); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ // New rule is now only rule.
+ suite.EqualValues(uint(0), *r.Order)
+}
+
+func (suite *RuleTestSuite) TestGetRuleByID() {
+ rule, err := suite.state.DB.GetRuleByID(
+ context.Background(),
+ suite.testRules["rule1"].ID,
+ )
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.NotNil(rule)
+}
+
+func (suite *RuleTestSuite) TestGetRulesByID() {
+ ruleIDs := make([]string, 0, len(suite.testRules))
+ for _, rule := range suite.testRules {
+ ruleIDs = append(ruleIDs, rule.ID)
+ }
+
+ rules, err := suite.state.DB.GetRulesByIDs(
+ context.Background(),
+ ruleIDs,
+ )
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.Len(rules, len(suite.testRules))
+}
+
+func (suite *RuleTestSuite) TestGetActiveRules() {
+ var activeRules int
+ for _, rule := range suite.testRules {
+ if !*rule.Deleted {
+ activeRules++
+ }
+ }
+
+ rules, err := suite.state.DB.GetActiveRules(context.Background())
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.Len(rules, activeRules)
+}
+
+func TestRuleTestSuite(t *testing.T) {
+ suite.Run(t, new(RuleTestSuite))
+}
diff --git a/internal/db/db.go b/internal/db/db.go
index 567551c73..056d03e23 100644
--- a/internal/db/db.go
+++ b/internal/db/db.go
@@ -38,6 +38,7 @@ type DB interface {
Notification
Relationship
Report
+ Rule
Search
Session
Status
diff --git a/internal/db/rule.go b/internal/db/rule.go
new file mode 100644
index 000000000..651b8bced
--- /dev/null
+++ b/internal/db/rule.go
@@ -0,0 +1,42 @@
+// 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 db
+
+import (
+ "context"
+
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+// Rule handles getting/creation/deletion/updating of instance rules.
+type Rule interface {
+ // GetRuleByID gets one rule by its db id.
+ GetRuleByID(ctx context.Context, id string) (*gtsmodel.Rule, error)
+
+ // GetRulesByIDs gets multiple rules by their db idd.
+ GetRulesByIDs(ctx context.Context, ids []string) ([]*gtsmodel.Rule, error)
+
+ // GetRules gets all active (not deleted) rules.
+ GetActiveRules(ctx context.Context) ([]gtsmodel.Rule, error)
+
+ // PutRule puts the given rule in the database.
+ PutRule(ctx context.Context, rule *gtsmodel.Rule) error
+
+ // UpdateRule updates one rule by its db id.
+ UpdateRule(ctx context.Context, rule *gtsmodel.Rule) (*gtsmodel.Rule, error)
+}
diff --git a/internal/gtsmodel/instance.go b/internal/gtsmodel/instance.go
index 388f0f4ed..6d572f519 100644
--- a/internal/gtsmodel/instance.go
+++ b/internal/gtsmodel/instance.go
@@ -39,4 +39,5 @@ type Instance struct {
ContactAccount *Account `bun:"rel:belongs-to"` // account corresponding to contactAccountID
Reputation int64 `bun:",notnull,default:0"` // Reputation score of this instance
Version string `bun:",nullzero"` // Version of the software used on this instance
+ Rules []Rule `bun:"-"` // List of instance rules
}
diff --git a/internal/gtsmodel/report.go b/internal/gtsmodel/report.go
index e5b942563..b332ec348 100644
--- a/internal/gtsmodel/report.go
+++ b/internal/gtsmodel/report.go
@@ -37,6 +37,8 @@ type Report struct {
Comment string `bun:",nullzero"` // comment / explanation for this report, by the reporter
StatusIDs []string `bun:"statuses,array"` // database IDs of any statuses referenced by this report
Statuses []*Status `bun:"-"` // statuses corresponding to StatusIDs
+ RuleIDs []string `bun:"rules,array"` // database IDs of any rules referenced by this report
+ Rules []*Rule `bun:"-"` // rules corresponding to RuleIDs
Forwarded *bool `bun:",nullzero,notnull,default:false"` // flag to indicate report should be forwarded to remote instance
ActionTaken string `bun:",nullzero"` // string description of what action was taken in response to this report
ActionTakenAt time.Time `bun:"type:timestamptz,nullzero"` // time at which action was taken, if any
diff --git a/internal/gtsmodel/rule.go b/internal/gtsmodel/rule.go
new file mode 100644
index 000000000..76fa6f7bd
--- /dev/null
+++ b/internal/gtsmodel/rule.go
@@ -0,0 +1,30 @@
+// 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 gtsmodel
+
+import "time"
+
+// Rule models an instance rule set by the admin
+type Rule struct {
+ ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
+ CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
+ UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+ Text string `bun:",nullzero"` // text content of the rule
+ Order *uint `bun:",nullzero,notnull,unique"` // rule ordering, index from 0
+ Deleted *bool `bun:",nullzero,notnull,default:false"` // has this rule been deleted, still kept in database for reference in historic reports
+}
diff --git a/internal/processing/admin/rule.go b/internal/processing/admin/rule.go
new file mode 100644
index 000000000..40a2bdcf3
--- /dev/null
+++ b/internal/processing/admin/rule.go
@@ -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 (
+ "context"
+ "errors"
+ "fmt"
+
+ apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/id"
+ "github.com/superseriousbusiness/gotosocial/internal/util"
+)
+
+// RulesGet returns all rules stored on this instance.
+func (p *Processor) RulesGet(
+ ctx context.Context,
+) ([]*apimodel.AdminInstanceRule, gtserror.WithCode) {
+ rules, err := p.state.DB.GetActiveRules(ctx)
+
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ apiRules := make([]*apimodel.AdminInstanceRule, len(rules))
+
+ for i := range rules {
+ apiRules[i] = p.tc.InstanceRuleToAdminAPIRule(&rules[i])
+ }
+
+ return apiRules, nil
+}
+
+// RuleGet returns one rule, with the given ID.
+func (p *Processor) RuleGet(ctx context.Context, id string) (*apimodel.AdminInstanceRule, gtserror.WithCode) {
+ rule, err := p.state.DB.GetRuleByID(ctx, id)
+ if err != nil {
+ if err == db.ErrNoEntries {
+ return nil, gtserror.NewErrorNotFound(err)
+ }
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ return p.tc.InstanceRuleToAdminAPIRule(rule), nil
+}
+
+// RuleCreate adds a new rule to the instance.
+func (p *Processor) RuleCreate(ctx context.Context, form *apimodel.InstanceRuleCreateRequest) (*apimodel.AdminInstanceRule, gtserror.WithCode) {
+ ruleID, err := id.NewRandomULID()
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("error creating id for new instance rule: %s", err), "error creating rule ID")
+ }
+
+ rule := &gtsmodel.Rule{
+ ID: ruleID,
+ Text: form.Text,
+ }
+
+ if err = p.state.DB.PutRule(ctx, rule); err != nil {
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ return p.tc.InstanceRuleToAdminAPIRule(rule), nil
+}
+
+// RuleUpdate updates text for an existing rule.
+func (p *Processor) RuleUpdate(ctx context.Context, id string, form *apimodel.InstanceRuleCreateRequest) (*apimodel.AdminInstanceRule, gtserror.WithCode) {
+ rule, err := p.state.DB.GetRuleByID(ctx, id)
+ if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ err = fmt.Errorf("RuleUpdate: no rule with id %s found in the db", id)
+ return nil, gtserror.NewErrorNotFound(err)
+ }
+ err := fmt.Errorf("RuleUpdate: db error: %s", err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ rule.Text = form.Text
+
+ updatedRule, err := p.state.DB.UpdateRule(ctx, rule)
+
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ return p.tc.InstanceRuleToAdminAPIRule(updatedRule), nil
+}
+
+// RuleDelete deletes an existing rule.
+func (p *Processor) RuleDelete(ctx context.Context, id string) (*apimodel.AdminInstanceRule, gtserror.WithCode) {
+ rule, err := p.state.DB.GetRuleByID(ctx, id)
+ if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ err = fmt.Errorf("RuleUpdate: no rule with id %s found in the db", id)
+ return nil, gtserror.NewErrorNotFound(err)
+ }
+ err := fmt.Errorf("RuleUpdate: db error: %s", err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ rule.Deleted = util.Ptr(true)
+ deletedRule, err := p.state.DB.UpdateRule(ctx, rule)
+
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ return p.tc.InstanceRuleToAdminAPIRule(deletedRule), nil
+}
diff --git a/internal/processing/instance.go b/internal/processing/instance.go
index edcfe5418..2faef7527 100644
--- a/internal/processing/instance.go
+++ b/internal/processing/instance.go
@@ -136,6 +136,15 @@ func (p *Processor) InstancePeersGet(ctx context.Context, includeSuspended bool,
return domains, nil
}
+func (p *Processor) InstanceGetRules(ctx context.Context) ([]apimodel.InstanceRule, gtserror.WithCode) {
+ i, err := p.getThisInstance(ctx)
+ if err != nil {
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error fetching instance: %s", err))
+ }
+
+ return p.tc.InstanceRulesToAPIRules(i.Rules), nil
+}
+
func (p *Processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSettingsUpdateRequest) (*apimodel.InstanceV1, gtserror.WithCode) {
// fetch the instance entry from the db for processing
host := config.GetHost()
diff --git a/internal/processing/report/create.go b/internal/processing/report/create.go
index a6cce8e80..48f9c1ee4 100644
--- a/internal/processing/report/create.go
+++ b/internal/processing/report/create.go
@@ -64,6 +64,13 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form
}
}
+ // fetch rules by IDs given in the report form (noop if no rules given)
+ rules, err := p.state.DB.GetRulesByIDs(ctx, form.RuleIDs)
+ if err != nil {
+ err = fmt.Errorf("db error fetching report target rules: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
reportID := id.NewULID()
report := &gtsmodel.Report{
ID: reportID,
@@ -75,6 +82,8 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form
Comment: form.Comment,
StatusIDs: form.StatusIDs,
Statuses: statuses,
+ RuleIDs: form.RuleIDs,
+ Rules: rules,
Forwarded: &form.Forward,
}
diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go
index 73992fc0e..774b68157 100644
--- a/internal/typeutils/converter.go
+++ b/internal/typeutils/converter.go
@@ -83,6 +83,10 @@ type TypeConverter interface {
InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Instance) (*apimodel.InstanceV1, error)
// InstanceToAPIV2Instance converts a gts instance into its api equivalent for serving at /api/v2/instance
InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Instance) (*apimodel.InstanceV2, error)
+ // InstanceRulesToAPIRules converts all local instance rules into their api equivalent for serving at /api/v1/instance/rules
+ InstanceRulesToAPIRules(r []gtsmodel.Rule) []apimodel.InstanceRule
+ // InstanceRuleToAdminAPIRule converts a local instance rule into its api equivalent for serving at /api/v1/admin/instance/rules/:id
+ InstanceRuleToAdminAPIRule(r *gtsmodel.Rule) *apimodel.AdminInstanceRule
// RelationshipToAPIRelationship converts a gts relationship into its api equivalent for serving in various places
RelationshipToAPIRelationship(ctx context.Context, r *gtsmodel.Relationship) (*apimodel.Relationship, error)
// NotificationToAPINotification converts a gts notification into a api notification
diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go
index ab04f6ccc..050997bda 100644
--- a/internal/typeutils/internaltofrontend.go
+++ b/internal/typeutils/internaltofrontend.go
@@ -738,6 +738,32 @@ func (c *converter) VisToAPIVis(ctx context.Context, m gtsmodel.Visibility) apim
return ""
}
+func (c *converter) InstanceRuleToAPIRule(r gtsmodel.Rule) apimodel.InstanceRule {
+ return apimodel.InstanceRule{
+ ID: r.ID,
+ Text: r.Text,
+ }
+}
+
+func (c *converter) InstanceRulesToAPIRules(r []gtsmodel.Rule) []apimodel.InstanceRule {
+ rules := make([]apimodel.InstanceRule, len(r))
+
+ for i, v := range r {
+ rules[i] = c.InstanceRuleToAPIRule(v)
+ }
+
+ return rules
+}
+
+func (c *converter) InstanceRuleToAdminAPIRule(r *gtsmodel.Rule) *apimodel.AdminInstanceRule {
+ return &apimodel.AdminInstanceRule{
+ ID: r.ID,
+ CreatedAt: util.FormatISO8601(r.CreatedAt),
+ UpdatedAt: util.FormatISO8601(r.UpdatedAt),
+ Text: r.Text,
+ }
+}
+
func (c *converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Instance) (*apimodel.InstanceV1, error) {
instance := &apimodel.InstanceV1{
URI: i.URI,
@@ -752,6 +778,7 @@ func (c *converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins
ApprovalRequired: config.GetAccountsApprovalRequired(),
InvitesEnabled: false, // todo: not supported yet
MaxTootChars: uint(config.GetStatusesMaxChars()),
+ Rules: c.InstanceRulesToAPIRules(i.Rules),
}
if config.GetInstanceInjectMastodonVersion() {
@@ -854,7 +881,7 @@ func (c *converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins
Description: i.Description,
Usage: apimodel.InstanceV2Usage{}, // todo: not implemented
Languages: []string{}, // todo: not implemented
- Rules: []interface{}{}, // todo: not implemented
+ Rules: c.InstanceRulesToAPIRules(i.Rules),
}
if config.GetInstanceInjectMastodonVersion() {
@@ -1051,7 +1078,7 @@ func (c *converter) ReportToAPIReport(ctx context.Context, r *gtsmodel.Report) (
Comment: r.Comment,
Forwarded: *r.Forwarded,
StatusIDs: r.StatusIDs,
- RuleIDs: []int{}, // todo: not supported yet
+ RuleIDs: r.RuleIDs,
}
if !r.ActionTakenAt.IsZero() {
@@ -1144,6 +1171,20 @@ func (c *converter) ReportToAdminAPIReport(ctx context.Context, r *gtsmodel.Repo
statuses = append(statuses, status)
}
+ rules := make([]*apimodel.InstanceRule, 0, len(r.RuleIDs))
+ if len(r.RuleIDs) != 0 && len(r.Rules) == 0 {
+ r.Rules, err = c.db.GetRulesByIDs(ctx, r.RuleIDs)
+ if err != nil {
+ return nil, fmt.Errorf("ReportToAdminAPIReport: error getting rules from the db: %w", err)
+ }
+ }
+ for _, v := range r.Rules {
+ rules = append(rules, &apimodel.InstanceRule{
+ ID: v.ID,
+ Text: v.Text,
+ })
+ }
+
if ac := r.ActionTaken; ac != "" {
actionTakenComment = &ac
}
@@ -1163,7 +1204,7 @@ func (c *converter) ReportToAdminAPIReport(ctx context.Context, r *gtsmodel.Repo
ActionTakenByAccount: actionTakenByAccount,
ActionTakenComment: actionTakenComment,
Statuses: statuses,
- Rules: []interface{}{}, // not implemented
+ Rules: rules,
}, nil
}
diff --git a/internal/typeutils/internaltofrontend_test.go b/internal/typeutils/internaltofrontend_test.go
index d99a31e25..9f72c6d2e 100644
--- a/internal/typeutils/internaltofrontend_test.go
+++ b/internal/typeutils/internaltofrontend_test.go
@@ -603,6 +603,7 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV1ToFrontend() {
b, err := json.MarshalIndent(instance, "", " ")
suite.NoError(err)
+ // FIXME: "rules" is empty from the database, because it's not fetched through db.GetInstance
suite.Equal(`{
"uri": "http://localhost:8080",
"account_domain": "localhost:8080",
@@ -689,7 +690,8 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV1ToFrontend() {
"name": "admin"
}
},
- "max_toot_chars": 5000
+ "max_toot_chars": 5000,
+ "rules": []
}`, string(b))
}
@@ -887,7 +889,10 @@ func (suite *InternalToFrontendTestSuite) TestReportToFrontend1() {
"status_ids": [
"01FVW7JHQFSFK166WWKR8CBA6M"
],
- "rule_ids": [],
+ "rule_ids": [
+ "01GP3AWY4CRDVRNZKW0TEAMB51",
+ "01GP3DFY9XQ1TJMZT5BGAZPXX3"
+ ],
"target_account": {
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
"username": "foss_satan",
@@ -1177,7 +1182,7 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() {
"created_by_application_id": "01F8MGXQRHYF5QPMTMXP78QC2F"
},
"statuses": [],
- "rule_ids": [],
+ "rules": [],
"action_taken_comment": "user was warned not to be a turtle anymore"
}`, string(b))
}
@@ -1380,7 +1385,16 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend2() {
"poll": null
}
],
- "rule_ids": [],
+ "rules": [
+ {
+ "id": "01GP3AWY4CRDVRNZKW0TEAMB51",
+ "text": "Be gay"
+ },
+ {
+ "id": "01GP3DFY9XQ1TJMZT5BGAZPXX3",
+ "text": "Do crime"
+ }
+ ],
"action_taken_comment": null
}`, string(b))
}
@@ -1603,7 +1617,7 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontendSuspendedLoca
"created_by_application_id": "01F8MGXQRHYF5QPMTMXP78QC2F"
},
"statuses": [],
- "rule_ids": [],
+ "rules": [],
"action_taken_comment": "user was warned not to be a turtle anymore"
}`, string(b))
}