gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

block.go (6245B)


      1 // GoToSocial
      2 // Copyright (C) GoToSocial Authors admin@gotosocial.org
      3 // SPDX-License-Identifier: AGPL-3.0-or-later
      4 //
      5 // This program is free software: you can redistribute it and/or modify
      6 // it under the terms of the GNU Affero General Public License as published by
      7 // the Free Software Foundation, either version 3 of the License, or
      8 // (at your option) any later version.
      9 //
     10 // This program is distributed in the hope that it will be useful,
     11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 // GNU Affero General Public License for more details.
     14 //
     15 // You should have received a copy of the GNU Affero General Public License
     16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18 package account
     19 
     20 import (
     21 	"context"
     22 	"errors"
     23 	"fmt"
     24 
     25 	"github.com/superseriousbusiness/gotosocial/internal/ap"
     26 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
     27 	"github.com/superseriousbusiness/gotosocial/internal/db"
     28 	"github.com/superseriousbusiness/gotosocial/internal/gtserror"
     29 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
     30 	"github.com/superseriousbusiness/gotosocial/internal/id"
     31 	"github.com/superseriousbusiness/gotosocial/internal/messages"
     32 	"github.com/superseriousbusiness/gotosocial/internal/uris"
     33 )
     34 
     35 // BlockCreate handles the creation of a block from requestingAccount to targetAccountID, either remote or local.
     36 func (p *Processor) BlockCreate(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) {
     37 	targetAccount, existingBlock, errWithCode := p.getBlockTarget(ctx, requestingAccount, targetAccountID)
     38 	if errWithCode != nil {
     39 		return nil, errWithCode
     40 	}
     41 
     42 	if existingBlock != nil {
     43 		// Block already exists, nothing to do.
     44 		return p.RelationshipGet(ctx, requestingAccount, targetAccountID)
     45 	}
     46 
     47 	// Create and store a new block.
     48 	blockID := id.NewULID()
     49 	blockURI := uris.GenerateURIForBlock(requestingAccount.Username, blockID)
     50 	block := &gtsmodel.Block{
     51 		ID:              blockID,
     52 		URI:             blockURI,
     53 		AccountID:       requestingAccount.ID,
     54 		Account:         requestingAccount,
     55 		TargetAccountID: targetAccountID,
     56 		TargetAccount:   targetAccount,
     57 	}
     58 
     59 	if err := p.state.DB.PutBlock(ctx, block); err != nil {
     60 		err = fmt.Errorf("BlockCreate: error creating block in db: %w", err)
     61 		return nil, gtserror.NewErrorInternalError(err)
     62 	}
     63 
     64 	// Ensure each account unfollows the other.
     65 	// We only care about processing unfollow side
     66 	// effects from requesting account -> target
     67 	// account, since requesting account is ours,
     68 	// and target account might not be.
     69 	msgs, err := p.unfollow(ctx, requestingAccount, targetAccount)
     70 	if err != nil {
     71 		err = fmt.Errorf("BlockCreate: error unfollowing: %w", err)
     72 		return nil, gtserror.NewErrorInternalError(err)
     73 	}
     74 
     75 	// Ensure unfollowed in other direction;
     76 	// ignore/don't process returned messages.
     77 	if _, err := p.unfollow(ctx, targetAccount, requestingAccount); err != nil {
     78 		err = fmt.Errorf("BlockCreate: error unfollowing: %w", err)
     79 		return nil, gtserror.NewErrorInternalError(err)
     80 	}
     81 
     82 	// Process block side effects (federation etc).
     83 	msgs = append(msgs, messages.FromClientAPI{
     84 		APObjectType:   ap.ActivityBlock,
     85 		APActivityType: ap.ActivityCreate,
     86 		GTSModel:       block,
     87 		OriginAccount:  requestingAccount,
     88 		TargetAccount:  targetAccount,
     89 	})
     90 
     91 	// Batch queue accreted client api messages.
     92 	p.state.Workers.EnqueueClientAPI(ctx, msgs...)
     93 
     94 	return p.RelationshipGet(ctx, requestingAccount, targetAccountID)
     95 }
     96 
     97 // BlockRemove handles the removal of a block from requestingAccount to targetAccountID, either remote or local.
     98 func (p *Processor) BlockRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) {
     99 	targetAccount, existingBlock, errWithCode := p.getBlockTarget(ctx, requestingAccount, targetAccountID)
    100 	if errWithCode != nil {
    101 		return nil, errWithCode
    102 	}
    103 
    104 	if existingBlock == nil {
    105 		// Already not blocked, nothing to do.
    106 		return p.RelationshipGet(ctx, requestingAccount, targetAccountID)
    107 	}
    108 
    109 	// We got a block, remove it from the db.
    110 	if err := p.state.DB.DeleteBlockByID(ctx, existingBlock.ID); err != nil {
    111 		err := fmt.Errorf("BlockRemove: error removing block from db: %w", err)
    112 		return nil, gtserror.NewErrorInternalError(err)
    113 	}
    114 
    115 	// Populate account fields for convenience.
    116 	existingBlock.Account = requestingAccount
    117 	existingBlock.TargetAccount = targetAccount
    118 
    119 	// Process block removal side effects (federation etc).
    120 	p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
    121 		APObjectType:   ap.ActivityBlock,
    122 		APActivityType: ap.ActivityUndo,
    123 		GTSModel:       existingBlock,
    124 		OriginAccount:  requestingAccount,
    125 		TargetAccount:  targetAccount,
    126 	})
    127 
    128 	return p.RelationshipGet(ctx, requestingAccount, targetAccountID)
    129 }
    130 
    131 func (p *Processor) getBlockTarget(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*gtsmodel.Account, *gtsmodel.Block, gtserror.WithCode) {
    132 	// Account should not block or unblock itself.
    133 	if requestingAccount.ID == targetAccountID {
    134 		err := fmt.Errorf("getBlockTarget: account %s cannot block or unblock itself", requestingAccount.ID)
    135 		return nil, nil, gtserror.NewErrorNotAcceptable(err, err.Error())
    136 	}
    137 
    138 	// Ensure target account retrievable.
    139 	targetAccount, err := p.state.DB.GetAccountByID(ctx, targetAccountID)
    140 	if err != nil {
    141 		if !errors.Is(err, db.ErrNoEntries) {
    142 			// Real db error.
    143 			err = fmt.Errorf("getBlockTarget: db error looking for target account %s: %w", targetAccountID, err)
    144 			return nil, nil, gtserror.NewErrorInternalError(err)
    145 		}
    146 		// Account not found.
    147 		err = fmt.Errorf("getBlockTarget: target account %s not found in the db", targetAccountID)
    148 		return nil, nil, gtserror.NewErrorNotFound(err, err.Error())
    149 	}
    150 
    151 	// Check if currently blocked.
    152 	block, err := p.state.DB.GetBlock(ctx, requestingAccount.ID, targetAccountID)
    153 	if err != nil && !errors.Is(err, db.ErrNoEntries) {
    154 		err = fmt.Errorf("getBlockTarget: db error checking existing block: %w", err)
    155 		return nil, nil, gtserror.NewErrorInternalError(err)
    156 	}
    157 
    158 	return targetAccount, block, nil
    159 }