mirror of
1
Fork 0

[feature/chore] Add Move database functions + cache (#2647)

* [feature/chore] Add Move database functions + cache

* add move mem ratio to envparsing.sh

* update comment
This commit is contained in:
tobi 2024-03-06 11:18:57 +01:00 committed by GitHub
parent 61a2b91f45
commit b22e213e15
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 671 additions and 1 deletions

View File

@ -75,6 +75,7 @@ func (c *Caches) Init() {
c.initMarker() c.initMarker()
c.initMedia() c.initMedia()
c.initMention() c.initMention()
c.initMove()
c.initNotification() c.initNotification()
c.initPoll() c.initPoll()
c.initPollVote() c.initPollVote()
@ -135,6 +136,7 @@ func (c *Caches) Sweep(threshold float64) {
c.GTS.Marker.Trim(threshold) c.GTS.Marker.Trim(threshold)
c.GTS.Media.Trim(threshold) c.GTS.Media.Trim(threshold)
c.GTS.Mention.Trim(threshold) c.GTS.Mention.Trim(threshold)
c.GTS.Move.Trim(threshold)
c.GTS.Notification.Trim(threshold) c.GTS.Notification.Trim(threshold)
c.GTS.Poll.Trim(threshold) c.GTS.Poll.Trim(threshold)
c.GTS.Report.Trim(threshold) c.GTS.Report.Trim(threshold)

32
internal/cache/db.go vendored
View File

@ -117,6 +117,9 @@ type GTSCaches struct {
// Mention provides access to the gtsmodel Mention database cache. // Mention provides access to the gtsmodel Mention database cache.
Mention structr.Cache[*gtsmodel.Mention] Mention structr.Cache[*gtsmodel.Mention]
// Move provides access to the gtsmodel Move database cache.
Move structr.Cache[*gtsmodel.Move]
// Notification provides access to the gtsmodel Notification database cache. // Notification provides access to the gtsmodel Notification database cache.
Notification structr.Cache[*gtsmodel.Notification] Notification structr.Cache[*gtsmodel.Notification]
@ -185,6 +188,8 @@ func (c *Caches) initAccount() {
a2.AvatarMediaAttachment = nil a2.AvatarMediaAttachment = nil
a2.HeaderMediaAttachment = nil a2.HeaderMediaAttachment = nil
a2.Emojis = nil a2.Emojis = nil
a2.AlsoKnownAs = nil
a2.Move = nil
return a2 return a2
} }
@ -816,6 +821,33 @@ func (c *Caches) initMention() {
}) })
} }
func (c *Caches) initMove() {
// Calculate maximum cache size.
cap := calculateResultCacheMax(
sizeofMove(), // model in-mem size.
config.GetCacheMoveMemRatio(),
)
log.Infof(nil, "cache size = %d", cap)
c.GTS.Move.Init(structr.Config[*gtsmodel.Move]{
Indices: []structr.IndexConfig{
{Fields: "ID"},
{Fields: "URI"},
{Fields: "OriginURI,TargetURI"},
{Fields: "OriginURI", Multiple: true},
{Fields: "TargetURI", Multiple: true},
},
MaxSize: cap,
IgnoreErr: ignoreErrors,
CopyValue: func(m1 *gtsmodel.Move) *gtsmodel.Move {
m2 := new(gtsmodel.Move)
*m2 = *m1
return m2
},
})
}
func (c *Caches) initNotification() { func (c *Caches) initNotification() {
// Calculate maximum cache size. // Calculate maximum cache size.
cap := calculateResultCacheMax( cap := calculateResultCacheMax(

View File

@ -54,6 +54,10 @@ func (c *Caches) OnInvalidateAccount(account *gtsmodel.Account) {
// Invalidate this account's block lists. // Invalidate this account's block lists.
c.GTS.BlockIDs.Invalidate(account.ID) c.GTS.BlockIDs.Invalidate(account.ID)
// Invalidate this account's Move(s).
c.GTS.Move.Invalidate("OriginURI", account.URI)
c.GTS.Move.Invalidate("TargetURI", account.URI)
} }
func (c *Caches) OnInvalidateBlock(block *gtsmodel.Block) { func (c *Caches) OnInvalidateBlock(block *gtsmodel.Block) {

View File

@ -460,6 +460,19 @@ func sizeofMention() uintptr {
})) }))
} }
func sizeofMove() uintptr {
return uintptr(size.Of(&gtsmodel.Move{
ID: exampleID,
CreatedAt: exampleTime,
UpdatedAt: exampleTime,
AttemptedAt: exampleTime,
SucceededAt: exampleTime,
OriginURI: exampleURI,
TargetURI: exampleURI,
URI: exampleURI,
}))
}
func sizeofNotification() uintptr { func sizeofNotification() uintptr {
return uintptr(size.Of(&gtsmodel.Notification{ return uintptr(size.Of(&gtsmodel.Notification{
ID: exampleID, ID: exampleID,

View File

@ -215,6 +215,7 @@ type CacheConfiguration struct {
MarkerMemRatio float64 `name:"marker-mem-ratio"` MarkerMemRatio float64 `name:"marker-mem-ratio"`
MediaMemRatio float64 `name:"media-mem-ratio"` MediaMemRatio float64 `name:"media-mem-ratio"`
MentionMemRatio float64 `name:"mention-mem-ratio"` MentionMemRatio float64 `name:"mention-mem-ratio"`
MoveMemRatio float64 `name:"move-mem-ratio"`
NotificationMemRatio float64 `name:"notification-mem-ratio"` NotificationMemRatio float64 `name:"notification-mem-ratio"`
PollMemRatio float64 `name:"poll-mem-ratio"` PollMemRatio float64 `name:"poll-mem-ratio"`
PollVoteMemRatio float64 `name:"poll-vote-mem-ratio"` PollVoteMemRatio float64 `name:"poll-vote-mem-ratio"`

View File

@ -179,6 +179,7 @@ var Defaults = Configuration{
MarkerMemRatio: 0.5, MarkerMemRatio: 0.5,
MediaMemRatio: 4, MediaMemRatio: 4,
MentionMemRatio: 2, MentionMemRatio: 2,
MoveMemRatio: 0.1,
NotificationMemRatio: 2, NotificationMemRatio: 2,
PollMemRatio: 1, PollMemRatio: 1,
PollVoteMemRatio: 2, PollVoteMemRatio: 2,

View File

@ -3325,6 +3325,31 @@ func GetCacheMentionMemRatio() float64 { return global.GetCacheMentionMemRatio()
// SetCacheMentionMemRatio safely sets the value for global configuration 'Cache.MentionMemRatio' field // SetCacheMentionMemRatio safely sets the value for global configuration 'Cache.MentionMemRatio' field
func SetCacheMentionMemRatio(v float64) { global.SetCacheMentionMemRatio(v) } func SetCacheMentionMemRatio(v float64) { global.SetCacheMentionMemRatio(v) }
// GetCacheMoveMemRatio safely fetches the Configuration value for state's 'Cache.MoveMemRatio' field
func (st *ConfigState) GetCacheMoveMemRatio() (v float64) {
st.mutex.RLock()
v = st.config.Cache.MoveMemRatio
st.mutex.RUnlock()
return
}
// SetCacheMoveMemRatio safely sets the Configuration value for state's 'Cache.MoveMemRatio' field
func (st *ConfigState) SetCacheMoveMemRatio(v float64) {
st.mutex.Lock()
defer st.mutex.Unlock()
st.config.Cache.MoveMemRatio = v
st.reloadToViper()
}
// CacheMoveMemRatioFlag returns the flag name for the 'Cache.MoveMemRatio' field
func CacheMoveMemRatioFlag() string { return "cache-move-mem-ratio" }
// GetCacheMoveMemRatio safely fetches the value for global configuration 'Cache.MoveMemRatio' field
func GetCacheMoveMemRatio() float64 { return global.GetCacheMoveMemRatio() }
// SetCacheMoveMemRatio safely sets the value for global configuration 'Cache.MoveMemRatio' field
func SetCacheMoveMemRatio(v float64) { global.SetCacheMoveMemRatio(v) }
// GetCacheNotificationMemRatio safely fetches the Configuration value for state's 'Cache.NotificationMemRatio' field // GetCacheNotificationMemRatio safely fetches the Configuration value for state's 'Cache.NotificationMemRatio' field
func (st *ConfigState) GetCacheNotificationMemRatio() (v float64) { func (st *ConfigState) GetCacheNotificationMemRatio() (v float64) {
st.mutex.RLock() st.mutex.RLock()

View File

@ -304,6 +304,17 @@ func (a *accountDB) PopulateAccount(ctx context.Context, account *gtsmodel.Accou
account.AlsoKnownAs = alsoKnownAs account.AlsoKnownAs = alsoKnownAs
} }
if account.Move == nil && account.MoveID != "" {
// Account move is not set, fetch from database.
account.Move, err = a.state.DB.GetMoveByID(
ctx,
account.MovedToURI,
)
if err != nil {
errs.Appendf("error populating move: %w", err)
}
}
if account.MovedTo == nil && account.MovedToURI != "" { if account.MovedTo == nil && account.MovedToURI != "" {
// Account movedTo is not set, fetch from database. // Account movedTo is not set, fetch from database.
account.MovedTo, err = a.state.DB.GetAccountByURI( account.MovedTo, err = a.state.DB.GetAccountByURI(

View File

@ -67,6 +67,7 @@ type DBService struct {
db.Marker db.Marker
db.Media db.Media
db.Mention db.Mention
db.Move
db.Notification db.Notification
db.Poll db.Poll
db.Relationship db.Relationship
@ -221,6 +222,10 @@ func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) {
db: db, db: db,
state: state, state: state,
}, },
Move: &moveDB{
db: db,
state: state,
},
Notification: &notificationDB{ Notification: &notificationDB{
db: db, db: db,
state: state, state: state,

View File

@ -0,0 +1,61 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// 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"
"strings"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/uptrace/bun"
)
func init() {
up := func(ctx context.Context, db *bun.DB) error {
_, err := db.ExecContext(ctx,
"ALTER TABLE ? ADD COLUMN ? CHAR(26)",
bun.Ident("accounts"), bun.Ident("move_id"),
)
if err != nil {
e := err.Error()
if !(strings.Contains(e, "already exists") ||
strings.Contains(e, "duplicate column name") ||
strings.Contains(e, "SQLSTATE 42701")) {
return err
}
}
// Create "moves" table.
if _, err := db.NewCreateTable().
IfNotExists().
Model(&gtsmodel.Move{}).
Exec(ctx); err != nil {
return err
}
return nil
}
down := func(ctx context.Context, db *bun.DB) error {
return nil
}
if err := Migrations.Register(up, down); err != nil {
panic(err)
}
}

236
internal/db/bundb/move.go Normal file
View File

@ -0,0 +1,236 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// 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 bundb
import (
"context"
"errors"
"fmt"
"net/url"
"time"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
)
type moveDB struct {
db *bun.DB
state *state.State
}
func (m *moveDB) GetMoveByID(
ctx context.Context,
id string,
) (*gtsmodel.Move, error) {
return m.getMove(
ctx,
"ID",
func(move *gtsmodel.Move) error {
return m.db.
NewSelect().
Model(move).
Where("? = ?", bun.Ident("move.id"), id).
Scan(ctx)
},
id,
)
}
func (m *moveDB) GetMoveByURI(
ctx context.Context,
uri string,
) (*gtsmodel.Move, error) {
return m.getMove(
ctx,
"URI",
func(move *gtsmodel.Move) error {
return m.db.
NewSelect().
Model(move).
Where("? = ?", bun.Ident("move.uri"), uri).
Scan(ctx)
},
uri,
)
}
func (m *moveDB) GetMoveByOriginTarget(
ctx context.Context,
originURI string,
targetURI string,
) (*gtsmodel.Move, error) {
return m.getMove(
ctx,
"OriginURI,TargetURI",
func(move *gtsmodel.Move) error {
return m.db.
NewSelect().
Model(move).
Where("? = ?", bun.Ident("move.origin_uri"), originURI).
Where("? = ?", bun.Ident("move.target_uri"), targetURI).
Scan(ctx)
},
originURI, targetURI,
)
}
func (m *moveDB) GetLatestMoveSuccessInvolvingURIs(
ctx context.Context,
uri1 string,
uri2 string,
) (time.Time, error) {
// Get at most 1 latest Move
// involving the provided URIs.
var moves []*gtsmodel.Move
err := m.db.
NewSelect().
Model(&moves).
Column("succeeded_at").
Where("? = ?", bun.Ident("move.origin_uri"), uri1).
WhereOr("? = ?", bun.Ident("move.origin_uri"), uri2).
WhereOr("? = ?", bun.Ident("move.target_uri"), uri1).
WhereOr("? = ?", bun.Ident("move.target_uri"), uri2).
Order("id DESC").
Limit(1).
Scan(ctx)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return time.Time{}, err
}
if len(moves) != 1 {
return time.Time{}, nil
}
return moves[0].SucceededAt, nil
}
func (m *moveDB) GetLatestMoveAttemptInvolvingURIs(
ctx context.Context,
uri1 string,
uri2 string,
) (time.Time, error) {
// Get at most 1 latest Move
// involving the provided URIs.
var moves []*gtsmodel.Move
err := m.db.
NewSelect().
Model(&moves).
Column("attempted_at").
Where("? = ?", bun.Ident("move.origin_uri"), uri1).
WhereOr("? = ?", bun.Ident("move.origin_uri"), uri2).
WhereOr("? = ?", bun.Ident("move.target_uri"), uri1).
WhereOr("? = ?", bun.Ident("move.target_uri"), uri2).
Order("id DESC").
Limit(1).
Scan(ctx)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return time.Time{}, err
}
if len(moves) != 1 {
return time.Time{}, nil
}
return moves[0].AttemptedAt, nil
}
func (m *moveDB) getMove(
ctx context.Context,
lookup string,
dbQuery func(*gtsmodel.Move) error,
keyParts ...any,
) (*gtsmodel.Move, error) {
move, err := m.state.Caches.GTS.Move.LoadOne(lookup, func() (*gtsmodel.Move, error) {
var move gtsmodel.Move
// Not cached! Perform database query.
if err := dbQuery(&move); err != nil {
return nil, err
}
return &move, nil
}, keyParts...)
if err != nil {
return nil, err
}
if gtscontext.Barebones(ctx) {
return move, nil
}
// Populate the Move by parsing out the URIs.
if move.Origin == nil {
move.Origin, err = url.Parse(move.OriginURI)
if err != nil {
return nil, fmt.Errorf("error parsing Move originURI: %w", err)
}
}
if move.Target == nil {
move.Target, err = url.Parse(move.TargetURI)
if err != nil {
return nil, fmt.Errorf("error parsing Move originURI: %w", err)
}
}
return move, nil
}
func (m *moveDB) PutMove(ctx context.Context, move *gtsmodel.Move) error {
return m.state.Caches.GTS.Move.Store(move, func() error {
_, err := m.db.
NewInsert().
Model(move).
Exec(ctx)
return err
})
}
func (m *moveDB) UpdateMove(ctx context.Context, move *gtsmodel.Move, columns ...string) error {
move.UpdatedAt = time.Now()
if len(columns) > 0 {
// If we're updating by column,
// ensure "updated_at" is included.
columns = append(columns, "updated_at")
}
return m.state.Caches.GTS.Move.Store(move, func() error {
_, err := m.db.
NewUpdate().
Model(move).
Column(columns...).
Where("? = ?", bun.Ident("move.id"), move.ID).
Exec(ctx)
return err
})
}
func (m *moveDB) DeleteMoveByID(ctx context.Context, id string) error {
defer m.state.Caches.GTS.Move.Invalidate("ID", id)
_, err := m.db.
NewDelete().
TableExpr("? AS ?", bun.Ident("moves"), bun.Ident("move")).
Where("? = ?", bun.Ident("move.id"), id).
Exec(ctx)
return err
}

View File

@ -0,0 +1,168 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// 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 bundb_test
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
type MoveTestSuite struct {
BunDBStandardTestSuite
}
func (suite *MoveTestSuite) TestMoveIntegration() {
ctx := context.Background()
firstMove := &gtsmodel.Move{
ID: "01HPPN38MZYEC6WBTR21J6241N",
OriginURI: "https://example.org/users/my_old_account",
TargetURI: "https://somewhere.else.net/users/my_new_account",
URI: "https://example.org/users/my_old_account/activities/Move/652e8361-0182-407d-8b01-4447e7fd10c0",
}
// Put the move.
if err := suite.state.DB.PutMove(ctx, firstMove); err != nil {
suite.FailNow(err.Error())
}
// Test various ways of retrieving the Move.
if _, err := suite.state.DB.GetMoveByID(ctx, firstMove.ID); err != nil {
suite.FailNow(err.Error())
}
if _, err := suite.state.DB.GetMoveByOriginTarget(ctx, firstMove.OriginURI, firstMove.TargetURI); err != nil {
suite.FailNow(err.Error())
}
// Keep the last one, and check fields set on it.
dbMove, err := suite.state.DB.GetMoveByURI(ctx, firstMove.URI)
if err != nil {
suite.FailNow(err.Error())
}
// Created/Updated should be set when
// it's first inserted into the db.
suite.NotZero(dbMove.CreatedAt)
suite.NotZero(dbMove.UpdatedAt)
// URIs should be parsed and set
// on the move on population.
suite.NotNil(dbMove.Origin)
suite.NotNil(dbMove.Target)
// These should not be set as
// they have no default values.
suite.Zero(dbMove.AttemptedAt)
suite.Zero(dbMove.SucceededAt)
// Update the Move to emulate
// us succeeding in processing it.
dbMove.AttemptedAt = time.Now()
dbMove.SucceededAt = dbMove.AttemptedAt
if err := suite.state.DB.UpdateMove(
ctx,
dbMove,
"attempted_at",
"succeeded_at",
); err != nil {
suite.FailNow(err.Error())
}
// Store dbMove as firstMove var.
firstMove = dbMove
// Store another Move involving one
// of the original URIs, and mark
// this one as succeeded. Use a time
// a few seconds into the future to
// make sure it's differentiated
// from the first move.
secondMove := &gtsmodel.Move{
ID: "01HPPPNQWRMQTXRFEPKDV3A4W7",
OriginURI: "https://somewhere.else.net/users/my_new_account",
TargetURI: "http://localhost:8080/users/the_mighty_zork",
URI: "https://somewhere.else.net/activities/01HPPPPPC089VJGV0967P5YQS5",
AttemptedAt: time.Now().Add(5 * time.Second),
SucceededAt: time.Now().Add(5 * time.Second),
}
if err := suite.state.DB.PutMove(ctx, secondMove); err != nil {
suite.FailNow(err.Error())
}
// Test getting succeeded using the
// URI shared between the two Moves,
// and some random account.
ts, err := suite.state.DB.GetLatestMoveSuccessInvolvingURIs(
ctx,
secondMove.OriginURI,
"https://a.secret.third.place/users/mystery_meat",
)
if err != nil {
suite.FailNow(err.Error())
}
// Time should be equivalent to secondMove.
suite.EqualValues(secondMove.SucceededAt.UnixMilli(), ts.UnixMilli())
// Test getting succeeded using
// both URIs from the first move.
ts, err = suite.state.DB.GetLatestMoveSuccessInvolvingURIs(
ctx,
firstMove.OriginURI,
firstMove.TargetURI,
)
if err != nil {
suite.FailNow(err.Error())
}
// Time should be equivalent to secondMove.
suite.EqualValues(secondMove.SucceededAt.UnixMilli(), ts.UnixMilli())
// Test getting succeeded using
// URI from the first Move, and
// some random account.
ts, err = suite.state.DB.GetLatestMoveSuccessInvolvingURIs(
ctx,
firstMove.OriginURI,
"https://a.secret.third.place/users/mystery_meat",
)
if err != nil {
suite.FailNow(err.Error())
}
// Time should be equivalent to firstMove.
suite.EqualValues(firstMove.SucceededAt.UnixMilli(), ts.UnixMilli())
// Delete the first Move.
if err := suite.state.DB.DeleteMoveByID(ctx, firstMove.ID); err != nil {
suite.FailNow(err.Error())
}
// Ensure first Move deleted.
_, err = suite.state.DB.GetMoveByID(ctx, firstMove.ID)
suite.ErrorIs(err, db.ErrNoEntries)
}
func TestMoveTestSuite(t *testing.T) {
suite.Run(t, new(MoveTestSuite))
}

View File

@ -37,6 +37,7 @@ type DB interface {
Marker Marker
Media Media
Mention Mention
Move
Notification Notification
Poll Poll
Relationship Relationship

56
internal/db/move.go Normal file
View File

@ -0,0 +1,56 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// 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 db
import (
"context"
"time"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
type Move interface {
// GetMoveByID gets one Move with the given internal ID.
GetMoveByID(ctx context.Context, id string) (*gtsmodel.Move, error)
// GetMoveByURI gets one Move with the given AP URI.
GetMoveByURI(ctx context.Context, uri string) (*gtsmodel.Move, error)
// GetMoveByOriginTarget gets one move with the given originURI and targetURI.
GetMoveByOriginTarget(ctx context.Context, originURI string, targetURI string) (*gtsmodel.Move, error)
// GetLatestMoveSuccessInvolvingURIs gets the time of
// the latest successfully-processed Move that includes
// either uri1 or uri2 in target or origin positions.
GetLatestMoveSuccessInvolvingURIs(ctx context.Context, uri1 string, uri2 string) (time.Time, error)
// GetLatestMoveAttemptInvolvingURIs gets the time
// of the latest Move attempt that includes either
// uri1 or uri2 in target or origin positions.
GetLatestMoveAttemptInvolvingURIs(ctx context.Context, uri1 string, uri2 string) (time.Time, error)
// PutMove puts the given Move in the database.
PutMove(ctx context.Context, move *gtsmodel.Move) error
// UpdateMove updates the given Move by primary key.
// Updates specific columns if provided, all columns if not.
UpdateMove(ctx context.Context, move *gtsmodel.Move, columns ...string) error
// DeleteMoveByID deletes a move with the given internal ID.
DeleteMoveByID(ctx context.Context, id string) error
}

View File

@ -23,6 +23,7 @@ package gtsmodel
import ( import (
"crypto/rsa" "crypto/rsa"
"slices"
"strings" "strings"
"time" "time"
@ -54,8 +55,10 @@ type Account struct {
Memorial *bool `bun:",default:false"` // Is this a memorial account, ie., has the user passed away? Memorial *bool `bun:",default:false"` // Is this a memorial account, ie., has the user passed away?
AlsoKnownAsURIs []string `bun:"also_known_as_uris,array"` // This account is associated with these account URIs. AlsoKnownAsURIs []string `bun:"also_known_as_uris,array"` // This account is associated with these account URIs.
AlsoKnownAs []*Account `bun:"-"` // This account is associated with these accounts (field not stored in the db). AlsoKnownAs []*Account `bun:"-"` // This account is associated with these accounts (field not stored in the db).
MovedToURI string `bun:",nullzero"` // This account has moved to this account URI. MovedToURI string `bun:",nullzero"` // This account has (or claims to have) moved to this account URI. Even if this field is set the move may not yet have been processed. Check `move` for this.
MovedTo *Account `bun:"-"` // This account has moved to this account (field not stored in the db). MovedTo *Account `bun:"-"` // This account has moved to this account (field not stored in the db).
MoveID string `bun:""` // ID of a Move in the database for this account. Only set if we received or created a Move activity for which this account URI was the origin.
Move *Move `bun:"-"` // Move corresponding to MoveID, if set.
Bot *bool `bun:",default:false"` // Does this account identify itself as a bot? Bot *bool `bun:",default:false"` // Does this account identify itself as a bot?
Reason string `bun:""` // What reason was given for signing up when this account was created? Reason string `bun:""` // What reason was given for signing up when this account was created?
Locked *bool `bun:",default:true"` // Does this account need an approval for new followers? Locked *bool `bun:",default:true"` // Does this account need an approval for new followers?
@ -172,6 +175,18 @@ func (a *Account) PubKeyExpired() bool {
a.PublicKeyExpiresAt.Before(time.Now()) a.PublicKeyExpiresAt.Before(time.Now())
} }
// IsAliasedTo returns true if account
// is aliased to the given account URI.
func (a *Account) IsAliasedTo(uri string) bool {
return slices.Contains(a.AlsoKnownAsURIs, uri)
}
// IsSuspended returns true if account
// has been suspended from this instance.
func (a *Account) IsSuspended() bool {
return !a.SuspendedAt.IsZero()
}
// AccountToEmoji is an intermediate struct to facilitate the many2many relationship between an account and one or more emojis. // AccountToEmoji is an intermediate struct to facilitate the many2many relationship between an account and one or more emojis.
type AccountToEmoji struct { type AccountToEmoji struct {
AccountID string `bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"` AccountID string `bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"`

38
internal/gtsmodel/move.go Normal file
View File

@ -0,0 +1,38 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// 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 gtsmodel
import (
"net/url"
"time"
)
// Move represents an ActivityPub "Move" activity
// received (or created) by this instance.
type Move struct {
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database.
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // When was item created.
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // When was item last updated.
AttemptedAt time.Time `bun:"type:timestamptz,nullzero"` // When was processing of the Move to TargetURI last attempted by our instance (zero if not yet attempted).
SucceededAt time.Time `bun:"type:timestamptz,nullzero"` // When did the processing of the Move to TargetURI succeed according to our criteria (zero if not yet complete).
OriginURI string `bun:",nullzero,notnull,unique:moveorigintarget"` // OriginURI of the Move. Ie., the Move Object.
Origin *url.URL `bun:"-"` // URL corresponding to OriginURI. Not stored in the database.
TargetURI string `bun:",nullzero,notnull,unique:moveorigintarget"` // TargetURI of the Move. Ie., the Move Target.
Target *url.URL `bun:"-"` // URL corresponding to TargetURI. Not stored in the database.
URI string `bun:",nullzero,notnull,unique"` // ActivityPub ID/URI of the Move Activity itself.
}

View File

@ -46,6 +46,7 @@ EXPECT=$(cat << "EOF"
"media-mem-ratio": 4, "media-mem-ratio": 4,
"memory-target": 104857600, "memory-target": 104857600,
"mention-mem-ratio": 2, "mention-mem-ratio": 2,
"move-mem-ratio": 0.1,
"notification-mem-ratio": 2, "notification-mem-ratio": 2,
"poll-mem-ratio": 1, "poll-mem-ratio": 1,
"poll-vote-ids-mem-ratio": 2, "poll-vote-ids-mem-ratio": 2,