Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Store admin actions in the db, prevent conflicting actions #2167

Merged
merged 16 commits into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion cmd/gotosocial/action/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,14 @@ var Start action.GTSAction = func(ctx context.Context) error {
// Create the processor using all the other services we've created so far.
processor := processing.NewProcessor(typeConverter, federator, oauthServer, mediaManager, &state, emailSender)

// Set state client / federator worker enqueue functions
// Set state client / federator asynchronous worker enqueue functions
state.Workers.EnqueueClientAPI = processor.Workers().EnqueueClientAPI
state.Workers.EnqueueFediAPI = processor.Workers().EnqueueFediAPI

// Set state client / federator synchronous processing functions.
state.Workers.ProcessFromClientAPI = processor.Workers().ProcessFromClientAPI
state.Workers.ProcessFromFediAPI = processor.Workers().ProcessFromFediAPI

/*
HTTP router initialization
*/
Expand Down
6 changes: 6 additions & 0 deletions docs/api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3633,6 +3633,8 @@ paths:
description: not found
"406":
description: not acceptable
"409":
description: 'Conflict: There is already an admin action running that conflicts with this action. Check the error message in the response body for more information. This is a temporary error; it should be possible to process this action if you try again in a bit.'
"500":
description: internal server error
security:
Expand Down Expand Up @@ -4022,6 +4024,8 @@ paths:
description: not found
"406":
description: not acceptable
"409":
description: 'Conflict: There is already an admin action running that conflicts with this action. Check the error message in the response body for more information. This is a temporary error; it should be possible to process this action if you try again in a bit.'
"500":
description: internal server error
security:
Expand Down Expand Up @@ -4056,6 +4060,8 @@ paths:
description: not found
"406":
description: not acceptable
"409":
description: 'Conflict: There is already an admin action running that conflicts with this action. Check the error message in the response body for more information. This is a temporary error; it should be possible to process this action if you try again in a bit.'
"500":
description: internal server error
security:
Expand Down
11 changes: 8 additions & 3 deletions internal/api/client/admin/accountaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ import (
// description: not found
// '406':
// description: not acceptable
// '409':
// description: >-
// Conflict: There is already an admin action running that conflicts with this action.
// Check the error message in the response body for more information. This is a temporary
// error; it should be possible to process this action if you try again in a bit.
// '500':
// description: internal server error
func (m *Module) AccountActionPOSTHandler(c *gin.Context) {
Expand All @@ -94,7 +99,7 @@ func (m *Module) AccountActionPOSTHandler(c *gin.Context) {
return
}

form := &apimodel.AdminAccountActionRequest{}
form := &apimodel.AdminActionRequest{}
if err := c.ShouldBind(form); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
return
Expand All @@ -112,9 +117,9 @@ func (m *Module) AccountActionPOSTHandler(c *gin.Context) {
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
return
}
form.TargetAccountID = targetAcctID
form.TargetID = targetAcctID

if errWithCode := m.processor.Admin().AccountAction(c.Request.Context(), authed.Account, form); errWithCode != nil {
if _, errWithCode := m.processor.Admin().AccountAction(c.Request.Context(), authed.Account, form); errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
Expand Down
7 changes: 6 additions & 1 deletion internal/api/client/admin/domainblockcreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ import (
// description: not found
// '406':
// description: not acceptable
// '409':
// description: >-
// Conflict: There is already an admin action running that conflicts with this action.
// Check the error message in the response body for more information. This is a temporary
// error; it should be possible to process this action if you try again in a bit.
// '500':
// description: internal server error
func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) {
Expand Down Expand Up @@ -159,7 +164,7 @@ func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) {

if !importing {
// Single domain block creation.
domainBlock, errWithCode := m.processor.Admin().DomainBlockCreate(
domainBlock, _, errWithCode := m.processor.Admin().DomainBlockCreate(
c.Request.Context(),
authed.Account,
form.Domain,
Expand Down
7 changes: 6 additions & 1 deletion internal/api/client/admin/domainblockdelete.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ import (
// description: not found
// '406':
// description: not acceptable
// '409':
// description: >-
// Conflict: There is already an admin action running that conflicts with this action.
// Check the error message in the response body for more information. This is a temporary
// error; it should be possible to process this action if you try again in a bit.
// '500':
// description: internal server error
func (m *Module) DomainBlockDELETEHandler(c *gin.Context) {
Expand Down Expand Up @@ -93,7 +98,7 @@ func (m *Module) DomainBlockDELETEHandler(c *gin.Context) {
return
}

domainBlock, errWithCode := m.processor.Admin().DomainBlockDelete(c.Request.Context(), authed.Account, domainBlockID)
domainBlock, _, errWithCode := m.processor.Admin().DomainBlockDelete(c.Request.Context(), authed.Account, domainBlockID)
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
Expand Down
13 changes: 8 additions & 5 deletions internal/api/model/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,16 +163,19 @@ type AdminEmoji struct {
URI string `json:"uri"`
}

// AdminAccountActionRequest models the admin view of an account's details.
// AdminActionRequest models a request
// for an admin action to be performed.
//
// swagger:ignore
type AdminAccountActionRequest struct {
// Type of the account action. One of disable, silence, suspend.
type AdminActionRequest struct {
// Category of the target entity.
Category string `form:"-" json:"-" xml:"-"`
// Type of admin action to take. One of disable, silence, suspend.
Type string `form:"type" json:"type" xml:"type"`
// Text describing why an action was taken.
Text string `form:"text" json:"text" xml:"text"`
// ID of the account to be acted on.
TargetAccountID string `form:"-" json:"-" xml:"-"`
// ID of the target entity.
TargetID string `form:"-" json:"-" xml:"-"`
}

// MediaCleanupRequest models admin media cleanup parameters
Expand Down
19 changes: 19 additions & 0 deletions internal/db/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,23 @@ type Admin interface {
// Ie., if the instance is hosted at 'example.org' the instance will have a domain of 'example.org'.
// This is needed for things like serving instance information through /api/v1/instance
CreateInstanceInstance(ctx context.Context) error

/*
ACTION FUNCS
*/

// GetAdminAction returns the admin action with the given ID.
GetAdminAction(ctx context.Context, id string) (*gtsmodel.AdminAction, error)

// GetAdminActions gets all admin actions from the database.
GetAdminActions(ctx context.Context) ([]*gtsmodel.AdminAction, error)

// PutAdminAction puts one admin action in the database.
PutAdminAction(ctx context.Context, action *gtsmodel.AdminAction) error

// UpdateAdminAction updates one admin action by its ID.
UpdateAdminAction(ctx context.Context, action *gtsmodel.AdminAction, columns ...string) error

// DeleteAdminAction deletes admin action with the given ID.
DeleteAdminAction(ctx context.Context, id string) error
}
66 changes: 66 additions & 0 deletions internal/db/bundb/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,69 @@ func (a *adminDB) CreateInstanceInstance(ctx context.Context) error {
log.Infof(ctx, "created instance instance %s with id %s", host, i.ID)
return nil
}

/*
ACTION FUNCS
*/

func (a *adminDB) GetAdminAction(ctx context.Context, id string) (*gtsmodel.AdminAction, error) {
action := new(gtsmodel.AdminAction)

if err := a.db.
NewSelect().
Model(action).
Scan(ctx); err != nil {
return nil, err
}

return action, nil
}

func (a *adminDB) GetAdminActions(ctx context.Context) ([]*gtsmodel.AdminAction, error) {
actions := make([]*gtsmodel.AdminAction, 0)

if err := a.db.
NewSelect().
Model(&actions).
Scan(ctx); err != nil {
return nil, err
}

return actions, nil
}

func (a *adminDB) PutAdminAction(ctx context.Context, action *gtsmodel.AdminAction) error {
_, err := a.db.
NewInsert().
Model(action).
Exec(ctx)

return err
}

func (a *adminDB) UpdateAdminAction(ctx context.Context, action *gtsmodel.AdminAction, columns ...string) error {
// Update the action's last-updated
action.UpdatedAt = time.Now()
if len(columns) != 0 {
columns = append(columns, "updated_at")
}

_, err := a.db.
NewUpdate().
Model(action).
Where("? = ?", bun.Ident("admin_action.id"), action.ID).
Column(columns...).
Exec(ctx)

return err
}

func (a *adminDB) DeleteAdminAction(ctx context.Context, id string) error {
_, err := a.db.
NewDelete().
TableExpr("? AS ?", bun.Ident("admin_actions"), bun.Ident("admin_action")).
Where("? = ?", bun.Ident("admin_action"), id).
Exec(ctx)

return err
}
132 changes: 132 additions & 0 deletions internal/db/bundb/migrations/20230828101322_admin_action_locking.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// GoToSocial
// Copyright (C) GoToSocial Authors [email protected]
// 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"

"github.com/uptrace/bun"

oldmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20220315160814_admin_account_actions"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util"
)

func init() {
up := func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
// Select all old actions.
var adminAccountActions []*oldmodel.AdminAccountAction
if err := tx.
NewSelect().
Model(&adminAccountActions).
Scan(ctx); err != nil {
return err
}

// Create the new table.
if _, err := tx.
NewCreateTable().
Model(&gtsmodel.AdminAction{}).
Exec(ctx); err != nil {
return err
}

// Index new table properly.
for index, columns := range map[string][]string{
"account_actions_id_idx": {"id"},
// Eg., select all actions of given category.
"account_actions_target_category_idx": {"target_category"},
// Eg., select all actions targeting given id.
"account_actions_target_id_idx": {"target_id"},
// Eg., select all actions of given type.
"account_actions_type_idx": {"type"},
// Eg., select all actions by given account id.
"account_actions_account_id_idx": {"account_id"},
} {
if _, err := tx.
NewCreateIndex().
Table("admin_actions").
Index(index).
Column(columns...).
Exec(ctx); err != nil {
return err
}
}

// Insert old format entries into new table.
for _, oldAction := range adminAccountActions {
newAction := &gtsmodel.AdminAction{
ID: oldAction.ID,
CreatedAt: oldAction.CreatedAt,
UpdatedAt: oldAction.UpdatedAt,
TargetCategory: gtsmodel.AdminActionCategoryAccount,
TargetID: oldAction.TargetAccountID,
Type: gtsmodel.NewAdminActionType(string(oldAction.Type)),
AccountID: oldAction.AccountID,
Text: oldAction.Text,
SendEmail: util.Ptr(oldAction.SendEmail),
ReportIDs: []string{oldAction.ReportID},
}

if _, err := tx.
NewInsert().
Model(newAction).
Exec(ctx); err != nil {
return err
}
}

// Drop the old table.
if _, err := tx.
NewDropTable().
Table("admin_account_actions").
Exec(ctx); err != nil {
return err
}

// Drop any remaining old indexes.
for _, idxName := range []string{
"admin_account_actions_pkey",
"admin_account_actions_account_id_idx",
"admin_account_actions_target_account_id_idx",
"admin_account_actions_type_idx",
} {
if _, err := tx.
NewDropIndex().
Index(idxName).
IfExists().
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)
}
}
Loading