mirror of
				https://github.com/superseriousbusiness/gotosocial
				synced 2025-06-05 21:59:39 +02:00 
			
		
		
		
	[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:
		
							
								
								
									
										2
									
								
								internal/cache/cache.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								internal/cache/cache.go
									
									
									
									
										vendored
									
									
								
							| @@ -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
									
									
								
							
							
						
						
									
										32
									
								
								internal/cache/db.go
									
									
									
									
										vendored
									
									
								
							| @@ -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( | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								internal/cache/invalidate.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								internal/cache/invalidate.go
									
									
									
									
										vendored
									
									
								
							| @@ -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) { | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								internal/cache/size.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								internal/cache/size.go
									
									
									
									
										vendored
									
									
								
							| @@ -460,6 +460,19 @@ func sizeofMention() uintptr { | |||||||
| 	})) | 	})) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func sizeofMove() uintptr { | ||||||
|  | 	return uintptr(size.Of(>smodel.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(>smodel.Notification{ | 	return uintptr(size.Of(>smodel.Notification{ | ||||||
| 		ID:               exampleID, | 		ID:               exampleID, | ||||||
|   | |||||||
| @@ -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"` | ||||||
|   | |||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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() | ||||||
|   | |||||||
| @@ -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( | ||||||
|   | |||||||
| @@ -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: ¬ificationDB{ | 		Notification: ¬ificationDB{ | ||||||
| 			db:    db, | 			db:    db, | ||||||
| 			state: state, | 			state: state, | ||||||
|   | |||||||
| @@ -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(>smodel.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
									
								
							
							
						
						
									
										236
									
								
								internal/db/bundb/move.go
									
									
									
									
									
										Normal 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 | ||||||
|  | } | ||||||
							
								
								
									
										168
									
								
								internal/db/bundb/move_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								internal/db/bundb/move_test.go
									
									
									
									
									
										Normal 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 := >smodel.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 := >smodel.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)) | ||||||
|  | } | ||||||
| @@ -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
									
								
							
							
						
						
									
										56
									
								
								internal/db/move.go
									
									
									
									
									
										Normal 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 | ||||||
|  | } | ||||||
| @@ -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
									
								
							
							
						
						
									
										38
									
								
								internal/gtsmodel/move.go
									
									
									
									
									
										Normal 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. | ||||||
|  | } | ||||||
| @@ -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, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user