diff options
Diffstat (limited to 'internal/db')
-rw-r--r-- | internal/db/bundb/admin.go | 5 | ||||
-rw-r--r-- | internal/db/bundb/bundb.go | 13 | ||||
-rw-r--r-- | internal/db/bundb/user.go | 151 | ||||
-rw-r--r-- | internal/db/bundb/user_test.go | 73 | ||||
-rw-r--r-- | internal/db/db.go | 1 | ||||
-rw-r--r-- | internal/db/user.go | 42 |
6 files changed, 281 insertions, 4 deletions
diff --git a/internal/db/bundb/admin.go b/internal/db/bundb/admin.go index f66ed0294..9fa78eca0 100644 --- a/internal/db/bundb/admin.go +++ b/internal/db/bundb/admin.go @@ -30,6 +30,7 @@ import ( "time" "github.com/superseriousbusiness/gotosocial/internal/ap" + "github.com/superseriousbusiness/gotosocial/internal/cache" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" @@ -40,7 +41,8 @@ import ( ) type adminDB struct { - conn *DBConn + conn *DBConn + userCache *cache.UserCache } func (a *adminDB) IsUsernameAvailable(ctx context.Context, username string) (bool, db.Error) { @@ -175,6 +177,7 @@ func (a *adminDB) NewSignup(ctx context.Context, username string, reason string, Exec(ctx); err != nil { return nil, a.conn.ProcessError(err) } + a.userCache.Put(u) return u, nil } diff --git a/internal/db/bundb/bundb.go b/internal/db/bundb/bundb.go index 1579fae76..70a44d4c1 100644 --- a/internal/db/bundb/bundb.go +++ b/internal/db/bundb/bundb.go @@ -87,6 +87,7 @@ type DBService struct { db.Session db.Status db.Timeline + db.User conn *DBConn } @@ -181,13 +182,15 @@ func NewBunDBService(ctx context.Context) (db.DB, error) { notifCache.SetTTL(time.Minute*5, false) notifCache.Start(time.Second * 10) - // Prepare domain block cache + // Prepare other caches blockCache := cache.NewDomainBlockCache() + userCache := cache.NewUserCache() ps := &DBService{ Account: accounts, Admin: &adminDB{ - conn: conn, + conn: conn, + userCache: userCache, }, Basic: &basicDB{ conn: conn, @@ -219,7 +222,11 @@ func NewBunDBService(ctx context.Context) (db.DB, error) { }, Status: status, Timeline: timeline, - conn: conn, + User: &userDB{ + conn: conn, + cache: userCache, + }, + conn: conn, } // we can confidently return this useable service now diff --git a/internal/db/bundb/user.go b/internal/db/bundb/user.go new file mode 100644 index 000000000..46f24c4b2 --- /dev/null +++ b/internal/db/bundb/user.go @@ -0,0 +1,151 @@ +/* + GoToSocial + Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + + 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 bundb + +import ( + "context" + "time" + + "github.com/superseriousbusiness/gotosocial/internal/cache" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/uptrace/bun" +) + +type userDB struct { + conn *DBConn + cache *cache.UserCache +} + +func (u *userDB) newUserQ(user *gtsmodel.User) *bun.SelectQuery { + return u.conn. + NewSelect(). + Model(user). + Relation("Account") +} + +func (u *userDB) getUser(ctx context.Context, cacheGet func() (*gtsmodel.User, bool), dbQuery func(*gtsmodel.User) error) (*gtsmodel.User, db.Error) { + // Attempt to fetch cached user + user, cached := cacheGet() + + if !cached { + user = >smodel.User{} + + // Not cached! Perform database query + err := dbQuery(user) + if err != nil { + return nil, u.conn.ProcessError(err) + } + + // Place in the cache + u.cache.Put(user) + } + + return user, nil +} + +func (u *userDB) GetUserByID(ctx context.Context, id string) (*gtsmodel.User, db.Error) { + return u.getUser( + ctx, + func() (*gtsmodel.User, bool) { + return u.cache.GetByID(id) + }, + func(user *gtsmodel.User) error { + return u.newUserQ(user).Where("user.id = ?", id).Scan(ctx) + }, + ) +} + +func (u *userDB) GetUserByAccountID(ctx context.Context, accountID string) (*gtsmodel.User, db.Error) { + return u.getUser( + ctx, + func() (*gtsmodel.User, bool) { + return u.cache.GetByAccountID(accountID) + }, + func(user *gtsmodel.User) error { + return u.newUserQ(user).Where("user.account_id = ?", accountID).Scan(ctx) + }, + ) +} + +func (u *userDB) GetUserByEmailAddress(ctx context.Context, emailAddress string) (*gtsmodel.User, db.Error) { + return u.getUser( + ctx, + func() (*gtsmodel.User, bool) { + return u.cache.GetByEmail(emailAddress) + }, + func(user *gtsmodel.User) error { + return u.newUserQ(user).Where("user.email = ?", emailAddress).Scan(ctx) + }, + ) +} + +func (u *userDB) GetUserByConfirmationToken(ctx context.Context, confirmationToken string) (*gtsmodel.User, db.Error) { + return u.getUser( + ctx, + func() (*gtsmodel.User, bool) { + return u.cache.GetByConfirmationToken(confirmationToken) + }, + func(user *gtsmodel.User) error { + return u.newUserQ(user).Where("user.confirmation_token = ?", confirmationToken).Scan(ctx) + }, + ) +} + +func (u *userDB) PutUser(ctx context.Context, user *gtsmodel.User) (*gtsmodel.User, db.Error) { + if _, err := u.conn. + NewInsert(). + Model(user). + Exec(ctx); err != nil { + return nil, u.conn.ProcessError(err) + } + + u.cache.Put(user) + return user, nil +} + +func (u *userDB) UpdateUser(ctx context.Context, user *gtsmodel.User, columns ...string) (*gtsmodel.User, db.Error) { + // Update the user's last-updated + user.UpdatedAt = time.Now() + + if _, err := u.conn. + NewUpdate(). + Model(user). + WherePK(). + Column(columns...). + Exec(ctx); err != nil { + return nil, u.conn.ProcessError(err) + } + + u.cache.Invalidate(user.ID) + return user, nil +} + +func (u *userDB) DeleteUserByID(ctx context.Context, userID string) db.Error { + if _, err := u.conn. + NewDelete(). + Model(>smodel.User{ID: userID}). + WherePK(). + Exec(ctx); err != nil { + return u.conn.ProcessError(err) + } + + u.cache.Invalidate(userID) + return nil +} diff --git a/internal/db/bundb/user_test.go b/internal/db/bundb/user_test.go new file mode 100644 index 000000000..6ad59fc8e --- /dev/null +++ b/internal/db/bundb/user_test.go @@ -0,0 +1,73 @@ +/* + GoToSocial + Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + + 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 bundb_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +type UserTestSuite struct { + BunDBStandardTestSuite +} + +func (suite *UserTestSuite) TestGetUser() { + user, err := suite.db.GetUserByID(context.Background(), suite.testUsers["local_account_1"].ID) + suite.NoError(err) + suite.NotNil(user) +} + +func (suite *UserTestSuite) TestGetUserByEmailAddress() { + user, err := suite.db.GetUserByEmailAddress(context.Background(), suite.testUsers["local_account_1"].Email) + suite.NoError(err) + suite.NotNil(user) +} + +func (suite *UserTestSuite) TestGetUserByAccountID() { + user, err := suite.db.GetUserByAccountID(context.Background(), suite.testAccounts["local_account_1"].ID) + suite.NoError(err) + suite.NotNil(user) +} + +func (suite *UserTestSuite) TestUpdateUserSelectedColumns() { + testUser := suite.testUsers["local_account_1"] + user := >smodel.User{ + ID: testUser.ID, + Email: "whatever", + Locale: "es", + } + + user, err := suite.db.UpdateUser(context.Background(), user, "email", "locale") + suite.NoError(err) + suite.NotNil(user) + + dbUser, err := suite.db.GetUserByID(context.Background(), testUser.ID) + suite.NoError(err) + suite.NotNil(dbUser) + suite.Equal("whatever", dbUser.Email) + suite.Equal("es", dbUser.Locale) + suite.Equal(testUser.AccountID, dbUser.AccountID) +} + +func TestUserTestSuite(t *testing.T) { + suite.Run(t, new(UserTestSuite)) +} diff --git a/internal/db/db.go b/internal/db/db.go index 0c1f2602a..52a76ecdb 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -44,6 +44,7 @@ type DB interface { Session Status Timeline + User /* USEFUL CONVERSION FUNCTIONS diff --git a/internal/db/user.go b/internal/db/user.go new file mode 100644 index 000000000..a4d48db56 --- /dev/null +++ b/internal/db/user.go @@ -0,0 +1,42 @@ +/* + GoToSocial + Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + + 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 db + +import ( + "context" + + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +// User contains functions related to user getting/setting/creation. +type User interface { + // GetUserByID returns one user with the given ID, or an error if something goes wrong. + GetUserByID(ctx context.Context, id string) (*gtsmodel.User, Error) + // GetUserByAccountID returns one user by its account ID, or an error if something goes wrong. + GetUserByAccountID(ctx context.Context, accountID string) (*gtsmodel.User, Error) + // GetUserByID returns one user with the given email address, or an error if something goes wrong. + GetUserByEmailAddress(ctx context.Context, emailAddress string) (*gtsmodel.User, Error) + // GetUserByConfirmationToken returns one user by its confirmation token, or an error if something goes wrong. + GetUserByConfirmationToken(ctx context.Context, confirmationToken string) (*gtsmodel.User, Error) + // UpdateUser updates one user by its primary key. If columns is set, only given columns + // will be updated. If not set, all columns will be updated. + UpdateUser(ctx context.Context, user *gtsmodel.User, columns ...string) (*gtsmodel.User, Error) + // DeleteUserByID deletes one user by its ID. + DeleteUserByID(ctx context.Context, userID string) Error +} |