mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-12-05 01:52:46 +00:00
[chore/refactor] start moving account preferences to its own separate struct
This commit is contained in:
parent
9770d54237
commit
8a36e11957
|
@ -398,9 +398,6 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
|
||||||
suite.EqualValues(requestingAccount.Reason, dbUpdatedAccount.Reason)
|
suite.EqualValues(requestingAccount.Reason, dbUpdatedAccount.Reason)
|
||||||
suite.EqualValues(requestingAccount.Locked, dbUpdatedAccount.Locked)
|
suite.EqualValues(requestingAccount.Locked, dbUpdatedAccount.Locked)
|
||||||
suite.EqualValues(requestingAccount.Discoverable, dbUpdatedAccount.Discoverable)
|
suite.EqualValues(requestingAccount.Discoverable, dbUpdatedAccount.Discoverable)
|
||||||
suite.EqualValues(requestingAccount.Privacy, dbUpdatedAccount.Privacy)
|
|
||||||
suite.EqualValues(requestingAccount.Sensitive, dbUpdatedAccount.Sensitive)
|
|
||||||
suite.EqualValues(requestingAccount.Language, dbUpdatedAccount.Language)
|
|
||||||
suite.EqualValues(requestingAccount.URI, dbUpdatedAccount.URI)
|
suite.EqualValues(requestingAccount.URI, dbUpdatedAccount.URI)
|
||||||
suite.EqualValues(requestingAccount.URL, dbUpdatedAccount.URL)
|
suite.EqualValues(requestingAccount.URL, dbUpdatedAccount.URL)
|
||||||
suite.EqualValues(requestingAccount.InboxURI, dbUpdatedAccount.InboxURI)
|
suite.EqualValues(requestingAccount.InboxURI, dbUpdatedAccount.InboxURI)
|
||||||
|
@ -414,7 +411,6 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
|
||||||
suite.EqualValues(requestingAccount.SensitizedAt, dbUpdatedAccount.SensitizedAt)
|
suite.EqualValues(requestingAccount.SensitizedAt, dbUpdatedAccount.SensitizedAt)
|
||||||
suite.EqualValues(requestingAccount.SilencedAt, dbUpdatedAccount.SilencedAt)
|
suite.EqualValues(requestingAccount.SilencedAt, dbUpdatedAccount.SilencedAt)
|
||||||
suite.EqualValues(requestingAccount.SuspendedAt, dbUpdatedAccount.SuspendedAt)
|
suite.EqualValues(requestingAccount.SuspendedAt, dbUpdatedAccount.SuspendedAt)
|
||||||
suite.EqualValues(requestingAccount.HideCollections, dbUpdatedAccount.HideCollections)
|
|
||||||
suite.EqualValues(requestingAccount.SuspensionOrigin, dbUpdatedAccount.SuspensionOrigin)
|
suite.EqualValues(requestingAccount.SuspensionOrigin, dbUpdatedAccount.SuspensionOrigin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,7 +462,6 @@ func (suite *InboxPostTestSuite) TestPostDelete() {
|
||||||
suite.Empty(dbAccount.HeaderRemoteURL)
|
suite.Empty(dbAccount.HeaderRemoteURL)
|
||||||
suite.Empty(dbAccount.Reason)
|
suite.Empty(dbAccount.Reason)
|
||||||
suite.Empty(dbAccount.Fields)
|
suite.Empty(dbAccount.Fields)
|
||||||
suite.True(*dbAccount.HideCollections)
|
|
||||||
suite.False(*dbAccount.Discoverable)
|
suite.False(*dbAccount.Discoverable)
|
||||||
suite.WithinDuration(time.Now(), dbAccount.SuspendedAt, 30*time.Second)
|
suite.WithinDuration(time.Now(), dbAccount.SuspendedAt, 30*time.Second)
|
||||||
suite.Equal(dbAccount.ID, dbAccount.SuspensionOrigin)
|
suite.Equal(dbAccount.ID, dbAccount.SuspensionOrigin)
|
||||||
|
|
|
@ -477,7 +477,7 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceBadContentTypeFormDa
|
||||||
if err != nil {
|
if err != nil {
|
||||||
suite.FailNow(err.Error())
|
suite.FailNow(err.Error())
|
||||||
}
|
}
|
||||||
suite.Equal(data["source[status_content_type]"], dbAccount.StatusContentType)
|
suite.Equal(data["source[status_content_type]"], dbAccount.Preferences.StatusContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateStatusContentTypeBad() {
|
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateStatusContentTypeBad() {
|
||||||
|
|
|
@ -81,7 +81,7 @@ func (suite *AccountVerifyTestSuite) TestAccountVerifyGet() {
|
||||||
suite.Equal(2, apimodelAccount.FollowingCount)
|
suite.Equal(2, apimodelAccount.FollowingCount)
|
||||||
suite.Equal(5, apimodelAccount.StatusesCount)
|
suite.Equal(5, apimodelAccount.StatusesCount)
|
||||||
suite.EqualValues(gtsmodel.VisibilityPublic, apimodelAccount.Source.Privacy)
|
suite.EqualValues(gtsmodel.VisibilityPublic, apimodelAccount.Source.Privacy)
|
||||||
suite.Equal(testAccount.Language, apimodelAccount.Source.Language)
|
suite.Equal(testAccount.Preferences.StatusLanguage, apimodelAccount.Source.Language)
|
||||||
suite.Equal(testAccount.NoteRaw, apimodelAccount.Source.Note)
|
suite.Equal(testAccount.NoteRaw, apimodelAccount.Source.Note)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
internal/cache/cache.go
vendored
1
internal/cache/cache.go
vendored
|
@ -229,6 +229,7 @@ func (c *Caches) setuphooks() {
|
||||||
func (c *Caches) Sweep(threshold float64) {
|
func (c *Caches) Sweep(threshold float64) {
|
||||||
c.GTS.Account().Trim(threshold)
|
c.GTS.Account().Trim(threshold)
|
||||||
c.GTS.AccountNote().Trim(threshold)
|
c.GTS.AccountNote().Trim(threshold)
|
||||||
|
c.GTS.AccountPreferences().Trim(threshold)
|
||||||
c.GTS.Block().Trim(threshold)
|
c.GTS.Block().Trim(threshold)
|
||||||
c.GTS.BlockIDs().Trim(threshold)
|
c.GTS.BlockIDs().Trim(threshold)
|
||||||
c.GTS.Emoji().Trim(threshold)
|
c.GTS.Emoji().Trim(threshold)
|
||||||
|
|
81
internal/cache/gts.go
vendored
81
internal/cache/gts.go
vendored
|
@ -30,33 +30,34 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
type GTSCaches struct {
|
type GTSCaches struct {
|
||||||
account *result.Cache[*gtsmodel.Account]
|
account *result.Cache[*gtsmodel.Account]
|
||||||
accountNote *result.Cache[*gtsmodel.AccountNote]
|
accountNote *result.Cache[*gtsmodel.AccountNote]
|
||||||
block *result.Cache[*gtsmodel.Block]
|
accountPreferences *result.Cache[*gtsmodel.AccountPreferences]
|
||||||
blockIDs *SliceCache[string]
|
block *result.Cache[*gtsmodel.Block]
|
||||||
boostOfIDs *SliceCache[string]
|
blockIDs *SliceCache[string]
|
||||||
domainBlock *domain.BlockCache
|
boostOfIDs *SliceCache[string]
|
||||||
emoji *result.Cache[*gtsmodel.Emoji]
|
domainBlock *domain.BlockCache
|
||||||
emojiCategory *result.Cache[*gtsmodel.EmojiCategory]
|
emoji *result.Cache[*gtsmodel.Emoji]
|
||||||
follow *result.Cache[*gtsmodel.Follow]
|
emojiCategory *result.Cache[*gtsmodel.EmojiCategory]
|
||||||
followIDs *SliceCache[string]
|
follow *result.Cache[*gtsmodel.Follow]
|
||||||
followRequest *result.Cache[*gtsmodel.FollowRequest]
|
followIDs *SliceCache[string]
|
||||||
followRequestIDs *SliceCache[string]
|
followRequest *result.Cache[*gtsmodel.FollowRequest]
|
||||||
instance *result.Cache[*gtsmodel.Instance]
|
followRequestIDs *SliceCache[string]
|
||||||
inReplyToIDs *SliceCache[string]
|
instance *result.Cache[*gtsmodel.Instance]
|
||||||
list *result.Cache[*gtsmodel.List]
|
inReplyToIDs *SliceCache[string]
|
||||||
listEntry *result.Cache[*gtsmodel.ListEntry]
|
list *result.Cache[*gtsmodel.List]
|
||||||
marker *result.Cache[*gtsmodel.Marker]
|
listEntry *result.Cache[*gtsmodel.ListEntry]
|
||||||
media *result.Cache[*gtsmodel.MediaAttachment]
|
marker *result.Cache[*gtsmodel.Marker]
|
||||||
mention *result.Cache[*gtsmodel.Mention]
|
media *result.Cache[*gtsmodel.MediaAttachment]
|
||||||
notification *result.Cache[*gtsmodel.Notification]
|
mention *result.Cache[*gtsmodel.Mention]
|
||||||
report *result.Cache[*gtsmodel.Report]
|
notification *result.Cache[*gtsmodel.Notification]
|
||||||
status *result.Cache[*gtsmodel.Status]
|
report *result.Cache[*gtsmodel.Report]
|
||||||
statusFave *result.Cache[*gtsmodel.StatusFave]
|
status *result.Cache[*gtsmodel.Status]
|
||||||
statusFaveIDs *SliceCache[string]
|
statusFave *result.Cache[*gtsmodel.StatusFave]
|
||||||
tag *result.Cache[*gtsmodel.Tag]
|
statusFaveIDs *SliceCache[string]
|
||||||
tombstone *result.Cache[*gtsmodel.Tombstone]
|
tag *result.Cache[*gtsmodel.Tag]
|
||||||
user *result.Cache[*gtsmodel.User]
|
tombstone *result.Cache[*gtsmodel.Tombstone]
|
||||||
|
user *result.Cache[*gtsmodel.User]
|
||||||
|
|
||||||
// TODO: move out of GTS caches since unrelated to DB.
|
// TODO: move out of GTS caches since unrelated to DB.
|
||||||
webfinger *ttl.Cache[string, string] // TTL=24hr, sweep=5min
|
webfinger *ttl.Cache[string, string] // TTL=24hr, sweep=5min
|
||||||
|
@ -67,6 +68,7 @@ type GTSCaches struct {
|
||||||
func (c *GTSCaches) Init() {
|
func (c *GTSCaches) Init() {
|
||||||
c.initAccount()
|
c.initAccount()
|
||||||
c.initAccountNote()
|
c.initAccountNote()
|
||||||
|
c.initAccountPreferences()
|
||||||
c.initBlock()
|
c.initBlock()
|
||||||
c.initBlockIDs()
|
c.initBlockIDs()
|
||||||
c.initBoostOfIDs()
|
c.initBoostOfIDs()
|
||||||
|
@ -117,6 +119,11 @@ func (c *GTSCaches) AccountNote() *result.Cache[*gtsmodel.AccountNote] {
|
||||||
return c.accountNote
|
return c.accountNote
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AccountPreferences provides access to the gtsmodel AccountPreferences database cache.
|
||||||
|
func (c *GTSCaches) AccountPreferences() *result.Cache[*gtsmodel.AccountPreferences] {
|
||||||
|
return c.accountPreferences
|
||||||
|
}
|
||||||
|
|
||||||
// Block provides access to the gtsmodel Block (account) database cache.
|
// Block provides access to the gtsmodel Block (account) database cache.
|
||||||
func (c *GTSCaches) Block() *result.Cache[*gtsmodel.Block] {
|
func (c *GTSCaches) Block() *result.Cache[*gtsmodel.Block] {
|
||||||
return c.block
|
return c.block
|
||||||
|
@ -303,6 +310,26 @@ func (c *GTSCaches) initAccountNote() {
|
||||||
c.accountNote.IgnoreErrors(ignoreErrors)
|
c.accountNote.IgnoreErrors(ignoreErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *GTSCaches) initAccountPreferences() {
|
||||||
|
// Calculate maximum cache size.
|
||||||
|
cap := calculateResultCacheMax(
|
||||||
|
sizeofAccountPreferences(), // model in-mem size.
|
||||||
|
config.GetCacheAccountPreferencesMemRatio(),
|
||||||
|
)
|
||||||
|
log.Infof(nil, "AccountPreferences cache size = %d", cap)
|
||||||
|
|
||||||
|
c.accountPreferences = result.New([]result.Lookup{
|
||||||
|
{Name: "ID"},
|
||||||
|
{Name: "AccountID"},
|
||||||
|
}, func(p1 *gtsmodel.AccountPreferences) *gtsmodel.AccountPreferences {
|
||||||
|
p2 := new(gtsmodel.AccountPreferences)
|
||||||
|
*p2 = *p1
|
||||||
|
return p2
|
||||||
|
}, cap)
|
||||||
|
|
||||||
|
c.accountPreferences.IgnoreErrors(ignoreErrors)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *GTSCaches) initBlock() {
|
func (c *GTSCaches) initBlock() {
|
||||||
// Calculate maximum cache size.
|
// Calculate maximum cache size.
|
||||||
cap := calculateResultCacheMax(
|
cap := calculateResultCacheMax(
|
||||||
|
|
19
internal/cache/size.go
vendored
19
internal/cache/size.go
vendored
|
@ -155,6 +155,7 @@ func totalOfRatios() float64 {
|
||||||
return 0 +
|
return 0 +
|
||||||
config.GetCacheAccountMemRatio() +
|
config.GetCacheAccountMemRatio() +
|
||||||
config.GetCacheAccountNoteMemRatio() +
|
config.GetCacheAccountNoteMemRatio() +
|
||||||
|
config.GetCacheAccountPreferencesMemRatio() +
|
||||||
config.GetCacheBlockMemRatio() +
|
config.GetCacheBlockMemRatio() +
|
||||||
config.GetCacheBlockIDsMemRatio() +
|
config.GetCacheBlockIDsMemRatio() +
|
||||||
config.GetCacheBoostOfIDsMemRatio() +
|
config.GetCacheBoostOfIDsMemRatio() +
|
||||||
|
@ -199,9 +200,6 @@ func sizeofAccount() uintptr {
|
||||||
Bot: func() *bool { ok := true; return &ok }(),
|
Bot: func() *bool { ok := true; return &ok }(),
|
||||||
Locked: func() *bool { ok := true; return &ok }(),
|
Locked: func() *bool { ok := true; return &ok }(),
|
||||||
Discoverable: func() *bool { ok := false; return &ok }(),
|
Discoverable: func() *bool { ok := false; return &ok }(),
|
||||||
Privacy: gtsmodel.VisibilityFollowersOnly,
|
|
||||||
Sensitive: func() *bool { ok := true; return &ok }(),
|
|
||||||
Language: "fr",
|
|
||||||
URI: exampleURI,
|
URI: exampleURI,
|
||||||
URL: exampleURI,
|
URL: exampleURI,
|
||||||
InboxURI: exampleURI,
|
InboxURI: exampleURI,
|
||||||
|
@ -216,9 +214,7 @@ func sizeofAccount() uintptr {
|
||||||
SensitizedAt: time.Time{},
|
SensitizedAt: time.Time{},
|
||||||
SilencedAt: time.Now(),
|
SilencedAt: time.Now(),
|
||||||
SuspendedAt: time.Now(),
|
SuspendedAt: time.Now(),
|
||||||
HideCollections: func() *bool { ok := true; return &ok }(),
|
|
||||||
SuspensionOrigin: "",
|
SuspensionOrigin: "",
|
||||||
EnableRSS: func() *bool { ok := true; return &ok }(),
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,6 +227,19 @@ func sizeofAccountNote() uintptr {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sizeofAccountPreferences() uintptr {
|
||||||
|
return uintptr(size.Of(>smodel.AccountPreferences{
|
||||||
|
ID: exampleID,
|
||||||
|
AccountID: exampleID,
|
||||||
|
StatusLanguage: "fr",
|
||||||
|
StatusPrivacy: gtsmodel.VisibilityFollowersOnly,
|
||||||
|
StatusSensitive: func() *bool { ok := true; return &ok }(),
|
||||||
|
StatusContentType: "text/plain",
|
||||||
|
HideCollections: func() *bool { ok := true; return &ok }(),
|
||||||
|
EnableRSS: func() *bool { ok := true; return &ok }(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
func sizeofBlock() uintptr {
|
func sizeofBlock() uintptr {
|
||||||
return uintptr(size.Of(>smodel.Block{
|
return uintptr(size.Of(>smodel.Block{
|
||||||
ID: exampleID,
|
ID: exampleID,
|
||||||
|
|
|
@ -175,35 +175,36 @@ type HTTPClientConfiguration struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CacheConfiguration struct {
|
type CacheConfiguration struct {
|
||||||
MemoryTarget bytesize.Size `name:"memory-target"`
|
MemoryTarget bytesize.Size `name:"memory-target"`
|
||||||
AccountMemRatio float64 `name:"account-mem-ratio"`
|
AccountMemRatio float64 `name:"account-mem-ratio"`
|
||||||
AccountNoteMemRatio float64 `name:"account-note-mem-ratio"`
|
AccountNoteMemRatio float64 `name:"account-note-mem-ratio"`
|
||||||
BlockMemRatio float64 `name:"block-mem-ratio"`
|
AccountPreferencesMemRatio float64 `name:"account-preferences-mem-ratio"`
|
||||||
BlockIDsMemRatio float64 `name:"block-mem-ratio"`
|
BlockMemRatio float64 `name:"block-mem-ratio"`
|
||||||
BoostOfIDsMemRatio float64 `name:"boost-of-ids-mem-ratio"`
|
BlockIDsMemRatio float64 `name:"block-mem-ratio"`
|
||||||
EmojiMemRatio float64 `name:"emoji-mem-ratio"`
|
BoostOfIDsMemRatio float64 `name:"boost-of-ids-mem-ratio"`
|
||||||
EmojiCategoryMemRatio float64 `name:"emoji-category-mem-ratio"`
|
EmojiMemRatio float64 `name:"emoji-mem-ratio"`
|
||||||
FollowMemRatio float64 `name:"follow-mem-ratio"`
|
EmojiCategoryMemRatio float64 `name:"emoji-category-mem-ratio"`
|
||||||
FollowIDsMemRatio float64 `name:"follow-ids-mem-ratio"`
|
FollowMemRatio float64 `name:"follow-mem-ratio"`
|
||||||
FollowRequestMemRatio float64 `name:"follow-request-mem-ratio"`
|
FollowIDsMemRatio float64 `name:"follow-ids-mem-ratio"`
|
||||||
FollowRequestIDsMemRatio float64 `name:"follow-request-ids-mem-ratio"`
|
FollowRequestMemRatio float64 `name:"follow-request-mem-ratio"`
|
||||||
InReplyToIDsMemRatio float64 `name:"in-reply-to-ids-mem-ratio"`
|
FollowRequestIDsMemRatio float64 `name:"follow-request-ids-mem-ratio"`
|
||||||
InstanceMemRatio float64 `name:"instance-mem-ratio"`
|
InReplyToIDsMemRatio float64 `name:"in-reply-to-ids-mem-ratio"`
|
||||||
ListMemRatio float64 `name:"list-mem-ratio"`
|
InstanceMemRatio float64 `name:"instance-mem-ratio"`
|
||||||
ListEntryMemRatio float64 `name:"list-entry-mem-ratio"`
|
ListMemRatio float64 `name:"list-mem-ratio"`
|
||||||
MarkerMemRatio float64 `name:"marker-mem-ratio"`
|
ListEntryMemRatio float64 `name:"list-entry-mem-ratio"`
|
||||||
MediaMemRatio float64 `name:"media-mem-ratio"`
|
MarkerMemRatio float64 `name:"marker-mem-ratio"`
|
||||||
MentionMemRatio float64 `name:"mention-mem-ratio"`
|
MediaMemRatio float64 `name:"media-mem-ratio"`
|
||||||
NotificationMemRatio float64 `name:"notification-mem-ratio"`
|
MentionMemRatio float64 `name:"mention-mem-ratio"`
|
||||||
ReportMemRatio float64 `name:"report-mem-ratio"`
|
NotificationMemRatio float64 `name:"notification-mem-ratio"`
|
||||||
StatusMemRatio float64 `name:"status-mem-ratio"`
|
ReportMemRatio float64 `name:"report-mem-ratio"`
|
||||||
StatusFaveMemRatio float64 `name:"status-fave-mem-ratio"`
|
StatusMemRatio float64 `name:"status-mem-ratio"`
|
||||||
StatusFaveIDsMemRatio float64 `name:"status-fave-ids-mem-ratio"`
|
StatusFaveMemRatio float64 `name:"status-fave-mem-ratio"`
|
||||||
TagMemRatio float64 `name:"tag-mem-ratio"`
|
StatusFaveIDsMemRatio float64 `name:"status-fave-ids-mem-ratio"`
|
||||||
TombstoneMemRatio float64 `name:"tombstone-mem-ratio"`
|
TagMemRatio float64 `name:"tag-mem-ratio"`
|
||||||
UserMemRatio float64 `name:"user-mem-ratio"`
|
TombstoneMemRatio float64 `name:"tombstone-mem-ratio"`
|
||||||
WebfingerMemRatio float64 `name:"webfinger-mem-ratio"`
|
UserMemRatio float64 `name:"user-mem-ratio"`
|
||||||
VisibilityMemRatio float64 `name:"visibility-mem-ratio"`
|
WebfingerMemRatio float64 `name:"webfinger-mem-ratio"`
|
||||||
|
VisibilityMemRatio float64 `name:"visibility-mem-ratio"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalMap will marshal current Configuration into a map structure (useful for JSON/TOML/YAML).
|
// MarshalMap will marshal current Configuration into a map structure (useful for JSON/TOML/YAML).
|
||||||
|
|
|
@ -145,34 +145,35 @@
|
||||||
// when TODO items in the size.go source
|
// when TODO items in the size.go source
|
||||||
// file have been addressed, these should
|
// file have been addressed, these should
|
||||||
// be able to make some more sense :D
|
// be able to make some more sense :D
|
||||||
AccountMemRatio: 18,
|
AccountMemRatio: 18,
|
||||||
AccountNoteMemRatio: 0.1,
|
AccountNoteMemRatio: 0.1,
|
||||||
BlockMemRatio: 3,
|
AccountPreferencesMemRatio: 2,
|
||||||
BlockIDsMemRatio: 3,
|
BlockMemRatio: 3,
|
||||||
BoostOfIDsMemRatio: 3,
|
BlockIDsMemRatio: 3,
|
||||||
EmojiMemRatio: 3,
|
BoostOfIDsMemRatio: 3,
|
||||||
EmojiCategoryMemRatio: 0.1,
|
EmojiMemRatio: 3,
|
||||||
FollowMemRatio: 4,
|
EmojiCategoryMemRatio: 0.1,
|
||||||
FollowIDsMemRatio: 4,
|
FollowMemRatio: 4,
|
||||||
FollowRequestMemRatio: 2,
|
FollowIDsMemRatio: 4,
|
||||||
FollowRequestIDsMemRatio: 2,
|
FollowRequestMemRatio: 2,
|
||||||
InReplyToIDsMemRatio: 3,
|
FollowRequestIDsMemRatio: 2,
|
||||||
InstanceMemRatio: 1,
|
InReplyToIDsMemRatio: 3,
|
||||||
ListMemRatio: 3,
|
InstanceMemRatio: 1,
|
||||||
ListEntryMemRatio: 3,
|
ListMemRatio: 3,
|
||||||
MarkerMemRatio: 0.5,
|
ListEntryMemRatio: 3,
|
||||||
MediaMemRatio: 4,
|
MarkerMemRatio: 0.5,
|
||||||
MentionMemRatio: 5,
|
MediaMemRatio: 4,
|
||||||
NotificationMemRatio: 5,
|
MentionMemRatio: 5,
|
||||||
ReportMemRatio: 1,
|
NotificationMemRatio: 5,
|
||||||
StatusMemRatio: 18,
|
ReportMemRatio: 1,
|
||||||
StatusFaveMemRatio: 5,
|
StatusMemRatio: 18,
|
||||||
StatusFaveIDsMemRatio: 3,
|
StatusFaveMemRatio: 5,
|
||||||
TagMemRatio: 3,
|
StatusFaveIDsMemRatio: 3,
|
||||||
TombstoneMemRatio: 2,
|
TagMemRatio: 3,
|
||||||
UserMemRatio: 0.1,
|
TombstoneMemRatio: 2,
|
||||||
WebfingerMemRatio: 0.1,
|
UserMemRatio: 0.1,
|
||||||
VisibilityMemRatio: 2,
|
WebfingerMemRatio: 0.1,
|
||||||
|
VisibilityMemRatio: 2,
|
||||||
},
|
},
|
||||||
|
|
||||||
HTTPClient: HTTPClientConfiguration{
|
HTTPClient: HTTPClientConfiguration{
|
||||||
|
|
|
@ -2499,6 +2499,31 @@ func GetCacheAccountNoteMemRatio() float64 { return global.GetCacheAccountNoteMe
|
||||||
// SetCacheAccountNoteMemRatio safely sets the value for global configuration 'Cache.AccountNoteMemRatio' field
|
// SetCacheAccountNoteMemRatio safely sets the value for global configuration 'Cache.AccountNoteMemRatio' field
|
||||||
func SetCacheAccountNoteMemRatio(v float64) { global.SetCacheAccountNoteMemRatio(v) }
|
func SetCacheAccountNoteMemRatio(v float64) { global.SetCacheAccountNoteMemRatio(v) }
|
||||||
|
|
||||||
|
// GetCacheAccountPreferencesMemRatio safely fetches the Configuration value for state's 'Cache.AccountPreferencesMemRatio' field
|
||||||
|
func (st *ConfigState) GetCacheAccountPreferencesMemRatio() (v float64) {
|
||||||
|
st.mutex.RLock()
|
||||||
|
v = st.config.Cache.AccountPreferencesMemRatio
|
||||||
|
st.mutex.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCacheAccountPreferencesMemRatio safely sets the Configuration value for state's 'Cache.AccountPreferencesMemRatio' field
|
||||||
|
func (st *ConfigState) SetCacheAccountPreferencesMemRatio(v float64) {
|
||||||
|
st.mutex.Lock()
|
||||||
|
defer st.mutex.Unlock()
|
||||||
|
st.config.Cache.AccountPreferencesMemRatio = v
|
||||||
|
st.reloadToViper()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheAccountPreferencesMemRatioFlag returns the flag name for the 'Cache.AccountPreferencesMemRatio' field
|
||||||
|
func CacheAccountPreferencesMemRatioFlag() string { return "cache-account-preferences-mem-ratio" }
|
||||||
|
|
||||||
|
// GetCacheAccountPreferencesMemRatio safely fetches the value for global configuration 'Cache.AccountPreferencesMemRatio' field
|
||||||
|
func GetCacheAccountPreferencesMemRatio() float64 { return global.GetCacheAccountPreferencesMemRatio() }
|
||||||
|
|
||||||
|
// SetCacheAccountPreferencesMemRatio safely sets the value for global configuration 'Cache.AccountPreferencesMemRatio' field
|
||||||
|
func SetCacheAccountPreferencesMemRatio(v float64) { global.SetCacheAccountPreferencesMemRatio(v) }
|
||||||
|
|
||||||
// GetCacheBlockMemRatio safely fetches the Configuration value for state's 'Cache.BlockMemRatio' field
|
// GetCacheBlockMemRatio safely fetches the Configuration value for state's 'Cache.BlockMemRatio' field
|
||||||
func (st *ConfigState) GetCacheBlockMemRatio() (v float64) {
|
func (st *ConfigState) GetCacheBlockMemRatio() (v float64) {
|
||||||
st.mutex.RLock()
|
st.mutex.RLock()
|
||||||
|
|
|
@ -70,6 +70,16 @@ type Account interface {
|
||||||
// GetAccountCustomCSSByUsername returns the custom css of an account on this instance with the given username.
|
// GetAccountCustomCSSByUsername returns the custom css of an account on this instance with the given username.
|
||||||
GetAccountCustomCSSByUsername(ctx context.Context, username string) (string, error)
|
GetAccountCustomCSSByUsername(ctx context.Context, username string) (string, error)
|
||||||
|
|
||||||
|
// GetAccountPreferencesByAccountID returns preferences the the given *LOCAL* account.
|
||||||
|
// Will return an error for non-local accounts, or accounts with no preferences stored.
|
||||||
|
GetAccountPreferencesByAccountID(ctx context.Context, accountID string) (*gtsmodel.AccountPreferences, error)
|
||||||
|
|
||||||
|
// PutAccountPreferences inserts the given accountPreferences.
|
||||||
|
PutAccountPreferences(ctx context.Context, accountPreferences *gtsmodel.AccountPreferences) error
|
||||||
|
|
||||||
|
// UpdateAccountPreferences updates the given accountPreferences by ID.
|
||||||
|
UpdateAccountPreferences(ctx context.Context, accountPreferences *gtsmodel.AccountPreferences, columns ...string) error
|
||||||
|
|
||||||
// GetAccountFaves fetches faves/likes created by the target accountID.
|
// GetAccountFaves fetches faves/likes created by the target accountID.
|
||||||
GetAccountFaves(ctx context.Context, accountID string) ([]*gtsmodel.StatusFave, error)
|
GetAccountFaves(ctx context.Context, accountID string) ([]*gtsmodel.StatusFave, error)
|
||||||
|
|
||||||
|
|
|
@ -290,6 +290,16 @@ func (a *accountDB) PopulateAccount(ctx context.Context, account *gtsmodel.Accou
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if account.PreferencesID != "" && account.Preferences == nil {
|
||||||
|
account.Preferences, err = a.getAccountPreferencesByID(
|
||||||
|
ctx, // these are already barebones
|
||||||
|
account.PreferencesID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
errs.Appendf("error populating account preferences: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return errs.Combine()
|
return errs.Combine()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -456,12 +466,93 @@ func (a *accountDB) SetAccountHeaderOrAvatar(ctx context.Context, mediaAttachmen
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *accountDB) GetAccountCustomCSSByUsername(ctx context.Context, username string) (string, error) {
|
func (a *accountDB) GetAccountCustomCSSByUsername(ctx context.Context, username string) (string, error) {
|
||||||
account, err := a.GetAccountByUsernameDomain(ctx, username, "")
|
account, err := a.GetAccountByUsernameDomain(
|
||||||
|
gtscontext.SetBarebones(ctx),
|
||||||
|
username,
|
||||||
|
"",
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return account.CustomCSS, nil
|
if account.PreferencesID == "" {
|
||||||
|
return "", gtserror.Newf("no preferences stored for account %s", account.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
accountPrefs, err := a.state.DB.GetAccountPreferencesByAccountID(ctx, account.ID)
|
||||||
|
if err != nil {
|
||||||
|
return "", gtserror.Newf("error getting preferences for account %s: %w", account.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountPrefs.CustomCSS, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *accountDB) GetAccountPreferencesByAccountID(ctx context.Context, accountID string) (*gtsmodel.AccountPreferences, error) {
|
||||||
|
accountPrefs, err := a.state.Caches.GTS.AccountPreferences().Load("AccountID", func() (*gtsmodel.AccountPreferences, error) {
|
||||||
|
var accountPrefs gtsmodel.AccountPreferences
|
||||||
|
|
||||||
|
// Not cached! Perform database query
|
||||||
|
if err := a.db.NewSelect().
|
||||||
|
Model(&accountPrefs).
|
||||||
|
Where("? = ?", bun.Ident("account_preferences.account_id"), accountID).
|
||||||
|
Scan(ctx); err != nil {
|
||||||
|
return nil, a.db.ProcessError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &accountPrefs, nil
|
||||||
|
}, accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountPrefs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *accountDB) getAccountPreferencesByID(ctx context.Context, id string) (*gtsmodel.AccountPreferences, error) {
|
||||||
|
accountPrefs, err := a.state.Caches.GTS.AccountPreferences().Load("ID", func() (*gtsmodel.AccountPreferences, error) {
|
||||||
|
var accountPrefs gtsmodel.AccountPreferences
|
||||||
|
|
||||||
|
// Not cached! Perform database query
|
||||||
|
if err := a.db.NewSelect().
|
||||||
|
Model(&accountPrefs).
|
||||||
|
Where("? = ?", bun.Ident("account_preferences.id"), id).
|
||||||
|
Scan(ctx); err != nil {
|
||||||
|
return nil, a.db.ProcessError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &accountPrefs, nil
|
||||||
|
}, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountPrefs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *accountDB) PutAccountPreferences(ctx context.Context, accountPrefs *gtsmodel.AccountPreferences) error {
|
||||||
|
return a.state.Caches.GTS.AccountPreferences().Store(accountPrefs, func() error {
|
||||||
|
// insert the account preferences
|
||||||
|
_, err := a.db.NewInsert().Model(accountPrefs).Exec(ctx)
|
||||||
|
return a.db.ProcessError(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *accountDB) UpdateAccountPreferences(ctx context.Context, accountPrefs *gtsmodel.AccountPreferences, columns ...string) error {
|
||||||
|
accountPrefs.UpdatedAt = time.Now()
|
||||||
|
if len(columns) > 0 {
|
||||||
|
// If we're updating by column, ensure "updated_at" is included.
|
||||||
|
columns = append(columns, "updated_at")
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.state.Caches.GTS.AccountPreferences().Store(accountPrefs, func() error {
|
||||||
|
_, err := a.db.
|
||||||
|
NewUpdate().
|
||||||
|
Model(accountPrefs).
|
||||||
|
Where("? = ?", bun.Ident("account_preferences.id"), accountPrefs.ID).
|
||||||
|
Column(columns...).
|
||||||
|
Exec(ctx)
|
||||||
|
return a.db.ProcessError(err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *accountDB) GetAccountsUsingEmoji(ctx context.Context, emojiID string) ([]*gtsmodel.Account, error) {
|
func (a *accountDB) GetAccountsUsingEmoji(ctx context.Context, emojiID string) ([]*gtsmodel.Account, error) {
|
||||||
|
|
|
@ -333,14 +333,11 @@ func (suite *AccountTestSuite) TestInsertAccountWithDefaults() {
|
||||||
err = suite.db.Put(context.Background(), newAccount)
|
err = suite.db.Put(context.Background(), newAccount)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
suite.Equal("en", newAccount.Language)
|
|
||||||
suite.WithinDuration(time.Now(), newAccount.CreatedAt, 30*time.Second)
|
suite.WithinDuration(time.Now(), newAccount.CreatedAt, 30*time.Second)
|
||||||
suite.WithinDuration(time.Now(), newAccount.UpdatedAt, 30*time.Second)
|
suite.WithinDuration(time.Now(), newAccount.UpdatedAt, 30*time.Second)
|
||||||
suite.False(*newAccount.Memorial)
|
suite.False(*newAccount.Memorial)
|
||||||
suite.False(*newAccount.Bot)
|
suite.False(*newAccount.Bot)
|
||||||
suite.False(*newAccount.Discoverable)
|
suite.False(*newAccount.Discoverable)
|
||||||
suite.False(*newAccount.Sensitive)
|
|
||||||
suite.False(*newAccount.HideCollections)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccountTestSuite) TestGetAccountPinnedStatusesSomeResults() {
|
func (suite *AccountTestSuite) TestGetAccountPinnedStatusesSomeResults() {
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||||
|
@ -95,7 +96,10 @@ func (a *adminDB) NewSignup(ctx context.Context, newSignup gtsmodel.NewSignup) (
|
||||||
// If something went wrong previously while doing a new
|
// If something went wrong previously while doing a new
|
||||||
// sign up with this username, we might already have an
|
// sign up with this username, we might already have an
|
||||||
// account, so check first.
|
// account, so check first.
|
||||||
account, err := a.state.DB.GetAccountByUsernameDomain(ctx, newSignup.Username, "")
|
account, err := a.state.DB.GetAccountByUsernameDomain(
|
||||||
|
gtscontext.SetBarebones(ctx),
|
||||||
|
newSignup.Username, "",
|
||||||
|
)
|
||||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
// Real error occurred.
|
// Real error occurred.
|
||||||
err := gtserror.Newf("error checking for existing account: %w", err)
|
err := gtserror.Newf("error checking for existing account: %w", err)
|
||||||
|
@ -124,7 +128,6 @@ func (a *adminDB) NewSignup(ctx context.Context, newSignup gtsmodel.NewSignup) (
|
||||||
Username: newSignup.Username,
|
Username: newSignup.Username,
|
||||||
DisplayName: newSignup.Username,
|
DisplayName: newSignup.Username,
|
||||||
Reason: newSignup.Reason,
|
Reason: newSignup.Reason,
|
||||||
Privacy: gtsmodel.VisibilityDefault,
|
|
||||||
URI: uris.UserURI,
|
URI: uris.UserURI,
|
||||||
URL: uris.UserURL,
|
URL: uris.UserURL,
|
||||||
InboxURI: uris.InboxURI,
|
InboxURI: uris.InboxURI,
|
||||||
|
@ -136,6 +139,7 @@ func (a *adminDB) NewSignup(ctx context.Context, newSignup gtsmodel.NewSignup) (
|
||||||
PrivateKey: privKey,
|
PrivateKey: privKey,
|
||||||
PublicKey: &privKey.PublicKey,
|
PublicKey: &privKey.PublicKey,
|
||||||
PublicKeyURI: uris.PublicKeyURI,
|
PublicKeyURI: uris.PublicKeyURI,
|
||||||
|
PreferencesID: id.NewULID(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the new account!
|
// Insert the new account!
|
||||||
|
@ -145,6 +149,33 @@ func (a *adminDB) NewSignup(ctx context.Context, newSignup gtsmodel.NewSignup) (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Created or already had an account.
|
// Created or already had an account.
|
||||||
|
// Create account preferences
|
||||||
|
// if not done already.
|
||||||
|
accountPrefs, err := a.state.DB.GetAccountPreferencesByAccountID(ctx, account.ID)
|
||||||
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
|
// Real error.
|
||||||
|
err := gtserror.Newf("error checking for existing account preferences: %w", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if accountPrefs == nil {
|
||||||
|
// No preferences created for
|
||||||
|
// this account yet, do it now.
|
||||||
|
accountPrefs = >smodel.AccountPreferences{
|
||||||
|
ID: account.PreferencesID,
|
||||||
|
AccountID: account.ID,
|
||||||
|
StatusPrivacy: gtsmodel.VisibilityDefault,
|
||||||
|
StatusLanguage: newSignup.Locale,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.state.DB.PutAccountPreferences(ctx, accountPrefs); err != nil {
|
||||||
|
err := gtserror.Newf("db error inserting account preferences: %w", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
account.Preferences = accountPrefs
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure user not already created.
|
// Ensure user not already created.
|
||||||
user, err := a.state.DB.GetUserByAccountID(ctx, account.ID)
|
user, err := a.state.DB.GetUserByAccountID(ctx, account.ID)
|
||||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
|
|
|
@ -94,10 +94,6 @@ func (suite *BasicTestSuite) TestPutAccountWithBunDefaultFields() {
|
||||||
// to true, which is why we use pointers for bools in the first place
|
// to true, which is why we use pointers for bools in the first place
|
||||||
suite.True(*a.Locked)
|
suite.True(*a.Locked)
|
||||||
suite.False(*a.Discoverable)
|
suite.False(*a.Discoverable)
|
||||||
suite.Empty(a.Privacy)
|
|
||||||
suite.False(*a.Sensitive)
|
|
||||||
suite.Equal("en", a.Language)
|
|
||||||
suite.Empty(a.StatusContentType)
|
|
||||||
suite.Equal(testAccount.URI, a.URI)
|
suite.Equal(testAccount.URI, a.URI)
|
||||||
suite.Equal(testAccount.URL, a.URL)
|
suite.Equal(testAccount.URL, a.URL)
|
||||||
suite.Zero(testAccount.FetchedAt)
|
suite.Zero(testAccount.FetchedAt)
|
||||||
|
@ -113,7 +109,6 @@ func (suite *BasicTestSuite) TestPutAccountWithBunDefaultFields() {
|
||||||
suite.Zero(a.SensitizedAt)
|
suite.Zero(a.SensitizedAt)
|
||||||
suite.Zero(a.SilencedAt)
|
suite.Zero(a.SilencedAt)
|
||||||
suite.Zero(a.SuspendedAt)
|
suite.Zero(a.SuspendedAt)
|
||||||
suite.False(*a.HideCollections)
|
|
||||||
suite.Empty(a.SuspensionOrigin)
|
suite.Empty(a.SuspensionOrigin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,55 +32,50 @@
|
||||||
|
|
||||||
// Account represents either a local or a remote fediverse account, gotosocial or otherwise (mastodon, pleroma, etc).
|
// Account represents either a local or a remote fediverse account, gotosocial or otherwise (mastodon, pleroma, etc).
|
||||||
type Account struct {
|
type Account struct {
|
||||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created.
|
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created.
|
||||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item was last updated.
|
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item was last updated.
|
||||||
FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // when was item (remote) last fetched.
|
FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // when was item (remote) last fetched.
|
||||||
Username string `bun:",nullzero,notnull,unique:usernamedomain"` // Username of the account, should just be a string of [a-zA-Z0-9_]. Can be added to domain to create the full username in the form ``[username]@[domain]`` eg., ``user_96@example.org``. Username and domain should be unique *with* each other
|
Username string `bun:",nullzero,notnull,unique:usernamedomain"` // Username of the account, should just be a string of [a-zA-Z0-9_]. Can be added to domain to create the full username in the form ``[username]@[domain]`` eg., ``user_96@example.org``. Username and domain should be unique *with* each other
|
||||||
Domain string `bun:",nullzero,unique:usernamedomain"` // Domain of the account, will be null if this is a local account, otherwise something like ``example.org``. Should be unique with username.
|
Domain string `bun:",nullzero,unique:usernamedomain"` // Domain of the account, will be null if this is a local account, otherwise something like ``example.org``. Should be unique with username.
|
||||||
AvatarMediaAttachmentID string `bun:"type:CHAR(26),nullzero"` // Database ID of the media attachment, if present
|
AvatarMediaAttachmentID string `bun:"type:CHAR(26),nullzero"` // Database ID of the media attachment, if present
|
||||||
AvatarMediaAttachment *MediaAttachment `bun:"rel:belongs-to"` // MediaAttachment corresponding to avatarMediaAttachmentID
|
AvatarMediaAttachment *MediaAttachment `bun:"rel:belongs-to"` // MediaAttachment corresponding to avatarMediaAttachmentID
|
||||||
AvatarRemoteURL string `bun:",nullzero"` // For a non-local account, where can the header be fetched?
|
AvatarRemoteURL string `bun:",nullzero"` // For a non-local account, where can the header be fetched?
|
||||||
HeaderMediaAttachmentID string `bun:"type:CHAR(26),nullzero"` // Database ID of the media attachment, if present
|
HeaderMediaAttachmentID string `bun:"type:CHAR(26),nullzero"` // Database ID of the media attachment, if present
|
||||||
HeaderMediaAttachment *MediaAttachment `bun:"rel:belongs-to"` // MediaAttachment corresponding to headerMediaAttachmentID
|
HeaderMediaAttachment *MediaAttachment `bun:"rel:belongs-to"` // MediaAttachment corresponding to headerMediaAttachmentID
|
||||||
HeaderRemoteURL string `bun:",nullzero"` // For a non-local account, where can the header be fetched?
|
HeaderRemoteURL string `bun:",nullzero"` // For a non-local account, where can the header be fetched?
|
||||||
DisplayName string `bun:""` // DisplayName for this account. Can be empty, then just the Username will be used for display purposes.
|
DisplayName string `bun:""` // DisplayName for this account. Can be empty, then just the Username will be used for display purposes.
|
||||||
EmojiIDs []string `bun:"emojis,array"` // Database IDs of any emojis used in this account's bio, display name, etc
|
EmojiIDs []string `bun:"emojis,array"` // Database IDs of any emojis used in this account's bio, display name, etc
|
||||||
Emojis []*Emoji `bun:"attached_emojis,m2m:account_to_emojis"` // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
|
Emojis []*Emoji `bun:"attached_emojis,m2m:account_to_emojis"` // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
|
||||||
Fields []*Field // A slice of of fields that this account has added to their profile.
|
Fields []*Field `bun:""` // A slice of of fields that this account has added to their profile.
|
||||||
FieldsRaw []*Field // The raw (unparsed) content of fields that this account has added to their profile, without conversion to HTML, only available when requester = target
|
FieldsRaw []*Field `bun:""` // The raw (unparsed) content of fields that this account has added to their profile, without conversion to HTML, only available when requester = target
|
||||||
Note string `bun:""` // A note that this account has on their profile (ie., the account's bio/description of themselves)
|
Note string `bun:""` // A note that this account has on their profile (ie., the account's bio/description of themselves)
|
||||||
NoteRaw string `bun:""` // The raw contents of .Note without conversion to HTML, only available when requester = target
|
NoteRaw string `bun:""` // The raw contents of .Note without conversion to HTML, only available when requester = target
|
||||||
Memorial *bool `bun:",default:false"` // Is this a memorial account, ie., has the user passed away?
|
Memorial *bool `bun:",default:false"` // Is this a memorial account, ie., has the user passed away?
|
||||||
AlsoKnownAs string `bun:"type:CHAR(26),nullzero"` // This account is associated with x account id (TODO: migrate to be AlsoKnownAsID)
|
AlsoKnownAs string `bun:"type:CHAR(26),nullzero"` // This account is associated with x account id (TODO: migrate to be AlsoKnownAsID)
|
||||||
MovedToAccountID string `bun:"type:CHAR(26),nullzero"` // This account has moved this account id in the database
|
MovedToAccountID string `bun:"type:CHAR(26),nullzero"` // This account has moved this account id in the database
|
||||||
Bot *bool `bun:",default:false"` // Does this account identify itself as a bot?
|
Bot *bool `bun:",default:false"` // Does this account identify itself as a bot?
|
||||||
Reason string `bun:""` // What reason was given for signing up when this account was created?
|
Reason string `bun:""` // What reason was given for signing up when this account was created?
|
||||||
Locked *bool `bun:",default:true"` // Does this account need an approval for new followers?
|
Locked *bool `bun:",default:true"` // Does this account need an approval for new followers?
|
||||||
Discoverable *bool `bun:",default:false"` // Should this account be shown in the instance's profile directory?
|
Discoverable *bool `bun:",default:false"` // Should this account be shown in the instance's profile directory?
|
||||||
Privacy Visibility `bun:",nullzero"` // Default post privacy for this account
|
URI string `bun:",nullzero,notnull,unique"` // ActivityPub URI for this account.
|
||||||
Sensitive *bool `bun:",default:false"` // Set posts from this account to sensitive by default?
|
URL string `bun:",nullzero,unique"` // Web URL for this account's profile
|
||||||
Language string `bun:",nullzero,notnull,default:'en'"` // What language does this account post in?
|
InboxURI string `bun:",nullzero,unique"` // Address of this account's ActivityPub inbox, for sending activity to
|
||||||
StatusContentType string `bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts).
|
SharedInboxURI *string `bun:""` // Address of this account's ActivityPub sharedInbox. Gotcha warning: this is a string pointer because it has three possible states: 1. We don't know yet if the account has a shared inbox -- null. 2. We know it doesn't have a shared inbox -- empty string. 3. We know it does have a shared inbox -- url string.
|
||||||
CustomCSS string `bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses.
|
OutboxURI string `bun:",nullzero,unique"` // Address of this account's activitypub outbox
|
||||||
URI string `bun:",nullzero,notnull,unique"` // ActivityPub URI for this account.
|
FollowingURI string `bun:",nullzero,unique"` // URI for getting the following list of this account
|
||||||
URL string `bun:",nullzero,unique"` // Web URL for this account's profile
|
FollowersURI string `bun:",nullzero,unique"` // URI for getting the followers list of this account
|
||||||
InboxURI string `bun:",nullzero,unique"` // Address of this account's ActivityPub inbox, for sending activity to
|
FeaturedCollectionURI string `bun:",nullzero,unique"` // URL for getting the featured collection list of this account
|
||||||
SharedInboxURI *string `bun:""` // Address of this account's ActivityPub sharedInbox. Gotcha warning: this is a string pointer because it has three possible states: 1. We don't know yet if the account has a shared inbox -- null. 2. We know it doesn't have a shared inbox -- empty string. 3. We know it does have a shared inbox -- url string.
|
ActorType string `bun:",nullzero,notnull"` // What type of activitypub actor is this account?
|
||||||
OutboxURI string `bun:",nullzero,unique"` // Address of this account's activitypub outbox
|
PrivateKey *rsa.PrivateKey `bun:""` // Privatekey for validating activitypub requests, will only be defined for local accounts
|
||||||
FollowingURI string `bun:",nullzero,unique"` // URI for getting the following list of this account
|
PublicKey *rsa.PublicKey `bun:",notnull"` // Publickey for encoding activitypub requests, will be defined for both local and remote accounts
|
||||||
FollowersURI string `bun:",nullzero,unique"` // URI for getting the followers list of this account
|
PublicKeyURI string `bun:",nullzero,notnull,unique"` // Web-reachable location of this account's public key
|
||||||
FeaturedCollectionURI string `bun:",nullzero,unique"` // URL for getting the featured collection list of this account
|
SensitizedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this account set to have all its media shown as sensitive?
|
||||||
ActorType string `bun:",nullzero,notnull"` // What type of activitypub actor is this account?
|
SilencedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this account silenced (eg., statuses only visible to followers, not public)?
|
||||||
PrivateKey *rsa.PrivateKey `bun:""` // Privatekey for validating activitypub requests, will only be defined for local accounts
|
SuspendedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this account suspended (eg., don't allow it to log in/post, don't accept media/posts from this account)
|
||||||
PublicKey *rsa.PublicKey `bun:",notnull"` // Publickey for encoding activitypub requests, will be defined for both local and remote accounts
|
SuspensionOrigin string `bun:"type:CHAR(26),nullzero"` // id of the database entry that caused this account to become suspended -- can be an account ID or a domain block ID
|
||||||
PublicKeyURI string `bun:",nullzero,notnull,unique"` // Web-reachable location of this account's public key
|
PreferencesID string `bun:"type:CHAR(26),nullzero"` // ID of the LocalAccountPreferences entry for this account. Only set for local accounts.
|
||||||
SensitizedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this account set to have all its media shown as sensitive?
|
Preferences *AccountPreferences `bun:"-"` // Preferences corresponding to PreferencesID. Local accounts only.
|
||||||
SilencedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this account silenced (eg., statuses only visible to followers, not public)?
|
|
||||||
SuspendedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this account suspended (eg., don't allow it to log in/post, don't accept media/posts from this account)
|
|
||||||
HideCollections *bool `bun:",default:false"` // Hide this account's collections
|
|
||||||
SuspensionOrigin string `bun:"type:CHAR(26),nullzero"` // id of the database entry that caused this account to become suspended -- can be an account ID or a domain block ID
|
|
||||||
EnableRSS *bool `bun:",default:false"` // enable RSS feed subscription for this account's public posts at [URL]/feed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsLocal returns whether account is a local user account.
|
// IsLocal returns whether account is a local user account.
|
||||||
|
|
34
internal/gtsmodel/accountpreferences.go
Normal file
34
internal/gtsmodel/accountpreferences.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// GoToSocial
|
||||||
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package gtsmodel
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type AccountPreferences struct {
|
||||||
|
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database.
|
||||||
|
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Creation time of this item.
|
||||||
|
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Last updated time of this time.
|
||||||
|
AccountID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of the account to which these preferences correspond.
|
||||||
|
StatusLanguage string `bun:",nullzero,notnull,default:'en'"` // Default post language for this account.
|
||||||
|
StatusPrivacy Visibility `bun:",nullzero"` // Default post privacy for this account.
|
||||||
|
StatusSensitive *bool `bun:",nullzero,notnull,default:false"` // Set posts from this account to sensitive by default.
|
||||||
|
StatusContentType string `bun:",nullzero"` // Default format for statuses posted by this account.
|
||||||
|
HideCollections *bool `bun:",nullzero,notnull,default:false"` // Hide this account's collections.
|
||||||
|
EnableRSS *bool `bun:",nullzero,notnull,default:false"` // Enable RSS feed subscription for this account's public posts at [URL]/feed
|
||||||
|
CustomCSS string `bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses.
|
||||||
|
}
|
|
@ -114,14 +114,6 @@ func (c *converter) ASRepresentationToAccount(ctx context.Context, accountable a
|
||||||
memorial := false
|
memorial := false
|
||||||
acct.Memorial = &memorial
|
acct.Memorial = &memorial
|
||||||
|
|
||||||
// assume not sensitive (todo)
|
|
||||||
sensitive := false
|
|
||||||
acct.Sensitive = &sensitive
|
|
||||||
|
|
||||||
// assume not hide collections (todo)
|
|
||||||
hideCollections := false
|
|
||||||
acct.HideCollections = &hideCollections
|
|
||||||
|
|
||||||
// locked aka manuallyApprovesFollowers
|
// locked aka manuallyApprovesFollowers
|
||||||
locked := true
|
locked := true
|
||||||
acct.Locked = &locked // assume locked by default
|
acct.Locked = &locked // assume locked by default
|
||||||
|
@ -140,10 +132,6 @@ func (c *converter) ASRepresentationToAccount(ctx context.Context, accountable a
|
||||||
acct.Discoverable = &d
|
acct.Discoverable = &d
|
||||||
}
|
}
|
||||||
|
|
||||||
// assume not rss feed
|
|
||||||
enableRSS := false
|
|
||||||
acct.EnableRSS = &enableRSS
|
|
||||||
|
|
||||||
// url property
|
// url property
|
||||||
url, err := ap.ExtractURL(accountable)
|
url, err := ap.ExtractURL(accountable)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
@ -59,29 +59,47 @@ func toMastodonVersion(in string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *converter) AccountToAPIAccountSensitive(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) {
|
func (c *converter) AccountToAPIAccountSensitive(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) {
|
||||||
// we can build this sensitive account easily by first getting the public account....
|
// Build sensitive view of account by first
|
||||||
|
// getting the public account and then adding
|
||||||
|
// additional information for the eyes of this
|
||||||
|
// account owner only.
|
||||||
apiAccount, err := c.AccountToAPIAccountPublic(ctx, a)
|
apiAccount, err := c.AccountToAPIAccountPublic(ctx, a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// then adding the Source object to it...
|
// Ensure account preferences populated.
|
||||||
|
// Accounts passed in to this function
|
||||||
// check pending follow requests aimed at this account
|
// should always be local accounts, so
|
||||||
frc, err := c.db.CountAccountFollowRequests(ctx, a.ID)
|
// a missing account preferences struct
|
||||||
if err != nil {
|
// is a real problem!
|
||||||
return nil, fmt.Errorf("error counting follow requests: %s", err)
|
if a.Preferences == nil {
|
||||||
|
var err error
|
||||||
|
a.Preferences, err = c.db.GetAccountPreferencesByAccountID(ctx, a.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, gtserror.Newf("error getting account preferences: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statusContentType := string(apimodel.StatusContentTypeDefault)
|
// Count pending follow requests aimed at this account.
|
||||||
if a.StatusContentType != "" {
|
frc, err := c.db.CountAccountFollowRequests(ctx, a.ID)
|
||||||
statusContentType = a.StatusContentType
|
if err != nil {
|
||||||
|
return nil, gtserror.Newf("error counting follow requests: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use default status content type
|
||||||
|
// if account has no other preference.
|
||||||
|
var statusContentType string
|
||||||
|
if a.Preferences.StatusContentType != "" {
|
||||||
|
statusContentType = a.Preferences.StatusContentType
|
||||||
|
} else {
|
||||||
|
statusContentType = string(apimodel.StatusContentTypeDefault)
|
||||||
}
|
}
|
||||||
|
|
||||||
apiAccount.Source = &apimodel.Source{
|
apiAccount.Source = &apimodel.Source{
|
||||||
Privacy: c.VisToAPIVis(ctx, a.Privacy),
|
Privacy: c.VisToAPIVis(ctx, a.Preferences.StatusPrivacy),
|
||||||
Sensitive: *a.Sensitive,
|
Sensitive: *a.Preferences.StatusSensitive,
|
||||||
Language: a.Language,
|
Language: a.Preferences.StatusLanguage,
|
||||||
StatusContentType: statusContentType,
|
StatusContentType: statusContentType,
|
||||||
Note: a.NoteRaw,
|
Note: a.NoteRaw,
|
||||||
Fields: c.fieldsToAPIFields(a.FieldsRaw),
|
Fields: c.fieldsToAPIFields(a.FieldsRaw),
|
||||||
|
@ -104,23 +122,23 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
|
||||||
|
|
||||||
followersCount, err := c.db.CountAccountFollowers(ctx, a.ID)
|
followersCount, err := c.db.CountAccountFollowers(ctx, a.ID)
|
||||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
return nil, fmt.Errorf("AccountToAPIAccountPublic: error counting followers: %w", err)
|
return nil, gtserror.Newf("error counting followers: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
followingCount, err := c.db.CountAccountFollows(ctx, a.ID)
|
followingCount, err := c.db.CountAccountFollows(ctx, a.ID)
|
||||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
return nil, fmt.Errorf("AccountToAPIAccountPublic: error counting following: %w", err)
|
return nil, gtserror.Newf("error counting following: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
statusesCount, err := c.db.CountAccountStatuses(ctx, a.ID)
|
statusesCount, err := c.db.CountAccountStatuses(ctx, a.ID)
|
||||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
return nil, fmt.Errorf("AccountToAPIAccountPublic: error counting statuses: %w", err)
|
return nil, gtserror.Newf("error counting statuses: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastStatusAt *string
|
var lastStatusAt *string
|
||||||
lastPosted, err := c.db.GetAccountLastPosted(ctx, a.ID, false)
|
lastPosted, err := c.db.GetAccountLastPosted(ctx, a.ID, false)
|
||||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
return nil, fmt.Errorf("AccountToAPIAccountPublic: error counting statuses: %w", err)
|
return nil, gtserror.Newf("error counting statuses: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !lastPosted.IsZero() {
|
if !lastPosted.IsZero() {
|
||||||
|
@ -164,40 +182,55 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
|
||||||
// - Role.
|
// - Role.
|
||||||
|
|
||||||
var (
|
var (
|
||||||
acct string
|
acct string
|
||||||
role *apimodel.AccountRole
|
role *apimodel.AccountRole
|
||||||
|
customCSS string
|
||||||
|
enableRSS bool
|
||||||
)
|
)
|
||||||
|
|
||||||
if a.IsRemote() {
|
switch {
|
||||||
// Domain may be in Punycode,
|
|
||||||
// de-punify it just in case.
|
// Only extra thing we can do for
|
||||||
|
// remote accounts is properly
|
||||||
|
// de-punify their domain.
|
||||||
|
case a.IsRemote():
|
||||||
d, err := util.DePunify(a.Domain)
|
d, err := util.DePunify(a.Domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("AccountToAPIAccountPublic: error de-punifying domain %s for account id %s: %w", a.Domain, a.ID, err)
|
return nil, gtserror.Newf("error de-punifying domain %s for account %s: %w", a.Domain, a.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
acct = a.Username + "@" + d
|
acct = a.Username + "@" + d
|
||||||
} else {
|
|
||||||
// This is a local account, try to
|
|
||||||
// fetch more info. Skip for instance
|
|
||||||
// accounts since they have no user.
|
|
||||||
if !a.IsInstance() {
|
|
||||||
user, err := c.db.GetUserByAccountID(ctx, a.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("AccountToAPIAccountPublic: error getting user from database for account id %s: %w", a.ID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
// This is a non-instance local
|
||||||
case *user.Admin:
|
// account; fetch more info using
|
||||||
role = &apimodel.AccountRole{Name: apimodel.AccountRoleAdmin}
|
// the account User. We skip this
|
||||||
case *user.Moderator:
|
// step for instance accounts since
|
||||||
role = &apimodel.AccountRole{Name: apimodel.AccountRoleModerator}
|
// they have no user.
|
||||||
default:
|
case !a.IsInstance():
|
||||||
role = &apimodel.AccountRole{Name: apimodel.AccountRoleUser}
|
user, err := c.db.GetUserByAccountID(ctx, a.ID)
|
||||||
}
|
if err != nil {
|
||||||
|
return nil, gtserror.Newf("db error getting user for account %s: %w", a.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
acct = a.Username // omit domain
|
switch {
|
||||||
|
case *user.Admin:
|
||||||
|
role = &apimodel.AccountRole{Name: apimodel.AccountRoleAdmin}
|
||||||
|
case *user.Moderator:
|
||||||
|
role = &apimodel.AccountRole{Name: apimodel.AccountRoleModerator}
|
||||||
|
default:
|
||||||
|
role = &apimodel.AccountRole{Name: apimodel.AccountRoleUser}
|
||||||
|
}
|
||||||
|
|
||||||
|
fallthrough // to below lines
|
||||||
|
|
||||||
|
// Fetch info which applies to
|
||||||
|
// local instance account and
|
||||||
|
// to actual local accounts.
|
||||||
|
default:
|
||||||
|
if a.Preferences != nil {
|
||||||
|
customCSS = a.Preferences.CustomCSS
|
||||||
|
enableRSS = *a.Preferences.EnableRSS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remaining properties are simple and
|
// Remaining properties are simple and
|
||||||
|
@ -225,8 +258,8 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
|
||||||
Emojis: apiEmojis,
|
Emojis: apiEmojis,
|
||||||
Fields: fields,
|
Fields: fields,
|
||||||
Suspended: !a.SuspendedAt.IsZero(),
|
Suspended: !a.SuspendedAt.IsZero(),
|
||||||
CustomCSS: a.CustomCSS,
|
CustomCSS: customCSS,
|
||||||
EnableRSS: *a.EnableRSS,
|
EnableRSS: enableRSS,
|
||||||
Role: role,
|
Role: role,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,7 +312,7 @@ func (c *converter) AccountToAPIAccountBlocked(ctx context.Context, a *gtsmodel.
|
||||||
if !a.IsInstance() {
|
if !a.IsInstance() {
|
||||||
user, err := c.db.GetUserByAccountID(ctx, a.ID)
|
user, err := c.db.GetUserByAccountID(ctx, a.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("AccountToAPIAccountPublic: error getting user from database for account id %s: %w", a.ID, err)
|
return nil, gtserror.Newf("error getting user from database for account id %s: %w", a.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|
Loading…
Reference in a new issue