[feature] Implement /api/v1/reports endpoints on client API (#1330)

* start adding report client api

* route + test reports get

* start report create endpoint

* you can create reports now babyy

* stub account report processor

* add single reportGet endpoint

* fix test

* add more filtering params to /api/v1/reports GET

* update swagger

* use marshalIndent in tests

* add + test missing Link info
This commit is contained in:
tobi 2023-01-23 13:14:21 +01:00 committed by GitHub
commit e9747247d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 2184 additions and 20 deletions

View file

@ -35,6 +35,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/api/client/lists"
"github.com/superseriousbusiness/gotosocial/internal/api/client/media"
"github.com/superseriousbusiness/gotosocial/internal/api/client/notifications"
"github.com/superseriousbusiness/gotosocial/internal/api/client/reports"
"github.com/superseriousbusiness/gotosocial/internal/api/client/search"
"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses"
"github.com/superseriousbusiness/gotosocial/internal/api/client/streaming"
@ -63,6 +64,7 @@ type Client struct {
lists *lists.Module // api/v1/lists
media *media.Module // api/v1/media, api/v2/media
notifications *notifications.Module // api/v1/notifications
reports *reports.Module // api/v1/reports
search *search.Module // api/v1/search, api/v2/search
statuses *statuses.Module // api/v1/statuses
streaming *streaming.Module // api/v1/streaming
@ -97,6 +99,7 @@ func (c *Client) Route(r router.Router, m ...gin.HandlerFunc) {
c.lists.Route(h)
c.media.Route(h)
c.notifications.Route(h)
c.reports.Route(h)
c.search.Route(h)
c.statuses.Route(h)
c.streaming.Route(h)
@ -122,6 +125,7 @@ func NewClient(db db.DB, p processing.Processor) *Client {
lists: lists.New(p),
media: media.New(p),
notifications: notifications.New(p),
reports: reports.New(p),
search: search.New(p),
statuses: statuses.New(p),
streaming: streaming.New(p, time.Second*30, 4096),

View file

@ -0,0 +1,112 @@
/*
GoToSocial
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package reports
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"
"github.com/superseriousbusiness/gotosocial/internal/regexes"
)
// ReportPOSTHandler swagger:operation POST /api/v1/reports reportCreate
//
// Create a new user report with the given parameters.
//
// ---
// tags:
// - reports
//
// consumes:
// - application/json
// - application/xml
// - application/x-www-form-urlencoded
//
// produces:
// - application/json
//
// security:
// - OAuth2 Bearer:
// - write:reports
//
// responses:
// '200':
// description: The created report.
// schema:
// "$ref": "#/definitions/report"
// '400':
// description: bad request
// '401':
// description: unauthorized
// '404':
// description: not found
// '406':
// description: not acceptable
// '500':
// description: internal server error
func (m *Module) ReportPOSTHandler(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.InstanceGet)
return
}
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
return
}
form := &apimodel.ReportCreateRequest{}
if err := c.ShouldBind(form); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
return
}
if form.AccountID == "" {
err = errors.New("account_id must be set")
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
return
}
if !regexes.ULID.MatchString(form.AccountID) {
err = errors.New("account_id was not valid")
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
return
}
if length := len([]rune(form.Comment)); length > 1000 {
err = fmt.Errorf("comment length must be no more than 1000 chars, provided comment was %d chars", length)
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
return
}
apiReport, errWithCode := m.processor.ReportCreate(c.Request.Context(), authed, form)
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
return
}
c.JSON(http.StatusOK, apiReport)
}

View file

@ -0,0 +1,201 @@
/*
GoToSocial
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package reports_test
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"testing"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/api/client/reports"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/testrig"
)
type ReportCreateTestSuite struct {
ReportsStandardTestSuite
}
func (suite *ReportCreateTestSuite) createReport(expectedHTTPStatus int, expectedBody string, form *apimodel.ReportCreateRequest) (*apimodel.Report, error) {
// instantiate recorder + test context
recorder := httptest.NewRecorder()
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
// 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,
}
// trigger the handler
suite.reportsModule.ReportPOSTHandler(ctx)
// read the response
result := recorder.Result()
defer result.Body.Close()
b, err := ioutil.ReadAll(result.Body)
if err != nil {
return nil, err
}
errs := gtserror.MultiError{}
// check code + body
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
errs = append(errs, fmt.Sprintf("expected %d got %d", expectedHTTPStatus, resultCode))
}
// if we got an expected body, return early
if expectedBody != "" {
if string(b) != expectedBody {
errs = append(errs, fmt.Sprintf("expected %s got %s", expectedBody, string(b)))
}
return nil, errs.Combine()
}
resp := &apimodel.Report{}
if err := json.Unmarshal(b, resp); err != nil {
return nil, err
}
return resp, nil
}
func (suite *ReportCreateTestSuite) ReportOK(form *apimodel.ReportCreateRequest, report *apimodel.Report) {
suite.Equal(form.AccountID, report.TargetAccount.ID)
suite.Equal(form.StatusIDs, report.StatusIDs)
suite.Equal(form.Comment, report.Comment)
suite.Equal(form.Forward, report.Forwarded)
}
func (suite *ReportCreateTestSuite) TestCreateReport1() {
targetAccount := suite.testAccounts["remote_account_1"]
form := &apimodel.ReportCreateRequest{
AccountID: targetAccount.ID,
StatusIDs: []string{},
Comment: "",
Forward: false,
}
report, err := suite.createReport(http.StatusOK, "", form)
suite.NoError(err)
suite.NotEmpty(report)
suite.ReportOK(form, report)
}
func (suite *ReportCreateTestSuite) TestCreateReport2() {
targetAccount := suite.testAccounts["remote_account_1"]
targetStatus := suite.testStatuses["remote_account_1_status_1"]
form := &apimodel.ReportCreateRequest{
AccountID: targetAccount.ID,
StatusIDs: []string{targetStatus.ID},
Comment: "noooo don't post your so sexy aha",
Forward: true,
}
report, err := suite.createReport(http.StatusOK, "", form)
suite.NoError(err)
suite.NotEmpty(report)
suite.ReportOK(form, report)
}
func (suite *ReportCreateTestSuite) TestCreateReport3() {
form := &apimodel.ReportCreateRequest{}
report, err := suite.createReport(http.StatusBadRequest, `{"error":"Bad Request: account_id must be set"}`, form)
suite.NoError(err)
suite.Nil(report)
}
func (suite *ReportCreateTestSuite) TestCreateReport4() {
form := &apimodel.ReportCreateRequest{
AccountID: "boobs",
StatusIDs: []string{},
Comment: "",
Forward: true,
}
report, err := suite.createReport(http.StatusBadRequest, `{"error":"Bad Request: account_id was not valid"}`, form)
suite.NoError(err)
suite.Nil(report)
}
func (suite *ReportCreateTestSuite) TestCreateReport5() {
testAccount := suite.testAccounts["local_account_1"]
form := &apimodel.ReportCreateRequest{
AccountID: testAccount.ID,
}
report, err := suite.createReport(http.StatusBadRequest, `{"error":"Bad Request: cannot report your own account"}`, form)
suite.NoError(err)
suite.Nil(report)
}
func (suite *ReportCreateTestSuite) TestCreateReport6() {
targetAccount := suite.testAccounts["remote_account_1"]
form := &apimodel.ReportCreateRequest{
AccountID: targetAccount.ID,
Comment: "netus et malesuada fames ac turpis egestas sed tempus urna et pharetra pharetra massa massa ultricies mi quis hendrerit dolor magna eget est lorem ipsum dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas integer eget aliquet nibh praesent tristique magna sit amet purus gravida quis blandit turpis cursus in hac habitasse platea dictumst quisque sagittis purus sit amet volutpat consequat mauris nunc congue nisi vitae suscipit tellus mauris a diam maecenas sed enim ut sem viverra aliquet eget sit amet tellus cras adipiscing enim eu turpis egestas pretium aenean pharetra magna ac placerat vestibulum lectus mauris ultrices eros in cursus turpis massa tincidunt dui ut ornare lectus sit amet est placerat in egestas erat imperdiet sed euismod nisi porta lorem mollis aliquam ut porttitor leo a diam sollicitudin tempor id eu nisl nunc mi ipsum faucibus vitae aliquet nec ullamcorper sit amet risus nullam eget felis eget nunc lobortis mattis aliquam faucibus purus in massa tempor nec feugiat nisl pretium fusce id velit ut tortor pretium viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra tellus in hac habitasse platea dictumst vestibulum rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt lobortis feugiat vivamus at augue eget arcu dictum varius duis at consectetur lorem donec massa sapien faucibus et molestie ac feugiat sed lectus vestibulum mattis ullamcorper velit sed ullamcorper morbi tincidunt ornare massa eget ",
}
report, err := suite.createReport(http.StatusBadRequest, `{"error":"Bad Request: comment length must be no more than 1000 chars, provided comment was 1588 chars"}`, form)
suite.NoError(err)
suite.Nil(report)
}
func (suite *ReportCreateTestSuite) TestCreateReport7() {
form := &apimodel.ReportCreateRequest{
AccountID: "01GPGH5ENXWE5K65YNNXYWAJA4",
}
report, err := suite.createReport(http.StatusBadRequest, `{"error":"Bad Request: account with ID 01GPGH5ENXWE5K65YNNXYWAJA4 does not exist"}`, form)
suite.NoError(err)
suite.Nil(report)
}
func TestReportCreateTestSuite(t *testing.T) {
suite.Run(t, &ReportCreateTestSuite{})
}

View file

@ -0,0 +1,95 @@
/*
GoToSocial
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package reports
import (
"errors"
"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"
)
// ReportGETHandler swagger:operation GET /api/v1/reports/{id} reportGet
//
// Get one report with the given id.
//
// ---
// tags:
// - reports
//
// produces:
// - application/json
//
// parameters:
// -
// name: id
// type: string
// description: ID of the report
// in: path
// required: true
//
// security:
// - OAuth2 Bearer:
// - read:reports
//
// responses:
// '200':
// description: The requested report.
// schema:
// "$ref": "#/definitions/report"
// '400':
// description: bad request
// '401':
// description: unauthorized
// '404':
// description: not found
// '406':
// description: not acceptable
// '500':
// description: internal server error
func (m *Module) ReportGETHandler(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.InstanceGet)
return
}
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
return
}
targetReportID := c.Param(IDKey)
if targetReportID == "" {
err := errors.New("no report id specified")
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
return
}
report, errWithCode := m.processor.ReportGet(c.Request.Context(), authed, targetReportID)
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
return
}
c.JSON(http.StatusOK, report)
}

View file

@ -0,0 +1,159 @@
/*
GoToSocial
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package reports_test
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/api/client/reports"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/testrig"
)
type ReportGetTestSuite struct {
ReportsStandardTestSuite
}
func (suite *ReportGetTestSuite) getReport(expectedHTTPStatus int, expectedBody string, reportID string) (*apimodel.Report, error) {
// instantiate recorder + test context
recorder := httptest.NewRecorder()
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_2"])
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_2"]))
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_2"])
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_2"])
// create the request
ctx.Request = httptest.NewRequest(http.MethodGet, config.GetProtocol()+"://"+config.GetHost()+"/api/"+reports.BasePath+"/"+reportID, nil)
ctx.Request.Header.Set("accept", "application/json")
ctx.AddParam("id", reportID)
// trigger the handler
suite.reportsModule.ReportGETHandler(ctx)
// read the response
result := recorder.Result()
defer result.Body.Close()
b, err := ioutil.ReadAll(result.Body)
if err != nil {
return nil, err
}
errs := gtserror.MultiError{}
// check code + body
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
errs = append(errs, fmt.Sprintf("expected %d got %d", expectedHTTPStatus, resultCode))
}
// if we got an expected body, return early
if expectedBody != "" {
if string(b) != expectedBody {
errs = append(errs, fmt.Sprintf("expected %s got %s", expectedBody, string(b)))
}
return nil, errs.Combine()
}
resp := &apimodel.Report{}
if err := json.Unmarshal(b, resp); err != nil {
return nil, err
}
return resp, nil
}
func (suite *ReportGetTestSuite) TestGetReport1() {
targetReport := suite.testReports["local_account_2_report_remote_account_1"]
report, err := suite.getReport(http.StatusOK, "", targetReport.ID)
suite.NoError(err)
suite.NotNil(report)
b, err := json.MarshalIndent(&report, "", " ")
suite.NoError(err)
suite.Equal(`{
"id": "01GP3AWY4CRDVRNZKW0TEAMB5R",
"created_at": "2022-05-14T10:20:03.000Z",
"action_taken": false,
"action_taken_at": null,
"action_taken_comment": null,
"category": "other",
"comment": "dark souls sucks, please yeet this nerd",
"forwarded": true,
"status_ids": [
"01FVW7JHQFSFK166WWKR8CBA6M"
],
"rule_ids": [],
"target_account": {
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
"username": "foss_satan",
"acct": "foss_satan@fossbros-anonymous.io",
"display_name": "big gerald",
"locked": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
"url": "http://fossbros-anonymous.io/@foss_satan",
"avatar": "",
"avatar_static": "",
"header": "http://localhost:8080/assets/default_header.png",
"header_static": "http://localhost:8080/assets/default_header.png",
"followers_count": 0,
"following_count": 0,
"statuses_count": 1,
"last_status_at": "2021-09-20T10:40:37.000Z",
"emojis": [],
"fields": []
}
}`, string(b))
}
func (suite *ReportGetTestSuite) TestGetReport2() {
targetReport := suite.testReports["remote_account_1_report_local_account_2"]
report, err := suite.getReport(http.StatusNotFound, `{"error":"Not Found"}`, targetReport.ID)
suite.NoError(err)
suite.Nil(report)
}
func (suite *ReportGetTestSuite) TestGetReport3() {
report, err := suite.getReport(http.StatusBadRequest, `{"error":"Bad Request: no report id specified"}`, "")
suite.NoError(err)
suite.Nil(report)
}
func (suite *ReportGetTestSuite) TestGetReport4() {
report, err := suite.getReport(http.StatusNotFound, `{"error":"Not Found"}`, "01GPJWHQS1BG0SF0WZ1SABC4RZ")
suite.NoError(err)
suite.Nil(report)
}
func TestReportGetTestSuite(t *testing.T) {
suite.Run(t, &ReportGetTestSuite{})
}

View file

@ -0,0 +1,54 @@
/*
GoToSocial
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package reports
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/processing"
)
const (
BasePath = "/v1/reports"
IDKey = "id"
ResolvedKey = "resolved"
TargetAccountIDKey = "target_account_id"
MaxIDKey = "max_id"
SinceIDKey = "since_id"
MinIDKey = "min_id"
LimitKey = "limit"
BasePathWithID = BasePath + "/:" + IDKey
)
type Module struct {
processor processing.Processor
}
func New(processor processing.Processor) *Module {
return &Module{
processor: processor,
}
}
func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) {
attachHandler(http.MethodGet, BasePath, m.ReportsGETHandler)
attachHandler(http.MethodPost, BasePath, m.ReportPOSTHandler)
attachHandler(http.MethodGet, BasePathWithID, m.ReportGETHandler)
}

View file

@ -0,0 +1,93 @@
/*
GoToSocial
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package reports_test
import (
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/api/client/reports"
"github.com/superseriousbusiness/gotosocial/internal/concurrency"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/email"
"github.com/superseriousbusiness/gotosocial/internal/federation"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/messages"
"github.com/superseriousbusiness/gotosocial/internal/processing"
"github.com/superseriousbusiness/gotosocial/internal/storage"
"github.com/superseriousbusiness/gotosocial/testrig"
)
type ReportsStandardTestSuite struct {
suite.Suite
db db.DB
storage *storage.Driver
mediaManager media.Manager
federator federation.Federator
processor processing.Processor
emailSender email.Sender
sentEmails map[string]string
// standard suite models
testTokens map[string]*gtsmodel.Token
testClients map[string]*gtsmodel.Client
testApplications map[string]*gtsmodel.Application
testUsers map[string]*gtsmodel.User
testAccounts map[string]*gtsmodel.Account
testStatuses map[string]*gtsmodel.Status
testReports map[string]*gtsmodel.Report
// module being tested
reportsModule *reports.Module
}
func (suite *ReportsStandardTestSuite) SetupSuite() {
suite.testTokens = testrig.NewTestTokens()
suite.testClients = testrig.NewTestClients()
suite.testApplications = testrig.NewTestApplications()
suite.testUsers = testrig.NewTestUsers()
suite.testAccounts = testrig.NewTestAccounts()
suite.testStatuses = testrig.NewTestStatuses()
suite.testReports = testrig.NewTestReports()
}
func (suite *ReportsStandardTestSuite) SetupTest() {
testrig.InitTestConfig()
testrig.InitTestLog()
fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1)
clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1)
suite.db = testrig.NewTestDB()
suite.storage = testrig.NewInMemoryStorage()
suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage)
suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker)
suite.sentEmails = make(map[string]string)
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker)
suite.reportsModule = reports.New(suite.processor)
testrig.StandardDBSetup(suite.db, nil)
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
suite.NoError(suite.processor.Start())
}
func (suite *ReportsStandardTestSuite) TearDownTest() {
testrig.StandardDBTeardown(suite.db)
testrig.StandardStorageTeardown(suite.storage)
}

View file

@ -0,0 +1,173 @@
/*
GoToSocial
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package reports
import (
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
// ReportsGETHandler swagger:operation GET /api/v1/reports reports
//
// See reports created by the requesting account.
//
// The reports will be returned in descending chronological order (newest first), with sequential IDs (bigger = newer).
//
// The next and previous queries can be parsed from the returned Link header.
//
// Example:
//
// ```
// <https://example.org/api/v1/reports?limit=20&max_id=01FC0SKA48HNSVR6YKZCQGS2V8>; rel="next", <https://example.org/api/v1/reports?limit=20&min_id=01FC0SKW5JK2Q4EVAV2B462YY0>; rel="prev"
// ````
//
// ---
// tags:
// - reports
//
// produces:
// - application/json
//
// parameters:
// -
// name: resolved
// type: boolean
// description: >-
// If set to true, only resolved reports will be returned.
// If false, only unresolved reports will be returned.
// If unset, reports will not be filtered on their resolved status.
// in: query
// -
// name: target_account_id
// type: string
// description: Return only reports that target the given account id.
// in: query
// -
// name: max_id
// type: string
// description: >-
// Return only reports *OLDER* than the given max ID.
// The report with the specified ID will not be included in the response.
// in: query
// -
// name: since_id
// type: string
// description: >-
// Return only reports *NEWER* than the given since ID.
// The report with the specified ID will not be included in the response.
// This parameter is functionally equivalent to min_id.
// in: query
// -
// name: min_id
// type: string
// description: >-
// Return only reports *NEWER* than the given min ID.
// The report with the specified ID will not be included in the response.
// This parameter is functionally equivalent to since_id.
// in: query
// -
// name: limit
// type: integer
// description: >-
// Number of reports to return.
// If less than 1, will be clamped to 1.
// If more than 100, will be clamped to 100.
// default: 20
// in: query
//
// security:
// - OAuth2 Bearer:
// - read:reports
//
// responses:
// '200':
// name: reports
// description: Array of reports.
// schema:
// type: array
// items:
// "$ref": "#/definitions/report"
// '400':
// description: bad request
// '401':
// description: unauthorized
// '404':
// description: not found
// '406':
// description: not acceptable
// '500':
// description: internal server error
func (m *Module) ReportsGETHandler(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.InstanceGet)
return
}
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
return
}
var resolved *bool
if resolvedString := c.Query(ResolvedKey); resolvedString != "" {
i, err := strconv.ParseBool(resolvedString)
if err != nil {
err := fmt.Errorf("error parsing %s: %s", ResolvedKey, err)
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
return
}
resolved = &i
}
limit := 20
if limitString := c.Query(LimitKey); limitString != "" {
i, err := strconv.Atoi(limitString)
if err != nil {
err := fmt.Errorf("error parsing %s: %s", LimitKey, err)
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
return
}
// normalize
if i <= 0 {
i = 1
} else if i >= 100 {
i = 100
}
limit = i
}
resp, errWithCode := m.processor.ReportsGet(c.Request.Context(), authed, resolved, c.Query(TargetAccountIDKey), c.Query(MaxIDKey), c.Query(SinceIDKey), c.Query(MinIDKey), limit)
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
return
}
if resp.LinkHeader != "" {
c.Header("Link", resp.LinkHeader)
}
c.JSON(http.StatusOK, resp.Items)
}

View file

@ -0,0 +1,376 @@
/*
GoToSocial
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package reports_test
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/api/client/reports"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/testrig"
)
type ReportsGetTestSuite struct {
ReportsStandardTestSuite
}
func (suite *ReportsGetTestSuite) getReports(
account *gtsmodel.Account,
token *gtsmodel.Token,
user *gtsmodel.User,
expectedHTTPStatus int,
resolved *bool,
targetAccountID string,
maxID string,
sinceID string,
minID string,
limit int,
) ([]*apimodel.Report, string, error) {
// instantiate recorder + test context
recorder := httptest.NewRecorder()
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
ctx.Set(oauth.SessionAuthorizedAccount, account)
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(token))
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
ctx.Set(oauth.SessionAuthorizedUser, user)
// create the request URI
requestPath := reports.BasePath + "?" + reports.LimitKey + "=" + strconv.Itoa(limit)
if resolved != nil {
requestPath = requestPath + "&" + reports.ResolvedKey + "=" + strconv.FormatBool(*resolved)
}
if targetAccountID != "" {
requestPath = requestPath + "&" + reports.TargetAccountIDKey + "=" + targetAccountID
}
if maxID != "" {
requestPath = requestPath + "&" + reports.MaxIDKey + "=" + maxID
}
if sinceID != "" {
requestPath = requestPath + "&" + reports.SinceIDKey + "=" + sinceID
}
if minID != "" {
requestPath = requestPath + "&" + reports.MinIDKey + "=" + minID
}
baseURI := config.GetProtocol() + "://" + config.GetHost()
requestURI := baseURI + "/api/" + requestPath
// create the request
ctx.Request = httptest.NewRequest(http.MethodGet, requestURI, nil)
ctx.Request.Header.Set("accept", "application/json")
// trigger the handler
suite.reportsModule.ReportsGETHandler(ctx)
// read the response
result := recorder.Result()
defer result.Body.Close()
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
return nil, "", fmt.Errorf("expected %d got %d", expectedHTTPStatus, resultCode)
}
b, err := ioutil.ReadAll(result.Body)
if err != nil {
return nil, "", err
}
resp := []*apimodel.Report{}
if err := json.Unmarshal(b, &resp); err != nil {
return nil, "", err
}
return resp, result.Header.Get("Link"), nil
}
func (suite *ReportsGetTestSuite) TestGetReports() {
testAccount := suite.testAccounts["local_account_2"]
testToken := suite.testTokens["local_account_2"]
testUser := suite.testUsers["local_account_2"]
reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, nil, "", "", "", "", 20)
suite.NoError(err)
suite.NotEmpty(reports)
b, err := json.MarshalIndent(&reports, "", " ")
suite.NoError(err)
suite.Equal(`[
{
"id": "01GP3AWY4CRDVRNZKW0TEAMB5R",
"created_at": "2022-05-14T10:20:03.000Z",
"action_taken": false,
"action_taken_at": null,
"action_taken_comment": null,
"category": "other",
"comment": "dark souls sucks, please yeet this nerd",
"forwarded": true,
"status_ids": [
"01FVW7JHQFSFK166WWKR8CBA6M"
],
"rule_ids": [],
"target_account": {
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
"username": "foss_satan",
"acct": "foss_satan@fossbros-anonymous.io",
"display_name": "big gerald",
"locked": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
"url": "http://fossbros-anonymous.io/@foss_satan",
"avatar": "",
"avatar_static": "",
"header": "http://localhost:8080/assets/default_header.png",
"header_static": "http://localhost:8080/assets/default_header.png",
"followers_count": 0,
"following_count": 0,
"statuses_count": 1,
"last_status_at": "2021-09-20T10:40:37.000Z",
"emojis": [],
"fields": []
}
}
]`, string(b))
suite.Equal(`<http://localhost:8080/api/v1/reports?limit=20&max_id=01GP3AWY4CRDVRNZKW0TEAMB5R>; rel="next", <http://localhost:8080/api/v1/reports?limit=20&min_id=01GP3AWY4CRDVRNZKW0TEAMB5R>; rel="prev"`, link)
}
func (suite *ReportsGetTestSuite) TestGetReports2() {
testAccount := suite.testAccounts["local_account_2"]
testToken := suite.testTokens["local_account_2"]
testUser := suite.testUsers["local_account_2"]
reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, nil, "", "01GP3AWY4CRDVRNZKW0TEAMB5R", "", "", 20)
suite.NoError(err)
suite.Empty(reports)
b, err := json.MarshalIndent(&reports, "", " ")
suite.NoError(err)
suite.Equal(`[]`, string(b))
suite.Empty(link)
}
func (suite *ReportsGetTestSuite) TestGetReports3() {
testAccount := suite.testAccounts["local_account_1"]
testToken := suite.testTokens["local_account_1"]
testUser := suite.testUsers["local_account_1"]
reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, nil, "", "", "", "", 20)
suite.NoError(err)
suite.Empty(reports)
b, err := json.MarshalIndent(&reports, "", " ")
suite.NoError(err)
suite.Equal(`[]`, string(b))
suite.Empty(link)
}
func (suite *ReportsGetTestSuite) TestGetReports4() {
testAccount := suite.testAccounts["local_account_2"]
testToken := suite.testTokens["local_account_2"]
testUser := suite.testUsers["local_account_2"]
resolved := testrig.FalseBool()
reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, resolved, "", "", "", "", 20)
suite.NoError(err)
suite.NotEmpty(reports)
b, err := json.MarshalIndent(&reports, "", " ")
suite.NoError(err)
suite.Equal(`[
{
"id": "01GP3AWY4CRDVRNZKW0TEAMB5R",
"created_at": "2022-05-14T10:20:03.000Z",
"action_taken": false,
"action_taken_at": null,
"action_taken_comment": null,
"category": "other",
"comment": "dark souls sucks, please yeet this nerd",
"forwarded": true,
"status_ids": [
"01FVW7JHQFSFK166WWKR8CBA6M"
],
"rule_ids": [],
"target_account": {
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
"username": "foss_satan",
"acct": "foss_satan@fossbros-anonymous.io",
"display_name": "big gerald",
"locked": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
"url": "http://fossbros-anonymous.io/@foss_satan",
"avatar": "",
"avatar_static": "",
"header": "http://localhost:8080/assets/default_header.png",
"header_static": "http://localhost:8080/assets/default_header.png",
"followers_count": 0,
"following_count": 0,
"statuses_count": 1,
"last_status_at": "2021-09-20T10:40:37.000Z",
"emojis": [],
"fields": []
}
}
]`, string(b))
suite.Equal(`<http://localhost:8080/api/v1/reports?limit=20&max_id=01GP3AWY4CRDVRNZKW0TEAMB5R&resolved=false>; rel="next", <http://localhost:8080/api/v1/reports?limit=20&min_id=01GP3AWY4CRDVRNZKW0TEAMB5R&resolved=false>; rel="prev"`, link)
}
func (suite *ReportsGetTestSuite) TestGetReports5() {
testAccount := suite.testAccounts["local_account_1"]
testToken := suite.testTokens["local_account_1"]
testUser := suite.testUsers["local_account_1"]
resolved := testrig.TrueBool()
reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, resolved, "", "", "", "", 20)
suite.NoError(err)
suite.Empty(reports)
b, err := json.MarshalIndent(&reports, "", " ")
suite.NoError(err)
suite.Equal(`[]`, string(b))
suite.Empty(link)
}
func (suite *ReportsGetTestSuite) TestGetReports6() {
testAccount := suite.testAccounts["local_account_2"]
testToken := suite.testTokens["local_account_2"]
testUser := suite.testUsers["local_account_2"]
reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, nil, "01F8MH5ZK5VRH73AKHQM6Y9VNX", "", "", "", 20)
suite.NoError(err)
suite.NotEmpty(reports)
b, err := json.MarshalIndent(&reports, "", " ")
suite.NoError(err)
suite.Equal(`[
{
"id": "01GP3AWY4CRDVRNZKW0TEAMB5R",
"created_at": "2022-05-14T10:20:03.000Z",
"action_taken": false,
"action_taken_at": null,
"action_taken_comment": null,
"category": "other",
"comment": "dark souls sucks, please yeet this nerd",
"forwarded": true,
"status_ids": [
"01FVW7JHQFSFK166WWKR8CBA6M"
],
"rule_ids": [],
"target_account": {
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
"username": "foss_satan",
"acct": "foss_satan@fossbros-anonymous.io",
"display_name": "big gerald",
"locked": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
"url": "http://fossbros-anonymous.io/@foss_satan",
"avatar": "",
"avatar_static": "",
"header": "http://localhost:8080/assets/default_header.png",
"header_static": "http://localhost:8080/assets/default_header.png",
"followers_count": 0,
"following_count": 0,
"statuses_count": 1,
"last_status_at": "2021-09-20T10:40:37.000Z",
"emojis": [],
"fields": []
}
}
]`, string(b))
suite.Equal(`<http://localhost:8080/api/v1/reports?limit=20&max_id=01GP3AWY4CRDVRNZKW0TEAMB5R&target_account_id=01F8MH5ZK5VRH73AKHQM6Y9VNX>; rel="next", <http://localhost:8080/api/v1/reports?limit=20&min_id=01GP3AWY4CRDVRNZKW0TEAMB5R&target_account_id=01F8MH5ZK5VRH73AKHQM6Y9VNX>; rel="prev"`, link)
}
func (suite *ReportsGetTestSuite) TestGetReports7() {
testAccount := suite.testAccounts["local_account_2"]
testToken := suite.testTokens["local_account_2"]
testUser := suite.testUsers["local_account_2"]
resolved := testrig.FalseBool()
reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, resolved, "01F8MH5ZK5VRH73AKHQM6Y9VNX", "", "", "", 20)
suite.NoError(err)
suite.NotEmpty(reports)
b, err := json.MarshalIndent(&reports, "", " ")
suite.NoError(err)
suite.Equal(`[
{
"id": "01GP3AWY4CRDVRNZKW0TEAMB5R",
"created_at": "2022-05-14T10:20:03.000Z",
"action_taken": false,
"action_taken_at": null,
"action_taken_comment": null,
"category": "other",
"comment": "dark souls sucks, please yeet this nerd",
"forwarded": true,
"status_ids": [
"01FVW7JHQFSFK166WWKR8CBA6M"
],
"rule_ids": [],
"target_account": {
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
"username": "foss_satan",
"acct": "foss_satan@fossbros-anonymous.io",
"display_name": "big gerald",
"locked": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
"url": "http://fossbros-anonymous.io/@foss_satan",
"avatar": "",
"avatar_static": "",
"header": "http://localhost:8080/assets/default_header.png",
"header_static": "http://localhost:8080/assets/default_header.png",
"followers_count": 0,
"following_count": 0,
"statuses_count": 1,
"last_status_at": "2021-09-20T10:40:37.000Z",
"emojis": [],
"fields": []
}
}
]`, string(b))
suite.Equal(`<http://localhost:8080/api/v1/reports?limit=20&max_id=01GP3AWY4CRDVRNZKW0TEAMB5R&resolved=false&target_account_id=01F8MH5ZK5VRH73AKHQM6Y9VNX>; rel="next", <http://localhost:8080/api/v1/reports?limit=20&min_id=01GP3AWY4CRDVRNZKW0TEAMB5R&resolved=false&target_account_id=01F8MH5ZK5VRH73AKHQM6Y9VNX>; rel="prev"`, link)
}
func TestReportsGetTestSuite(t *testing.T) {
suite.Run(t, &ReportsGetTestSuite{})
}

View file

@ -58,26 +58,7 @@ type AdminAccountInfo struct {
// AdminReportInfo models the admin view of a report.
type AdminReportInfo struct {
// The ID of the report in the database.
ID string `json:"id"`
// The action taken to resolve this report.
ActionTaken string `json:"action_taken"`
// An optional reason for reporting.
Comment string `json:"comment"`
// The time the report was filed. (ISO 8601 Datetime)
CreatedAt string `json:"created_at"`
// The time of last action on this report. (ISO 8601 Datetime)
UpdatedAt string `json:"updated_at"`
// The account which filed the report.
Account *Account `json:"account"`
// The account being reported.
TargetAccount *Account `json:"target_account"`
// The account of the moderator assigned to this report.
AssignedAccount *Account `json:"assigned_account"`
// The action taken by the moderator who handled the report.
ActionTakenByAccount string `json:"action_taken_by_account"`
// Statuses attached to the report, for context.
Statuses []Status `json:"statuses"`
Report
}
// AdminEmoji models the admin view of a custom emoji.

View file

@ -0,0 +1,97 @@
/*
GoToSocial
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package model
// Report models a moderation report submitted to the instance, either via the client API or via the federated API.
//
// swagger:model report
type Report struct {
// ID of the report.
// example: 01FBVD42CQ3ZEEVMW180SBX03B
ID string `json:"id"`
// The date when this report was created (ISO 8601 Datetime).
// example: 2021-07-30T09:20:25+00:00
CreatedAt string `json:"created_at"`
// Whether an action has been taken by an admin in response to this report.
// example: false
ActionTaken bool `json:"action_taken"`
// If an action was taken, at what time was this done? (ISO 8601 Datetime)
// Will be null if not set / no action yet taken.
// example: 2021-07-30T09:20:25+00:00
ActionTakenAt *string `json:"action_taken_at"`
// 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.
ActionComment *string `json:"action_taken_comment"`
// Under what category was this report created?
// example: spam
Category string `json:"category"`
// Comment submitted when the report was created.
// Will be empty if no comment was submitted.
// example: This person has been harassing me.
Comment string `json:"comment"`
// Bool to indicate that report should be federated to remote instance.
// example: true
Forwarded bool `json:"forwarded"`
// Array of IDs of statuses that were submitted along with this report.
// Will be empty if no status IDs were submitted.
// example: ["01GPBN5YDY6JKBWE44H7YQBDCQ","01GPBN65PDWSBPWVDD0SQCFFY3"]
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"`
// Account that was reported.
TargetAccount *Account `json:"target_account"`
}
// ReportCreateRequest models user report creation parameters.
//
// swagger:parameters reportCreate
type ReportCreateRequest struct {
// ID of the account to report.
// example: 01GPE75FXSH2EGFBF85NXPH3KP
// in: formData
// required: true
AccountID string `form:"account_id" json:"account_id" xml:"account_id"`
// IDs of statuses to attach to the report to provide additional context.
// example: ["01GPE76N4SBVRZ8K24TW51ZZQ4","01GPE76WN9JZE62EPT3Q9FRRD4"]
// in: formData
StatusIDs []string `form:"status_ids[]" json:"status_ids" xml:"status_ids"`
// The reason for the report. Default maximum of 1000 characters.
// example: Anti-Blackness, transphobia.
// in: formData
Comment string `form:"comment" json:"comment" xml:"comment"`
// If the account is remote, should the report be forwarded to the remote admin?
// example: true
// default: false
// in: formData
Forward bool `form:"forward" json:"forward" xml:"forward"`
// Specify if the report is due to spam, violation of enumerated instance rules, or some other reason.
// Currently only 'other' is supported.
// example: other
// default: other
// 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]
// in: formData
RuleIDs []int `form:"rule_ids[]" json:"rule_ids" xml:"rule_ids"`
}