diff options
Diffstat (limited to 'internal/db')
| -rw-r--r-- | internal/db/bundb/bundb.go | 7 | ||||
| -rw-r--r-- | internal/db/bundb/relationship.go | 204 | ||||
| -rw-r--r-- | internal/db/bundb/relationship_test.go | 128 | ||||
| -rw-r--r-- | internal/db/relationship.go | 15 | 
4 files changed, 298 insertions, 56 deletions
diff --git a/internal/db/bundb/bundb.go b/internal/db/bundb/bundb.go index b316f2106..163174456 100644 --- a/internal/db/bundb/bundb.go +++ b/internal/db/bundb/bundb.go @@ -166,6 +166,7 @@ func NewBunDBService(ctx context.Context) (db.DB, error) {  	notif := ¬ificationDB{conn: conn}  	status := &statusDB{conn: conn}  	emoji := &emojiDB{conn: conn} +	relationship := &relationshipDB{conn: conn}  	timeline := &timelineDB{conn: conn}  	tombstone := &tombstoneDB{conn: conn}  	user := &userDB{conn: conn} @@ -174,6 +175,7 @@ func NewBunDBService(ctx context.Context) (db.DB, error) {  	account.emojis = emoji  	account.status = status  	admin.users = user +	relationship.accounts = account  	status.accounts = account  	status.emojis = emoji  	status.mentions = mention @@ -185,6 +187,7 @@ func NewBunDBService(ctx context.Context) (db.DB, error) {  	emoji.init()  	mention.init()  	notif.init() +	relationship.init()  	status.init()  	tombstone.init()  	user.init() @@ -209,9 +212,7 @@ func NewBunDBService(ctx context.Context) (db.DB, error) {  		},  		Mention:      mention,  		Notification: notif, -		Relationship: &relationshipDB{ -			conn: conn, -		}, +		Relationship: relationship,  		Session: &sessionDB{  			conn: conn,  		}, diff --git a/internal/db/bundb/relationship.go b/internal/db/bundb/relationship.go index 66e48e441..f6df95524 100644 --- a/internal/db/bundb/relationship.go +++ b/internal/db/bundb/relationship.go @@ -21,23 +21,37 @@ package bundb  import (  	"context"  	"database/sql" +	"errors"  	"fmt" +	"time" +	"codeberg.org/gruf/go-cache/v3/result"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/uptrace/bun"  )  type relationshipDB struct { -	conn *DBConn +	conn       *DBConn +	accounts   *accountDB +	blockCache *result.Cache[*gtsmodel.Block]  } -func (r *relationshipDB) newBlockQ(block *gtsmodel.Block) *bun.SelectQuery { -	return r.conn. -		NewSelect(). -		Model(block). -		Relation("Account"). -		Relation("TargetAccount") +func (r *relationshipDB) init() { +	// Initialize block result cache +	r.blockCache = result.NewSized([]result.Lookup{ +		{Name: "ID"}, +		{Name: "AccountID.TargetAccountID"}, +		{Name: "URI"}, +	}, func(b1 *gtsmodel.Block) *gtsmodel.Block { +		b2 := new(gtsmodel.Block) +		*b2 = *b1 +		return b2 +	}, 1000) + +	// Set cache TTL and start sweep routine +	r.blockCache.SetTTL(time.Minute*5, false) +	r.blockCache.Start(time.Second * 10)  }  func (r *relationshipDB) newFollowQ(follow interface{}) *bun.SelectQuery { @@ -49,43 +63,143 @@ func (r *relationshipDB) newFollowQ(follow interface{}) *bun.SelectQuery {  }  func (r *relationshipDB) IsBlocked(ctx context.Context, account1 string, account2 string, eitherDirection bool) (bool, db.Error) { +	// Look for a block in direction of account1->account2 +	block1, err := r.getBlock(ctx, account1, account2) +	if err != nil && !errors.Is(err, db.ErrNoEntries) { +		return false, err +	} + +	if block1 != nil { +		// account1 blocks account2 +		return true, nil +	} else if !eitherDirection { +		// Don't check for mutli-directional +		return false, nil +	} + +	// Look for a block in direction of account2->account1 +	block2, err := r.getBlock(ctx, account2, account1) +	if err != nil && !errors.Is(err, db.ErrNoEntries) { +		return false, err +	} + +	return (block2 != nil), nil +} + +func (r *relationshipDB) GetBlock(ctx context.Context, account1 string, account2 string) (*gtsmodel.Block, db.Error) { +	// Fetch block from database +	block, err := r.getBlock(ctx, account1, account2) +	if err != nil { +		return nil, err +	} + +	// Set the block originating account +	block.Account, err = r.accounts.GetAccountByID(ctx, block.AccountID) +	if err != nil { +		return nil, err +	} + +	// Set the block target account +	block.TargetAccount, err = r.accounts.GetAccountByID(ctx, block.TargetAccountID) +	if err != nil { +		return nil, err +	} + +	return block, nil +} + +func (r *relationshipDB) getBlock(ctx context.Context, account1 string, account2 string) (*gtsmodel.Block, db.Error) { +	return r.blockCache.Load("AccountID.TargetAccountID", func() (*gtsmodel.Block, error) { +		var block gtsmodel.Block + +		q := r.conn.NewSelect().Model(&block). +			Where("? = ?", bun.Ident("block.account_id"), account1). +			Where("? = ?", bun.Ident("block.target_account_id"), account2) +		if err := q.Scan(ctx); err != nil { +			return nil, r.conn.ProcessError(err) +		} + +		return &block, nil +	}, account1, account2) +} + +func (r *relationshipDB) PutBlock(ctx context.Context, block *gtsmodel.Block) db.Error { +	return r.blockCache.Store(block, func() error { +		_, err := r.conn.NewInsert().Model(block).Exec(ctx) +		return r.conn.ProcessError(err) +	}) +} + +func (r *relationshipDB) DeleteBlockByID(ctx context.Context, id string) db.Error { +	if _, err := r.conn. +		NewDelete(). +		TableExpr("? AS ?", bun.Ident("blocks"), bun.Ident("block")). +		Where("? = ?", bun.Ident("block.id"), id). +		Exec(ctx); err != nil { +		return r.conn.ProcessError(err) +	} + +	// Drop any old value from cache by this ID +	r.blockCache.Invalidate("ID", id) +	return nil +} + +func (r *relationshipDB) DeleteBlockByURI(ctx context.Context, uri string) db.Error { +	if _, err := r.conn. +		NewDelete(). +		TableExpr("? AS ?", bun.Ident("blocks"), bun.Ident("block")). +		Where("? = ?", bun.Ident("block.uri"), uri). +		Exec(ctx); err != nil { +		return r.conn.ProcessError(err) +	} + +	// Drop any old value from cache by this URI +	r.blockCache.Invalidate("URI", uri) +	return nil +} + +func (r *relationshipDB) DeleteBlocksByOriginAccountID(ctx context.Context, originAccountID string) db.Error { +	blockIDs := []string{} +  	q := r.conn.  		NewSelect().  		TableExpr("? AS ?", bun.Ident("blocks"), bun.Ident("block")). -		Column("block.id") +		Column("block.id"). +		Where("? = ?", bun.Ident("block.account_id"), originAccountID) -	if eitherDirection { -		q = q. -			WhereGroup(" OR ", func(inner *bun.SelectQuery) *bun.SelectQuery { -				return inner. -					Where("? = ?", bun.Ident("block.account_id"), account1). -					Where("? = ?", bun.Ident("block.target_account_id"), account2) -			}). -			WhereGroup(" OR ", func(inner *bun.SelectQuery) *bun.SelectQuery { -				return inner. -					Where("? = ?", bun.Ident("block.account_id"), account2). -					Where("? = ?", bun.Ident("block.target_account_id"), account1) -			}) -	} else { -		q = q. -			Where("? = ?", bun.Ident("block.account_id"), account1). -			Where("? = ?", bun.Ident("block.target_account_id"), account2) +	if err := q.Scan(ctx, &blockIDs); err != nil { +		return r.conn.ProcessError(err)  	} -	return r.conn.Exists(ctx, q) +	for _, blockID := range blockIDs { +		if err := r.DeleteBlockByID(ctx, blockID); err != nil { +			return err +		} +	} + +	return nil  } -func (r *relationshipDB) GetBlock(ctx context.Context, account1 string, account2 string) (*gtsmodel.Block, db.Error) { -	block := >smodel.Block{} +func (r *relationshipDB) DeleteBlocksByTargetAccountID(ctx context.Context, targetAccountID string) db.Error { +	blockIDs := []string{} -	q := r.newBlockQ(block). -		Where("? = ?", bun.Ident("block.account_id"), account1). -		Where("? = ?", bun.Ident("block.target_account_id"), account2) +	q := r.conn. +		NewSelect(). +		TableExpr("? AS ?", bun.Ident("blocks"), bun.Ident("block")). +		Column("block.id"). +		Where("? = ?", bun.Ident("block.target_account_id"), targetAccountID) -	if err := q.Scan(ctx); err != nil { -		return nil, r.conn.ProcessError(err) +	if err := q.Scan(ctx, &blockIDs); err != nil { +		return r.conn.ProcessError(err)  	} -	return block, nil + +	for _, blockID := range blockIDs { +		if err := r.DeleteBlockByID(ctx, blockID); err != nil { +			return err +		} +	} + +	return nil  }  func (r *relationshipDB) GetRelationship(ctx context.Context, requestingAccount string, targetAccount string) (*gtsmodel.Relationship, db.Error) { @@ -144,30 +258,18 @@ func (r *relationshipDB) GetRelationship(ctx context.Context, requestingAccount  	rel.Requested = requested  	// check if the requesting account is blocking the target account -	blockingQ := r.conn. -		NewSelect(). -		TableExpr("? AS ?", bun.Ident("blocks"), bun.Ident("block")). -		Column("block.id"). -		Where("? = ?", bun.Ident("block.account_id"), requestingAccount). -		Where("? = ?", bun.Ident("block.target_account_id"), targetAccount) -	blocking, err := r.conn.Exists(ctx, blockingQ) -	if err != nil { +	blockA2T, err := r.getBlock(ctx, requestingAccount, targetAccount) +	if err != nil && !errors.Is(err, db.ErrNoEntries) {  		return nil, fmt.Errorf("GetRelationship: error checking blocking: %s", err)  	} -	rel.Blocking = blocking +	rel.Blocking = (blockA2T != nil)  	// check if the requesting account is blocked by the target account -	blockedByQ := r.conn. -		NewSelect(). -		TableExpr("? AS ?", bun.Ident("blocks"), bun.Ident("block")). -		Column("block.id"). -		Where("? = ?", bun.Ident("block.account_id"), targetAccount). -		Where("? = ?", bun.Ident("block.target_account_id"), requestingAccount) -	blockedBy, err := r.conn.Exists(ctx, blockedByQ) -	if err != nil { +	blockT2A, err := r.getBlock(ctx, targetAccount, requestingAccount) +	if err != nil && !errors.Is(err, db.ErrNoEntries) {  		return nil, fmt.Errorf("GetRelationship: error checking blockedBy: %s", err)  	} -	rel.BlockedBy = blockedBy +	rel.BlockedBy = (blockT2A != nil)  	return rel, nil  } diff --git a/internal/db/bundb/relationship_test.go b/internal/db/bundb/relationship_test.go index 3df16e2f3..fa0f2f1bc 100644 --- a/internal/db/bundb/relationship_test.go +++ b/internal/db/bundb/relationship_test.go @@ -47,7 +47,7 @@ func (suite *RelationshipTestSuite) TestIsBlocked() {  	suite.False(blocked)  	// have account1 block account2 -	if err := suite.db.Put(ctx, >smodel.Block{ +	if err := suite.db.PutBlock(ctx, >smodel.Block{  		ID:              "01G202BCSXXJZ70BHB5KCAHH8C",  		URI:             "http://localhost:8080/some_block_uri_1",  		AccountID:       account1, @@ -81,7 +81,7 @@ func (suite *RelationshipTestSuite) TestGetBlock() {  	account1 := suite.testAccounts["local_account_1"].ID  	account2 := suite.testAccounts["local_account_2"].ID -	if err := suite.db.Put(ctx, >smodel.Block{ +	if err := suite.db.PutBlock(ctx, >smodel.Block{  		ID:              "01G202BCSXXJZ70BHB5KCAHH8C",  		URI:             "http://localhost:8080/some_block_uri_1",  		AccountID:       account1, @@ -96,6 +96,130 @@ func (suite *RelationshipTestSuite) TestGetBlock() {  	suite.Equal("01G202BCSXXJZ70BHB5KCAHH8C", block.ID)  } +func (suite *RelationshipTestSuite) TestDeleteBlockByID() { +	ctx := context.Background() + +	// put a block in first +	account1 := suite.testAccounts["local_account_1"].ID +	account2 := suite.testAccounts["local_account_2"].ID +	if err := suite.db.PutBlock(ctx, >smodel.Block{ +		ID:              "01G202BCSXXJZ70BHB5KCAHH8C", +		URI:             "http://localhost:8080/some_block_uri_1", +		AccountID:       account1, +		TargetAccountID: account2, +	}); err != nil { +		suite.FailNow(err.Error()) +	} + +	// make sure the block is in the db +	block, err := suite.db.GetBlock(ctx, account1, account2) +	suite.NoError(err) +	suite.NotNil(block) +	suite.Equal("01G202BCSXXJZ70BHB5KCAHH8C", block.ID) + +	// delete the block by ID +	err = suite.db.DeleteBlockByID(ctx, "01G202BCSXXJZ70BHB5KCAHH8C") +	suite.NoError(err) + +	// block should be gone +	block, err = suite.db.GetBlock(ctx, account1, account2) +	suite.ErrorIs(err, db.ErrNoEntries) +	suite.Nil(block) +} + +func (suite *RelationshipTestSuite) TestDeleteBlockByURI() { +	ctx := context.Background() + +	// put a block in first +	account1 := suite.testAccounts["local_account_1"].ID +	account2 := suite.testAccounts["local_account_2"].ID +	if err := suite.db.PutBlock(ctx, >smodel.Block{ +		ID:              "01G202BCSXXJZ70BHB5KCAHH8C", +		URI:             "http://localhost:8080/some_block_uri_1", +		AccountID:       account1, +		TargetAccountID: account2, +	}); err != nil { +		suite.FailNow(err.Error()) +	} + +	// make sure the block is in the db +	block, err := suite.db.GetBlock(ctx, account1, account2) +	suite.NoError(err) +	suite.NotNil(block) +	suite.Equal("01G202BCSXXJZ70BHB5KCAHH8C", block.ID) + +	// delete the block by uri +	err = suite.db.DeleteBlockByURI(ctx, "http://localhost:8080/some_block_uri_1") +	suite.NoError(err) + +	// block should be gone +	block, err = suite.db.GetBlock(ctx, account1, account2) +	suite.ErrorIs(err, db.ErrNoEntries) +	suite.Nil(block) +} + +func (suite *RelationshipTestSuite) TestDeleteBlocksByOriginAccountID() { +	ctx := context.Background() + +	// put a block in first +	account1 := suite.testAccounts["local_account_1"].ID +	account2 := suite.testAccounts["local_account_2"].ID +	if err := suite.db.PutBlock(ctx, >smodel.Block{ +		ID:              "01G202BCSXXJZ70BHB5KCAHH8C", +		URI:             "http://localhost:8080/some_block_uri_1", +		AccountID:       account1, +		TargetAccountID: account2, +	}); err != nil { +		suite.FailNow(err.Error()) +	} + +	// make sure the block is in the db +	block, err := suite.db.GetBlock(ctx, account1, account2) +	suite.NoError(err) +	suite.NotNil(block) +	suite.Equal("01G202BCSXXJZ70BHB5KCAHH8C", block.ID) + +	// delete the block by originAccountID +	err = suite.db.DeleteBlocksByOriginAccountID(ctx, account1) +	suite.NoError(err) + +	// block should be gone +	block, err = suite.db.GetBlock(ctx, account1, account2) +	suite.ErrorIs(err, db.ErrNoEntries) +	suite.Nil(block) +} + +func (suite *RelationshipTestSuite) TestDeleteBlocksByTargetAccountID() { +	ctx := context.Background() + +	// put a block in first +	account1 := suite.testAccounts["local_account_1"].ID +	account2 := suite.testAccounts["local_account_2"].ID +	if err := suite.db.PutBlock(ctx, >smodel.Block{ +		ID:              "01G202BCSXXJZ70BHB5KCAHH8C", +		URI:             "http://localhost:8080/some_block_uri_1", +		AccountID:       account1, +		TargetAccountID: account2, +	}); err != nil { +		suite.FailNow(err.Error()) +	} + +	// make sure the block is in the db +	block, err := suite.db.GetBlock(ctx, account1, account2) +	suite.NoError(err) +	suite.NotNil(block) +	suite.Equal("01G202BCSXXJZ70BHB5KCAHH8C", block.ID) + +	// delete the block by targetAccountID +	err = suite.db.DeleteBlocksByTargetAccountID(ctx, account2) +	suite.NoError(err) + +	// block should be gone +	block, err = suite.db.GetBlock(ctx, account1, account2) +	suite.ErrorIs(err, db.ErrNoEntries) +	suite.Nil(block) +} +  func (suite *RelationshipTestSuite) TestGetRelationship() {  	requestingAccount := suite.testAccounts["local_account_1"]  	targetAccount := suite.testAccounts["admin_account"] diff --git a/internal/db/relationship.go b/internal/db/relationship.go index 3dfc3dcc3..5ff08ad68 100644 --- a/internal/db/relationship.go +++ b/internal/db/relationship.go @@ -36,6 +36,21 @@ type Relationship interface {  	// not if you're just checking for the existence of a block.  	GetBlock(ctx context.Context, account1 string, account2 string) (*gtsmodel.Block, Error) +	// PutBlock attempts to place the given account block in the database. +	PutBlock(ctx context.Context, block *gtsmodel.Block) Error + +	// DeleteBlockByID removes block with given ID from the database. +	DeleteBlockByID(ctx context.Context, id string) Error + +	// DeleteBlockByURI removes block with given AP URI from the database. +	DeleteBlockByURI(ctx context.Context, uri string) Error + +	// DeleteBlocksByOriginAccountID removes any blocks with accountID equal to originAccountID. +	DeleteBlocksByOriginAccountID(ctx context.Context, originAccountID string) Error + +	// DeleteBlocksByTargetAccountID removes any blocks with given targetAccountID. +	DeleteBlocksByTargetAccountID(ctx context.Context, targetAccountID string) Error +  	// GetRelationship retrieves the relationship of the targetAccount to the requestingAccount.  	GetRelationship(ctx context.Context, requestingAccount string, targetAccount string) (*gtsmodel.Relationship, Error)  | 
