Merge user store and manager
This commit is contained in:
12
cmd/user.go
12
cmd/user.go
@@ -378,25 +378,19 @@ func createUserManager(c *cli.Context) (*user.Manager, error) {
|
|||||||
BcryptCost: user.DefaultUserPasswordBcryptCost,
|
BcryptCost: user.DefaultUserPasswordBcryptCost,
|
||||||
QueueWriterInterval: user.DefaultUserStatsQueueWriterInterval,
|
QueueWriterInterval: user.DefaultUserStatsQueueWriterInterval,
|
||||||
}
|
}
|
||||||
var store user.Store
|
|
||||||
if databaseURL != "" {
|
if databaseURL != "" {
|
||||||
pool, dbErr := db.OpenPostgres(databaseURL)
|
pool, dbErr := db.OpenPostgres(databaseURL)
|
||||||
if dbErr != nil {
|
if dbErr != nil {
|
||||||
return nil, dbErr
|
return nil, dbErr
|
||||||
}
|
}
|
||||||
store, err = user.NewPostgresStore(pool)
|
return user.NewPostgresManager(pool, authConfig)
|
||||||
} else if authFile != "" {
|
} else if authFile != "" {
|
||||||
if !util.FileExists(authFile) {
|
if !util.FileExists(authFile) {
|
||||||
return nil, errors.New("auth-file does not exist; please start the server at least once to create it")
|
return nil, errors.New("auth-file does not exist; please start the server at least once to create it")
|
||||||
}
|
}
|
||||||
store, err = user.NewSQLiteStore(authFile, authStartupQueries)
|
return user.NewSQLiteManager(authFile, authStartupQueries, authConfig)
|
||||||
} else {
|
|
||||||
return nil, errors.New("option database-url or auth-file not set; auth is unconfigured for this server")
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
return nil, errors.New("option database-url or auth-file not set; auth is unconfigured for this server")
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return user.NewManager(store, authConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readPasswordAndConfirm(c *cli.Context) (string, error) {
|
func readPasswordAndConfirm(c *cli.Context) (string, error) {
|
||||||
|
|||||||
@@ -235,19 +235,14 @@ func New(conf *Config) (*Server, error) {
|
|||||||
BcryptCost: conf.AuthBcryptCost,
|
BcryptCost: conf.AuthBcryptCost,
|
||||||
QueueWriterInterval: conf.AuthStatsQueueWriterInterval,
|
QueueWriterInterval: conf.AuthStatsQueueWriterInterval,
|
||||||
}
|
}
|
||||||
var store user.Store
|
|
||||||
if pool != nil {
|
if pool != nil {
|
||||||
store, err = user.NewPostgresStore(pool)
|
userManager, err = user.NewPostgresManager(pool, authConfig)
|
||||||
} else {
|
} else {
|
||||||
store, err = user.NewSQLiteStore(conf.AuthFile, conf.AuthStartupQueries)
|
userManager, err = user.NewSQLiteManager(conf.AuthFile, conf.AuthStartupQueries, authConfig)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
userManager, err = user.NewManager(store, authConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var firebaseClient *firebaseClient
|
var firebaseClient *firebaseClient
|
||||||
if conf.FirebaseKeyFile != "" {
|
if conf.FirebaseKeyFile != "" {
|
||||||
|
|||||||
1147
user/manager.go
1147
user/manager.go
File diff suppressed because it is too large
Load Diff
@@ -203,13 +203,14 @@ const (
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewPostgresStore creates a new PostgreSQL-backed user store using an existing database connection pool.
|
// NewPostgresManager creates a new Manager backed by a PostgreSQL database using an existing connection pool.
|
||||||
func NewPostgresStore(db *sql.DB) (Store, error) {
|
func NewPostgresManager(db *sql.DB, config *Config) (*Manager, error) {
|
||||||
if err := setupPostgres(db); err != nil {
|
if err := setupPostgres(db); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &commonStore{
|
manager := &Manager{
|
||||||
db: db,
|
config: config,
|
||||||
|
db: db,
|
||||||
queries: storeQueries{
|
queries: storeQueries{
|
||||||
// User queries
|
// User queries
|
||||||
selectUserByID: postgresSelectUserByIDQuery,
|
selectUserByID: postgresSelectUserByIDQuery,
|
||||||
@@ -277,5 +278,9 @@ func NewPostgresStore(db *sql.DB) (Store, error) {
|
|||||||
// Billing queries
|
// Billing queries
|
||||||
updateBilling: postgresUpdateBillingQuery,
|
updateBilling: postgresUpdateBillingQuery,
|
||||||
},
|
},
|
||||||
}, nil
|
}
|
||||||
|
if err := initManager(manager); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return manager, nil
|
||||||
}
|
}
|
||||||
@@ -201,8 +201,8 @@ const (
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewSQLiteStore creates a new SQLite-backed user store
|
// NewSQLiteManager creates a new Manager backed by a SQLite database
|
||||||
func NewSQLiteStore(filename, startupQueries string) (Store, error) {
|
func NewSQLiteManager(filename, startupQueries string, config *Config) (*Manager, error) {
|
||||||
parentDir := filepath.Dir(filename)
|
parentDir := filepath.Dir(filename)
|
||||||
if !util.FileExists(parentDir) {
|
if !util.FileExists(parentDir) {
|
||||||
return nil, fmt.Errorf("user database directory %s does not exist or is not accessible", parentDir)
|
return nil, fmt.Errorf("user database directory %s does not exist or is not accessible", parentDir)
|
||||||
@@ -217,8 +217,9 @@ func NewSQLiteStore(filename, startupQueries string) (Store, error) {
|
|||||||
if err := runSQLiteStartupQueries(db, startupQueries); err != nil {
|
if err := runSQLiteStartupQueries(db, startupQueries); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &commonStore{
|
manager := &Manager{
|
||||||
db: db,
|
config: config,
|
||||||
|
db: db,
|
||||||
queries: storeQueries{
|
queries: storeQueries{
|
||||||
selectUserByID: sqliteSelectUserByIDQuery,
|
selectUserByID: sqliteSelectUserByIDQuery,
|
||||||
selectUserByName: sqliteSelectUserByNameQuery,
|
selectUserByName: sqliteSelectUserByNameQuery,
|
||||||
@@ -275,5 +276,9 @@ func NewSQLiteStore(filename, startupQueries string) (Store, error) {
|
|||||||
deletePhoneNumber: sqliteDeletePhoneNumberQuery,
|
deletePhoneNumber: sqliteDeletePhoneNumberQuery,
|
||||||
updateBilling: sqliteUpdateBillingQuery,
|
updateBilling: sqliteUpdateBillingQuery,
|
||||||
},
|
},
|
||||||
}, nil
|
}
|
||||||
|
if err := initManager(manager); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return manager, nil
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
1059
user/store.go
1059
user/store.go
File diff suppressed because it is too large
Load Diff
@@ -1,713 +0,0 @@
|
|||||||
package user_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
dbtest "heckel.io/ntfy/v2/db/test"
|
|
||||||
"heckel.io/ntfy/v2/user"
|
|
||||||
)
|
|
||||||
|
|
||||||
func forEachStoreBackend(t *testing.T, f func(t *testing.T, store user.Store)) {
|
|
||||||
t.Run("sqlite", func(t *testing.T) {
|
|
||||||
store, err := user.NewSQLiteStore(filepath.Join(t.TempDir(), "user.db"), "")
|
|
||||||
require.Nil(t, err)
|
|
||||||
t.Cleanup(func() { store.Close() })
|
|
||||||
f(t, store)
|
|
||||||
})
|
|
||||||
t.Run("postgres", func(t *testing.T) {
|
|
||||||
testDB := dbtest.CreateTestPostgres(t)
|
|
||||||
store, err := user.NewPostgresStore(testDB)
|
|
||||||
require.Nil(t, err)
|
|
||||||
f(t, store)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreAddUser(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "phil", u.Name)
|
|
||||||
require.Equal(t, user.RoleUser, u.Role)
|
|
||||||
require.False(t, u.Provisioned)
|
|
||||||
require.NotEmpty(t, u.ID)
|
|
||||||
require.NotEmpty(t, u.SyncTopic)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreAddUserAlreadyExists(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
require.Equal(t, user.ErrUserExists, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreRemoveUser(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "phil", u.Name)
|
|
||||||
|
|
||||||
require.Nil(t, store.RemoveUser("phil"))
|
|
||||||
_, err = store.User("phil")
|
|
||||||
require.Equal(t, user.ErrUserNotFound, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreUserByID(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleAdmin, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
u2, err := store.UserByID(u.ID)
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, u.Name, u2.Name)
|
|
||||||
require.Equal(t, u.ID, u2.ID)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreUserByToken(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
tk, err := store.CreateToken(u.ID, "tk_test123", "test token", time.Now(), netip.MustParseAddr("1.2.3.4"), time.Now().Add(24*time.Hour), 0, false)
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "tk_test123", tk.Value)
|
|
||||||
|
|
||||||
u2, err := store.UserByToken(tk.Value)
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "phil", u2.Name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreUserByStripeCustomer(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
require.Nil(t, store.ChangeBilling("phil", &user.Billing{
|
|
||||||
StripeCustomerID: "cus_test123",
|
|
||||||
StripeSubscriptionID: "sub_test123",
|
|
||||||
}))
|
|
||||||
|
|
||||||
u, err := store.UserByStripeCustomer("cus_test123")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "phil", u.Name)
|
|
||||||
require.Equal(t, "cus_test123", u.Billing.StripeCustomerID)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreUsers(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
require.Nil(t, store.AddUser("ben", "benhash", user.RoleAdmin, false))
|
|
||||||
|
|
||||||
users, err := store.Users()
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.True(t, len(users) >= 3) // phil, ben, and the everyone user
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreUsersCount(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
count, err := store.UsersCount()
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.True(t, count >= 1) // At least the everyone user
|
|
||||||
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
count2, err := store.UsersCount()
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, count+1, count2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreChangePassword(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "philhash", u.Hash)
|
|
||||||
|
|
||||||
require.Nil(t, store.ChangePassword("phil", "newhash"))
|
|
||||||
u, err = store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "newhash", u.Hash)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreChangeRole(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, user.RoleUser, u.Role)
|
|
||||||
|
|
||||||
require.Nil(t, store.ChangeRole("phil", user.RoleAdmin))
|
|
||||||
u, err = store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, user.RoleAdmin, u.Role)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreTokens(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
expires := now.Add(24 * time.Hour)
|
|
||||||
origin := netip.MustParseAddr("9.9.9.9")
|
|
||||||
|
|
||||||
tk, err := store.CreateToken(u.ID, "tk_abc", "my token", now, origin, expires, 0, false)
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "tk_abc", tk.Value)
|
|
||||||
require.Equal(t, "my token", tk.Label)
|
|
||||||
|
|
||||||
// Get single token
|
|
||||||
tk2, err := store.Token(u.ID, "tk_abc")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "tk_abc", tk2.Value)
|
|
||||||
require.Equal(t, "my token", tk2.Label)
|
|
||||||
|
|
||||||
// Get all tokens
|
|
||||||
tokens, err := store.Tokens(u.ID)
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Len(t, tokens, 1)
|
|
||||||
require.Equal(t, "tk_abc", tokens[0].Value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreTokenChange(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
expires := time.Now().Add(time.Hour)
|
|
||||||
_, err = store.CreateToken(u.ID, "tk_abc", "old label", time.Now(), netip.MustParseAddr("1.2.3.4"), expires, 0, false)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
newExpires := time.Now().Add(2 * time.Hour)
|
|
||||||
require.Nil(t, store.ChangeToken(u.ID, "tk_abc", "new label", newExpires))
|
|
||||||
tk, err := store.Token(u.ID, "tk_abc")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "new label", tk.Label)
|
|
||||||
require.Equal(t, newExpires.Unix(), tk.Expires.Unix())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreTokenRemove(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
_, err = store.CreateToken(u.ID, "tk_abc", "label", time.Now(), netip.MustParseAddr("1.2.3.4"), time.Now().Add(time.Hour), 0, false)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
require.Nil(t, store.RemoveToken(u.ID, "tk_abc"))
|
|
||||||
_, err = store.Token(u.ID, "tk_abc")
|
|
||||||
require.Equal(t, user.ErrTokenNotFound, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreTokenRemoveExpired(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
// Create expired token and active token
|
|
||||||
_, err = store.CreateToken(u.ID, "tk_expired", "expired", time.Now(), netip.MustParseAddr("1.2.3.4"), time.Now().Add(-time.Hour), 0, false)
|
|
||||||
require.Nil(t, err)
|
|
||||||
_, err = store.CreateToken(u.ID, "tk_active", "active", time.Now(), netip.MustParseAddr("1.2.3.4"), time.Now().Add(time.Hour), 0, false)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
require.Nil(t, store.RemoveExpiredTokens())
|
|
||||||
|
|
||||||
// Expired token should be gone
|
|
||||||
_, err = store.Token(u.ID, "tk_expired")
|
|
||||||
require.Equal(t, user.ErrTokenNotFound, err)
|
|
||||||
|
|
||||||
// Active token should still exist
|
|
||||||
tk, err := store.Token(u.ID, "tk_active")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "tk_active", tk.Value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreTokenCreatePrunesExcess(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
// Create 2 tokens with no pruning
|
|
||||||
for i, name := range []string{"tk_a", "tk_b"} {
|
|
||||||
_, err = store.CreateToken(u.ID, name, name, time.Now(), netip.MustParseAddr("1.2.3.4"), time.Now().Add(time.Duration(i+1)*time.Hour), 0, false)
|
|
||||||
require.Nil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a 3rd token with maxTokenCount=2, which should prune tk_a (earliest expiry)
|
|
||||||
_, err = store.CreateToken(u.ID, "tk_c", "tk_c", time.Now(), netip.MustParseAddr("1.2.3.4"), time.Now().Add(3*time.Hour), 2, false)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
tokens, err := store.Tokens(u.ID)
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, 2, len(tokens))
|
|
||||||
|
|
||||||
// tk_a should be removed (earliest expiry)
|
|
||||||
_, err = store.Token(u.ID, "tk_a")
|
|
||||||
require.Equal(t, user.ErrTokenNotFound, err)
|
|
||||||
|
|
||||||
// tk_b and tk_c should remain
|
|
||||||
_, err = store.Token(u.ID, "tk_b")
|
|
||||||
require.Nil(t, err)
|
|
||||||
_, err = store.Token(u.ID, "tk_c")
|
|
||||||
require.Nil(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreTokenUpdateLastAccess(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
_, err = store.CreateToken(u.ID, "tk_abc", "label", time.Now(), netip.MustParseAddr("1.2.3.4"), time.Now().Add(time.Hour), 0, false)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
newTime := time.Now().Add(5 * time.Minute)
|
|
||||||
newOrigin := netip.MustParseAddr("5.5.5.5")
|
|
||||||
require.Nil(t, store.UpdateTokenLastAccess(map[string]*user.TokenUpdate{"tk_abc": {LastAccess: newTime, LastOrigin: newOrigin}}))
|
|
||||||
|
|
||||||
tk, err := store.Token(u.ID, "tk_abc")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, newTime.Unix(), tk.LastAccess.Unix())
|
|
||||||
require.Equal(t, newOrigin, tk.LastOrigin)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreAllowAccess(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
|
|
||||||
require.Nil(t, store.AllowAccess("phil", "mytopic", true, true, "", false))
|
|
||||||
grants, err := store.Grants("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Len(t, grants, 1)
|
|
||||||
require.Equal(t, "mytopic", grants[0].TopicPattern)
|
|
||||||
require.True(t, grants[0].Permission.IsReadWrite())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreAllowAccessReadOnly(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
|
|
||||||
require.Nil(t, store.AllowAccess("phil", "announcements", true, false, "", false))
|
|
||||||
grants, err := store.Grants("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Len(t, grants, 1)
|
|
||||||
require.True(t, grants[0].Permission.IsRead())
|
|
||||||
require.False(t, grants[0].Permission.IsWrite())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreResetAccess(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
require.Nil(t, store.AllowAccess("phil", "topic1", true, true, "", false))
|
|
||||||
require.Nil(t, store.AllowAccess("phil", "topic2", true, false, "", false))
|
|
||||||
|
|
||||||
grants, err := store.Grants("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Len(t, grants, 2)
|
|
||||||
|
|
||||||
require.Nil(t, store.ResetAccess("phil", "topic1"))
|
|
||||||
grants, err = store.Grants("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Len(t, grants, 1)
|
|
||||||
require.Equal(t, "topic2", grants[0].TopicPattern)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreResetAccessAll(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
require.Nil(t, store.AllowAccess("phil", "topic1", true, true, "", false))
|
|
||||||
require.Nil(t, store.AllowAccess("phil", "topic2", true, false, "", false))
|
|
||||||
|
|
||||||
require.Nil(t, store.ResetAccess("phil", ""))
|
|
||||||
grants, err := store.Grants("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Len(t, grants, 0)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreAuthorizeTopicAccess(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
require.Nil(t, store.AllowAccess("phil", "mytopic", true, true, "", false))
|
|
||||||
|
|
||||||
read, write, found, err := store.AuthorizeTopicAccess("phil", "mytopic")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.True(t, found)
|
|
||||||
require.True(t, read)
|
|
||||||
require.True(t, write)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreAuthorizeTopicAccessNotFound(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
|
|
||||||
_, _, found, err := store.AuthorizeTopicAccess("phil", "other")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.False(t, found)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreAuthorizeTopicAccessDenyAll(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
require.Nil(t, store.AllowAccess("phil", "secret", false, false, "", false))
|
|
||||||
|
|
||||||
read, write, found, err := store.AuthorizeTopicAccess("phil", "secret")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.True(t, found)
|
|
||||||
require.False(t, read)
|
|
||||||
require.False(t, write)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreReservations(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
require.Nil(t, store.AllowAccess("phil", "mytopic", true, true, "phil", false))
|
|
||||||
require.Nil(t, store.AllowAccess(user.Everyone, "mytopic", true, false, "phil", false))
|
|
||||||
|
|
||||||
reservations, err := store.Reservations("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Len(t, reservations, 1)
|
|
||||||
require.Equal(t, "mytopic", reservations[0].Topic)
|
|
||||||
require.True(t, reservations[0].Owner.IsReadWrite())
|
|
||||||
require.True(t, reservations[0].Everyone.IsRead())
|
|
||||||
require.False(t, reservations[0].Everyone.IsWrite())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreReservationsCount(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
require.Nil(t, store.AllowAccess("phil", "topic1", true, true, "phil", false))
|
|
||||||
require.Nil(t, store.AllowAccess("phil", "topic2", true, true, "phil", false))
|
|
||||||
|
|
||||||
count, err := store.ReservationsCount("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, int64(2), count)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreHasReservation(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
require.Nil(t, store.AllowAccess("phil", "mytopic", true, true, "phil", false))
|
|
||||||
|
|
||||||
has, err := store.HasReservation("phil", "mytopic")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.True(t, has)
|
|
||||||
|
|
||||||
has, err = store.HasReservation("phil", "other")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.False(t, has)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreReservationOwner(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
require.Nil(t, store.AllowAccess("phil", "mytopic", true, true, "phil", false))
|
|
||||||
|
|
||||||
owner, err := store.ReservationOwner("mytopic")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.NotEmpty(t, owner) // Returns the user ID
|
|
||||||
|
|
||||||
owner, err = store.ReservationOwner("unowned")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Empty(t, owner)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreTiers(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
tier := &user.Tier{
|
|
||||||
ID: "ti_test",
|
|
||||||
Code: "pro",
|
|
||||||
Name: "Pro",
|
|
||||||
MessageLimit: 5000,
|
|
||||||
MessageExpiryDuration: 24 * time.Hour,
|
|
||||||
EmailLimit: 100,
|
|
||||||
CallLimit: 10,
|
|
||||||
ReservationLimit: 20,
|
|
||||||
AttachmentFileSizeLimit: 10 * 1024 * 1024,
|
|
||||||
AttachmentTotalSizeLimit: 100 * 1024 * 1024,
|
|
||||||
AttachmentExpiryDuration: 48 * time.Hour,
|
|
||||||
AttachmentBandwidthLimit: 500 * 1024 * 1024,
|
|
||||||
}
|
|
||||||
require.Nil(t, store.AddTier(tier))
|
|
||||||
|
|
||||||
// Get by code
|
|
||||||
t2, err := store.Tier("pro")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "ti_test", t2.ID)
|
|
||||||
require.Equal(t, "pro", t2.Code)
|
|
||||||
require.Equal(t, "Pro", t2.Name)
|
|
||||||
require.Equal(t, int64(5000), t2.MessageLimit)
|
|
||||||
require.Equal(t, int64(100), t2.EmailLimit)
|
|
||||||
require.Equal(t, int64(10), t2.CallLimit)
|
|
||||||
require.Equal(t, int64(20), t2.ReservationLimit)
|
|
||||||
|
|
||||||
// List all tiers
|
|
||||||
tiers, err := store.Tiers()
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Len(t, tiers, 1)
|
|
||||||
require.Equal(t, "pro", tiers[0].Code)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreTierUpdate(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
tier := &user.Tier{
|
|
||||||
ID: "ti_test",
|
|
||||||
Code: "pro",
|
|
||||||
Name: "Pro",
|
|
||||||
}
|
|
||||||
require.Nil(t, store.AddTier(tier))
|
|
||||||
|
|
||||||
tier.Name = "Professional"
|
|
||||||
tier.MessageLimit = 9999
|
|
||||||
require.Nil(t, store.UpdateTier(tier))
|
|
||||||
|
|
||||||
t2, err := store.Tier("pro")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "Professional", t2.Name)
|
|
||||||
require.Equal(t, int64(9999), t2.MessageLimit)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreTierRemove(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
tier := &user.Tier{
|
|
||||||
ID: "ti_test",
|
|
||||||
Code: "pro",
|
|
||||||
Name: "Pro",
|
|
||||||
}
|
|
||||||
require.Nil(t, store.AddTier(tier))
|
|
||||||
|
|
||||||
t2, err := store.Tier("pro")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "pro", t2.Code)
|
|
||||||
|
|
||||||
require.Nil(t, store.RemoveTier("pro"))
|
|
||||||
_, err = store.Tier("pro")
|
|
||||||
require.Equal(t, user.ErrTierNotFound, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreTierByStripePrice(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
tier := &user.Tier{
|
|
||||||
ID: "ti_test",
|
|
||||||
Code: "pro",
|
|
||||||
Name: "Pro",
|
|
||||||
StripeMonthlyPriceID: "price_monthly",
|
|
||||||
StripeYearlyPriceID: "price_yearly",
|
|
||||||
}
|
|
||||||
require.Nil(t, store.AddTier(tier))
|
|
||||||
|
|
||||||
t2, err := store.TierByStripePrice("price_monthly")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "pro", t2.Code)
|
|
||||||
|
|
||||||
t3, err := store.TierByStripePrice("price_yearly")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "pro", t3.Code)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreChangeTier(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
tier := &user.Tier{
|
|
||||||
ID: "ti_test",
|
|
||||||
Code: "pro",
|
|
||||||
Name: "Pro",
|
|
||||||
}
|
|
||||||
require.Nil(t, store.AddTier(tier))
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
require.Nil(t, store.ChangeTier("phil", "pro"))
|
|
||||||
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.NotNil(t, u.Tier)
|
|
||||||
require.Equal(t, "pro", u.Tier.Code)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStorePhoneNumbers(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
require.Nil(t, store.AddPhoneNumber(u.ID, "+1234567890"))
|
|
||||||
require.Nil(t, store.AddPhoneNumber(u.ID, "+0987654321"))
|
|
||||||
|
|
||||||
numbers, err := store.PhoneNumbers(u.ID)
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Len(t, numbers, 2)
|
|
||||||
|
|
||||||
require.Nil(t, store.RemovePhoneNumber(u.ID, "+1234567890"))
|
|
||||||
numbers, err = store.PhoneNumbers(u.ID)
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Len(t, numbers, 1)
|
|
||||||
require.Equal(t, "+0987654321", numbers[0])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreChangeSettings(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
lang := "de"
|
|
||||||
prefs := &user.Prefs{Language: &lang}
|
|
||||||
require.Nil(t, store.ChangeSettings(u.ID, prefs))
|
|
||||||
|
|
||||||
u2, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.NotNil(t, u2.Prefs)
|
|
||||||
require.Equal(t, "de", *u2.Prefs.Language)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreChangeBilling(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
|
|
||||||
billing := &user.Billing{
|
|
||||||
StripeCustomerID: "cus_123",
|
|
||||||
StripeSubscriptionID: "sub_456",
|
|
||||||
}
|
|
||||||
require.Nil(t, store.ChangeBilling("phil", billing))
|
|
||||||
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, "cus_123", u.Billing.StripeCustomerID)
|
|
||||||
require.Equal(t, "sub_456", u.Billing.StripeSubscriptionID)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreUpdateStats(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
stats := &user.Stats{Messages: 42, Emails: 3, Calls: 1}
|
|
||||||
require.Nil(t, store.UpdateStats(map[string]*user.Stats{u.ID: stats}))
|
|
||||||
|
|
||||||
u2, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, int64(42), u2.Stats.Messages)
|
|
||||||
require.Equal(t, int64(3), u2.Stats.Emails)
|
|
||||||
require.Equal(t, int64(1), u2.Stats.Calls)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreResetStats(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
require.Nil(t, store.UpdateStats(map[string]*user.Stats{u.ID: {Messages: 42, Emails: 3, Calls: 1}}))
|
|
||||||
require.Nil(t, store.ResetStats())
|
|
||||||
|
|
||||||
u2, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, int64(0), u2.Stats.Messages)
|
|
||||||
require.Equal(t, int64(0), u2.Stats.Emails)
|
|
||||||
require.Equal(t, int64(0), u2.Stats.Calls)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreMarkUserRemoved(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
require.Nil(t, store.MarkUserRemoved(u.ID, u.Name))
|
|
||||||
|
|
||||||
u2, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.True(t, u2.Deleted)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreRemoveDeletedUsers(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
u, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
require.Nil(t, store.MarkUserRemoved(u.ID, u.Name))
|
|
||||||
|
|
||||||
// RemoveDeletedUsers only removes users past the hard-delete duration (7 days).
|
|
||||||
// Immediately after marking, the user should still exist.
|
|
||||||
require.Nil(t, store.RemoveDeletedUsers())
|
|
||||||
u2, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.True(t, u2.Deleted)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreAllGrants(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
require.Nil(t, store.AddUser("ben", "benhash", user.RoleUser, false))
|
|
||||||
phil, err := store.User("phil")
|
|
||||||
require.Nil(t, err)
|
|
||||||
ben, err := store.User("ben")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
require.Nil(t, store.AllowAccess("phil", "topic1", true, true, "", false))
|
|
||||||
require.Nil(t, store.AllowAccess("ben", "topic2", true, false, "", false))
|
|
||||||
|
|
||||||
grants, err := store.AllGrants()
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Contains(t, grants, phil.ID)
|
|
||||||
require.Contains(t, grants, ben.ID)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreOtherAccessCount(t *testing.T) {
|
|
||||||
forEachStoreBackend(t, func(t *testing.T, store user.Store) {
|
|
||||||
require.Nil(t, store.AddUser("phil", "philhash", user.RoleUser, false))
|
|
||||||
require.Nil(t, store.AddUser("ben", "benhash", user.RoleUser, false))
|
|
||||||
require.Nil(t, store.AllowAccess("ben", "mytopic", true, true, "ben", false))
|
|
||||||
|
|
||||||
count, err := store.OtherAccessCount("phil", "mytopic")
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, 1, count)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"heckel.io/ntfy/v2/log"
|
|
||||||
"heckel.io/ntfy/v2/payments"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"heckel.io/ntfy/v2/log"
|
||||||
|
"heckel.io/ntfy/v2/payments"
|
||||||
)
|
)
|
||||||
|
|
||||||
// User is a struct that represents a user
|
// User is a struct that represents a user
|
||||||
@@ -273,3 +275,78 @@ var (
|
|||||||
ErrProvisionedUserChange = errors.New("cannot change or delete provisioned user")
|
ErrProvisionedUserChange = errors.New("cannot change or delete provisioned user")
|
||||||
ErrProvisionedTokenChange = errors.New("cannot change or delete provisioned token")
|
ErrProvisionedTokenChange = errors.New("cannot change or delete provisioned token")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// storeQueries holds the database-specific SQL queries
|
||||||
|
type storeQueries struct {
|
||||||
|
// User queries
|
||||||
|
selectUserByID string
|
||||||
|
selectUserByName string
|
||||||
|
selectUserByToken string
|
||||||
|
selectUserByStripeCustomerID string
|
||||||
|
selectUsernames string
|
||||||
|
selectUserCount string
|
||||||
|
selectUserIDFromUsername string
|
||||||
|
insertUser string
|
||||||
|
updateUserPass string
|
||||||
|
updateUserRole string
|
||||||
|
updateUserProvisioned string
|
||||||
|
updateUserPrefs string
|
||||||
|
updateUserStats string
|
||||||
|
updateUserStatsResetAll string
|
||||||
|
updateUserTier string
|
||||||
|
updateUserDeleted string
|
||||||
|
deleteUser string
|
||||||
|
deleteUserTier string
|
||||||
|
deleteUsersMarked string
|
||||||
|
|
||||||
|
// Access queries
|
||||||
|
selectTopicPerms string
|
||||||
|
selectUserAllAccess string
|
||||||
|
selectUserAccess string
|
||||||
|
selectUserReservations string
|
||||||
|
selectUserReservationsCount string
|
||||||
|
selectUserReservationsOwner string
|
||||||
|
selectUserHasReservation string
|
||||||
|
selectOtherAccessCount string
|
||||||
|
upsertUserAccess string
|
||||||
|
deleteUserAccess string
|
||||||
|
deleteUserAccessProvisioned string
|
||||||
|
deleteTopicAccess string
|
||||||
|
deleteAllAccess string
|
||||||
|
|
||||||
|
// Token queries
|
||||||
|
selectToken string
|
||||||
|
selectTokens string
|
||||||
|
selectTokenCount string
|
||||||
|
selectAllProvisionedTokens string
|
||||||
|
upsertToken string
|
||||||
|
updateToken string
|
||||||
|
updateTokenLastAccess string
|
||||||
|
deleteToken string
|
||||||
|
deleteProvisionedToken string
|
||||||
|
deleteAllToken string
|
||||||
|
deleteExpiredTokens string
|
||||||
|
deleteExcessTokens string
|
||||||
|
|
||||||
|
// Tier queries
|
||||||
|
insertTier string
|
||||||
|
selectTiers string
|
||||||
|
selectTierByCode string
|
||||||
|
selectTierByPriceID string
|
||||||
|
updateTier string
|
||||||
|
deleteTier string
|
||||||
|
|
||||||
|
// Phone queries
|
||||||
|
selectPhoneNumbers string
|
||||||
|
insertPhoneNumber string
|
||||||
|
deletePhoneNumber string
|
||||||
|
|
||||||
|
// Billing queries
|
||||||
|
updateBilling string
|
||||||
|
}
|
||||||
|
|
||||||
|
// execer is satisfied by both *sql.DB and *sql.Tx, allowing helper methods
|
||||||
|
// to be used both standalone and within a transaction.
|
||||||
|
type execer interface {
|
||||||
|
Exec(query string, args ...any) (sql.Result, error)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user