| 
									
										
										
										
											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-03-05 18:31:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | // Package gtsmodel contains types used *internally* by GoToSocial and added/removed/selected from the database. | 
					
						
							| 
									
										
										
										
											2021-03-15 16:15:14 +01:00
										 |  |  | // These types should never be serialized and/or sent out via public APIs, as they contain sensitive information. | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | // The annotation used on these structs is for handling them via the bun-db ORM. | 
					
						
							|  |  |  | // See here for more info on bun model annotations: https://bun.uptrace.dev/guide/models.html | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | package gtsmodel | 
					
						
							| 
									
										
										
										
											2021-03-05 18:31:12 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	"crypto/rsa" | 
					
						
							| 
									
										
										
										
											2024-03-06 11:18:57 +01:00
										 |  |  | 	"slices" | 
					
						
							| 
									
										
										
										
											2023-02-03 20:03:05 +00:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2021-03-05 18:31:12 +01:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2023-02-03 20:03:05 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | 
					
						
							| 
									
										
											  
											
												[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/log" | 
					
						
							| 
									
										
										
										
											2021-03-05 18:31:12 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-06 14:39:40 +02:00
										 |  |  | // Account represents either a local or a remote ActivityPub actor. | 
					
						
							|  |  |  | // https://www.w3.org/TR/activitypub/#actor-objects | 
					
						
							| 
									
										
										
										
											2021-03-15 23:05:24 +01:00
										 |  |  | type Account struct { | 
					
						
							| 
									
										
										
										
											2025-04-06 14:39:40 +02:00
										 |  |  | 	// Database ID of the account. | 
					
						
							|  |  |  | 	ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Datetime when the account was created. | 
					
						
							|  |  |  | 	// Corresponds to ActivityStreams `published` prop. | 
					
						
							|  |  |  | 	CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Datetime when was the account was last updated, | 
					
						
							|  |  |  | 	// ie., when the actor last sent out an Update | 
					
						
							|  |  |  | 	// activity, or if never, when it was `published`. | 
					
						
							|  |  |  | 	UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Datetime when the account was last fetched / | 
					
						
							|  |  |  | 	// dereferenced by this GoToSocial instance. | 
					
						
							|  |  |  | 	FetchedAt time.Time `bun:"type:timestamptz,nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Username of the account. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to AS `preferredUsername` prop, which gives | 
					
						
							|  |  |  | 	// no uniqueness guarantee. However, we do enforce uniqueness | 
					
						
							|  |  |  | 	// for it as, in practice, it always is and we rely on this. | 
					
						
							|  |  |  | 	Username string `bun:",nullzero,notnull,unique:accounts_username_domain_uniq"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Domain of the account, discovered via webfinger. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Null if this is a local account, otherwise | 
					
						
							|  |  |  | 	// something like `example.org`. | 
					
						
							|  |  |  | 	Domain string `bun:",nullzero,unique:accounts_username_domain_uniq"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Database ID of the account's avatar MediaAttachment, if set. | 
					
						
							|  |  |  | 	AvatarMediaAttachmentID string `bun:"type:CHAR(26),nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// MediaAttachment corresponding to AvatarMediaAttachmentID. | 
					
						
							|  |  |  | 	AvatarMediaAttachment *MediaAttachment `bun:"-"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// URL of the avatar media. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Null for local accounts. | 
					
						
							|  |  |  | 	AvatarRemoteURL string `bun:",nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Database ID of the account's header MediaAttachment, if set. | 
					
						
							|  |  |  | 	HeaderMediaAttachmentID string `bun:"type:CHAR(26),nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// MediaAttachment corresponding to HeaderMediaAttachmentID. | 
					
						
							|  |  |  | 	HeaderMediaAttachment *MediaAttachment `bun:"-"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// URL of the header media. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Null for local accounts. | 
					
						
							|  |  |  | 	HeaderRemoteURL string `bun:",nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Display name for this account, if set. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to the ActivityStreams `name` property. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// If null, fall back to username for display purposes. | 
					
						
							|  |  |  | 	DisplayName string `bun:",nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Database IDs of any emojis used in | 
					
						
							|  |  |  | 	// this account's bio, display name, etc | 
					
						
							|  |  |  | 	EmojiIDs []string `bun:"emojis,array"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Emojis corresponding to EmojiIDs. | 
					
						
							|  |  |  | 	Emojis []*Emoji `bun:"-"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// A slice of of key/value fields that | 
					
						
							|  |  |  | 	// this account has added to their profile. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to schema.org PropertyValue types in `attachments`. | 
					
						
							|  |  |  | 	Fields []*Field `bun:",nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// The raw (unparsed) content of fields that this | 
					
						
							|  |  |  | 	// account has added to their profile, before | 
					
						
							|  |  |  | 	// conversion to HTML. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Only set for local accounts. | 
					
						
							|  |  |  | 	FieldsRaw []*Field `bun:",nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// A note that this account has on their profile | 
					
						
							|  |  |  | 	// (ie., the account's bio/description of themselves). | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to the ActivityStreams `summary` property. | 
					
						
							|  |  |  | 	Note string `bun:",nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// The raw (unparsed) version of Note, before conversion to HTML. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Only set for local accounts. | 
					
						
							|  |  |  | 	NoteRaw string `bun:",nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ActivityPub URI/IDs by which this account is also known. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to the ActivityStreams `alsoKnownAs` property. | 
					
						
							|  |  |  | 	AlsoKnownAsURIs []string `bun:"also_known_as_uris,array"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Accounts matching AlsoKnownAsURIs. | 
					
						
							|  |  |  | 	AlsoKnownAs []*Account `bun:"-"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// URI/ID to which the account has (or claims to have) moved. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to the ActivityStreams `movedTo` property. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Even if this field is set the move may not yet have been | 
					
						
							|  |  |  | 	// processed. Check `move` for this. | 
					
						
							|  |  |  | 	MovedToURI string `bun:",nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Account matching MovedToURI. | 
					
						
							|  |  |  | 	MovedTo *Account `bun:"-"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ID of a Move in the database for this account. | 
					
						
							|  |  |  | 	// Only set if we received or created a Move activity | 
					
						
							|  |  |  | 	// for which this account URI was the origin. | 
					
						
							|  |  |  | 	MoveID string `bun:"type:CHAR(26),nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Move corresponding to MoveID, if set. | 
					
						
							|  |  |  | 	Move *Move `bun:"-"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// True if account requires manual approval of Follows. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to AS `manuallyApprovesFollowers` prop. | 
					
						
							|  |  |  | 	Locked *bool `bun:",nullzero,notnull,default:true"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// True if account has opted in to being shown in | 
					
						
							|  |  |  | 	// directories and exposed to search engines. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to the toot `discoverable` property. | 
					
						
							|  |  |  | 	Discoverable *bool `bun:",nullzero,notnull,default:false"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ActivityPub URI/ID for this account. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Must be set, must be unique. | 
					
						
							|  |  |  | 	URI string `bun:",nullzero,notnull,unique"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// URL at which a web representation of this | 
					
						
							|  |  |  | 	// account should be available, if set. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to ActivityStreams `url` prop. | 
					
						
							|  |  |  | 	URL string `bun:",nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// URI of the actor's inbox. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to ActivityPub `inbox` property. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// According to AP this MUST be set, but some | 
					
						
							|  |  |  | 	// implementations don't set it for service actors. | 
					
						
							|  |  |  | 	InboxURI string `bun:",nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// URI/ID of this account's sharedInbox, if set. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to ActivityPub `endpoints.sharedInbox`. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Gotcha warning: this is a string pointer because | 
					
						
							|  |  |  | 	// it has three possible states: | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	//   1. null: We don't know (yet) if actor has a shared inbox. | 
					
						
							|  |  |  | 	//   2. empty: We know it doesn't have a shared inbox. | 
					
						
							|  |  |  | 	//   3. not empty: We know it does have a shared inbox. | 
					
						
							|  |  |  | 	SharedInboxURI *string `bun:""` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// URI/ID of the actor's outbox. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to ActivityPub `outbox` property. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// According to AP this MUST be set, but some | 
					
						
							|  |  |  | 	// implementations don't set it for service actors. | 
					
						
							|  |  |  | 	OutboxURI string `bun:",nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// URI/ID of the actor's following collection. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to ActivityPub `following` property. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// According to AP this SHOULD be set. | 
					
						
							|  |  |  | 	FollowingURI string `bun:",nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// URI/ID of the actor's followers collection. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to ActivityPub `followers` property. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// According to AP this SHOULD be set. | 
					
						
							|  |  |  | 	FollowersURI string `bun:",nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// URI/ID of the actor's featured collection. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to the Toot `featured` property. | 
					
						
							|  |  |  | 	FeaturedCollectionURI string `bun:",nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ActivityStreams type of the actor. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Application, Group, Organization, Person, or Service. | 
					
						
							|  |  |  | 	ActorType AccountActorType `bun:",nullzero,notnull"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Private key for signing http requests. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Only defined for local accounts | 
					
						
							|  |  |  | 	PrivateKey *rsa.PrivateKey `bun:""` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Public key for authorizing signed http requests. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Defined for both local and remote accounts | 
					
						
							|  |  |  | 	PublicKey *rsa.PublicKey `bun:",notnull"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Dereferenceable location of this actor's public key. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Corresponds to https://w3id.org/security/v1 `publicKey.id`. | 
					
						
							|  |  |  | 	PublicKeyURI string `bun:",nullzero,notnull,unique"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Datetime at which public key will expire/has expired, | 
					
						
							|  |  |  | 	// and should be fetched again as appropriate. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Only ever set for remote accounts. | 
					
						
							|  |  |  | 	PublicKeyExpiresAt time.Time `bun:"type:timestamptz,nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Datetime at which account was marked as a "memorial", | 
					
						
							|  |  |  | 	// ie., user owning the account has passed away. | 
					
						
							|  |  |  | 	MemorializedAt time.Time `bun:"type:timestamptz,nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Datetime at which account was set to | 
					
						
							|  |  |  | 	// have all its media shown as sensitive. | 
					
						
							|  |  |  | 	SensitizedAt time.Time `bun:"type:timestamptz,nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Datetime at which account was silenced. | 
					
						
							|  |  |  | 	SilencedAt time.Time `bun:"type:timestamptz,nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Datetime at which account was suspended. | 
					
						
							|  |  |  | 	SuspendedAt time.Time `bun:"type:timestamptz,nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ID of the database entry that caused this account to | 
					
						
							|  |  |  | 	// be suspended. Can be an account ID or a domain block ID. | 
					
						
							|  |  |  | 	SuspensionOrigin string `bun:"type:CHAR(26),nullzero"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// gtsmodel.AccountSettings for this account. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Local, non-instance-actor accounts only. | 
					
						
							|  |  |  | 	Settings *AccountSettings `bun:"-"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// gtsmodel.AccountStats for this account. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Local accounts only. | 
					
						
							|  |  |  | 	Stats *AccountStats `bun:"-"` | 
					
						
							| 
									
										
										
										
											2021-03-05 18:31:12 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-30 12:52:03 +00:00
										 |  |  | // UsernameDomain returns account @username@domain (missing domain if local). | 
					
						
							|  |  |  | func (a *Account) UsernameDomain() string { | 
					
						
							|  |  |  | 	if a.IsLocal() { | 
					
						
							|  |  |  | 		return "@" + a.Username | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return "@" + a.Username + "@" + a.Domain | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-03 20:03:05 +00:00
										 |  |  | // IsLocal returns whether account is a local user account. | 
					
						
							| 
									
										
											  
											
												[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
										 |  |  | func (a *Account) IsLocal() bool { | 
					
						
							| 
									
										
										
										
											2025-01-30 12:52:03 +00:00
										 |  |  | 	return a.Domain == "" || | 
					
						
							|  |  |  | 		a.Domain == config.GetHost() || | 
					
						
							|  |  |  | 		a.Domain == config.GetAccountDomain() | 
					
						
							| 
									
										
										
										
											2023-02-03 20:03:05 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IsRemote returns whether account is a remote user account. | 
					
						
							| 
									
										
											  
											
												[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
										 |  |  | func (a *Account) IsRemote() bool { | 
					
						
							| 
									
										
										
										
											2023-02-03 20:03:05 +00:00
										 |  |  | 	return !a.IsLocal() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-26 14:17:10 +01:00
										 |  |  | // IsNew returns whether an account is "new" in the sense | 
					
						
							|  |  |  | // that it has not been previously stored in the database. | 
					
						
							|  |  |  | func (a *Account) IsNew() bool { | 
					
						
							|  |  |  | 	return a.CreatedAt.IsZero() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-03 20:03:05 +00:00
										 |  |  | // IsInstance returns whether account is an instance internal actor account. | 
					
						
							| 
									
										
											  
											
												[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
										 |  |  | func (a *Account) IsInstance() bool { | 
					
						
							| 
									
										
										
										
											2023-05-09 17:05:35 +02:00
										 |  |  | 	if a.IsLocal() { | 
					
						
							|  |  |  | 		// Check if our instance account. | 
					
						
							|  |  |  | 		return a.Username == config.GetHost() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if remote instance account. | 
					
						
							| 
									
										
										
										
											2023-02-03 20:03:05 +00:00
										 |  |  | 	return a.Username == a.Domain || | 
					
						
							|  |  |  | 		a.FollowersURI == "" || | 
					
						
							|  |  |  | 		a.FollowingURI == "" || | 
					
						
							| 
									
										
										
										
											2023-06-21 18:26:40 +02:00
										 |  |  | 		(a.Username == "internal.fetch" && strings.Contains(a.Note, "internal service actor")) || | 
					
						
							|  |  |  | 		a.Username == "instance.actor" // <- misskey | 
					
						
							| 
									
										
										
										
											2023-02-03 20:03:05 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | // EmojisPopulated returns whether emojis are | 
					
						
							|  |  |  | // populated according to current EmojiIDs. | 
					
						
							| 
									
										
											  
											
												[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
										 |  |  | func (a *Account) EmojisPopulated() bool { | 
					
						
							|  |  |  | 	if len(a.EmojiIDs) != len(a.Emojis) { | 
					
						
							|  |  |  | 		// this is the quickest indicator. | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Emojis must be in same order. | 
					
						
							|  |  |  | 	for i, id := range a.EmojiIDs { | 
					
						
							|  |  |  | 		if a.Emojis[i] == nil { | 
					
						
							|  |  |  | 			log.Warnf(nil, "nil emoji in slice for account %s", a.URI) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if a.Emojis[i].ID != id { | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | // AlsoKnownAsPopulated returns whether alsoKnownAs accounts | 
					
						
							|  |  |  | // are populated according to current AlsoKnownAsURIs. | 
					
						
							|  |  |  | func (a *Account) AlsoKnownAsPopulated() bool { | 
					
						
							|  |  |  | 	if len(a.AlsoKnownAsURIs) != len(a.AlsoKnownAs) { | 
					
						
							|  |  |  | 		// this is the quickest indicator. | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Accounts must be in same order. | 
					
						
							|  |  |  | 	for i, uri := range a.AlsoKnownAsURIs { | 
					
						
							|  |  |  | 		if a.AlsoKnownAs[i] == nil { | 
					
						
							|  |  |  | 			log.Warnf(nil, "nil account in alsoKnownAs slice for account %s", a.URI) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if a.AlsoKnownAs[i].URI != uri { | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-12 11:43:12 +02:00
										 |  |  | // PubKeyExpired returns true if the account's public key | 
					
						
							|  |  |  | // has been marked as expired, and the expiry time has passed. | 
					
						
							|  |  |  | func (a *Account) PubKeyExpired() bool { | 
					
						
							|  |  |  | 	if a == nil { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return !a.PublicKeyExpiresAt.IsZero() && | 
					
						
							|  |  |  | 		a.PublicKeyExpiresAt.Before(time.Now()) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-06 11:18:57 +01:00
										 |  |  | // IsAliasedTo returns true if account | 
					
						
							|  |  |  | // is aliased to the given account URI. | 
					
						
							|  |  |  | func (a *Account) IsAliasedTo(uri string) bool { | 
					
						
							|  |  |  | 	return slices.Contains(a.AlsoKnownAsURIs, uri) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IsSuspended returns true if account | 
					
						
							|  |  |  | // has been suspended from this instance. | 
					
						
							|  |  |  | func (a *Account) IsSuspended() bool { | 
					
						
							|  |  |  | 	return !a.SuspendedAt.IsZero() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-12 15:34:08 +01:00
										 |  |  | // IsMoving returns true if | 
					
						
							|  |  |  | // account is Moving or has Moved. | 
					
						
							|  |  |  | func (a *Account) IsMoving() bool { | 
					
						
							|  |  |  | 	return a.MovedToURI != "" || a.MoveID != "" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | // AccountToEmoji is an intermediate struct to facilitate the many2many relationship between an account and one or more emojis. | 
					
						
							|  |  |  | type AccountToEmoji struct { | 
					
						
							| 
									
										
										
										
											2023-08-06 12:22:40 +02:00
										 |  |  | 	AccountID string   `bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"` | 
					
						
							|  |  |  | 	Account   *Account `bun:"rel:belongs-to"` | 
					
						
							|  |  |  | 	EmojiID   string   `bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"` | 
					
						
							|  |  |  | 	Emoji     *Emoji   `bun:"rel:belongs-to"` | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | // Field represents a key value field on an account, for things like pronouns, website, etc. | 
					
						
							|  |  |  | // VerifiedAt is optional, to be used only if Value is a URL to a webpage that contains the | 
					
						
							|  |  |  | // username of the user. | 
					
						
							|  |  |  | type Field struct { | 
					
						
							| 
									
										
										
										
											2023-08-06 12:22:40 +02:00
										 |  |  | 	Name       string    // Name of this field. | 
					
						
							|  |  |  | 	Value      string    // Value of this field. | 
					
						
							|  |  |  | 	VerifiedAt time.Time `bun:",nullzero"` // This field was verified at (optional). | 
					
						
							| 
									
										
										
										
											2021-03-07 13:05:33 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-09-01 18:29:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-06 14:39:40 +02:00
										 |  |  | // AccountActorType is the ActivityStreams type of an actor. | 
					
						
							|  |  |  | type AccountActorType enumType | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	AccountActorTypeUnknown      AccountActorType = 0 | 
					
						
							|  |  |  | 	AccountActorTypeApplication  AccountActorType = 1 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-application | 
					
						
							|  |  |  | 	AccountActorTypeGroup        AccountActorType = 2 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-group | 
					
						
							|  |  |  | 	AccountActorTypeOrganization AccountActorType = 3 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-organization | 
					
						
							|  |  |  | 	AccountActorTypePerson       AccountActorType = 4 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-person | 
					
						
							|  |  |  | 	AccountActorTypeService      AccountActorType = 5 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-service | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // String returns a stringified form of AccountActorType. | 
					
						
							|  |  |  | func (t AccountActorType) String() string { | 
					
						
							|  |  |  | 	switch t { | 
					
						
							|  |  |  | 	case AccountActorTypeApplication: | 
					
						
							|  |  |  | 		return "Application" | 
					
						
							|  |  |  | 	case AccountActorTypeGroup: | 
					
						
							|  |  |  | 		return "Group" | 
					
						
							|  |  |  | 	case AccountActorTypeOrganization: | 
					
						
							|  |  |  | 		return "Organization" | 
					
						
							|  |  |  | 	case AccountActorTypePerson: | 
					
						
							|  |  |  | 		return "Person" | 
					
						
							|  |  |  | 	case AccountActorTypeService: | 
					
						
							|  |  |  | 		return "Service" | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		panic("invalid notification type") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ParseAccountActorType returns an | 
					
						
							|  |  |  | // actor type from the given value. | 
					
						
							|  |  |  | func ParseAccountActorType(in string) AccountActorType { | 
					
						
							|  |  |  | 	switch strings.ToLower(in) { | 
					
						
							|  |  |  | 	case "application": | 
					
						
							|  |  |  | 		return AccountActorTypeApplication | 
					
						
							|  |  |  | 	case "group": | 
					
						
							|  |  |  | 		return AccountActorTypeGroup | 
					
						
							|  |  |  | 	case "organization": | 
					
						
							|  |  |  | 		return AccountActorTypeOrganization | 
					
						
							|  |  |  | 	case "person": | 
					
						
							|  |  |  | 		return AccountActorTypePerson | 
					
						
							|  |  |  | 	case "service": | 
					
						
							|  |  |  | 		return AccountActorTypeService | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return AccountActorTypeUnknown | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t AccountActorType) IsBot() bool { | 
					
						
							|  |  |  | 	return t == AccountActorTypeApplication || t == AccountActorTypeService | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-01 18:29:25 +02:00
										 |  |  | // Relationship describes a requester's relationship with another account. | 
					
						
							|  |  |  | type Relationship struct { | 
					
						
							|  |  |  | 	ID                  string // The account id. | 
					
						
							|  |  |  | 	Following           bool   // Are you following this user? | 
					
						
							|  |  |  | 	ShowingReblogs      bool   // Are you receiving this user's boosts in your home timeline? | 
					
						
							|  |  |  | 	Notifying           bool   // Have you enabled notifications for this user? | 
					
						
							|  |  |  | 	FollowedBy          bool   // Are you followed by this user? | 
					
						
							|  |  |  | 	Blocking            bool   // Are you blocking this user? | 
					
						
							|  |  |  | 	BlockedBy           bool   // Is this user blocking you? | 
					
						
							|  |  |  | 	Muting              bool   // Are you muting this user? | 
					
						
							|  |  |  | 	MutingNotifications bool   // Are you muting notifications from this user? | 
					
						
							| 
									
										
										
										
											2024-02-20 18:50:54 +01:00
										 |  |  | 	Requested           bool   // Do you have a pending follow request targeting this user? | 
					
						
							|  |  |  | 	RequestedBy         bool   // Does the user have a pending follow request targeting you? | 
					
						
							| 
									
										
										
										
											2021-09-01 18:29:25 +02:00
										 |  |  | 	DomainBlocking      bool   // Are you blocking this user's domain? | 
					
						
							|  |  |  | 	Endorsed            bool   // Are you featuring this user on your profile? | 
					
						
							|  |  |  | 	Note                string // Your note on this account. | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-03-25 18:32:24 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Theme represents a user-selected | 
					
						
							|  |  |  | // CSS theme for an account. | 
					
						
							|  |  |  | type Theme struct { | 
					
						
							|  |  |  | 	// User-facing title of this theme. | 
					
						
							|  |  |  | 	Title string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// User-facing description of this theme. | 
					
						
							|  |  |  | 	Description string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// FileName of this theme in the themes | 
					
						
							|  |  |  | 	// directory (eg., `light-blurple.css`). | 
					
						
							|  |  |  | 	FileName string | 
					
						
							|  |  |  | } |