mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[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:
@@ -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 {
|
||||
|
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
39
internal/processing/report.go
Normal file
39
internal/processing/report.go
Normal 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)
|
||||
}
|
103
internal/processing/report/create.go
Normal file
103
internal/processing/report/create.go
Normal 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 := >smodel.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
|
||||
}
|
51
internal/processing/report/getreport.go
Normal file
51
internal/processing/report/getreport.go
Normal 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
|
||||
}
|
79
internal/processing/report/getreports.go
Normal file
79
internal/processing/report/getreports.go
Normal 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,
|
||||
})
|
||||
}
|
51
internal/processing/report/report.go
Normal file
51
internal/processing/report/report.go
Normal 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,
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user