[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
parent 605dfca1af
commit e9747247d5
26 changed files with 2184 additions and 20 deletions

View File

@@ -121,6 +121,12 @@ func (p *processor) ProcessFromClientAPI(ctx context.Context, clientMsg messages
// DELETE ACCOUNT/PROFILE
return p.processDeleteAccountFromClientAPI(ctx, clientMsg)
}
case ap.ActivityFlag:
// FLAG
if clientMsg.APObjectType == ap.ObjectProfile {
// FLAG/REPORT A PROFILE
return p.processReportAccountFromClientAPI(ctx, clientMsg)
}
}
return nil
}
@@ -338,6 +344,13 @@ func (p *processor) processDeleteAccountFromClientAPI(ctx context.Context, clien
return p.accountProcessor.Delete(ctx, clientMsg.TargetAccount, origin)
}
func (p *processor) processReportAccountFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error {
// TODO: in a separate PR, handle side effects of flag/report
// 1. email admin(s)
// 2. federate report if necessary
return nil
}
// TODO: move all the below functions into federation.Federator
func (p *processor) federateAccountDelete(ctx context.Context, account *gtsmodel.Account) error {

View File

@@ -38,6 +38,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/processing/admin"
federationProcessor "github.com/superseriousbusiness/gotosocial/internal/processing/federation"
mediaProcessor "github.com/superseriousbusiness/gotosocial/internal/processing/media"
"github.com/superseriousbusiness/gotosocial/internal/processing/report"
"github.com/superseriousbusiness/gotosocial/internal/processing/status"
"github.com/superseriousbusiness/gotosocial/internal/processing/streaming"
"github.com/superseriousbusiness/gotosocial/internal/processing/user"
@@ -232,6 +233,13 @@ type Processor interface {
// The user belonging to the confirmed email is also returned.
UserConfirmEmail(ctx context.Context, token string) (*gtsmodel.User, gtserror.WithCode)
// ReportsGet returns reports created by the given user.
ReportsGet(ctx context.Context, authed *oauth.Auth, resolved *bool, targetAccountID string, maxID string, sinceID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode)
// ReportGet returns one report created by the given user.
ReportGet(ctx context.Context, authed *oauth.Auth, id string) (*apimodel.Report, gtserror.WithCode)
// ReportCreate creates a new report using the given account and form.
ReportCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.ReportCreateRequest) (*apimodel.Report, gtserror.WithCode)
/*
FEDERATION API-FACING PROCESSING FUNCTIONS
These functions are intended to be called when the federating client needs an immediate (ie., synchronous) reply
@@ -303,6 +311,7 @@ type processor struct {
mediaProcessor mediaProcessor.Processor
userProcessor user.Processor
federationProcessor federationProcessor.Processor
reportProcessor report.Processor
}
// NewProcessor returns a new Processor.
@@ -326,6 +335,7 @@ func NewProcessor(
mediaProcessor := mediaProcessor.New(db, tc, mediaManager, federator.TransportController(), storage)
userProcessor := user.New(db, emailSender)
federationProcessor := federationProcessor.New(db, tc, federator)
reportProcessor := report.New(db, tc, clientWorker)
filter := visibility.NewFilter(db)
return &processor{
@@ -348,6 +358,7 @@ func NewProcessor(
mediaProcessor: mediaProcessor,
userProcessor: userProcessor,
federationProcessor: federationProcessor,
reportProcessor: reportProcessor,
}
}

View File

@@ -0,0 +1,39 @@
/*
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 processing
import (
"context"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
func (p *processor) ReportsGet(ctx context.Context, authed *oauth.Auth, resolved *bool, targetAccountID string, maxID string, sinceID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) {
return p.reportProcessor.ReportsGet(ctx, authed.Account, resolved, targetAccountID, maxID, sinceID, minID, limit)
}
func (p *processor) ReportGet(ctx context.Context, authed *oauth.Auth, id string) (*apimodel.Report, gtserror.WithCode) {
return p.reportProcessor.ReportGet(ctx, authed.Account, id)
}
func (p *processor) ReportCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.ReportCreateRequest) (*apimodel.Report, gtserror.WithCode) {
return p.reportProcessor.Create(ctx, authed.Account, form)
}

View File

@@ -0,0 +1,103 @@
/*
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 report
import (
"context"
"errors"
"fmt"
"github.com/superseriousbusiness/gotosocial/internal/ap"
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/messages"
"github.com/superseriousbusiness/gotosocial/internal/uris"
)
func (p *processor) Create(ctx context.Context, account *gtsmodel.Account, form *apimodel.ReportCreateRequest) (*apimodel.Report, gtserror.WithCode) {
if account.ID == form.AccountID {
err := errors.New("cannot report your own account")
return nil, gtserror.NewErrorBadRequest(err, err.Error())
}
// validate + fetch target account
targetAccount, err := p.db.GetAccountByID(ctx, form.AccountID)
if err != nil {
if errors.Is(err, db.ErrNoEntries) {
err = fmt.Errorf("account with ID %s does not exist", form.AccountID)
return nil, gtserror.NewErrorBadRequest(err, err.Error())
}
err = fmt.Errorf("db error fetching report target account: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
// fetch statuses by IDs given in the report form (noop if no statuses given)
statuses, err := p.db.GetStatuses(ctx, form.StatusIDs)
if err != nil {
err = fmt.Errorf("db error fetching report target statuses: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
for _, s := range statuses {
if s.AccountID != form.AccountID {
err = fmt.Errorf("status with ID %s does not belong to account %s", s.ID, form.AccountID)
return nil, gtserror.NewErrorBadRequest(err, err.Error())
}
}
reportID, err := id.NewULID()
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
report := &gtsmodel.Report{
ID: reportID,
URI: uris.GenerateURIForReport(reportID),
AccountID: account.ID,
Account: account,
TargetAccountID: form.AccountID,
TargetAccount: targetAccount,
Comment: form.Comment,
StatusIDs: form.StatusIDs,
Statuses: statuses,
Forwarded: &form.Forward,
}
if err := p.db.PutReport(ctx, report); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
p.clientWorker.Queue(messages.FromClientAPI{
APObjectType: ap.ObjectProfile,
APActivityType: ap.ActivityFlag,
GTSModel: report,
OriginAccount: account,
})
apiReport, err := p.tc.ReportToAPIReport(ctx, report)
if err != nil {
err = fmt.Errorf("error converting report to frontend representation: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
return apiReport, nil
}

View File

@@ -0,0 +1,51 @@
/*
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 report
import (
"context"
"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"
)
func (p *processor) ReportGet(ctx context.Context, account *gtsmodel.Account, id string) (*apimodel.Report, gtserror.WithCode) {
report, err := p.db.GetReportByID(ctx, id)
if err != nil {
if err == db.ErrNoEntries {
return nil, gtserror.NewErrorNotFound(err)
}
return nil, gtserror.NewErrorInternalError(err)
}
if report.AccountID != account.ID {
err = fmt.Errorf("report with id %s does not belong to account %s", report.ID, account.ID)
return nil, gtserror.NewErrorNotFound(err)
}
apiReport, err := p.tc.ReportToAPIReport(ctx, report)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting report to api: %s", err))
}
return apiReport, nil
}

View File

@@ -0,0 +1,79 @@
/*
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 report
import (
"context"
"fmt"
"strconv"
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/util"
)
func (p *processor) ReportsGet(ctx context.Context, account *gtsmodel.Account, resolved *bool, targetAccountID string, maxID string, sinceID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) {
reports, err := p.db.GetReports(ctx, resolved, account.ID, targetAccountID, maxID, sinceID, minID, limit)
if err != nil {
if err == db.ErrNoEntries {
return util.EmptyPageableResponse(), nil
}
return nil, gtserror.NewErrorInternalError(err)
}
count := len(reports)
items := make([]interface{}, 0, count)
nextMaxIDValue := ""
prevMinIDValue := ""
for i, r := range reports {
item, err := p.tc.ReportToAPIReport(ctx, r)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting report to api: %s", err))
}
if i == count-1 {
nextMaxIDValue = item.ID
}
if i == 0 {
prevMinIDValue = item.ID
}
items = append(items, item)
}
extraQueryParams := []string{}
if resolved != nil {
extraQueryParams = append(extraQueryParams, "resolved="+strconv.FormatBool(*resolved))
}
if targetAccountID != "" {
extraQueryParams = append(extraQueryParams, "target_account_id="+targetAccountID)
}
return util.PackagePageableResponse(util.PageableResponseParams{
Items: items,
Path: "/api/v1/reports",
NextMaxIDValue: nextMaxIDValue,
PrevMinIDValue: prevMinIDValue,
Limit: limit,
ExtraQueryParams: extraQueryParams,
})
}

View File

@@ -0,0 +1,51 @@
/*
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 report
import (
"context"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/concurrency"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/messages"
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
)
type Processor interface {
ReportsGet(ctx context.Context, account *gtsmodel.Account, resolved *bool, targetAccountID string, maxID string, sinceID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode)
ReportGet(ctx context.Context, account *gtsmodel.Account, id string) (*apimodel.Report, gtserror.WithCode)
Create(ctx context.Context, account *gtsmodel.Account, form *apimodel.ReportCreateRequest) (*apimodel.Report, gtserror.WithCode)
}
type processor struct {
db db.DB
tc typeutils.TypeConverter
clientWorker *concurrency.WorkerPool[messages.FromClientAPI]
}
func New(db db.DB, tc typeutils.TypeConverter, clientWorker *concurrency.WorkerPool[messages.FromClientAPI]) Processor {
return &processor{
tc: tc,
db: db,
clientWorker: clientWorker,
}
}