diff --git a/internal/api/activitypub/users/inboxpost_test.go b/internal/api/activitypub/users/inboxpost_test.go
index 715231ecc..89e1cec41 100644
--- a/internal/api/activitypub/users/inboxpost_test.go
+++ b/internal/api/activitypub/users/inboxpost_test.go
@@ -395,12 +395,8 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
suite.EqualValues(requestingAccount.AlsoKnownAsURIs, dbUpdatedAccount.AlsoKnownAsURIs)
suite.EqualValues(requestingAccount.MovedToURI, dbUpdatedAccount.MovedToURI)
suite.EqualValues(requestingAccount.Bot, dbUpdatedAccount.Bot)
- suite.EqualValues(requestingAccount.Reason, dbUpdatedAccount.Reason)
suite.EqualValues(requestingAccount.Locked, dbUpdatedAccount.Locked)
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.URL, dbUpdatedAccount.URL)
suite.EqualValues(requestingAccount.InboxURI, dbUpdatedAccount.InboxURI)
@@ -414,7 +410,6 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
suite.EqualValues(requestingAccount.SensitizedAt, dbUpdatedAccount.SensitizedAt)
suite.EqualValues(requestingAccount.SilencedAt, dbUpdatedAccount.SilencedAt)
suite.EqualValues(requestingAccount.SuspendedAt, dbUpdatedAccount.SuspendedAt)
- suite.EqualValues(requestingAccount.HideCollections, dbUpdatedAccount.HideCollections)
suite.EqualValues(requestingAccount.SuspensionOrigin, dbUpdatedAccount.SuspensionOrigin)
}
@@ -464,9 +459,7 @@ func (suite *InboxPostTestSuite) TestPostDelete() {
suite.Empty(dbAccount.AvatarRemoteURL)
suite.Empty(dbAccount.HeaderMediaAttachmentID)
suite.Empty(dbAccount.HeaderRemoteURL)
- suite.Empty(dbAccount.Reason)
suite.Empty(dbAccount.Fields)
- suite.True(*dbAccount.HideCollections)
suite.False(*dbAccount.Discoverable)
suite.WithinDuration(time.Now(), dbAccount.SuspendedAt, 30*time.Second)
suite.Equal(dbAccount.ID, dbAccount.SuspensionOrigin)
diff --git a/internal/api/client/accounts/accountupdate_test.go b/internal/api/client/accounts/accountupdate_test.go
index 73e33390f..50443ceb6 100644
--- a/internal/api/client/accounts/accountupdate_test.go
+++ b/internal/api/client/accounts/accountupdate_test.go
@@ -481,7 +481,7 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceBadContentTypeFormDa
if err != nil {
suite.FailNow(err.Error())
}
- suite.Equal(data["source[status_content_type]"][0], dbAccount.StatusContentType)
+ suite.Equal(data["source[status_content_type]"][0], dbAccount.Settings.StatusContentType)
}
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateStatusContentTypeBad() {
diff --git a/internal/api/client/accounts/accountverify_test.go b/internal/api/client/accounts/accountverify_test.go
index 744488bf3..eccda8b2e 100644
--- a/internal/api/client/accounts/accountverify_test.go
+++ b/internal/api/client/accounts/accountverify_test.go
@@ -81,7 +81,7 @@ func (suite *AccountVerifyTestSuite) TestAccountVerifyGet() {
suite.Equal(2, apimodelAccount.FollowingCount)
suite.Equal(7, apimodelAccount.StatusesCount)
suite.EqualValues(gtsmodel.VisibilityPublic, apimodelAccount.Source.Privacy)
- suite.Equal(testAccount.Language, apimodelAccount.Source.Language)
+ suite.Equal(testAccount.Settings.Language, apimodelAccount.Source.Language)
suite.Equal(testAccount.NoteRaw, apimodelAccount.Source.Note)
}
diff --git a/internal/api/client/statuses/statuscreate_test.go b/internal/api/client/statuses/statuscreate_test.go
index ab7c67abf..b49e72ead 100644
--- a/internal/api/client/statuses/statuscreate_test.go
+++ b/internal/api/client/statuses/statuscreate_test.go
@@ -103,16 +103,22 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() {
}
func (suite *StatusCreateTestSuite) TestPostNewStatusMarkdown() {
- // set default post language of account 1 to markdown
- testAccount := suite.testAccounts["local_account_1"]
- testAccount.StatusContentType = "text/markdown"
- a := testAccount
+ // Copy zork.
+ testAccount := >smodel.Account{}
+ *testAccount = *suite.testAccounts["local_account_1"]
- err := suite.db.UpdateAccount(context.Background(), a)
+ // Copy zork's settings.
+ settings := >smodel.AccountSettings{}
+ *settings = *suite.testAccounts["local_account_1"].Settings
+ testAccount.Settings = settings
+
+ // set default post language of zork to markdown
+ testAccount.Settings.StatusContentType = "text/markdown"
+ err := suite.db.UpdateAccountSettings(context.Background(), testAccount.Settings)
if err != nil {
suite.FailNow(err.Error())
}
- suite.Equal(a.StatusContentType, "text/markdown")
+ suite.Equal(testAccount.Settings.StatusContentType, "text/markdown")
t := suite.testTokens["local_account_1"]
oauthToken := oauth.DBTokenToToken(t)
@@ -122,7 +128,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusMarkdown() {
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
- ctx.Set(oauth.SessionAuthorizedAccount, a)
+ ctx.Set(oauth.SessionAuthorizedAccount, testAccount)
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil)
ctx.Request.Header.Set("accept", "application/json")
diff --git a/internal/api/wellknown/webfinger/webfingerget_test.go b/internal/api/wellknown/webfinger/webfingerget_test.go
index c9bd088be..84562187d 100644
--- a/internal/api/wellknown/webfinger/webfingerget_test.go
+++ b/internal/api/wellknown/webfinger/webfingerget_test.go
@@ -100,7 +100,6 @@ func (suite *WebfingerGetTestSuite) funkifyAccountDomain(host string, accountDom
targetAccount := >smodel.Account{
ID: "01FG1K8EA7SYHEC7V6XKVNC4ZA",
Username: "new_account_domain_user",
- Privacy: gtsmodel.VisibilityDefault,
URI: "http://" + host + "/users/new_account_domain_user",
URL: "http://" + host + "/@new_account_domain_user",
InboxURI: "http://" + host + "/users/new_account_domain_user/inbox",
@@ -118,6 +117,10 @@ func (suite *WebfingerGetTestSuite) funkifyAccountDomain(host string, accountDom
suite.FailNow(err.Error())
}
+ if err := suite.db.PutAccountSettings(context.Background(), >smodel.AccountSettings{AccountID: targetAccount.ID}); err != nil {
+ suite.FailNow(err.Error())
+ }
+
return targetAccount
}
diff --git a/internal/cache/cache.go b/internal/cache/cache.go
index e2fe43a1f..fd715b8e6 100644
--- a/internal/cache/cache.go
+++ b/internal/cache/cache.go
@@ -53,6 +53,7 @@ func (c *Caches) Init() {
c.initAccount()
c.initAccountCounts()
c.initAccountNote()
+ c.initAccountSettings()
c.initApplication()
c.initBlock()
c.initBlockIDs()
@@ -119,6 +120,7 @@ func (c *Caches) Stop() {
func (c *Caches) Sweep(threshold float64) {
c.GTS.Account.Trim(threshold)
c.GTS.AccountNote.Trim(threshold)
+ c.GTS.AccountSettings.Trim(threshold)
c.GTS.Block.Trim(threshold)
c.GTS.BlockIDs.Trim(threshold)
c.GTS.Emoji.Trim(threshold)
diff --git a/internal/cache/db.go b/internal/cache/db.go
index 00dfe204a..ff38c1d93 100644
--- a/internal/cache/db.go
+++ b/internal/cache/db.go
@@ -43,6 +43,9 @@ type GTSCaches struct {
Pinned int
}]
+ // AccountSettings provides access to the gtsmodel AccountSettings database cache.
+ AccountSettings structr.Cache[*gtsmodel.AccountSettings]
+
// Application provides access to the gtsmodel Application database cache.
Application structr.Cache[*gtsmodel.Application]
@@ -190,6 +193,7 @@ func (c *Caches) initAccount() {
a2.Emojis = nil
a2.AlsoKnownAs = nil
a2.Move = nil
+ a2.Settings = nil
return a2
}
@@ -262,6 +266,29 @@ func (c *Caches) initAccountNote() {
})
}
+func (c *Caches) initAccountSettings() {
+ // Calculate maximum cache size.
+ cap := calculateResultCacheMax(
+ sizeofAccountSettings(), // model in-mem size.
+ config.GetCacheAccountSettingsMemRatio(),
+ )
+
+ log.Infof(nil, "cache size = %d", cap)
+
+ c.GTS.AccountSettings.Init(structr.Config[*gtsmodel.AccountSettings]{
+ Indices: []structr.IndexConfig{
+ {Fields: "AccountID"},
+ },
+ MaxSize: cap,
+ IgnoreErr: ignoreErrors,
+ CopyValue: func(s1 *gtsmodel.AccountSettings) *gtsmodel.AccountSettings {
+ s2 := new(gtsmodel.AccountSettings)
+ *s2 = *s1
+ return s2
+ },
+ })
+}
+
func (c *Caches) initApplication() {
// Calculate maximum cache size.
cap := calculateResultCacheMax(
diff --git a/internal/cache/size.go b/internal/cache/size.go
index b1c431c55..080fefea3 100644
--- a/internal/cache/size.go
+++ b/internal/cache/size.go
@@ -28,6 +28,7 @@
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
+ "github.com/superseriousbusiness/gotosocial/internal/util"
)
const (
@@ -219,9 +220,6 @@ func sizeofAccount() uintptr {
Bot: func() *bool { ok := true; return &ok }(),
Locked: func() *bool { ok := true; return &ok }(),
Discoverable: func() *bool { ok := false; return &ok }(),
- Privacy: gtsmodel.VisibilityFollowersOnly,
- Sensitive: func() *bool { ok := true; return &ok }(),
- Language: "fr",
URI: exampleURI,
URL: exampleURI,
InboxURI: exampleURI,
@@ -236,9 +234,7 @@ func sizeofAccount() uintptr {
SensitizedAt: exampleTime,
SilencedAt: exampleTime,
SuspendedAt: exampleTime,
- HideCollections: func() *bool { ok := true; return &ok }(),
SuspensionOrigin: exampleID,
- EnableRSS: func() *bool { ok := true; return &ok }(),
}))
}
@@ -251,6 +247,22 @@ func sizeofAccountNote() uintptr {
}))
}
+func sizeofAccountSettings() uintptr {
+ return uintptr(size.Of(>smodel.AccountSettings{
+ AccountID: exampleID,
+ CreatedAt: exampleTime,
+ UpdatedAt: exampleTime,
+ Reason: exampleText,
+ Privacy: gtsmodel.VisibilityFollowersOnly,
+ Sensitive: util.Ptr(true),
+ Language: "fr",
+ StatusContentType: "text/plain",
+ CustomCSS: exampleText,
+ EnableRSS: util.Ptr(true),
+ HideCollections: util.Ptr(false),
+ }))
+}
+
func sizeofApplication() uintptr {
return uintptr(size.Of(>smodel.Application{
ID: exampleID,
diff --git a/internal/config/config.go b/internal/config/config.go
index f4ea64f93..a6d27217f 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -195,6 +195,7 @@ type CacheConfiguration struct {
MemoryTarget bytesize.Size `name:"memory-target"`
AccountMemRatio float64 `name:"account-mem-ratio"`
AccountNoteMemRatio float64 `name:"account-note-mem-ratio"`
+ AccountSettingsMemRatio float64 `name:"account-settings-mem-ratio"`
ApplicationMemRatio float64 `name:"application-mem-ratio"`
BlockMemRatio float64 `name:"block-mem-ratio"`
BlockIDsMemRatio float64 `name:"block-mem-ratio"`
diff --git a/internal/config/defaults.go b/internal/config/defaults.go
index 6ca508d5a..99a2e24cb 100644
--- a/internal/config/defaults.go
+++ b/internal/config/defaults.go
@@ -159,6 +159,7 @@
// be able to make some more sense :D
AccountMemRatio: 5,
AccountNoteMemRatio: 1,
+ AccountSettingsMemRatio: 0.1,
ApplicationMemRatio: 0.1,
BlockMemRatio: 2,
BlockIDsMemRatio: 3,
diff --git a/internal/config/helpers.gen.go b/internal/config/helpers.gen.go
index 5f65a6e28..39c163d20 100644
--- a/internal/config/helpers.gen.go
+++ b/internal/config/helpers.gen.go
@@ -2825,6 +2825,31 @@ func GetCacheAccountNoteMemRatio() float64 { return global.GetCacheAccountNoteMe
// SetCacheAccountNoteMemRatio safely sets the value for global configuration 'Cache.AccountNoteMemRatio' field
func SetCacheAccountNoteMemRatio(v float64) { global.SetCacheAccountNoteMemRatio(v) }
+// GetCacheAccountSettingsMemRatio safely fetches the Configuration value for state's 'Cache.AccountSettingsMemRatio' field
+func (st *ConfigState) GetCacheAccountSettingsMemRatio() (v float64) {
+ st.mutex.RLock()
+ v = st.config.Cache.AccountSettingsMemRatio
+ st.mutex.RUnlock()
+ return
+}
+
+// SetCacheAccountSettingsMemRatio safely sets the Configuration value for state's 'Cache.AccountSettingsMemRatio' field
+func (st *ConfigState) SetCacheAccountSettingsMemRatio(v float64) {
+ st.mutex.Lock()
+ defer st.mutex.Unlock()
+ st.config.Cache.AccountSettingsMemRatio = v
+ st.reloadToViper()
+}
+
+// CacheAccountSettingsMemRatioFlag returns the flag name for the 'Cache.AccountSettingsMemRatio' field
+func CacheAccountSettingsMemRatioFlag() string { return "cache-account-settings-mem-ratio" }
+
+// GetCacheAccountSettingsMemRatio safely fetches the value for global configuration 'Cache.AccountSettingsMemRatio' field
+func GetCacheAccountSettingsMemRatio() float64 { return global.GetCacheAccountSettingsMemRatio() }
+
+// SetCacheAccountSettingsMemRatio safely sets the value for global configuration 'Cache.AccountSettingsMemRatio' field
+func SetCacheAccountSettingsMemRatio(v float64) { global.SetCacheAccountSettingsMemRatio(v) }
+
// GetCacheApplicationMemRatio safely fetches the Configuration value for state's 'Cache.ApplicationMemRatio' field
func (st *ConfigState) GetCacheApplicationMemRatio() (v float64) {
st.mutex.RLock()
diff --git a/internal/db/account.go b/internal/db/account.go
index 505ca4004..3de72c5a8 100644
--- a/internal/db/account.go
+++ b/internal/db/account.go
@@ -117,4 +117,13 @@ type Account interface {
// GetInstanceAccount returns the instance account for the given domain.
// If domain is empty, this instance account will be returned.
GetInstanceAccount(ctx context.Context, domain string) (*gtsmodel.Account, error)
+
+ // Get local account settings with the given ID.
+ GetAccountSettings(ctx context.Context, id string) (*gtsmodel.AccountSettings, error)
+
+ // Store local account settings.
+ PutAccountSettings(ctx context.Context, settings *gtsmodel.AccountSettings) error
+
+ // Update local account settings.
+ UpdateAccountSettings(ctx context.Context, settings *gtsmodel.AccountSettings, columns ...string) error
}
diff --git a/internal/db/bundb/account.go b/internal/db/bundb/account.go
index 5b1dab143..870c2ff55 100644
--- a/internal/db/bundb/account.go
+++ b/internal/db/bundb/account.go
@@ -338,6 +338,17 @@ func (a *accountDB) PopulateAccount(ctx context.Context, account *gtsmodel.Accou
}
}
+ if account.IsLocal() && account.Settings == nil && !account.IsInstance() {
+ // Account settings not set, fetch from db.
+ account.Settings, err = a.state.DB.GetAccountSettings(
+ ctx, // these are already barebones
+ account.ID,
+ )
+ if err != nil {
+ errs.Appendf("error populating account settings: %w", err)
+ }
+ }
+
return errs.Combine()
}
@@ -504,12 +515,22 @@ func (a *accountDB) SetAccountHeaderOrAvatar(ctx context.Context, mediaAttachmen
}
func (a *accountDB) GetAccountCustomCSSByUsername(ctx context.Context, username string) (string, error) {
+ // Get local account.
account, err := a.GetAccountByUsernameDomain(ctx, username, "")
if err != nil {
return "", err
}
- return account.CustomCSS, nil
+ // Ensure settings populated, in case
+ // barebones context was passed.
+ if account.Settings == nil {
+ account.Settings, err = a.GetAccountSettings(ctx, account.ID)
+ if err != nil {
+ return "", err
+ }
+ }
+
+ return account.Settings.CustomCSS, nil
}
func (a *accountDB) GetAccountsUsingEmoji(ctx context.Context, emojiID string) ([]*gtsmodel.Account, error) {
@@ -780,3 +801,68 @@ func (a *accountDB) GetAccountWebStatuses(ctx context.Context, accountID string,
return a.state.DB.GetStatusesByIDs(ctx, statusIDs)
}
+
+func (a *accountDB) GetAccountSettings(
+ ctx context.Context,
+ accountID string,
+) (*gtsmodel.AccountSettings, error) {
+ // Fetch settings from db cache with loader callback.
+ return a.state.Caches.GTS.AccountSettings.LoadOne(
+ "AccountID",
+ func() (*gtsmodel.AccountSettings, error) {
+ // Not cached! Perform database query.
+ var settings gtsmodel.AccountSettings
+ if err := a.db.
+ NewSelect().
+ Model(&settings).
+ Where("? = ?", bun.Ident("account_settings.account_id"), accountID).
+ Scan(ctx); err != nil {
+ return nil, err
+ }
+ return &settings, nil
+ },
+ accountID,
+ )
+}
+
+func (a *accountDB) PutAccountSettings(
+ ctx context.Context,
+ settings *gtsmodel.AccountSettings,
+) error {
+ return a.state.Caches.GTS.AccountSettings.Store(settings, func() error {
+ if _, err := a.db.
+ NewInsert().
+ Model(settings).
+ Exec(ctx); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func (a *accountDB) UpdateAccountSettings(
+ ctx context.Context,
+ settings *gtsmodel.AccountSettings,
+ columns ...string,
+) error {
+ return a.state.Caches.GTS.AccountSettings.Store(settings, func() error {
+ settings.UpdatedAt = time.Now()
+ if len(columns) > 0 {
+ // If we're updating by column,
+ // ensure "updated_at" is included.
+ columns = append(columns, "updated_at")
+ }
+
+ if _, err := a.db.
+ NewUpdate().
+ Model(settings).
+ Column(columns...).
+ Where("? = ?", bun.Ident("account_settings.account_id"), settings.AccountID).
+ Exec(ctx); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
diff --git a/internal/db/bundb/account_test.go b/internal/db/bundb/account_test.go
index 268c25c59..21e04dedc 100644
--- a/internal/db/bundb/account_test.go
+++ b/internal/db/bundb/account_test.go
@@ -216,6 +216,8 @@ func (suite *AccountTestSuite) TestGetAccountBy() {
a2.AvatarMediaAttachment = nil
a1.Emojis = nil
a2.Emojis = nil
+ a1.Settings = nil
+ a2.Settings = nil
// Clear database-set fields.
a1.CreatedAt = time.Time{}
@@ -439,15 +441,11 @@ func (suite *AccountTestSuite) TestInsertAccountWithDefaults() {
err = suite.db.Put(context.Background(), newAccount)
suite.NoError(err)
- suite.Equal("en", newAccount.Language)
suite.WithinDuration(time.Now(), newAccount.CreatedAt, 30*time.Second)
suite.WithinDuration(time.Now(), newAccount.UpdatedAt, 30*time.Second)
suite.True(*newAccount.Locked)
- suite.False(*newAccount.Memorial)
suite.False(*newAccount.Bot)
suite.False(*newAccount.Discoverable)
- suite.False(*newAccount.Sensitive)
- suite.False(*newAccount.HideCollections)
}
func (suite *AccountTestSuite) TestGetAccountPinnedStatusesSomeResults() {
diff --git a/internal/db/bundb/admin.go b/internal/db/bundb/admin.go
index 70ae68026..832db1d8f 100644
--- a/internal/db/bundb/admin.go
+++ b/internal/db/bundb/admin.go
@@ -119,12 +119,21 @@ func (a *adminDB) NewSignup(ctx context.Context, newSignup gtsmodel.NewSignup) (
return nil, err
}
+ settings := >smodel.AccountSettings{
+ AccountID: accountID,
+ Reason: newSignup.Reason,
+ Privacy: gtsmodel.VisibilityDefault,
+ }
+
+ // Insert the settings!
+ if err := a.state.DB.PutAccountSettings(ctx, settings); err != nil {
+ return nil, err
+ }
+
account = >smodel.Account{
ID: accountID,
Username: newSignup.Username,
DisplayName: newSignup.Username,
- Reason: newSignup.Reason,
- Privacy: gtsmodel.VisibilityDefault,
URI: uris.UserURI,
URL: uris.UserURL,
InboxURI: uris.InboxURI,
@@ -136,6 +145,7 @@ func (a *adminDB) NewSignup(ctx context.Context, newSignup gtsmodel.NewSignup) (
PrivateKey: privKey,
PublicKey: &privKey.PublicKey,
PublicKeyURI: uris.PublicKeyURI,
+ Settings: settings,
}
// Insert the new account!
diff --git a/internal/db/bundb/basic_test.go b/internal/db/bundb/basic_test.go
index 5d5c1c2b9..6892291d2 100644
--- a/internal/db/bundb/basic_test.go
+++ b/internal/db/bundb/basic_test.go
@@ -85,19 +85,13 @@ func (suite *BasicTestSuite) TestPutAccountWithBunDefaultFields() {
suite.Nil(a.Fields)
suite.Empty(a.Note)
suite.Empty(a.NoteRaw)
- suite.False(*a.Memorial)
suite.Empty(a.AlsoKnownAsURIs)
suite.Empty(a.MovedToURI)
suite.False(*a.Bot)
- suite.Empty(a.Reason)
// Locked is especially important, since it's a bool that defaults
// to true, which is why we use pointers for bools in the first place
suite.True(*a.Locked)
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.URL, a.URL)
suite.Zero(testAccount.FetchedAt)
@@ -113,7 +107,6 @@ func (suite *BasicTestSuite) TestPutAccountWithBunDefaultFields() {
suite.Zero(a.SensitizedAt)
suite.Zero(a.SilencedAt)
suite.Zero(a.SuspendedAt)
- suite.False(*a.HideCollections)
suite.Empty(a.SuspensionOrigin)
}
diff --git a/internal/db/bundb/migrations/20240318115336_account_settings.go b/internal/db/bundb/migrations/20240318115336_account_settings.go
new file mode 100644
index 000000000..90d3ff420
--- /dev/null
+++ b/internal/db/bundb/migrations/20240318115336_account_settings.go
@@ -0,0 +1,122 @@
+// 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 .
+
+package migrations
+
+import (
+ "context"
+
+ oldgtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20230328203024_migration_fix"
+ newgtsmodel "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/util"
+
+ "github.com/uptrace/bun"
+)
+
+func init() {
+ up := func(ctx context.Context, db *bun.DB) error {
+ log.Info(ctx, "migrating account settings to new table, please wait...")
+ return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
+ // Columns we'll be moving
+ // to AccountSettings.
+ var columns = []string{
+ "reason",
+ "privacy",
+ "sensitive",
+ "language",
+ "status_content_type",
+ "custom_css",
+ "enable_rss",
+ "hide_collections",
+ }
+
+ // Create the new account settings table.
+ if _, err := tx.
+ NewCreateTable().
+ Model(&newgtsmodel.AccountSettings{}).
+ IfNotExists().
+ Exec(ctx); err != nil {
+ return err
+ }
+
+ // Select each local account.
+ accounts := []*oldgtsmodel.Account{}
+ if err := tx.
+ NewSelect().
+ TableExpr("? AS ?", bun.Ident("accounts"), bun.Ident("account")).
+ Column("account.id").
+ Column(columns...).
+ Join(
+ "JOIN ? AS ? ON ? = ?",
+ bun.Ident("users"), bun.Ident("user"),
+ bun.Ident("user.account_id"), bun.Ident("account.id"),
+ ).
+ Scan(ctx, &accounts); err != nil {
+ return err
+ }
+
+ // Create a settings entry for each existing account, taking
+ // values from the old account model (with sensible defaults).
+ for _, account := range accounts {
+ settings := &newgtsmodel.AccountSettings{
+ AccountID: account.ID,
+ CreatedAt: account.CreatedAt,
+ Reason: account.Reason,
+ Privacy: newgtsmodel.Visibility(account.Privacy),
+ Sensitive: util.Ptr(util.PtrValueOr(account.Sensitive, false)),
+ Language: account.Language,
+ StatusContentType: account.StatusContentType,
+ CustomCSS: account.CustomCSS,
+ EnableRSS: util.Ptr(util.PtrValueOr(account.EnableRSS, false)),
+ HideCollections: util.Ptr(util.PtrValueOr(account.HideCollections, false)),
+ }
+
+ // Insert the settings model.
+ if _, err := tx.
+ NewInsert().
+ Model(settings).
+ Exec(ctx); err != nil {
+ return err
+ }
+ }
+
+ // Drop now unused columns from accounts table.
+ for _, column := range columns {
+ if _, err := tx.
+ NewDropColumn().
+ Table("accounts").
+ Column(column).
+ Exec(ctx); err != nil {
+ return err
+ }
+ }
+
+ return nil
+ })
+ }
+
+ down := func(ctx context.Context, db *bun.DB) error {
+ return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
+ return nil
+ })
+ }
+
+ if err := Migrations.Register(up, down); err != nil {
+ panic(err)
+ }
+}
diff --git a/internal/federation/dereferencing/account.go b/internal/federation/dereferencing/account.go
index 5e81fb445..305b3f05c 100644
--- a/internal/federation/dereferencing/account.go
+++ b/internal/federation/dereferencing/account.go
@@ -743,9 +743,6 @@ func (d *Dereferencer) enrichAccount(
// Set time of update from the last-fetched date.
latestAcc.UpdatedAt = latestAcc.FetchedAt
- // Carry over existing account language.
- latestAcc.Language = account.Language
-
// This is an existing account, update the model in the database.
if err := d.state.DB.UpdateAccount(ctx, latestAcc); err != nil {
return nil, nil, gtserror.Newf("error updating database: %w", err)
diff --git a/internal/gtsmodel/account.go b/internal/gtsmodel/account.go
index 643dd62b8..2ac107e56 100644
--- a/internal/gtsmodel/account.go
+++ b/internal/gtsmodel/account.go
@@ -48,45 +48,38 @@ type Account struct {
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
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.
- 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
- 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
- Memorial *bool `bun:",default:false"` // Is this a memorial account, ie., has the user passed away?
- AlsoKnownAsURIs []string `bun:"also_known_as_uris,array"` // This account is associated with these account URIs.
- AlsoKnownAs []*Account `bun:"-"` // This account is associated with these accounts (field not stored in the db).
- MovedToURI string `bun:",nullzero"` // This account has (or claims to have) moved to this account URI. Even if this field is set the move may not yet have been processed. Check `move` for this.
- MovedTo *Account `bun:"-"` // This account has moved to this account (field not stored in the db).
- MoveID string `bun:"type:CHAR(26),nullzero"` // ID of a Move in the database for this account. Only set if we received or created a Move activity for which this account URI was the origin.
- Move *Move `bun:"-"` // Move corresponding to MoveID, if set.
- Bot *bool `bun:",default:false"` // Does this account identify itself as a bot?
- 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?
- 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
- Sensitive *bool `bun:",default:false"` // Set posts from this account to sensitive by default?
- Language string `bun:",nullzero,notnull,default:'en'"` // What language does this account post in?
- StatusContentType string `bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts).
- CustomCSS string `bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses.
- URI string `bun:",nullzero,notnull,unique"` // ActivityPub URI for this account.
- URL string `bun:",nullzero,unique"` // Web URL for this account's profile
- InboxURI string `bun:",nullzero,unique"` // Address of this account's ActivityPub inbox, for sending activity to
- 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.
- OutboxURI string `bun:",nullzero,unique"` // Address of this account's activitypub outbox
- FollowingURI string `bun:",nullzero,unique"` // URI for getting the following list of this account
- FollowersURI string `bun:",nullzero,unique"` // URI for getting the followers list of this account
- FeaturedCollectionURI string `bun:",nullzero,unique"` // URL for getting the featured collection list of this account
- ActorType string `bun:",nullzero,notnull"` // What type of activitypub actor is this account?
- PrivateKey *rsa.PrivateKey `bun:""` // Privatekey for signing activitypub requests, will only be defined for local accounts
- PublicKey *rsa.PublicKey `bun:",notnull"` // Publickey for authorizing signed activitypub requests, will be defined for both local and remote accounts
- PublicKeyURI string `bun:",nullzero,notnull,unique"` // Web-reachable location of this account's public key
- PublicKeyExpiresAt time.Time `bun:"type:timestamptz,nullzero"` // PublicKey will expire/has expired at given time, and should be fetched again as appropriate. Only ever set for remote accounts.
- SensitizedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this account set to have all its media shown as sensitive?
- 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
+ Fields []*Field `bun:""` // A slice of of fields that this account has added to their profile.
+ 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)
+ 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?
+ AlsoKnownAsURIs []string `bun:"also_known_as_uris,array"` // This account is associated with these account URIs.
+ AlsoKnownAs []*Account `bun:"-"` // This account is associated with these accounts (field not stored in the db).
+ MovedToURI string `bun:",nullzero"` // This account has (or claims to have) moved to this account URI. Even if this field is set the move may not yet have been processed. Check `move` for this.
+ MovedTo *Account `bun:"-"` // This account has moved to this account (field not stored in the db).
+ MoveID string `bun:"type:CHAR(26),nullzero"` // ID of a Move in the database for this account. Only set if we received or created a Move activity for which this account URI was the origin.
+ Move *Move `bun:"-"` // Move corresponding to MoveID, if set.
+ Bot *bool `bun:",default:false"` // Does this account identify itself as a bot?
+ 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?
+ URI string `bun:",nullzero,notnull,unique"` // ActivityPub URI for this account.
+ URL string `bun:",nullzero,unique"` // Web URL for this account's profile
+ InboxURI string `bun:",nullzero,unique"` // Address of this account's ActivityPub inbox, for sending activity to
+ 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.
+ OutboxURI string `bun:",nullzero,unique"` // Address of this account's activitypub outbox
+ FollowingURI string `bun:",nullzero,unique"` // URI for getting the following list of this account
+ FollowersURI string `bun:",nullzero,unique"` // URI for getting the followers list of this account
+ FeaturedCollectionURI string `bun:",nullzero,unique"` // URL for getting the featured collection list of this account
+ ActorType string `bun:",nullzero,notnull"` // What type of activitypub actor is this account?
+ PrivateKey *rsa.PrivateKey `bun:""` // Privatekey for signing activitypub requests, will only be defined for local accounts
+ PublicKey *rsa.PublicKey `bun:",notnull"` // Publickey for authorizing signed activitypub requests, will be defined for both local and remote accounts
+ PublicKeyURI string `bun:",nullzero,notnull,unique"` // Web-reachable location of this account's public key
+ PublicKeyExpiresAt time.Time `bun:"type:timestamptz,nullzero"` // PublicKey will expire/has expired at given time, and should be fetched again as appropriate. Only ever set for remote accounts.
+ SensitizedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this account set to have all its media shown as sensitive?
+ 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)
+ 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
+ Settings *AccountSettings `bun:"-"` // gtsmodel.AccountSettings for this account.
}
// IsLocal returns whether account is a local user account.
diff --git a/internal/gtsmodel/accountsettings.go b/internal/gtsmodel/accountsettings.go
new file mode 100644
index 000000000..cb5411050
--- /dev/null
+++ b/internal/gtsmodel/accountsettings.go
@@ -0,0 +1,35 @@
+// 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 .
+
+package gtsmodel
+
+import "time"
+
+// AccountSettings models settings / preferences for a local, non-instance account.
+type AccountSettings struct {
+ AccountID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // AccountID that owns this settings.
+ 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.
+ Reason string `bun:",nullzero"` // What reason was given for signing up when this account was created?
+ Privacy Visibility `bun:",nullzero"` // Default post privacy for this account
+ Sensitive *bool `bun:",nullzero,notnull,default:false"` // Set posts from this account to sensitive by default?
+ Language string `bun:",nullzero,notnull,default:'en'"` // What language does this account post in?
+ StatusContentType string `bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts).
+ CustomCSS string `bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses.
+ EnableRSS *bool `bun:",nullzero,notnull,default:false"` // enable RSS feed subscription for this account's public posts at [URL]/feed
+ HideCollections *bool `bun:",nullzero,notnull,default:false"` // Hide this account's followers/following collections.
+}
diff --git a/internal/processing/account/delete.go b/internal/processing/account/delete.go
index ff68a4638..2ae00194e 100644
--- a/internal/processing/account/delete.go
+++ b/internal/processing/account/delete.go
@@ -518,14 +518,9 @@ func stubbifyAccount(account *gtsmodel.Account, origin string) []string {
account.Memorial = util.Ptr(false)
account.AlsoKnownAsURIs = nil
account.MovedToURI = ""
- account.Reason = ""
account.Discoverable = util.Ptr(false)
- account.StatusContentType = ""
- account.CustomCSS = ""
account.SuspendedAt = now
account.SuspensionOrigin = origin
- account.HideCollections = util.Ptr(true)
- account.EnableRSS = util.Ptr(false)
return []string{
"fetched_at",
@@ -541,14 +536,9 @@ func stubbifyAccount(account *gtsmodel.Account, origin string) []string {
"memorial",
"also_known_as_uris",
"moved_to_uri",
- "reason",
"discoverable",
- "status_content_type",
- "custom_css",
"suspended_at",
"suspension_origin",
- "hide_collections",
- "enable_rss",
}
}
diff --git a/internal/processing/account/delete_test.go b/internal/processing/account/delete_test.go
index 95df3cec5..de7c8e08c 100644
--- a/internal/processing/account/delete_test.go
+++ b/internal/processing/account/delete_test.go
@@ -66,14 +66,9 @@ func (suite *AccountDeleteTestSuite) TestAccountDeleteLocal() {
suite.Zero(updatedAccount.NoteRaw)
suite.False(*updatedAccount.Memorial)
suite.Empty(updatedAccount.AlsoKnownAsURIs)
- suite.Zero(updatedAccount.Reason)
suite.False(*updatedAccount.Discoverable)
- suite.Zero(updatedAccount.StatusContentType)
- suite.Zero(updatedAccount.CustomCSS)
suite.WithinDuration(time.Now(), updatedAccount.SuspendedAt, 1*time.Minute)
suite.Equal(suspensionOrigin, updatedAccount.SuspensionOrigin)
- suite.True(*updatedAccount.HideCollections)
- suite.False(*updatedAccount.EnableRSS)
updatedUser, err := suite.db.GetUserByAccountID(ctx, testAccount.ID)
if err != nil {
diff --git a/internal/processing/account/rss.go b/internal/processing/account/rss.go
index df49af21f..f2c6cba5e 100644
--- a/internal/processing/account/rss.go
+++ b/internal/processing/account/rss.go
@@ -64,7 +64,7 @@ func (p *Processor) GetRSSFeedForUsername(ctx context.Context, username string)
}
// Ensure account has rss feed enabled.
- if !*account.EnableRSS {
+ if !*account.Settings.EnableRSS {
err = gtserror.New("account RSS feed not enabled")
return nil, never, gtserror.NewErrorNotFound(err)
}
diff --git a/internal/processing/account/update.go b/internal/processing/account/update.go
index 5dc93fa1d..7b5561138 100644
--- a/internal/processing/account/update.go
+++ b/internal/processing/account/update.go
@@ -47,6 +47,11 @@ func (p *Processor) selectNoteFormatter(contentType string) text.FormatFunc {
// Update processes the update of an account with the given form.
func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode) {
+ // Ensure account populated; we'll need settings.
+ if err := p.state.DB.PopulateAccount(ctx, account); err != nil {
+ log.Errorf(ctx, "error(s) populating account, will continue: %s", err)
+ }
+
if form.Discoverable != nil {
account.Discoverable = form.Discoverable
}
@@ -146,7 +151,7 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
}
// Format + set note according to user prefs.
- f := p.selectNoteFormatter(account.StatusContentType)
+ f := p.selectNoteFormatter(account.Settings.StatusContentType)
formatNoteResult := f(ctx, p.parseMention, account.ID, "", account.NoteRaw)
account.Note = formatNoteResult.HTML
@@ -227,11 +232,11 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
if err != nil {
return nil, gtserror.NewErrorBadRequest(err)
}
- account.Language = language
+ account.Settings.Language = language
}
if form.Source.Sensitive != nil {
- account.Sensitive = form.Source.Sensitive
+ account.Settings.Sensitive = form.Source.Sensitive
}
if form.Source.Privacy != nil {
@@ -239,7 +244,7 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
return nil, gtserror.NewErrorBadRequest(err)
}
privacy := typeutils.APIVisToVis(apimodel.Visibility(*form.Source.Privacy))
- account.Privacy = privacy
+ account.Settings.Privacy = privacy
}
if form.Source.StatusContentType != nil {
@@ -247,7 +252,7 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
return nil, gtserror.NewErrorBadRequest(err, err.Error())
}
- account.StatusContentType = *form.Source.StatusContentType
+ account.Settings.StatusContentType = *form.Source.StatusContentType
}
}
@@ -256,18 +261,21 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
if err := validate.CustomCSS(customCSS); err != nil {
return nil, gtserror.NewErrorBadRequest(err, err.Error())
}
- account.CustomCSS = text.SanitizeToPlaintext(customCSS)
+ account.Settings.CustomCSS = text.SanitizeToPlaintext(customCSS)
}
if form.EnableRSS != nil {
- account.EnableRSS = form.EnableRSS
+ account.Settings.EnableRSS = form.EnableRSS
}
- err := p.state.DB.UpdateAccount(ctx, account)
- if err != nil {
+ if err := p.state.DB.UpdateAccount(ctx, account); err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("could not update account %s: %s", account.ID, err))
}
+ if err := p.state.DB.UpdateAccountSettings(ctx, account.Settings); err != nil {
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("could not update account settings %s: %s", account.ID, err))
+ }
+
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
APObjectType: ap.ObjectProfile,
APActivityType: ap.ActivityUpdate,
diff --git a/internal/processing/account/update_test.go b/internal/processing/account/update_test.go
index 87b4ebd50..76ad3abe8 100644
--- a/internal/processing/account/update_test.go
+++ b/internal/processing/account/update_test.go
@@ -126,9 +126,15 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateWithMention() {
}
func (suite *AccountUpdateTestSuite) TestAccountUpdateWithMarkdownNote() {
+ // Copy zork.
testAccount := >smodel.Account{}
*testAccount = *suite.testAccounts["local_account_1"]
+ // Copy zork's settings.
+ settings := >smodel.AccountSettings{}
+ *settings = *suite.testAccounts["local_account_1"].Settings
+ testAccount.Settings = settings
+
var (
ctx = context.Background()
note = "*hello* ~~here~~ i am!"
@@ -136,8 +142,8 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateWithMarkdownNote() {
)
// Set status content type of account 1 to markdown for this test.
- testAccount.StatusContentType = "text/markdown"
- if err := suite.db.UpdateAccount(ctx, testAccount, "status_content_type"); err != nil {
+ testAccount.Settings.StatusContentType = "text/markdown"
+ if err := suite.db.UpdateAccountSettings(ctx, testAccount.Settings, "status_content_type"); err != nil {
suite.FailNow(err.Error())
}
diff --git a/internal/processing/preferences.go b/internal/processing/preferences.go
index 90fc86430..0a5f566ae 100644
--- a/internal/processing/preferences.go
+++ b/internal/processing/preferences.go
@@ -32,9 +32,9 @@ func (p *Processor) PreferencesGet(ctx context.Context, accountID string) (*apim
}
return &apimodel.Preferences{
- PostingDefaultVisibility: mastoPrefVisibility(act.Privacy),
- PostingDefaultSensitive: *act.Sensitive,
- PostingDefaultLanguage: act.Language,
+ PostingDefaultVisibility: mastoPrefVisibility(act.Settings.Privacy),
+ PostingDefaultSensitive: *act.Settings.Sensitive,
+ PostingDefaultLanguage: act.Settings.Language,
// The Reading* preferences don't appear to actually be settable by the
// client, so forcing some sensible defaults here
ReadingExpandMedia: "default",
diff --git a/internal/processing/status/create.go b/internal/processing/status/create.go
index 01ded74bd..d758fc0fb 100644
--- a/internal/processing/status/create.go
+++ b/internal/processing/status/create.go
@@ -50,6 +50,11 @@ func (p *Processor) Create(
*apimodel.Status,
gtserror.WithCode,
) {
+ // Ensure account populated; we'll need settings.
+ if err := p.state.DB.PopulateAccount(ctx, requester); err != nil {
+ log.Errorf(ctx, "error(s) populating account, will continue: %s", err)
+ }
+
// Generate new ID for status.
statusID := id.NewULID()
@@ -112,11 +117,11 @@ func (p *Processor) Create(
return nil, errWithCode
}
- if err := processVisibility(form, requester.Privacy, status); err != nil {
+ if err := processVisibility(form, requester.Settings.Privacy, status); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
- if err := processLanguage(form, requester.Language, status); err != nil {
+ if err := processLanguage(form, requester.Settings.Language, status); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@@ -369,7 +374,7 @@ func processLanguage(form *apimodel.AdvancedStatusCreateForm, accountDefaultLang
func (p *Processor) processContent(ctx context.Context, parseMention gtsmodel.ParseMentionFunc, form *apimodel.AdvancedStatusCreateForm, status *gtsmodel.Status) error {
if form.ContentType == "" {
// If content type wasn't specified, use the author's preferred content-type.
- contentType := apimodel.StatusContentType(status.Account.StatusContentType)
+ contentType := apimodel.StatusContentType(status.Account.Settings.StatusContentType)
form.ContentType = contentType
}
diff --git a/internal/processing/workers/fromfediapi_test.go b/internal/processing/workers/fromfediapi_test.go
index b7466ec73..1dbefca84 100644
--- a/internal/processing/workers/fromfediapi_test.go
+++ b/internal/processing/workers/fromfediapi_test.go
@@ -362,9 +362,7 @@ func (suite *FromFediAPITestSuite) TestProcessAccountDelete() {
suite.Empty(dbAccount.AvatarRemoteURL)
suite.Empty(dbAccount.HeaderMediaAttachmentID)
suite.Empty(dbAccount.HeaderRemoteURL)
- suite.Empty(dbAccount.Reason)
suite.Empty(dbAccount.Fields)
- suite.True(*dbAccount.HideCollections)
suite.False(*dbAccount.Discoverable)
suite.WithinDuration(time.Now(), dbAccount.SuspendedAt, 30*time.Second)
suite.Equal(dbAccount.ID, dbAccount.SuspensionOrigin)
diff --git a/internal/trans/import.go b/internal/trans/import.go
index 0fd539114..c77b439f5 100644
--- a/internal/trans/import.go
+++ b/internal/trans/import.go
@@ -25,6 +25,7 @@
"io"
"os"
+ "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/log"
transmodel "github.com/superseriousbusiness/gotosocial/internal/trans/model"
)
@@ -73,6 +74,14 @@ func (i *importer) inputEntry(ctx context.Context, entry transmodel.Entry) error
if err := i.putInDB(ctx, account); err != nil {
return fmt.Errorf("inputEntry: error adding account to database: %s", err)
}
+ if account.Domain == "" && account.Username != config.GetHost() {
+ // Local, non-instance account.
+ // Insert barebones settings model.
+ settings := &transmodel.AccountSettings{AccountID: account.ID}
+ if err := i.putInDB(ctx, settings); err != nil {
+ return fmt.Errorf("inputEntry: error adding account settings to database: %s", err)
+ }
+ }
log.Infof(ctx, "added account with id %s", account.ID)
return nil
case transmodel.TransBlock:
diff --git a/internal/trans/import_test.go b/internal/trans/import_test.go
index ac70efd29..5177ec45b 100644
--- a/internal/trans/import_test.go
+++ b/internal/trans/import_test.go
@@ -106,11 +106,6 @@ func (suite *ImportMinimalTestSuite) TestImportMinimalOK() {
suite.Equal(testAccountBefore.Memorial, testAccountAfter.Memorial)
suite.Equal(testAccountBefore.Bot, testAccountAfter.Bot)
suite.Equal(testAccountBefore.Locked, testAccountAfter.Locked)
- suite.Equal(testAccountBefore.Reason, testAccountAfter.Reason)
- suite.Equal(testAccountBefore.Privacy, testAccountAfter.Privacy)
- suite.Equal(testAccountBefore.Sensitive, testAccountAfter.Sensitive)
- suite.Equal(testAccountBefore.Language, testAccountAfter.Language)
- suite.Equal(testAccountBefore.StatusContentType, testAccountAfter.StatusContentType)
suite.Equal(testAccountBefore.URI, testAccountAfter.URI)
suite.Equal(testAccountBefore.URL, testAccountAfter.URL)
suite.Equal(testAccountBefore.InboxURI, testAccountAfter.InboxURI)
@@ -123,7 +118,6 @@ func (suite *ImportMinimalTestSuite) TestImportMinimalOK() {
suite.Equal(testAccountBefore.PublicKey, testAccountAfter.PublicKey)
suite.Equal(testAccountBefore.PublicKeyURI, testAccountAfter.PublicKeyURI)
suite.Equal(testAccountBefore.SuspendedAt, testAccountAfter.SuspendedAt)
- suite.Equal(testAccountBefore.HideCollections, testAccountAfter.HideCollections)
suite.Equal(testAccountBefore.SuspensionOrigin, testAccountAfter.SuspensionOrigin)
}
diff --git a/internal/trans/model/account.go b/internal/trans/model/account.go
index 73695f7be..097dea3a3 100644
--- a/internal/trans/model/account.go
+++ b/internal/trans/model/account.go
@@ -36,13 +36,8 @@ type Account struct {
NoteRaw string `json:"noteRaw,omitempty" bun:",nullzero"`
Memorial *bool `json:"memorial"`
Bot *bool `json:"bot"`
- Reason string `json:"reason,omitempty" bun:",nullzero"`
Locked *bool `json:"locked"`
Discoverable *bool `json:"discoverable"`
- Privacy string `json:"privacy,omitempty" bun:",nullzero"`
- Sensitive *bool `json:"sensitive"`
- Language string `json:"language,omitempty" bun:",nullzero"`
- StatusContentType string `json:"statusContentType,omitempty" bun:",nullzero"`
URI string `json:"uri" bun:",nullzero"`
URL string `json:"url" bun:",nullzero"`
InboxURI string `json:"inboxURI" bun:",nullzero"`
@@ -59,6 +54,9 @@ type Account struct {
SensitizedAt *time.Time `json:"sensitizedAt,omitempty" bun:",nullzero"`
SilencedAt *time.Time `json:"silencedAt,omitempty" bun:",nullzero"`
SuspendedAt *time.Time `json:"suspendedAt,omitempty" bun:",nullzero"`
- HideCollections *bool `json:"hideCollections"`
SuspensionOrigin string `json:"suspensionOrigin,omitempty" bun:",nullzero"`
}
+
+type AccountSettings struct {
+ AccountID string
+}
diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go
index b262030de..b5e713554 100644
--- a/internal/typeutils/astointernal.go
+++ b/internal/typeutils/astointernal.go
@@ -130,13 +130,8 @@ func (c *Converter) ASRepresentationToAccount(ctx context.Context, accountable a
// Extract account note (bio / summary).
acct.Note = ap.ExtractSummary(accountable)
- // Assume:
- // - memorial (TODO)
- // - sensitive (TODO)
- // - hide collections (TODO)
+ // Assume not memorial (todo)
acct.Memorial = util.Ptr(false)
- acct.Sensitive = util.Ptr(false)
- acct.HideCollections = util.Ptr(false)
// Extract 'manuallyApprovesFollowers' aka locked account (default = true).
manuallyApprovesFollowers := ap.GetManuallyApprovesFollowers(accountable)
@@ -146,9 +141,6 @@ func (c *Converter) ASRepresentationToAccount(ctx context.Context, accountable a
discoverable := ap.GetDiscoverable(accountable)
acct.Discoverable = &discoverable
- // Assume not an RSS feed.
- acct.EnableRSS = util.Ptr(false)
-
// Extract the URL property.
urls := ap.GetURL(accountable)
if len(urls) == 0 {
diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go
index df4598deb..0ff3c2268 100644
--- a/internal/typeutils/internaltofrontend.go
+++ b/internal/typeutils/internaltofrontend.go
@@ -78,14 +78,14 @@ func (c *Converter) AccountToAPIAccountSensitive(ctx context.Context, a *gtsmode
}
statusContentType := string(apimodel.StatusContentTypeDefault)
- if a.StatusContentType != "" {
- statusContentType = a.StatusContentType
+ if a.Settings.StatusContentType != "" {
+ statusContentType = a.Settings.StatusContentType
}
apiAccount.Source = &apimodel.Source{
- Privacy: c.VisToAPIVis(ctx, a.Privacy),
- Sensitive: *a.Sensitive,
- Language: a.Language,
+ Privacy: c.VisToAPIVis(ctx, a.Settings.Privacy),
+ Sensitive: *a.Settings.Sensitive,
+ Language: a.Settings.Language,
StatusContentType: statusContentType,
Note: a.NoteRaw,
Fields: c.fieldsToAPIFields(a.FieldsRaw),
@@ -170,10 +170,13 @@ func (c *Converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
// Bits that vary between remote + local accounts:
// - Account (acct) string.
// - Role.
+ // - Settings things (enableRSS, customCSS).
var (
- acct string
- role *apimodel.AccountRole
+ acct string
+ role *apimodel.AccountRole
+ enableRSS bool
+ customCSS string
)
if a.IsRemote() {
@@ -203,6 +206,9 @@ func (c *Converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
default:
role = &apimodel.AccountRole{Name: apimodel.AccountRoleUser}
}
+
+ enableRSS = *a.Settings.EnableRSS
+ customCSS = a.Settings.CustomCSS
}
acct = a.Username // omit domain
@@ -239,7 +245,6 @@ func (c *Converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
locked = boolPtrDef("locked", a.Locked, true)
discoverable = boolPtrDef("discoverable", a.Discoverable, false)
bot = boolPtrDef("bot", a.Bot, false)
- enableRSS = boolPtrDef("enableRSS", a.EnableRSS, false)
)
// Remaining properties are simple and
@@ -267,7 +272,7 @@ func (c *Converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
Emojis: apiEmojis,
Fields: fields,
Suspended: !a.SuspendedAt.IsZero(),
- CustomCSS: a.CustomCSS,
+ CustomCSS: customCSS,
EnableRSS: enableRSS,
Role: role,
Moved: moved,
@@ -376,6 +381,10 @@ func (c *Converter) AccountToAdminAPIAccount(ctx context.Context, a *gtsmodel.Ac
createdByApplicationID string
)
+ if err := c.state.DB.PopulateAccount(ctx, a); err != nil {
+ log.Errorf(ctx, "error(s) populating account, will continue: %s", err)
+ }
+
if a.IsRemote() {
// Domain may be in Punycode,
// de-punify it just in case.
@@ -404,8 +413,8 @@ func (c *Converter) AccountToAdminAPIAccount(ctx context.Context, a *gtsmodel.Ac
}
locale = user.Locale
- if user.Account.Reason != "" {
- inviteRequest = &user.Account.Reason
+ if a.Settings.Reason != "" {
+ inviteRequest = &a.Settings.Reason
}
if *user.Admin {
diff --git a/test/envparsing.sh b/test/envparsing.sh
index 72b7caa1d..ed136064d 100755
--- a/test/envparsing.sh
+++ b/test/envparsing.sh
@@ -26,6 +26,7 @@ EXPECT=$(cat << "EOF"
"cache": {
"account-mem-ratio": 5,
"account-note-mem-ratio": 1,
+ "account-settings-mem-ratio": 0.1,
"application-mem-ratio": 0.1,
"block-mem-ratio": 3,
"boost-of-ids-mem-ratio": 3,
diff --git a/testrig/db.go b/testrig/db.go
index a83d93b16..83bc46ec8 100644
--- a/testrig/db.go
+++ b/testrig/db.go
@@ -70,6 +70,7 @@
>smodel.Report{},
>smodel.Rule{},
>smodel.AccountNote{},
+ >smodel.AccountSettings{},
}
// NewTestDB returns a new initialized, empty database for testing.
@@ -206,6 +207,12 @@ func StandardDBSetup(db db.DB, accounts map[string]*gtsmodel.Account) {
}
}
+ for _, v := range NewTestAccountSettings() {
+ if err := db.Put(ctx, v); err != nil {
+ log.Panic(nil, err)
+ }
+ }
+
for _, v := range NewTestAttachments() {
if err := db.Put(ctx, v); err != nil {
log.Panic(nil, err)
diff --git a/testrig/testmodels.go b/testrig/testmodels.go
index 929317904..9d014bbca 100644
--- a/testrig/testmodels.go
+++ b/testrig/testmodels.go
@@ -286,6 +286,8 @@ func NewTestUsers() map[string]*gtsmodel.User {
// NewTestAccounts returns a map of accounts keyed by what type of account they are.
func NewTestAccounts() map[string]*gtsmodel.Account {
+ settings := NewTestAccountSettings()
+
accounts := map[string]*gtsmodel.Account{
"instance_account": {
ID: "01AY6P665V14JJR0AFVRT7311Y",
@@ -301,12 +303,8 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
CreatedAt: TimeMustParse("2020-05-17T13:10:59Z"),
UpdatedAt: TimeMustParse("2020-05-17T13:10:59Z"),
Bot: util.Ptr(false),
- Reason: "",
Locked: util.Ptr(false),
Discoverable: util.Ptr(true),
- Privacy: gtsmodel.VisibilityPublic,
- Sensitive: util.Ptr(false),
- Language: "en",
URI: "http://localhost:8080/users/localhost:8080",
URL: "http://localhost:8080/@localhost:8080",
PublicKeyURI: "http://localhost:8080/users/localhost:8080#main-key",
@@ -322,9 +320,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
- HideCollections: util.Ptr(false),
SuspensionOrigin: "",
- EnableRSS: util.Ptr(false),
},
"unconfirmed_account": {
ID: "01F8MH0BBE4FHXPH513MBVFHB0",
@@ -339,12 +335,8 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
CreatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
Bot: util.Ptr(false),
- Reason: "hi, please let me in! I'm looking for somewhere neato bombeato to hang out.",
Locked: util.Ptr(false),
Discoverable: util.Ptr(false),
- Privacy: gtsmodel.VisibilityPublic,
- Sensitive: util.Ptr(false),
- Language: "en",
URI: "http://localhost:8080/users/weed_lord420",
URL: "http://localhost:8080/@weed_lord420",
FetchedAt: time.Time{},
@@ -360,9 +352,8 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
- HideCollections: util.Ptr(false),
SuspensionOrigin: "",
- EnableRSS: util.Ptr(false),
+ Settings: settings["unconfirmed_account"],
},
"admin_account": {
ID: "01F8MH17FWEB39HZJ76B6VXSKF",
@@ -378,12 +369,8 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
CreatedAt: TimeMustParse("2022-05-17T13:10:59Z"),
UpdatedAt: TimeMustParse("2022-05-17T13:10:59Z"),
Bot: util.Ptr(false),
- Reason: "",
Locked: util.Ptr(false),
Discoverable: util.Ptr(true),
- Privacy: gtsmodel.VisibilityPublic,
- Sensitive: util.Ptr(false),
- Language: "en",
URI: "http://localhost:8080/users/admin",
URL: "http://localhost:8080/@admin",
PublicKeyURI: "http://localhost:8080/users/admin#main-key",
@@ -399,9 +386,8 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
- HideCollections: util.Ptr(false),
SuspensionOrigin: "",
- EnableRSS: util.Ptr(true),
+ Settings: settings["admin_account"],
},
"local_account_1": {
ID: "01F8MH1H7YV1Z7D2C8K2730QBF",
@@ -417,12 +403,8 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
CreatedAt: TimeMustParse("2022-05-20T11:09:18Z"),
UpdatedAt: TimeMustParse("2022-05-20T11:09:18Z"),
Bot: util.Ptr(false),
- Reason: "I wanna be on this damned webbed site so bad! Please! Wow",
Locked: util.Ptr(false),
Discoverable: util.Ptr(true),
- Privacy: gtsmodel.VisibilityPublic,
- Sensitive: util.Ptr(false),
- Language: "en",
URI: "http://localhost:8080/users/the_mighty_zork",
URL: "http://localhost:8080/@the_mighty_zork",
FetchedAt: time.Time{},
@@ -438,9 +420,8 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
- HideCollections: util.Ptr(false),
SuspensionOrigin: "",
- EnableRSS: util.Ptr(true),
+ Settings: settings["local_account_1"],
},
"local_account_2": {
ID: "01F8MH5NBDF2MV7CTC4Q5128HF",
@@ -475,12 +456,8 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
CreatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
Bot: util.Ptr(false),
- Reason: "",
Locked: util.Ptr(true),
Discoverable: util.Ptr(false),
- Privacy: gtsmodel.VisibilityFollowersOnly,
- Sensitive: util.Ptr(true),
- Language: "fr",
URI: "http://localhost:8080/users/1happyturtle",
URL: "http://localhost:8080/@1happyturtle",
FetchedAt: time.Time{},
@@ -496,9 +473,8 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
- HideCollections: util.Ptr(false),
SuspensionOrigin: "",
- EnableRSS: util.Ptr(false),
+ Settings: settings["local_account_2"],
},
"remote_account_1": {
ID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
@@ -514,8 +490,6 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
Bot: util.Ptr(false),
Locked: util.Ptr(false),
Discoverable: util.Ptr(true),
- Sensitive: util.Ptr(false),
- Language: "en",
URI: "http://fossbros-anonymous.io/users/foss_satan",
URL: "http://fossbros-anonymous.io/@foss_satan",
FetchedAt: time.Time{},
@@ -532,9 +506,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
- HideCollections: util.Ptr(false),
SuspensionOrigin: "",
- EnableRSS: util.Ptr(false),
},
"remote_account_2": {
ID: "01FHMQX3GAABWSM0S2VZEC2SWC",
@@ -550,8 +522,6 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
Bot: util.Ptr(false),
Locked: util.Ptr(true),
Discoverable: util.Ptr(true),
- Sensitive: util.Ptr(false),
- Language: "en",
URI: "http://example.org/users/Some_User",
URL: "http://example.org/@Some_User",
FetchedAt: time.Time{},
@@ -568,9 +538,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
- HideCollections: util.Ptr(false),
SuspensionOrigin: "",
- EnableRSS: util.Ptr(false),
},
"remote_account_3": {
ID: "062G5WYKY35KKD12EMSM3F8PJ8",
@@ -586,8 +554,6 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
Bot: util.Ptr(false),
Locked: util.Ptr(true),
Discoverable: util.Ptr(true),
- Sensitive: util.Ptr(false),
- Language: "en",
URI: "http://thequeenisstillalive.technology/users/her_fuckin_maj",
URL: "http://thequeenisstillalive.technology/@her_fuckin_maj",
FetchedAt: time.Time{},
@@ -604,10 +570,8 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
- HideCollections: util.Ptr(false),
SuspensionOrigin: "",
HeaderMediaAttachmentID: "01PFPMWK2FF0D9WMHEJHR07C3R",
- EnableRSS: util.Ptr(false),
},
"remote_account_4": {
ID: "07GZRBAEMBNKGZ8Z9VSKSXKR98",
@@ -622,8 +586,6 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
Bot: util.Ptr(false),
Locked: util.Ptr(false),
Discoverable: util.Ptr(false),
- Sensitive: util.Ptr(false),
- Language: "de",
URI: "https://xn--xample-ova.org/users/%C3%BCser",
URL: "https://xn--xample-ova.org/users/@%C3%BCser",
FetchedAt: time.Time{},
@@ -640,10 +602,8 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
- HideCollections: util.Ptr(false),
SuspensionOrigin: "",
HeaderMediaAttachmentID: "",
- EnableRSS: util.Ptr(false),
},
}
@@ -698,6 +658,55 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
return accounts
}
+func NewTestAccountSettings() map[string]*gtsmodel.AccountSettings {
+ return map[string]*gtsmodel.AccountSettings{
+ "unconfirmed_account": {
+ AccountID: "01F8MH0BBE4FHXPH513MBVFHB0",
+ CreatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
+ UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
+ Reason: "hi, please let me in! I'm looking for somewhere neato bombeato to hang out.",
+ Privacy: gtsmodel.VisibilityPublic,
+ Sensitive: util.Ptr(false),
+ Language: "en",
+ EnableRSS: util.Ptr(false),
+ HideCollections: util.Ptr(false),
+ },
+ "admin_account": {
+ AccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
+ CreatedAt: TimeMustParse("2022-05-17T13:10:59Z"),
+ UpdatedAt: TimeMustParse("2022-05-17T13:10:59Z"),
+ Reason: "",
+ Privacy: gtsmodel.VisibilityPublic,
+ Sensitive: util.Ptr(false),
+ Language: "en",
+ EnableRSS: util.Ptr(true),
+ HideCollections: util.Ptr(false),
+ },
+ "local_account_1": {
+ AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
+ CreatedAt: TimeMustParse("2022-05-20T11:09:18Z"),
+ UpdatedAt: TimeMustParse("2022-05-20T11:09:18Z"),
+ Reason: "I wanna be on this damned webbed site so bad! Please! Wow",
+ Privacy: gtsmodel.VisibilityPublic,
+ Sensitive: util.Ptr(false),
+ Language: "en",
+ EnableRSS: util.Ptr(true),
+ HideCollections: util.Ptr(false),
+ },
+ "local_account_2": {
+ AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
+ CreatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
+ UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
+ Reason: "",
+ Privacy: gtsmodel.VisibilityFollowersOnly,
+ Sensitive: util.Ptr(true),
+ Language: "fr",
+ EnableRSS: util.Ptr(false),
+ HideCollections: util.Ptr(false),
+ },
+ }
+}
+
func NewTestTombstones() map[string]*gtsmodel.Tombstone {
return map[string]*gtsmodel.Tombstone{
"https://somewhere.mysterious/users/rest_in_piss#main-key": {