mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-11-22 13:17:30 -06:00
[chore] Move local account settings to separate db table (#2770)
* [chore] Move local account settings to separate database model * don't use separate settings_id
This commit is contained in:
parent
0767647056
commit
7f4a0a1aeb
36 changed files with 525 additions and 191 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
122
internal/db/bundb/migrations/20240318115336_account_settings.go
Normal file
122
internal/db/bundb/migrations/20240318115336_account_settings.go
Normal file
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue