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.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 +411,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)
|
||||
}
|
||||
|
||||
|
@ -466,7 +462,6 @@ func (suite *InboxPostTestSuite) TestPostDelete() {
|
|||
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)
|
||||
|
|
|
@ -477,7 +477,7 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceBadContentTypeFormDa
|
|||
if err != nil {
|
||||
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() {
|
||||
|
|
|
@ -81,7 +81,7 @@ func (suite *AccountVerifyTestSuite) TestAccountVerifyGet() {
|
|||
suite.Equal(2, apimodelAccount.FollowingCount)
|
||||
suite.Equal(5, apimodelAccount.StatusesCount)
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
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) {
|
||||
c.GTS.Account().Trim(threshold)
|
||||
c.GTS.AccountNote().Trim(threshold)
|
||||
c.GTS.AccountPreferences().Trim(threshold)
|
||||
c.GTS.Block().Trim(threshold)
|
||||
c.GTS.BlockIDs().Trim(threshold)
|
||||
c.GTS.Emoji().Trim(threshold)
|
||||
|
|
27
internal/cache/gts.go
vendored
27
internal/cache/gts.go
vendored
|
@ -32,6 +32,7 @@
|
|||
type GTSCaches struct {
|
||||
account *result.Cache[*gtsmodel.Account]
|
||||
accountNote *result.Cache[*gtsmodel.AccountNote]
|
||||
accountPreferences *result.Cache[*gtsmodel.AccountPreferences]
|
||||
block *result.Cache[*gtsmodel.Block]
|
||||
blockIDs *SliceCache[string]
|
||||
boostOfIDs *SliceCache[string]
|
||||
|
@ -67,6 +68,7 @@ type GTSCaches struct {
|
|||
func (c *GTSCaches) Init() {
|
||||
c.initAccount()
|
||||
c.initAccountNote()
|
||||
c.initAccountPreferences()
|
||||
c.initBlock()
|
||||
c.initBlockIDs()
|
||||
c.initBoostOfIDs()
|
||||
|
@ -117,6 +119,11 @@ func (c *GTSCaches) AccountNote() *result.Cache[*gtsmodel.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.
|
||||
func (c *GTSCaches) Block() *result.Cache[*gtsmodel.Block] {
|
||||
return c.block
|
||||
|
@ -303,6 +310,26 @@ func (c *GTSCaches) initAccountNote() {
|
|||
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() {
|
||||
// Calculate maximum cache size.
|
||||
cap := calculateResultCacheMax(
|
||||
|
|
19
internal/cache/size.go
vendored
19
internal/cache/size.go
vendored
|
@ -155,6 +155,7 @@ func totalOfRatios() float64 {
|
|||
return 0 +
|
||||
config.GetCacheAccountMemRatio() +
|
||||
config.GetCacheAccountNoteMemRatio() +
|
||||
config.GetCacheAccountPreferencesMemRatio() +
|
||||
config.GetCacheBlockMemRatio() +
|
||||
config.GetCacheBlockIDsMemRatio() +
|
||||
config.GetCacheBoostOfIDsMemRatio() +
|
||||
|
@ -199,9 +200,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,
|
||||
|
@ -216,9 +214,7 @@ func sizeofAccount() uintptr {
|
|||
SensitizedAt: time.Time{},
|
||||
SilencedAt: time.Now(),
|
||||
SuspendedAt: time.Now(),
|
||||
HideCollections: func() *bool { ok := true; return &ok }(),
|
||||
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 {
|
||||
return uintptr(size.Of(>smodel.Block{
|
||||
ID: exampleID,
|
||||
|
|
|
@ -178,6 +178,7 @@ type CacheConfiguration struct {
|
|||
MemoryTarget bytesize.Size `name:"memory-target"`
|
||||
AccountMemRatio float64 `name:"account-mem-ratio"`
|
||||
AccountNoteMemRatio float64 `name:"account-note-mem-ratio"`
|
||||
AccountPreferencesMemRatio float64 `name:"account-preferences-mem-ratio"`
|
||||
BlockMemRatio float64 `name:"block-mem-ratio"`
|
||||
BlockIDsMemRatio float64 `name:"block-mem-ratio"`
|
||||
BoostOfIDsMemRatio float64 `name:"boost-of-ids-mem-ratio"`
|
||||
|
|
|
@ -147,6 +147,7 @@
|
|||
// be able to make some more sense :D
|
||||
AccountMemRatio: 18,
|
||||
AccountNoteMemRatio: 0.1,
|
||||
AccountPreferencesMemRatio: 2,
|
||||
BlockMemRatio: 3,
|
||||
BlockIDsMemRatio: 3,
|
||||
BoostOfIDsMemRatio: 3,
|
||||
|
|
|
@ -2499,6 +2499,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) }
|
||||
|
||||
// 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
|
||||
func (st *ConfigState) GetCacheBlockMemRatio() (v float64) {
|
||||
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(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(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()
|
||||
}
|
||||
|
||||
|
@ -456,12 +466,93 @@ func (a *accountDB) SetAccountHeaderOrAvatar(ctx context.Context, mediaAttachmen
|
|||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
|
|
|
@ -333,14 +333,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.False(*newAccount.Memorial)
|
||||
suite.False(*newAccount.Bot)
|
||||
suite.False(*newAccount.Discoverable)
|
||||
suite.False(*newAccount.Sensitive)
|
||||
suite.False(*newAccount.HideCollections)
|
||||
}
|
||||
|
||||
func (suite *AccountTestSuite) TestGetAccountPinnedStatusesSomeResults() {
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"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
|
||||
// sign up with this username, we might already have an
|
||||
// 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) {
|
||||
// Real error occurred.
|
||||
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,
|
||||
DisplayName: newSignup.Username,
|
||||
Reason: newSignup.Reason,
|
||||
Privacy: gtsmodel.VisibilityDefault,
|
||||
URI: uris.UserURI,
|
||||
URL: uris.UserURL,
|
||||
InboxURI: uris.InboxURI,
|
||||
|
@ -136,6 +139,7 @@ func (a *adminDB) NewSignup(ctx context.Context, newSignup gtsmodel.NewSignup) (
|
|||
PrivateKey: privKey,
|
||||
PublicKey: &privKey.PublicKey,
|
||||
PublicKeyURI: uris.PublicKeyURI,
|
||||
PreferencesID: id.NewULID(),
|
||||
}
|
||||
|
||||
// Insert the new account!
|
||||
|
@ -145,6 +149,33 @@ func (a *adminDB) NewSignup(ctx context.Context, newSignup gtsmodel.NewSignup) (
|
|||
}
|
||||
|
||||
// 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.
|
||||
user, err := a.state.DB.GetUserByAccountID(ctx, account.ID)
|
||||
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
|
||||
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 +109,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)
|
||||
}
|
||||
|
||||
|
|
|
@ -47,8 +47,8 @@ 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
|
||||
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?
|
||||
|
@ -58,11 +58,6 @@ type Account struct {
|
|||
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
|
||||
|
@ -78,9 +73,9 @@ type Account struct {
|
|||
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
|
||||
PreferencesID string `bun:"type:CHAR(26),nullzero"` // ID of the LocalAccountPreferences entry for this account. Only set for local accounts.
|
||||
Preferences *AccountPreferences `bun:"-"` // Preferences corresponding to PreferencesID. Local accounts only.
|
||||
}
|
||||
|
||||
// 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
|
||||
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 := true
|
||||
acct.Locked = &locked // assume locked by default
|
||||
|
@ -140,10 +132,6 @@ func (c *converter) ASRepresentationToAccount(ctx context.Context, accountable a
|
|||
acct.Discoverable = &d
|
||||
}
|
||||
|
||||
// assume not rss feed
|
||||
enableRSS := false
|
||||
acct.EnableRSS = &enableRSS
|
||||
|
||||
// url property
|
||||
url, err := ap.ExtractURL(accountable)
|
||||
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) {
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// then adding the Source object to it...
|
||||
|
||||
// check pending follow requests aimed at this account
|
||||
frc, err := c.db.CountAccountFollowRequests(ctx, a.ID)
|
||||
// Ensure account preferences populated.
|
||||
// Accounts passed in to this function
|
||||
// should always be local accounts, so
|
||||
// a missing account preferences struct
|
||||
// is a real problem!
|
||||
if a.Preferences == nil {
|
||||
var err error
|
||||
a.Preferences, err = c.db.GetAccountPreferencesByAccountID(ctx, a.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error counting follow requests: %s", err)
|
||||
return nil, gtserror.Newf("error getting account preferences: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
statusContentType := string(apimodel.StatusContentTypeDefault)
|
||||
if a.StatusContentType != "" {
|
||||
statusContentType = a.StatusContentType
|
||||
// Count pending follow requests aimed at this account.
|
||||
frc, err := c.db.CountAccountFollowRequests(ctx, a.ID)
|
||||
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{
|
||||
Privacy: c.VisToAPIVis(ctx, a.Privacy),
|
||||
Sensitive: *a.Sensitive,
|
||||
Language: a.Language,
|
||||
Privacy: c.VisToAPIVis(ctx, a.Preferences.StatusPrivacy),
|
||||
Sensitive: *a.Preferences.StatusSensitive,
|
||||
Language: a.Preferences.StatusLanguage,
|
||||
StatusContentType: statusContentType,
|
||||
Note: a.NoteRaw,
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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
|
||||
lastPosted, err := c.db.GetAccountLastPosted(ctx, a.ID, false)
|
||||
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() {
|
||||
|
@ -166,25 +184,32 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
|
|||
var (
|
||||
acct string
|
||||
role *apimodel.AccountRole
|
||||
customCSS string
|
||||
enableRSS bool
|
||||
)
|
||||
|
||||
if a.IsRemote() {
|
||||
// Domain may be in Punycode,
|
||||
// de-punify it just in case.
|
||||
switch {
|
||||
|
||||
// Only extra thing we can do for
|
||||
// remote accounts is properly
|
||||
// de-punify their domain.
|
||||
case a.IsRemote():
|
||||
d, err := util.DePunify(a.Domain)
|
||||
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
|
||||
} else {
|
||||
// This is a local account, try to
|
||||
// fetch more info. Skip for instance
|
||||
// accounts since they have no user.
|
||||
if !a.IsInstance() {
|
||||
|
||||
// This is a non-instance local
|
||||
// account; fetch more info using
|
||||
// the account User. We skip this
|
||||
// step for instance accounts since
|
||||
// they have no user.
|
||||
case !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)
|
||||
return nil, gtserror.Newf("db error getting user for account %s: %w", a.ID, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
|
@ -195,9 +220,17 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
|
|||
default:
|
||||
role = &apimodel.AccountRole{Name: apimodel.AccountRoleUser}
|
||||
}
|
||||
}
|
||||
|
||||
acct = a.Username // omit domain
|
||||
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
|
||||
|
@ -225,8 +258,8 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
|
|||
Emojis: apiEmojis,
|
||||
Fields: fields,
|
||||
Suspended: !a.SuspendedAt.IsZero(),
|
||||
CustomCSS: a.CustomCSS,
|
||||
EnableRSS: *a.EnableRSS,
|
||||
CustomCSS: customCSS,
|
||||
EnableRSS: enableRSS,
|
||||
Role: role,
|
||||
}
|
||||
|
||||
|
@ -279,7 +312,7 @@ func (c *converter) AccountToAPIAccountBlocked(ctx context.Context, a *gtsmodel.
|
|||
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)
|
||||
return nil, gtserror.Newf("error getting user from database for account id %s: %w", a.ID, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
|
|
Loading…
Reference in a new issue