| 
									
										
										
										
											2023-03-12 16:00:57 +01:00
										 |  |  | // 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/>. | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | package bundb | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2024-04-13 13:25:10 +02:00
										 |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"net/netip" | 
					
						
							| 
									
										
										
										
											2024-04-13 10:36:10 +01:00
										 |  |  | 	"slices" | 
					
						
							| 
									
										
										
										
											2022-12-01 16:06:09 +01:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" | 
					
						
							| 
									
										
										
										
											2023-05-07 19:53:21 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							| 
									
										
										
										
											2023-05-22 16:32:36 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/id" | 
					
						
							| 
									
										
										
										
											2022-07-19 09:47:55 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | 
					
						
							| 
									
										
										
										
											2024-04-13 13:25:10 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/paging" | 
					
						
							| 
									
										
										
										
											2022-12-08 17:35:14 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/state" | 
					
						
							| 
									
										
										
										
											2023-05-07 19:53:21 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/util" | 
					
						
							| 
									
										
										
										
											2024-11-11 15:45:19 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/util/xslices" | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	"github.com/uptrace/bun" | 
					
						
							| 
									
										
										
										
											2022-06-10 10:56:49 +02:00
										 |  |  | 	"github.com/uptrace/bun/dialect" | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type accountDB struct { | 
					
						
							| 
									
										
										
										
											2024-02-07 14:43:27 +00:00
										 |  |  | 	db    *bun.DB | 
					
						
							| 
									
										
										
										
											2022-12-08 17:35:14 +00:00
										 |  |  | 	state *state.State | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) GetAccountByID(ctx context.Context, id string) (*gtsmodel.Account, error) { | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 	return a.getAccount( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 		"ID", | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 		func(account *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | 			return a.db.NewSelect(). | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 				Model(account). | 
					
						
							|  |  |  | 				Where("? = ?", bun.Ident("account.id"), id). | 
					
						
							|  |  |  | 				Scan(ctx) | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 		id, | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 	) | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | func (a *accountDB) GetAccountsByIDs(ctx context.Context, ids []string) ([]*gtsmodel.Account, error) { | 
					
						
							| 
									
										
										
										
											2024-04-13 10:36:10 +01:00
										 |  |  | 	// Load all input account IDs via cache loader callback. | 
					
						
							| 
									
										
										
										
											2024-07-24 09:41:43 +01:00
										 |  |  | 	accounts, err := a.state.Caches.DB.Account.LoadIDs("ID", | 
					
						
							| 
									
										
										
										
											2024-04-13 10:36:10 +01:00
										 |  |  | 		ids, | 
					
						
							|  |  |  | 		func(uncached []string) ([]*gtsmodel.Account, error) { | 
					
						
							|  |  |  | 			// Preallocate expected length of uncached accounts. | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 			accounts := make([]*gtsmodel.Account, 0, len(uncached)) | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-13 10:36:10 +01:00
										 |  |  | 			// Perform database query scanning | 
					
						
							|  |  |  | 			// the remaining (uncached) account IDs. | 
					
						
							|  |  |  | 			if err := a.db.NewSelect(). | 
					
						
							|  |  |  | 				Model(&accounts). | 
					
						
							|  |  |  | 				Where("? IN (?)", bun.Ident("id"), bun.In(uncached)). | 
					
						
							|  |  |  | 				Scan(ctx); err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return accounts, nil | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Reorder the statuses by their | 
					
						
							|  |  |  | 	// IDs to ensure in correct order. | 
					
						
							|  |  |  | 	getID := func(a *gtsmodel.Account) string { return a.ID } | 
					
						
							| 
									
										
										
										
											2024-11-11 15:45:19 +00:00
										 |  |  | 	xslices.OrderBy(accounts, ids, getID) | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-13 10:36:10 +01:00
										 |  |  | 	if gtscontext.Barebones(ctx) { | 
					
						
							|  |  |  | 		// no need to fully populate. | 
					
						
							|  |  |  | 		return accounts, nil | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-13 10:36:10 +01:00
										 |  |  | 	// Populate all loaded accounts, removing those we fail to | 
					
						
							|  |  |  | 	// populate (removes needing so many nil checks everywhere). | 
					
						
							|  |  |  | 	accounts = slices.DeleteFunc(accounts, func(account *gtsmodel.Account) bool { | 
					
						
							|  |  |  | 		if err := a.PopulateAccount(ctx, account); err != nil { | 
					
						
							|  |  |  | 			log.Errorf(ctx, "error populating account %s: %v", account.ID, err) | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 	return accounts, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) GetAccountByURI(ctx context.Context, uri string) (*gtsmodel.Account, error) { | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 	return a.getAccount( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 		"URI", | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 		func(account *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | 			return a.db.NewSelect(). | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 				Model(account). | 
					
						
							|  |  |  | 				Where("? = ?", bun.Ident("account.uri"), uri). | 
					
						
							|  |  |  | 				Scan(ctx) | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 		uri, | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) GetAccountByURL(ctx context.Context, url string) (*gtsmodel.Account, error) { | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 	return a.getAccount( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 		"URL", | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 		func(account *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | 			return a.db.NewSelect(). | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 				Model(account). | 
					
						
							|  |  |  | 				Where("? = ?", bun.Ident("account.url"), url). | 
					
						
							|  |  |  | 				Scan(ctx) | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 		url, | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 	) | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) GetAccountByUsernameDomain(ctx context.Context, username string, domain string) (*gtsmodel.Account, error) { | 
					
						
							| 
									
										
										
										
											2023-05-07 19:53:21 +02:00
										 |  |  | 	if domain != "" { | 
					
						
							|  |  |  | 		// Normalize the domain as punycode | 
					
						
							|  |  |  | 		var err error | 
					
						
							|  |  |  | 		domain, err = util.Punify(domain) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-20 22:47:19 +02:00
										 |  |  | 	return a.getAccount( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							| 
									
										
										
										
											2024-01-19 12:57:29 +00:00
										 |  |  | 		"Username,Domain", | 
					
						
							| 
									
										
										
										
											2022-08-20 22:47:19 +02:00
										 |  |  | 		func(account *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | 			q := a.db.NewSelect(). | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 				Model(account) | 
					
						
							| 
									
										
										
										
											2022-08-20 22:47:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			if domain != "" { | 
					
						
							| 
									
										
										
										
											2022-12-01 16:06:09 +01:00
										 |  |  | 				q = q. | 
					
						
							|  |  |  | 					Where("LOWER(?) = ?", bun.Ident("account.username"), strings.ToLower(username)). | 
					
						
							|  |  |  | 					Where("? = ?", bun.Ident("account.domain"), domain) | 
					
						
							| 
									
										
										
										
											2022-08-20 22:47:19 +02:00
										 |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2022-12-01 16:06:09 +01:00
										 |  |  | 				q = q. | 
					
						
							|  |  |  | 					Where("? = ?", bun.Ident("account.username"), strings.ToLower(username)). // usernames on our instance are always lowercase | 
					
						
							|  |  |  | 					Where("? IS NULL", bun.Ident("account.domain")) | 
					
						
							| 
									
										
										
										
											2022-08-20 22:47:19 +02:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return q.Scan(ctx) | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 		username, | 
					
						
							|  |  |  | 		domain, | 
					
						
							| 
									
										
										
										
											2022-08-20 22:47:19 +02:00
										 |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) GetAccountByPubkeyID(ctx context.Context, id string) (*gtsmodel.Account, error) { | 
					
						
							| 
									
										
										
										
											2022-09-02 10:58:42 +01:00
										 |  |  | 	return a.getAccount( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 		"PublicKeyURI", | 
					
						
							| 
									
										
										
										
											2022-09-02 10:58:42 +01:00
										 |  |  | 		func(account *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | 			return a.db.NewSelect(). | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 				Model(account). | 
					
						
							|  |  |  | 				Where("? = ?", bun.Ident("account.public_key_uri"), id). | 
					
						
							|  |  |  | 				Scan(ctx) | 
					
						
							| 
									
										
										
										
											2022-09-02 10:58:42 +01:00
										 |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 		id, | 
					
						
							| 
									
										
										
										
											2022-09-02 10:58:42 +01:00
										 |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) GetAccountByInboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error) { | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 	return a.getAccount( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							|  |  |  | 		"InboxURI", | 
					
						
							|  |  |  | 		func(account *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | 			return a.db.NewSelect(). | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 				Model(account). | 
					
						
							|  |  |  | 				Where("? = ?", bun.Ident("account.inbox_uri"), uri). | 
					
						
							|  |  |  | 				Scan(ctx) | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		uri, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) GetAccountByOutboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error) { | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 	return a.getAccount( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							|  |  |  | 		"OutboxURI", | 
					
						
							|  |  |  | 		func(account *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | 			return a.db.NewSelect(). | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 				Model(account). | 
					
						
							|  |  |  | 				Where("? = ?", bun.Ident("account.outbox_uri"), uri). | 
					
						
							|  |  |  | 				Scan(ctx) | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		uri, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) GetAccountByFollowersURI(ctx context.Context, uri string) (*gtsmodel.Account, error) { | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 	return a.getAccount( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							|  |  |  | 		"FollowersURI", | 
					
						
							|  |  |  | 		func(account *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | 			return a.db.NewSelect(). | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 				Model(account). | 
					
						
							|  |  |  | 				Where("? = ?", bun.Ident("account.followers_uri"), uri). | 
					
						
							|  |  |  | 				Scan(ctx) | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		uri, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) GetAccountByFollowingURI(ctx context.Context, uri string) (*gtsmodel.Account, error) { | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 	return a.getAccount( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							|  |  |  | 		"FollowingURI", | 
					
						
							|  |  |  | 		func(account *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | 			return a.db.NewSelect(). | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 				Model(account). | 
					
						
							|  |  |  | 				Where("? = ?", bun.Ident("account.following_uri"), uri). | 
					
						
							|  |  |  | 				Scan(ctx) | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		uri, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) GetInstanceAccount(ctx context.Context, domain string) (*gtsmodel.Account, error) { | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 	var username string | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 	if domain == "" { | 
					
						
							|  |  |  | 		// I.e. our local instance account | 
					
						
							|  |  |  | 		username = config.GetHost() | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// A remote instance account | 
					
						
							|  |  |  | 		username = domain | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return a.GetAccountByUsernameDomain(ctx, username, domain) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | func (a *accountDB) GetAccountsByMovedToURI(ctx context.Context, uri string) ([]*gtsmodel.Account, error) { | 
					
						
							|  |  |  | 	var accountIDs []string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Find all account IDs with | 
					
						
							|  |  |  | 	// given moved_to_uri column. | 
					
						
							|  |  |  | 	if err := a.db.NewSelect(). | 
					
						
							|  |  |  | 		Table("accounts"). | 
					
						
							|  |  |  | 		Column("id"). | 
					
						
							|  |  |  | 		Where("? = ?", bun.Ident("moved_to_uri"), uri). | 
					
						
							|  |  |  | 		Scan(ctx, &accountIDs); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(accountIDs) == 0 { | 
					
						
							|  |  |  | 		return nil, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Return account models for all found IDs. | 
					
						
							|  |  |  | 	return a.GetAccountsByIDs(ctx, accountIDs) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-01 15:11:22 +02:00
										 |  |  | // GetAccounts selects accounts using the given parameters. | 
					
						
							|  |  |  | // Unlike with other functions, the paging for GetAccounts | 
					
						
							|  |  |  | // is done not by ID, but by a concatenation of `[domain]/@[username]`, | 
					
						
							|  |  |  | // which allows callers to page through accounts in alphabetical | 
					
						
							|  |  |  | // order (much more useful for an admin overview of accounts, | 
					
						
							|  |  |  | // for example, than paging by ID (which is random) or by account | 
					
						
							|  |  |  | // created at date, which is not particularly interesting). | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Generated queries will look something like this | 
					
						
							|  |  |  | // (SQLite example, maxID was provided so we're paging down): | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | //	SELECT "account"."id", (COALESCE("domain", '') || '/@' || "username") AS "domain_username" | 
					
						
							|  |  |  | //	FROM "accounts" AS "account" | 
					
						
							|  |  |  | //	WHERE ("domain_username" > '/@the_mighty_zork') | 
					
						
							|  |  |  | //	ORDER BY "domain_username" ASC | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // **NOTE ABOUT POSTGRES**: Postgres ordering expressions in | 
					
						
							|  |  |  | // this function specify COLLATE "C" to ensure that ordering | 
					
						
							|  |  |  | // is similar to SQLite (which uses BINARY ordering by default). | 
					
						
							|  |  |  | // This unfortunately means that A-Z > a-z, when ordering but | 
					
						
							|  |  |  | // that's an acceptable tradeoff for a query like this. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // See: | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | //   - https://www.postgresql.org/docs/current/collation.html#COLLATION-MANAGING-STANDARD | 
					
						
							|  |  |  | //   - https://sqlite.org/datatype3.html#collation | 
					
						
							| 
									
										
										
										
											2024-04-13 13:25:10 +02:00
										 |  |  | func (a *accountDB) GetAccounts( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	origin string, | 
					
						
							|  |  |  | 	status string, | 
					
						
							|  |  |  | 	mods bool, | 
					
						
							|  |  |  | 	invitedBy string, | 
					
						
							|  |  |  | 	username string, | 
					
						
							|  |  |  | 	displayName string, | 
					
						
							|  |  |  | 	domain string, | 
					
						
							|  |  |  | 	email string, | 
					
						
							|  |  |  | 	ip netip.Addr, | 
					
						
							|  |  |  | 	page *paging.Page, | 
					
						
							|  |  |  | ) ( | 
					
						
							|  |  |  | 	[]*gtsmodel.Account, | 
					
						
							|  |  |  | 	error, | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  | 	var ( | 
					
						
							| 
									
										
										
										
											2024-05-01 15:11:22 +02:00
										 |  |  | 		// We have to use different | 
					
						
							|  |  |  | 		// syntax for this query | 
					
						
							|  |  |  | 		// depending on dialect. | 
					
						
							|  |  |  | 		dbDialect = a.db.Dialect().Name() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-13 13:25:10 +02:00
										 |  |  | 		// local users lists, | 
					
						
							|  |  |  | 		// required for some | 
					
						
							|  |  |  | 		// limiting parameters. | 
					
						
							|  |  |  | 		users []*gtsmodel.User | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// lazyLoadUsers only loads the users | 
					
						
							|  |  |  | 		// slice if it's required by params. | 
					
						
							|  |  |  | 		lazyLoadUsers = func() (err error) { | 
					
						
							|  |  |  | 			if users == nil { | 
					
						
							|  |  |  | 				users, err = a.state.DB.GetAllUsers(gtscontext.SetBarebones(ctx)) | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					return fmt.Errorf("error getting users: %w", err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Get paging params. | 
					
						
							|  |  |  | 		minID = page.GetMin() | 
					
						
							|  |  |  | 		maxID = page.GetMax() | 
					
						
							|  |  |  | 		limit = page.GetLimit() | 
					
						
							|  |  |  | 		order = page.GetOrder() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Make educated guess for slice size | 
					
						
							|  |  |  | 		accountIDs  = make([]string, 0, limit) | 
					
						
							|  |  |  | 		accountIDIn []string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		useAccountIDIn bool | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	q := a.db. | 
					
						
							|  |  |  | 		NewSelect(). | 
					
						
							|  |  |  | 		TableExpr("? AS ?", bun.Ident("accounts"), bun.Ident("account")). | 
					
						
							|  |  |  | 		// Select only IDs from table | 
					
						
							|  |  |  | 		Column("account.id") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-01 15:11:22 +02:00
										 |  |  | 	var subQ *bun.RawQuery | 
					
						
							|  |  |  | 	if dbDialect == dialect.SQLite { | 
					
						
							|  |  |  | 		// For SQLite we can just select | 
					
						
							|  |  |  | 		// our indexed expression once | 
					
						
							|  |  |  | 		// as a column alias. | 
					
						
							|  |  |  | 		q = q.ColumnExpr( | 
					
						
							|  |  |  | 			"(COALESCE(?, ?) || ? || ?) AS ?", | 
					
						
							|  |  |  | 			bun.Ident("domain"), "", | 
					
						
							|  |  |  | 			"/@", | 
					
						
							|  |  |  | 			bun.Ident("username"), | 
					
						
							|  |  |  | 			bun.Ident("domain_username"), | 
					
						
							| 
									
										
										
										
											2024-04-13 13:25:10 +02:00
										 |  |  | 		) | 
					
						
							| 
									
										
										
										
											2024-05-01 15:11:22 +02:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		// Create a subquery for | 
					
						
							|  |  |  | 		// Postgres to reuse. | 
					
						
							|  |  |  | 		subQ = a.db.NewRaw( | 
					
						
							|  |  |  | 			"(COALESCE(?, ?) || ? || ?) COLLATE ?", | 
					
						
							|  |  |  | 			bun.Ident("domain"), "", | 
					
						
							|  |  |  | 			"/@", | 
					
						
							|  |  |  | 			bun.Ident("username"), | 
					
						
							|  |  |  | 			bun.Ident("C"), | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-04-13 13:25:10 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-01 15:11:22 +02:00
										 |  |  | 	// Return only accounts with `[domain]/@[username]` | 
					
						
							|  |  |  | 	// later in the alphabet (a-z) than provided maxID. | 
					
						
							|  |  |  | 	if maxID != "" { | 
					
						
							|  |  |  | 		if dbDialect == dialect.SQLite { | 
					
						
							|  |  |  | 			// Use aliased column. | 
					
						
							|  |  |  | 			q = q.Where("? > ?", bun.Ident("domain_username"), maxID) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			q = q.Where("? > ?", subQ, maxID) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-04-13 13:25:10 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-01 15:11:22 +02:00
										 |  |  | 	// Return only accounts with `[domain]/@[username]` | 
					
						
							|  |  |  | 	// earlier in the alphabet (a-z) than provided minID. | 
					
						
							| 
									
										
										
										
											2024-04-13 13:25:10 +02:00
										 |  |  | 	if minID != "" { | 
					
						
							| 
									
										
										
										
											2024-05-01 15:11:22 +02:00
										 |  |  | 		if dbDialect == dialect.SQLite { | 
					
						
							|  |  |  | 			// Use aliased column. | 
					
						
							|  |  |  | 			q = q.Where("? < ?", bun.Ident("domain_username"), minID) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			q = q.Where("? < ?", subQ, minID) | 
					
						
							| 
									
										
										
										
											2024-04-13 13:25:10 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch status { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case "active": | 
					
						
							|  |  |  | 		// Get only enabled accounts. | 
					
						
							|  |  |  | 		if err := lazyLoadUsers(); err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		for _, user := range users { | 
					
						
							|  |  |  | 			if !*user.Disabled { | 
					
						
							|  |  |  | 				accountIDIn = append(accountIDIn, user.AccountID) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		useAccountIDIn = true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case "pending": | 
					
						
							|  |  |  | 		// Get only unapproved accounts. | 
					
						
							|  |  |  | 		if err := lazyLoadUsers(); err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		for _, user := range users { | 
					
						
							|  |  |  | 			if !*user.Approved { | 
					
						
							|  |  |  | 				accountIDIn = append(accountIDIn, user.AccountID) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		useAccountIDIn = true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case "disabled": | 
					
						
							|  |  |  | 		// Get only disabled accounts. | 
					
						
							|  |  |  | 		if err := lazyLoadUsers(); err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		for _, user := range users { | 
					
						
							|  |  |  | 			if *user.Disabled { | 
					
						
							|  |  |  | 				accountIDIn = append(accountIDIn, user.AccountID) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		useAccountIDIn = true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case "silenced": | 
					
						
							|  |  |  | 		// Get only silenced accounts. | 
					
						
							|  |  |  | 		q = q.Where("? IS NOT NULL", bun.Ident("account.silenced_at")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case "suspended": | 
					
						
							|  |  |  | 		// Get only suspended accounts. | 
					
						
							|  |  |  | 		q = q.Where("? IS NOT NULL", bun.Ident("account.suspended_at")) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if mods { | 
					
						
							|  |  |  | 		// Get only mod accounts. | 
					
						
							|  |  |  | 		if err := lazyLoadUsers(); err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		for _, user := range users { | 
					
						
							|  |  |  | 			if *user.Moderator || *user.Admin { | 
					
						
							|  |  |  | 				accountIDIn = append(accountIDIn, user.AccountID) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		useAccountIDIn = true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// TODO: invitedBy | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if username != "" { | 
					
						
							|  |  |  | 		q = q.Where("? = ?", bun.Ident("account.username"), username) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if displayName != "" { | 
					
						
							|  |  |  | 		q = q.Where("? = ?", bun.Ident("account.display_name"), displayName) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if domain != "" { | 
					
						
							|  |  |  | 		q = q.Where("? = ?", bun.Ident("account.domain"), domain) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if email != "" { | 
					
						
							|  |  |  | 		if err := lazyLoadUsers(); err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		for _, user := range users { | 
					
						
							|  |  |  | 			if user.Email == email || user.UnconfirmedEmail == email { | 
					
						
							|  |  |  | 				accountIDIn = append(accountIDIn, user.AccountID) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		useAccountIDIn = true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Use ip if not zero value. | 
					
						
							|  |  |  | 	if ip.IsValid() { | 
					
						
							|  |  |  | 		if err := lazyLoadUsers(); err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		for _, user := range users { | 
					
						
							|  |  |  | 			if user.SignUpIP.String() == ip.String() { | 
					
						
							|  |  |  | 				accountIDIn = append(accountIDIn, user.AccountID) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		useAccountIDIn = true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if origin == "local" && !useAccountIDIn { | 
					
						
							|  |  |  | 		// In the case we're not already limiting | 
					
						
							|  |  |  | 		// by specific subset of account IDs, just | 
					
						
							|  |  |  | 		// use existing list of user.AccountIDs | 
					
						
							|  |  |  | 		// instead of adding WHERE to the query. | 
					
						
							|  |  |  | 		if err := lazyLoadUsers(); err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		for _, user := range users { | 
					
						
							|  |  |  | 			accountIDIn = append(accountIDIn, user.AccountID) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		useAccountIDIn = true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	} else if origin == "remote" { | 
					
						
							|  |  |  | 		if useAccountIDIn { | 
					
						
							|  |  |  | 			// useAccountIDIn specifically indicates | 
					
						
							|  |  |  | 			// a parameter that limits querying to | 
					
						
							|  |  |  | 			// local accounts, there will be none. | 
					
						
							|  |  |  | 			return nil, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Get only remote accounts. | 
					
						
							|  |  |  | 		q = q.Where("? IS NOT NULL", bun.Ident("account.domain")) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if useAccountIDIn { | 
					
						
							|  |  |  | 		if len(accountIDIn) == 0 { | 
					
						
							|  |  |  | 			// There will be no | 
					
						
							|  |  |  | 			// possible answer. | 
					
						
							|  |  |  | 			return nil, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		q = q.Where("? IN (?)", bun.Ident("account.id"), bun.In(accountIDIn)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if limit > 0 { | 
					
						
							|  |  |  | 		// Limit amount of | 
					
						
							|  |  |  | 		// accounts returned. | 
					
						
							|  |  |  | 		q = q.Limit(limit) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if order == paging.OrderAscending { | 
					
						
							|  |  |  | 		// Page up. | 
					
						
							| 
									
										
										
										
											2024-05-01 15:11:22 +02:00
										 |  |  | 		// It's counterintuitive because it | 
					
						
							|  |  |  | 		// says DESC in the query, but we're | 
					
						
							|  |  |  | 		// going backwards in the alphabet, | 
					
						
							|  |  |  | 		// and a < z in a string comparison. | 
					
						
							|  |  |  | 		if dbDialect == dialect.SQLite { | 
					
						
							|  |  |  | 			q = q.OrderExpr("? DESC", bun.Ident("domain_username")) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			q = q.OrderExpr("(?) DESC", subQ) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-04-13 13:25:10 +02:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		// Page down. | 
					
						
							| 
									
										
										
										
											2024-05-01 15:11:22 +02:00
										 |  |  | 		// It's counterintuitive because it | 
					
						
							|  |  |  | 		// says ASC in the query, but we're | 
					
						
							|  |  |  | 		// going forwards in the alphabet, | 
					
						
							|  |  |  | 		// and z > a in a string comparison. | 
					
						
							|  |  |  | 		if dbDialect == dialect.SQLite { | 
					
						
							|  |  |  | 			q = q.OrderExpr("? ASC", bun.Ident("domain_username")) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			q = q.OrderExpr("? ASC", subQ) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-04-13 13:25:10 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-01 15:11:22 +02:00
										 |  |  | 	if err := q.Scan(ctx, &accountIDs, new([]string)); err != nil { | 
					
						
							| 
									
										
										
										
											2024-04-13 13:25:10 +02:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(accountIDs) == 0 { | 
					
						
							|  |  |  | 		return nil, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If we're paging up, we still want accounts | 
					
						
							|  |  |  | 	// to be sorted by createdAt desc, so reverse ids slice. | 
					
						
							|  |  |  | 	if order == paging.OrderAscending { | 
					
						
							|  |  |  | 		slices.Reverse(accountIDs) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Return account IDs loaded from cache + db. | 
					
						
							|  |  |  | 	return a.state.DB.GetAccountsByIDs(ctx, accountIDs) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) getAccount(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Account) error, keyParts ...any) (*gtsmodel.Account, error) { | 
					
						
							| 
									
										
										
										
											2022-11-18 17:29:25 +00:00
										 |  |  | 	// Fetch account from database cache with loader callback | 
					
						
							| 
									
										
										
										
											2024-07-24 09:41:43 +01:00
										 |  |  | 	account, err := a.state.Caches.DB.Account.LoadOne(lookup, func() (*gtsmodel.Account, error) { | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 		var account gtsmodel.Account | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 		// Not cached! Perform database query | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 		if err := dbQuery(&account); err != nil { | 
					
						
							| 
									
										
										
										
											2023-08-17 17:26:21 +01:00
										 |  |  | 			return nil, err | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 		return &account, nil | 
					
						
							|  |  |  | 	}, keyParts...) | 
					
						
							| 
									
										
										
										
											2022-11-18 17:29:25 +00:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 	if gtscontext.Barebones(ctx) { | 
					
						
							|  |  |  | 		// no need to fully populate. | 
					
						
							|  |  |  | 		return account, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Further populate the account fields where applicable. | 
					
						
							|  |  |  | 	if err := a.PopulateAccount(ctx, account); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return account, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (a *accountDB) PopulateAccount(ctx context.Context, account *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2023-05-07 19:53:21 +02:00
										 |  |  | 	var ( | 
					
						
							|  |  |  | 		err  error | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 		errs = gtserror.NewMultiError(5) | 
					
						
							| 
									
										
										
										
											2023-05-07 19:53:21 +02:00
										 |  |  | 	) | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if account.AvatarMediaAttachment == nil && account.AvatarMediaAttachmentID != "" { | 
					
						
							|  |  |  | 		// Account avatar attachment is not set, fetch from database. | 
					
						
							|  |  |  | 		account.AvatarMediaAttachment, err = a.state.DB.GetAttachmentByID( | 
					
						
							|  |  |  | 			ctx, // these are already barebones | 
					
						
							|  |  |  | 			account.AvatarMediaAttachmentID, | 
					
						
							|  |  |  | 		) | 
					
						
							| 
									
										
										
										
											2023-02-03 20:03:05 +00:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2023-08-02 17:21:46 +02:00
										 |  |  | 			errs.Appendf("error populating account avatar: %w", err) | 
					
						
							| 
									
										
										
										
											2023-02-03 20:03:05 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 	if account.HeaderMediaAttachment == nil && account.HeaderMediaAttachmentID != "" { | 
					
						
							|  |  |  | 		// Account header attachment is not set, fetch from database. | 
					
						
							|  |  |  | 		account.HeaderMediaAttachment, err = a.state.DB.GetAttachmentByID( | 
					
						
							|  |  |  | 			ctx, // these are already barebones | 
					
						
							|  |  |  | 			account.HeaderMediaAttachmentID, | 
					
						
							|  |  |  | 		) | 
					
						
							| 
									
										
										
										
											2023-02-03 20:03:05 +00:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2023-08-02 17:21:46 +02:00
										 |  |  | 			errs.Appendf("error populating account header: %w", err) | 
					
						
							| 
									
										
										
										
											2023-02-03 20:03:05 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-06 10:45:46 +01:00
										 |  |  | 	// Only try to populate AlsoKnownAs for local accounts, | 
					
						
							|  |  |  | 	// since those are the only accounts to which it's relevant. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// AKA from remotes might have loads of random-ass values | 
					
						
							|  |  |  | 	// set here, and we don't want to do lots of failing DB calls. | 
					
						
							|  |  |  | 	if account.IsLocal() && !account.AlsoKnownAsPopulated() { | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 		// Account alsoKnownAs accounts are | 
					
						
							|  |  |  | 		// out-of-date with URIs, repopulate. | 
					
						
							|  |  |  | 		alsoKnownAs := make([]*gtsmodel.Account, 0) | 
					
						
							|  |  |  | 		for _, uri := range account.AlsoKnownAsURIs { | 
					
						
							|  |  |  | 			akaAcct, err := a.state.DB.GetAccountByURI( | 
					
						
							|  |  |  | 				gtscontext.SetBarebones(ctx), | 
					
						
							|  |  |  | 				uri, | 
					
						
							|  |  |  | 			) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				errs.Appendf("error populating also known as account %s: %w", uri, err) | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			alsoKnownAs = append(alsoKnownAs, akaAcct) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		account.AlsoKnownAs = alsoKnownAs | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-06 11:18:57 +01:00
										 |  |  | 	if account.Move == nil && account.MoveID != "" { | 
					
						
							|  |  |  | 		// Account move is not set, fetch from database. | 
					
						
							|  |  |  | 		account.Move, err = a.state.DB.GetMoveByID( | 
					
						
							|  |  |  | 			ctx, | 
					
						
							| 
									
										
										
										
											2024-03-06 15:40:37 +01:00
										 |  |  | 			account.MoveID, | 
					
						
							| 
									
										
										
										
											2024-03-06 11:18:57 +01:00
										 |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			errs.Appendf("error populating move: %w", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	if account.MovedTo == nil && account.MovedToURI != "" { | 
					
						
							| 
									
										
										
										
											2024-03-10 11:59:55 +01:00
										 |  |  | 		// Account movedTo is not set, try to fetch from database, | 
					
						
							|  |  |  | 		// but only error on real errors since this field is optional. | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 		account.MovedTo, err = a.state.DB.GetAccountByURI( | 
					
						
							|  |  |  | 			gtscontext.SetBarebones(ctx), | 
					
						
							|  |  |  | 			account.MovedToURI, | 
					
						
							|  |  |  | 		) | 
					
						
							| 
									
										
										
										
											2024-03-10 11:59:55 +01:00
										 |  |  | 		if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 			errs.Appendf("error populating moved to account: %w", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
											
										 
											2023-03-28 14:03:14 +01:00
										 |  |  | 	if !account.EmojisPopulated() { | 
					
						
							|  |  |  | 		// Account emojis are out-of-date with IDs, repopulate. | 
					
						
							|  |  |  | 		account.Emojis, err = a.state.DB.GetEmojisByIDs( | 
					
						
							|  |  |  | 			ctx, // these are already barebones | 
					
						
							|  |  |  | 			account.EmojiIDs, | 
					
						
							|  |  |  | 		) | 
					
						
							| 
									
										
										
										
											2022-11-18 17:29:25 +00:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2023-08-02 17:21:46 +02:00
										 |  |  | 			errs.Appendf("error populating account emojis: %w", err) | 
					
						
							| 
									
										
										
										
											2022-11-18 17:29:25 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-22 14:03:46 +01:00
										 |  |  | 	if account.IsLocal() && account.Settings == nil && !account.IsInstance() { | 
					
						
							|  |  |  | 		// Account settings not set, fetch from db. | 
					
						
							|  |  |  | 		account.Settings, err = a.state.DB.GetAccountSettings( | 
					
						
							|  |  |  | 			ctx, // these are already barebones | 
					
						
							|  |  |  | 			account.ID, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			errs.Appendf("error populating account settings: %w", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-02 12:15:11 +00:00
										 |  |  | 	// Get / Create stats for this account (handles case of already set). | 
					
						
							|  |  |  | 	if err := a.state.DB.PopulateAccountStats(ctx, account); err != nil { | 
					
						
							|  |  |  | 		errs.Appendf("error populating account stats: %w", err) | 
					
						
							| 
									
										
										
										
											2024-04-16 13:10:13 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	return errs.Combine() | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) PutAccount(ctx context.Context, account *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2024-07-24 09:41:43 +01:00
										 |  |  | 	return a.state.Caches.DB.Account.Store(account, func() error { | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 		// It is safe to run this database transaction within cache.Store | 
					
						
							|  |  |  | 		// as the cache does not attempt a mutex lock until AFTER hook. | 
					
						
							|  |  |  | 		// | 
					
						
							| 
									
										
										
										
											2024-02-07 14:43:27 +00:00
										 |  |  | 		return a.db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 			// create links between this account and any emojis it uses | 
					
						
							|  |  |  | 			for _, i := range account.EmojiIDs { | 
					
						
							|  |  |  | 				if _, err := tx.NewInsert().Model(>smodel.AccountToEmoji{ | 
					
						
							|  |  |  | 					AccountID: account.ID, | 
					
						
							|  |  |  | 					EmojiID:   i, | 
					
						
							|  |  |  | 				}).Exec(ctx); err != nil { | 
					
						
							|  |  |  | 					return err | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 			// insert the account | 
					
						
							|  |  |  | 			_, err := tx.NewInsert().Model(account).Exec(ctx) | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) UpdateAccount(ctx context.Context, account *gtsmodel.Account, columns ...string) error { | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	account.UpdatedAt = time.Now() | 
					
						
							| 
									
										
										
										
											2023-03-20 19:10:08 +01:00
										 |  |  | 	if len(columns) > 0 { | 
					
						
							|  |  |  | 		// If we're updating by column, ensure "updated_at" is included. | 
					
						
							|  |  |  | 		columns = append(columns, "updated_at") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-24 09:41:43 +01:00
										 |  |  | 	return a.state.Caches.DB.Account.Store(account, func() error { | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 		// It is safe to run this database transaction within cache.Store | 
					
						
							|  |  |  | 		// as the cache does not attempt a mutex lock until AFTER hook. | 
					
						
							|  |  |  | 		// | 
					
						
							| 
									
										
										
										
											2024-02-07 14:43:27 +00:00
										 |  |  | 		return a.db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 			// create links between this account and any emojis it uses | 
					
						
							|  |  |  | 			// first clear out any old emoji links | 
					
						
							| 
									
										
										
										
											2022-10-08 13:50:48 +02:00
										 |  |  | 			if _, err := tx. | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 				NewDelete(). | 
					
						
							|  |  |  | 				TableExpr("? AS ?", bun.Ident("account_to_emojis"), bun.Ident("account_to_emoji")). | 
					
						
							|  |  |  | 				Where("? = ?", bun.Ident("account_to_emoji.account_id"), account.ID). | 
					
						
							|  |  |  | 				Exec(ctx); err != nil { | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 			// now populate new emoji links | 
					
						
							|  |  |  | 			for _, i := range account.EmojiIDs { | 
					
						
							|  |  |  | 				if _, err := tx. | 
					
						
							|  |  |  | 					NewInsert(). | 
					
						
							|  |  |  | 					Model(>smodel.AccountToEmoji{ | 
					
						
							|  |  |  | 						AccountID: account.ID, | 
					
						
							|  |  |  | 						EmojiID:   i, | 
					
						
							|  |  |  | 					}).Exec(ctx); err != nil { | 
					
						
							|  |  |  | 					return err | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 			// update the account | 
					
						
							|  |  |  | 			_, err := tx.NewUpdate(). | 
					
						
							|  |  |  | 				Model(account). | 
					
						
							|  |  |  | 				Where("? = ?", bun.Ident("account.id"), account.ID). | 
					
						
							| 
									
										
										
										
											2023-03-20 19:10:08 +01:00
										 |  |  | 				Column(columns...). | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 				Exec(ctx) | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) DeleteAccount(ctx context.Context, id string) error { | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 	// Gather necessary fields from | 
					
						
							|  |  |  | 	// deleted for cache invaliation. | 
					
						
							|  |  |  | 	var deleted gtsmodel.Account | 
					
						
							|  |  |  | 	deleted.ID = id | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Delete account from database and any related links in a transaction. | 
					
						
							|  |  |  | 	if err := a.db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { | 
					
						
							| 
									
										
										
										
											2023-05-12 10:15:54 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-08 13:50:48 +02:00
										 |  |  | 		// clear out any emoji links | 
					
						
							|  |  |  | 		if _, err := tx. | 
					
						
							|  |  |  | 			NewDelete(). | 
					
						
							|  |  |  | 			TableExpr("? AS ?", bun.Ident("account_to_emojis"), bun.Ident("account_to_emoji")). | 
					
						
							|  |  |  | 			Where("? = ?", bun.Ident("account_to_emoji.account_id"), id). | 
					
						
							|  |  |  | 			Exec(ctx); err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// delete the account | 
					
						
							|  |  |  | 		_, err := tx. | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 			NewDelete(). | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 			Model(&deleted). | 
					
						
							|  |  |  | 			Where("? = ?", bun.Ident("id"), id). | 
					
						
							|  |  |  | 			Returning("?", bun.Ident("uri")). | 
					
						
							| 
									
										
										
										
											2022-10-08 13:50:48 +02:00
										 |  |  | 			Exec(ctx) | 
					
						
							|  |  |  | 		return err | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 	}); err != nil { | 
					
						
							| 
									
										
										
										
											2023-08-17 17:26:21 +01:00
										 |  |  | 		return err | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-10-08 13:50:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 	// Invalidate cached account by its ID, manually | 
					
						
							|  |  |  | 	// call invalidate hook in case not cached. | 
					
						
							|  |  |  | 	a.state.Caches.DB.Account.Invalidate("ID", id) | 
					
						
							|  |  |  | 	a.state.Caches.OnInvalidateAccount(&deleted) | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) GetAccountCustomCSSByUsername(ctx context.Context, username string) (string, error) { | 
					
						
							| 
									
										
										
										
											2024-03-22 14:03:46 +01:00
										 |  |  | 	// Get local account. | 
					
						
							| 
									
										
										
										
											2022-09-12 13:14:29 +02:00
										 |  |  | 	account, err := a.GetAccountByUsernameDomain(ctx, username, "") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return "", err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-22 14:03:46 +01:00
										 |  |  | 	// Ensure settings populated, in case | 
					
						
							|  |  |  | 	// barebones context was passed. | 
					
						
							|  |  |  | 	if account.Settings == nil { | 
					
						
							|  |  |  | 		account.Settings, err = a.GetAccountSettings(ctx, account.ID) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return "", err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return account.Settings.CustomCSS, nil | 
					
						
							| 
									
										
										
										
											2022-09-12 13:14:29 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | func (a *accountDB) GetAccountsUsingEmoji(ctx context.Context, emojiID string) ([]*gtsmodel.Account, error) { | 
					
						
							|  |  |  | 	var accountIDs []string | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-02 15:11:23 +01:00
										 |  |  | 	// SELECT all accounts using this emoji, | 
					
						
							|  |  |  | 	// using a relational table for improved perf. | 
					
						
							|  |  |  | 	if _, err := a.db.NewSelect(). | 
					
						
							|  |  |  | 		Table("account_to_emojis"). | 
					
						
							|  |  |  | 		Column("account_id"). | 
					
						
							|  |  |  | 		Where("? = ?", bun.Ident("emoji_id"), emojiID). | 
					
						
							|  |  |  | 		Exec(ctx, &accountIDs); err != nil { | 
					
						
							| 
									
										
										
										
											2023-08-17 17:26:21 +01:00
										 |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Convert account IDs into account objects. | 
					
						
							|  |  |  | 	return a.GetAccountsByIDs(ctx, accountIDs) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) GetAccountFaves(ctx context.Context, accountID string) ([]*gtsmodel.StatusFave, error) { | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	faves := new([]*gtsmodel.StatusFave) | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | 	if err := a.db. | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 		NewSelect(). | 
					
						
							|  |  |  | 		Model(faves). | 
					
						
							| 
									
										
										
										
											2022-10-08 13:50:48 +02:00
										 |  |  | 		Where("? = ?", bun.Ident("status_fave.account_id"), accountID). | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 		Scan(ctx); err != nil { | 
					
						
							| 
									
										
										
										
											2023-08-17 17:26:21 +01:00
										 |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	return *faves, nil | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) GetAccountStatuses(ctx context.Context, accountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, mediaOnly bool, publicOnly bool) ([]*gtsmodel.Status, error) { | 
					
						
							| 
									
										
										
										
											2023-05-22 16:32:36 +02:00
										 |  |  | 	// Ensure reasonable | 
					
						
							|  |  |  | 	if limit < 0 { | 
					
						
							|  |  |  | 		limit = 0 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Make educated guess for slice size | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		statusIDs   = make([]string, 0, limit) | 
					
						
							|  |  |  | 		frontToBack = true | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | 	q := a.db. | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 		NewSelect(). | 
					
						
							| 
									
										
										
										
											2022-10-08 13:50:48 +02:00
										 |  |  | 		TableExpr("? AS ?", bun.Ident("statuses"), bun.Ident("status")). | 
					
						
							| 
									
										
										
										
											2023-05-22 16:32:36 +02:00
										 |  |  | 		// Select only IDs from table | 
					
						
							| 
									
										
										
										
											2022-10-08 13:50:48 +02:00
										 |  |  | 		Column("status.id"). | 
					
						
							| 
									
										
										
										
											2023-05-22 16:32:36 +02:00
										 |  |  | 		Where("? = ?", bun.Ident("status.account_id"), accountID) | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 	if excludeReplies { | 
					
						
							| 
									
										
										
										
											2024-02-27 09:18:40 -08:00
										 |  |  | 		q = q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery { | 
					
						
							| 
									
										
										
										
											2022-08-27 05:35:31 -04:00
										 |  |  | 			return q. | 
					
						
							| 
									
										
										
										
											2023-05-22 16:32:36 +02:00
										 |  |  | 				// Do include self replies (threads), but | 
					
						
							|  |  |  | 				// don't include replies to other people. | 
					
						
							|  |  |  | 				Where("? = ?", bun.Ident("status.in_reply_to_account_id"), accountID). | 
					
						
							|  |  |  | 				WhereOr("? IS NULL", bun.Ident("status.in_reply_to_uri")) | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2024-02-27 09:18:40 -08:00
										 |  |  | 		// Don't include replies that mention other people: | 
					
						
							|  |  |  | 		// for example, an account's reply to its own reply to someone else. | 
					
						
							|  |  |  | 		q = whereArrayIsNullOrEmpty(q, bun.Ident("status.mentions")) | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-15 14:33:01 +02:00
										 |  |  | 	if excludeReblogs { | 
					
						
							| 
									
										
										
										
											2023-05-22 16:32:36 +02:00
										 |  |  | 		q = q.Where("? IS NULL", bun.Ident("status.boost_of_id")) | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	if mediaOnly { | 
					
						
							| 
									
										
										
										
											2023-05-22 16:32:36 +02:00
										 |  |  | 		// Attachments are stored as a json object; this | 
					
						
							|  |  |  | 		// implementation differs between SQLite and Postgres, | 
					
						
							| 
									
										
										
										
											2022-06-10 10:56:49 +02:00
										 |  |  | 		// so we have to be thorough to cover all eventualities | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 		q = q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery { | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | 			switch a.db.Dialect().Name() { | 
					
						
							| 
									
										
										
										
											2022-06-10 10:56:49 +02:00
										 |  |  | 			case dialect.PG: | 
					
						
							|  |  |  | 				return q. | 
					
						
							| 
									
										
										
										
											2022-10-08 13:50:48 +02:00
										 |  |  | 					Where("? IS NOT NULL", bun.Ident("status.attachments")). | 
					
						
							|  |  |  | 					Where("? != '{}'", bun.Ident("status.attachments")) | 
					
						
							| 
									
										
										
										
											2022-06-10 10:56:49 +02:00
										 |  |  | 			case dialect.SQLite: | 
					
						
							|  |  |  | 				return q. | 
					
						
							| 
									
										
										
										
											2022-10-08 13:50:48 +02:00
										 |  |  | 					Where("? IS NOT NULL", bun.Ident("status.attachments")). | 
					
						
							|  |  |  | 					Where("? != ''", bun.Ident("status.attachments")). | 
					
						
							|  |  |  | 					Where("? != 'null'", bun.Ident("status.attachments")). | 
					
						
							|  |  |  | 					Where("? != '{}'", bun.Ident("status.attachments")). | 
					
						
							|  |  |  | 					Where("? != '[]'", bun.Ident("status.attachments")) | 
					
						
							| 
									
										
										
										
											2022-06-10 10:56:49 +02:00
										 |  |  | 			default: | 
					
						
							| 
									
										
										
										
											2023-02-17 12:02:29 +01:00
										 |  |  | 				log.Panic(ctx, "db dialect was neither pg nor sqlite") | 
					
						
							| 
									
										
										
										
											2022-06-10 10:56:49 +02:00
										 |  |  | 				return q | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 	if publicOnly { | 
					
						
							| 
									
										
										
										
											2022-10-08 13:50:48 +02:00
										 |  |  | 		q = q.Where("? = ?", bun.Ident("status.visibility"), gtsmodel.VisibilityPublic) | 
					
						
							| 
									
										
										
										
											2021-08-26 22:06:34 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-22 16:32:36 +02:00
										 |  |  | 	// return only statuses LOWER (ie., older) than maxID | 
					
						
							|  |  |  | 	if maxID == "" { | 
					
						
							|  |  |  | 		maxID = id.Highest | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	q = q.Where("? < ?", bun.Ident("status.id"), maxID) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if minID != "" { | 
					
						
							|  |  |  | 		// return only statuses HIGHER (ie., newer) than minID | 
					
						
							|  |  |  | 		q = q.Where("? > ?", bun.Ident("status.id"), minID) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// page up | 
					
						
							|  |  |  | 		frontToBack = false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if limit > 0 { | 
					
						
							|  |  |  | 		// limit amount of statuses returned | 
					
						
							|  |  |  | 		q = q.Limit(limit) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if frontToBack { | 
					
						
							|  |  |  | 		// Page down. | 
					
						
							|  |  |  | 		q = q.Order("status.id DESC") | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// Page up. | 
					
						
							|  |  |  | 		q = q.Order("status.id ASC") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-10 16:18:21 +01:00
										 |  |  | 	if err := q.Scan(ctx, &statusIDs); err != nil { | 
					
						
							| 
									
										
										
										
											2023-08-17 17:26:21 +01:00
										 |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-19 12:57:29 +00:00
										 |  |  | 	if len(statusIDs) == 0 { | 
					
						
							|  |  |  | 		return nil, db.ErrNoEntries | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-22 16:32:36 +02:00
										 |  |  | 	// If we're paging up, we still want statuses | 
					
						
							|  |  |  | 	// to be sorted by ID desc, so reverse ids slice. | 
					
						
							|  |  |  | 	// https://zchee.github.io/golang-wiki/SliceTricks/#reversing | 
					
						
							|  |  |  | 	if !frontToBack { | 
					
						
							|  |  |  | 		for l, r := 0, len(statusIDs)-1; l < r; l, r = l+1, r-1 { | 
					
						
							|  |  |  | 			statusIDs[l], statusIDs[r] = statusIDs[r], statusIDs[l] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-19 12:57:29 +00:00
										 |  |  | 	return a.state.DB.GetStatusesByIDs(ctx, statusIDs) | 
					
						
							| 
									
										
										
										
											2022-07-13 09:57:47 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func (a *accountDB) GetAccountPinnedStatuses(ctx context.Context, accountID string) ([]*gtsmodel.Status, error) { | 
					
						
							| 
									
										
										
										
											2023-02-25 13:16:30 +01:00
										 |  |  | 	statusIDs := []string{} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | 	q := a.db. | 
					
						
							| 
									
										
										
										
											2023-02-25 13:16:30 +01:00
										 |  |  | 		NewSelect(). | 
					
						
							|  |  |  | 		TableExpr("? AS ?", bun.Ident("statuses"), bun.Ident("status")). | 
					
						
							|  |  |  | 		Column("status.id"). | 
					
						
							|  |  |  | 		Where("? = ?", bun.Ident("status.account_id"), accountID). | 
					
						
							|  |  |  | 		Where("? IS NOT NULL", bun.Ident("status.pinned_at")). | 
					
						
							|  |  |  | 		Order("status.pinned_at DESC") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := q.Scan(ctx, &statusIDs); err != nil { | 
					
						
							| 
									
										
										
										
											2023-08-17 17:26:21 +01:00
										 |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2023-02-25 13:16:30 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-19 12:57:29 +00:00
										 |  |  | 	if len(statusIDs) == 0 { | 
					
						
							|  |  |  | 		return nil, db.ErrNoEntries | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return a.state.DB.GetStatusesByIDs(ctx, statusIDs) | 
					
						
							| 
									
										
										
										
											2023-02-25 13:16:30 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-09 18:07:25 +02:00
										 |  |  | func (a *accountDB) GetAccountWebStatuses( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	account *gtsmodel.Account, | 
					
						
							|  |  |  | 	limit int, | 
					
						
							|  |  |  | 	maxID string, | 
					
						
							|  |  |  | ) ([]*gtsmodel.Status, error) { | 
					
						
							|  |  |  | 	// Check for an easy case: account exposes no statuses via the web. | 
					
						
							|  |  |  | 	webVisibility := account.Settings.WebVisibility | 
					
						
							|  |  |  | 	if webVisibility == gtsmodel.VisibilityNone { | 
					
						
							|  |  |  | 		return nil, db.ErrNoEntries | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-22 16:32:36 +02:00
										 |  |  | 	// Ensure reasonable | 
					
						
							|  |  |  | 	if limit < 0 { | 
					
						
							|  |  |  | 		limit = 0 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Make educated guess for slice size | 
					
						
							|  |  |  | 	statusIDs := make([]string, 0, limit) | 
					
						
							| 
									
										
										
										
											2022-07-10 16:18:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | 	q := a.db. | 
					
						
							| 
									
										
										
										
											2022-07-13 09:57:47 +02:00
										 |  |  | 		NewSelect(). | 
					
						
							| 
									
										
										
										
											2022-10-08 13:50:48 +02:00
										 |  |  | 		TableExpr("? AS ?", bun.Ident("statuses"), bun.Ident("status")). | 
					
						
							| 
									
										
										
										
											2023-05-22 16:32:36 +02:00
										 |  |  | 		// Select only IDs from table | 
					
						
							| 
									
										
										
										
											2022-10-08 13:50:48 +02:00
										 |  |  | 		Column("status.id"). | 
					
						
							| 
									
										
										
										
											2024-09-09 18:07:25 +02:00
										 |  |  | 		Where("? = ?", bun.Ident("status.account_id"), account.ID). | 
					
						
							| 
									
										
										
										
											2023-05-22 16:32:36 +02:00
										 |  |  | 		// Don't show replies or boosts. | 
					
						
							|  |  |  | 		Where("? IS NULL", bun.Ident("status.in_reply_to_uri")). | 
					
						
							| 
									
										
										
										
											2024-09-09 18:07:25 +02:00
										 |  |  | 		Where("? IS NULL", bun.Ident("status.boost_of_id")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Select statuses for this account according | 
					
						
							|  |  |  | 	// to their web visibility preference. | 
					
						
							|  |  |  | 	switch webVisibility { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case gtsmodel.VisibilityPublic: | 
					
						
							| 
									
										
										
										
											2023-05-22 16:32:36 +02:00
										 |  |  | 		// Only Public statuses. | 
					
						
							| 
									
										
										
										
											2024-09-09 18:07:25 +02:00
										 |  |  | 		q = q.Where("? = ?", bun.Ident("status.visibility"), gtsmodel.VisibilityPublic) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case gtsmodel.VisibilityUnlocked: | 
					
						
							|  |  |  | 		// Public or Unlocked. | 
					
						
							|  |  |  | 		visis := []gtsmodel.Visibility{ | 
					
						
							|  |  |  | 			gtsmodel.VisibilityPublic, | 
					
						
							|  |  |  | 			gtsmodel.VisibilityUnlocked, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		q = q.Where("? IN (?)", bun.Ident("status.visibility"), bun.In(visis)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return nil, gtserror.Newf( | 
					
						
							|  |  |  | 			"unrecognized web visibility for account %s: %s", | 
					
						
							|  |  |  | 			account.ID, webVisibility, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Don't show local-only statuses on the web view. | 
					
						
							|  |  |  | 	q = q.Where("? = ?", bun.Ident("status.federated"), true) | 
					
						
							| 
									
										
										
										
											2022-07-10 16:18:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-22 16:32:36 +02:00
										 |  |  | 	// return only statuses LOWER (ie., older) than maxID | 
					
						
							|  |  |  | 	if maxID == "" { | 
					
						
							|  |  |  | 		maxID = id.Highest | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	q = q.Where("? < ?", bun.Ident("status.id"), maxID) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if limit > 0 { | 
					
						
							|  |  |  | 		// limit amount of statuses returned | 
					
						
							|  |  |  | 		q = q.Limit(limit) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if limit > 0 { | 
					
						
							|  |  |  | 		// limit amount of statuses returned | 
					
						
							|  |  |  | 		q = q.Limit(limit) | 
					
						
							| 
									
										
										
										
											2022-07-10 16:18:21 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-22 16:32:36 +02:00
										 |  |  | 	q = q.Order("status.id DESC") | 
					
						
							| 
									
										
										
										
											2022-07-13 09:57:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if err := q.Scan(ctx, &statusIDs); err != nil { | 
					
						
							| 
									
										
										
										
											2023-08-17 17:26:21 +01:00
										 |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2022-07-13 09:57:47 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(statusIDs) == 0 { | 
					
						
							|  |  |  | 		return nil, db.ErrNoEntries | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-19 12:57:29 +00:00
										 |  |  | 	return a.state.DB.GetStatusesByIDs(ctx, statusIDs) | 
					
						
							| 
									
										
										
										
											2022-07-13 09:57:47 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2024-03-22 14:03:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | func (a *accountDB) GetAccountSettings( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	accountID string, | 
					
						
							|  |  |  | ) (*gtsmodel.AccountSettings, error) { | 
					
						
							|  |  |  | 	// Fetch settings from db cache with loader callback. | 
					
						
							| 
									
										
										
										
											2024-07-24 09:41:43 +01:00
										 |  |  | 	return a.state.Caches.DB.AccountSettings.LoadOne( | 
					
						
							| 
									
										
										
										
											2024-03-22 14:03:46 +01:00
										 |  |  | 		"AccountID", | 
					
						
							|  |  |  | 		func() (*gtsmodel.AccountSettings, error) { | 
					
						
							|  |  |  | 			// Not cached! Perform database query. | 
					
						
							|  |  |  | 			var settings gtsmodel.AccountSettings | 
					
						
							|  |  |  | 			if err := a.db. | 
					
						
							|  |  |  | 				NewSelect(). | 
					
						
							|  |  |  | 				Model(&settings). | 
					
						
							|  |  |  | 				Where("? = ?", bun.Ident("account_settings.account_id"), accountID). | 
					
						
							|  |  |  | 				Scan(ctx); err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return &settings, nil | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		accountID, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (a *accountDB) PutAccountSettings( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	settings *gtsmodel.AccountSettings, | 
					
						
							|  |  |  | ) error { | 
					
						
							| 
									
										
										
										
											2024-07-24 09:41:43 +01:00
										 |  |  | 	return a.state.Caches.DB.AccountSettings.Store(settings, func() error { | 
					
						
							| 
									
										
										
										
											2024-03-22 14:03:46 +01:00
										 |  |  | 		if _, err := a.db. | 
					
						
							|  |  |  | 			NewInsert(). | 
					
						
							|  |  |  | 			Model(settings). | 
					
						
							|  |  |  | 			Exec(ctx); err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (a *accountDB) UpdateAccountSettings( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	settings *gtsmodel.AccountSettings, | 
					
						
							|  |  |  | 	columns ...string, | 
					
						
							|  |  |  | ) error { | 
					
						
							| 
									
										
										
										
											2024-07-24 09:41:43 +01:00
										 |  |  | 	return a.state.Caches.DB.AccountSettings.Store(settings, func() error { | 
					
						
							| 
									
										
										
										
											2024-03-22 14:03:46 +01:00
										 |  |  | 		settings.UpdatedAt = time.Now() | 
					
						
							| 
									
										
										
										
											2024-09-09 18:07:25 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		switch { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		case len(columns) != 0: | 
					
						
							| 
									
										
										
										
											2024-03-22 14:03:46 +01:00
										 |  |  | 			// If we're updating by column, | 
					
						
							|  |  |  | 			// ensure "updated_at" is included. | 
					
						
							|  |  |  | 			columns = append(columns, "updated_at") | 
					
						
							| 
									
										
										
										
											2024-09-09 18:07:25 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// If we're updating web_visibility we should | 
					
						
							|  |  |  | 			// fall through + invalidate visibility cache. | 
					
						
							|  |  |  | 			if !slices.Contains(columns, "web_visibility") { | 
					
						
							|  |  |  | 				break // No need to invalidate. | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Fallthrough | 
					
						
							|  |  |  | 			// to invalidate. | 
					
						
							|  |  |  | 			fallthrough | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		case len(columns) == 0: | 
					
						
							|  |  |  | 			// Status visibility may be changing for this account. | 
					
						
							|  |  |  | 			// Clear the visibility cache for unauthed requesters. | 
					
						
							|  |  |  | 			// | 
					
						
							|  |  |  | 			// todo: invalidate JUST this account's statuses. | 
					
						
							|  |  |  | 			defer a.state.Caches.Visibility.Clear() | 
					
						
							| 
									
										
										
										
											2024-03-22 14:03:46 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if _, err := a.db. | 
					
						
							|  |  |  | 			NewUpdate(). | 
					
						
							|  |  |  | 			Model(settings). | 
					
						
							|  |  |  | 			Column(columns...). | 
					
						
							|  |  |  | 			Where("? = ?", bun.Ident("account_settings.account_id"), settings.AccountID). | 
					
						
							|  |  |  | 			Exec(ctx); err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-04-16 13:10:13 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | func (a *accountDB) PopulateAccountStats(ctx context.Context, account *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2024-08-02 12:15:11 +00:00
										 |  |  | 	if account.Stats != nil { | 
					
						
							|  |  |  | 		// Already populated! | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-16 13:10:13 +02:00
										 |  |  | 	// Fetch stats from db cache with loader callback. | 
					
						
							| 
									
										
										
										
											2024-07-24 09:41:43 +01:00
										 |  |  | 	stats, err := a.state.Caches.DB.AccountStats.LoadOne( | 
					
						
							| 
									
										
										
										
											2024-04-16 13:10:13 +02:00
										 |  |  | 		"AccountID", | 
					
						
							|  |  |  | 		func() (*gtsmodel.AccountStats, error) { | 
					
						
							|  |  |  | 			// Not cached! Perform database query. | 
					
						
							|  |  |  | 			var stats gtsmodel.AccountStats | 
					
						
							|  |  |  | 			if err := a.db. | 
					
						
							|  |  |  | 				NewSelect(). | 
					
						
							|  |  |  | 				Model(&stats). | 
					
						
							|  |  |  | 				Where("? = ?", bun.Ident("account_stats.account_id"), account.ID). | 
					
						
							|  |  |  | 				Scan(ctx); err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return &stats, nil | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		account.ID, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		// Real error. | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if stats == nil { | 
					
						
							|  |  |  | 		// Don't have stats yet, generate them. | 
					
						
							|  |  |  | 		return a.RegenerateAccountStats(ctx, account) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// We have a stats, attach | 
					
						
							|  |  |  | 	// it to the account. | 
					
						
							|  |  |  | 	account.Stats = stats | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if this is a local | 
					
						
							|  |  |  | 	// stats by looking at the | 
					
						
							|  |  |  | 	// account they pertain to. | 
					
						
							|  |  |  | 	if account.IsRemote() { | 
					
						
							|  |  |  | 		// Account is remote. Updating | 
					
						
							|  |  |  | 		// stats for remote accounts is | 
					
						
							|  |  |  | 		// handled in the dereferencer. | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		// Nothing more to do! | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Stats account is local, check | 
					
						
							|  |  |  | 	// if we need to regenerate. | 
					
						
							|  |  |  | 	const statsFreshness = 48 * time.Hour | 
					
						
							|  |  |  | 	expiry := stats.RegeneratedAt.Add(statsFreshness) | 
					
						
							|  |  |  | 	if time.Now().After(expiry) { | 
					
						
							|  |  |  | 		// Stats have expired, regenerate them. | 
					
						
							|  |  |  | 		return a.RegenerateAccountStats(ctx, account) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Stats are still fresh. | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-11 11:54:59 +02:00
										 |  |  | func (a *accountDB) StubAccountStats(ctx context.Context, account *gtsmodel.Account) error { | 
					
						
							|  |  |  | 	stats := >smodel.AccountStats{ | 
					
						
							|  |  |  | 		AccountID:           account.ID, | 
					
						
							|  |  |  | 		RegeneratedAt:       time.Now(), | 
					
						
							|  |  |  | 		FollowersCount:      util.Ptr(0), | 
					
						
							|  |  |  | 		FollowingCount:      util.Ptr(0), | 
					
						
							|  |  |  | 		FollowRequestsCount: util.Ptr(0), | 
					
						
							|  |  |  | 		StatusesCount:       util.Ptr(0), | 
					
						
							|  |  |  | 		StatusesPinnedCount: util.Ptr(0), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Upsert this stats in case a race | 
					
						
							|  |  |  | 	// meant someone else inserted it first. | 
					
						
							| 
									
										
										
										
											2024-07-24 09:41:43 +01:00
										 |  |  | 	if err := a.state.Caches.DB.AccountStats.Store(stats, func() error { | 
					
						
							| 
									
										
										
										
											2024-06-11 11:54:59 +02:00
										 |  |  | 		if _, err := NewUpsert(a.db). | 
					
						
							|  |  |  | 			Model(stats). | 
					
						
							|  |  |  | 			Constraint("account_id"). | 
					
						
							|  |  |  | 			Exec(ctx); err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	account.Stats = stats | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-16 13:10:13 +02:00
										 |  |  | func (a *accountDB) RegenerateAccountStats(ctx context.Context, account *gtsmodel.Account) error { | 
					
						
							|  |  |  | 	// Initialize a new stats struct. | 
					
						
							|  |  |  | 	stats := >smodel.AccountStats{ | 
					
						
							|  |  |  | 		AccountID:     account.ID, | 
					
						
							|  |  |  | 		RegeneratedAt: time.Now(), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Count followers outside of transaction since | 
					
						
							|  |  |  | 	// it uses a cache + requires its own db calls. | 
					
						
							|  |  |  | 	followerIDs, err := a.state.DB.GetAccountFollowerIDs(ctx, account.ID, nil) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	stats.FollowersCount = util.Ptr(len(followerIDs)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Count following outside of transaction since | 
					
						
							|  |  |  | 	// it uses a cache + requires its own db calls. | 
					
						
							|  |  |  | 	followIDs, err := a.state.DB.GetAccountFollowIDs(ctx, account.ID, nil) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	stats.FollowingCount = util.Ptr(len(followIDs)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Count follow requests outside of transaction since | 
					
						
							|  |  |  | 	// it uses a cache + requires its own db calls. | 
					
						
							|  |  |  | 	followRequestIDs, err := a.state.DB.GetAccountFollowRequestIDs(ctx, account.ID, nil) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	stats.FollowRequestsCount = util.Ptr(len(followRequestIDs)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Populate remaining stats struct fields. | 
					
						
							|  |  |  | 	// This can be done inside a transaction. | 
					
						
							|  |  |  | 	if err := a.db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { | 
					
						
							|  |  |  | 		var err error | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-24 11:49:37 +02:00
										 |  |  | 		// Scan database for account statuses, ignoring | 
					
						
							|  |  |  | 		// statuses that are currently pending approval. | 
					
						
							| 
									
										
										
										
											2024-04-16 13:10:13 +02:00
										 |  |  | 		statusesCount, err := tx.NewSelect(). | 
					
						
							| 
									
										
										
										
											2024-08-24 11:49:37 +02:00
										 |  |  | 			TableExpr("? AS ?", bun.Ident("statuses"), bun.Ident("status")). | 
					
						
							|  |  |  | 			Where("? = ?", bun.Ident("status.account_id"), account.ID). | 
					
						
							|  |  |  | 			Where("NOT ? = ?", bun.Ident("status.pending_approval"), true). | 
					
						
							| 
									
										
										
										
											2024-04-16 13:10:13 +02:00
										 |  |  | 			Count(ctx) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		stats.StatusesCount = &statusesCount | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-24 11:49:37 +02:00
										 |  |  | 		// Scan database for pinned statuses, ignoring | 
					
						
							|  |  |  | 		// statuses that are currently pending approval. | 
					
						
							| 
									
										
										
										
											2024-04-16 13:10:13 +02:00
										 |  |  | 		statusesPinnedCount, err := tx.NewSelect(). | 
					
						
							| 
									
										
										
										
											2024-08-24 11:49:37 +02:00
										 |  |  | 			TableExpr("? AS ?", bun.Ident("statuses"), bun.Ident("status")). | 
					
						
							|  |  |  | 			Where("? = ?", bun.Ident("status.account_id"), account.ID). | 
					
						
							|  |  |  | 			Where("? IS NOT NULL", bun.Ident("status.pinned_at")). | 
					
						
							|  |  |  | 			Where("NOT ? = ?", bun.Ident("status.pending_approval"), true). | 
					
						
							| 
									
										
										
										
											2024-04-16 13:10:13 +02:00
										 |  |  | 			Count(ctx) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		stats.StatusesPinnedCount = &statusesPinnedCount | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-24 11:49:37 +02:00
										 |  |  | 		// Scan database for last status, ignoring | 
					
						
							|  |  |  | 		// statuses that are currently pending approval. | 
					
						
							| 
									
										
										
										
											2024-04-16 13:10:13 +02:00
										 |  |  | 		lastStatusAt := time.Time{} | 
					
						
							|  |  |  | 		err = tx. | 
					
						
							|  |  |  | 			NewSelect(). | 
					
						
							|  |  |  | 			TableExpr("? AS ?", bun.Ident("statuses"), bun.Ident("status")). | 
					
						
							|  |  |  | 			Column("status.created_at"). | 
					
						
							|  |  |  | 			Where("? = ?", bun.Ident("status.account_id"), account.ID). | 
					
						
							| 
									
										
										
										
											2024-08-24 11:49:37 +02:00
										 |  |  | 			Where("NOT ? = ?", bun.Ident("status.pending_approval"), true). | 
					
						
							| 
									
										
										
										
											2024-04-16 13:10:13 +02:00
										 |  |  | 			Order("status.id DESC"). | 
					
						
							|  |  |  | 			Limit(1). | 
					
						
							|  |  |  | 			Scan(ctx, &lastStatusAt) | 
					
						
							|  |  |  | 		if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		stats.LastStatusAt = lastStatusAt | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Upsert this stats in case a race | 
					
						
							|  |  |  | 	// meant someone else inserted it first. | 
					
						
							| 
									
										
										
										
											2024-07-24 09:41:43 +01:00
										 |  |  | 	if err := a.state.Caches.DB.AccountStats.Store(stats, func() error { | 
					
						
							| 
									
										
										
										
											2024-04-16 13:10:13 +02:00
										 |  |  | 		if _, err := NewUpsert(a.db). | 
					
						
							|  |  |  | 			Model(stats). | 
					
						
							|  |  |  | 			Constraint("account_id"). | 
					
						
							|  |  |  | 			Exec(ctx); err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	account.Stats = stats | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (a *accountDB) UpdateAccountStats(ctx context.Context, stats *gtsmodel.AccountStats, columns ...string) error { | 
					
						
							| 
									
										
										
										
											2024-07-24 09:41:43 +01:00
										 |  |  | 	return a.state.Caches.DB.AccountStats.Store(stats, func() error { | 
					
						
							| 
									
										
										
										
											2024-04-16 13:10:13 +02:00
										 |  |  | 		if _, err := a.db. | 
					
						
							|  |  |  | 			NewUpdate(). | 
					
						
							|  |  |  | 			Model(stats). | 
					
						
							|  |  |  | 			Column(columns...). | 
					
						
							|  |  |  | 			Where("? = ?", bun.Ident("account_stats.account_id"), stats.AccountID). | 
					
						
							|  |  |  | 			Exec(ctx); err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (a *accountDB) DeleteAccountStats(ctx context.Context, accountID string) error { | 
					
						
							| 
									
										
										
										
											2024-07-24 09:41:43 +01:00
										 |  |  | 	defer a.state.Caches.DB.AccountStats.Invalidate("AccountID", accountID) | 
					
						
							| 
									
										
										
										
											2024-04-16 13:10:13 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if _, err := a.db. | 
					
						
							|  |  |  | 		NewDelete(). | 
					
						
							|  |  |  | 		Table("account_stats"). | 
					
						
							|  |  |  | 		Where("? = ?", bun.Ident("account_id"), accountID). | 
					
						
							|  |  |  | 		Exec(ctx); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } |