mirror of
https://github.com/SigNoz/signoz.git
synced 2026-03-10 23:42:08 +00:00
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* feat: deprecate user invite table * fix: handle soft deleted users flow * fix: handle edge cases for authentication and reset password flow * feat: integration tests with fixes for new flow * fix: array for grants * fix: edge cases for reset token and context api * chore: remove all code related to old invite flow * fix: openapi specs * fix: integration tests and minor naming change * fix: integration tests fmtlint * feat: improve invitation email template * fix: role tests * fix: context api * fix: openapi frontend * chore: rename countbyorgid to activecountbyorgid * fix: a deleted user cannot recycled, creating a new one * feat: migrate existing invites to user as pending invite status * fix: error from GetUsersByEmailAndOrgID * feat: add backward compatibility to existing apis using new invite flow * chore: change ordering of apis in server * chore: change ordering of apis in server * fix: filter active users in role and org id check * fix: check deleted user in reset password flow * chore: address some review comments, add back countbyorgid method * chore: move to bulk inserts for migrating existing invites * fix: wrap funcs to transactions, and fix openapi specs * fix: move reset link method to types, also move authz grants outside transation * fix: transaction issues * feat: helper method ErrIfDeleted for user * fix: error code for errifdeleted in user * fix: soft delete store method * fix: password authn tests also add old invite flow test * fix: callbackauthn tests * fix: remove extra oidc tests * fix: callback authn tests oidc * chore: address review comments and optimise bulk invite api * fix: use db ctx in various places * fix: fix duplicate email invite issue and add partial invite * fix: openapi specs * fix: errifpending * fix: user status persistence * fix: edge cases * chore: add tests for partial index too * feat: use composite unique index on users table instead of partial one * chore: move duplicate email check to unmarshaljson and query user again in accept invite * fix: make 068 migratin idempotent * chore: remove unused emails var * chore: add a temp filter to show only active users in frontend until next frontend fix * chore: remove one check from register flow testing until temp code is removed * chore: remove commented code from tests * chore: address frontend review comments * chore: address frontend review comments
706 lines
18 KiB
Go
706 lines
18 KiB
Go
package impluser
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/SigNoz/signoz/pkg/errors"
|
|
"github.com/SigNoz/signoz/pkg/factory"
|
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
|
"github.com/SigNoz/signoz/pkg/types"
|
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
|
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
|
|
"github.com/SigNoz/signoz/pkg/valuer"
|
|
"github.com/uptrace/bun"
|
|
)
|
|
|
|
type store struct {
|
|
sqlstore sqlstore.SQLStore
|
|
settings factory.ProviderSettings
|
|
}
|
|
|
|
func NewStore(sqlstore sqlstore.SQLStore, settings factory.ProviderSettings) types.UserStore {
|
|
return &store{sqlstore: sqlstore, settings: settings}
|
|
}
|
|
|
|
func (store *store) CreatePassword(ctx context.Context, password *types.FactorPassword) error {
|
|
_, err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewInsert().
|
|
Model(password).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrPasswordAlreadyExists, "password for user %s already exists", password.UserID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (store *store) CreateUser(ctx context.Context, user *types.User) error {
|
|
_, err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewInsert().
|
|
Model(user).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrUserAlreadyExists, "user with email %s already exists in org %s", user.Email, user.OrgID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (store *store) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*types.User, error) {
|
|
var users []*types.User
|
|
|
|
err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewSelect().
|
|
Model(&users).
|
|
Where("email = ?", email).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (store *store) GetUser(ctx context.Context, id valuer.UUID) (*types.User, error) {
|
|
user := new(types.User)
|
|
|
|
err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewSelect().
|
|
Model(user).
|
|
Where("id = ?", id).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user with id %s does not exist", id)
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (store *store) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.User, error) {
|
|
user := new(types.User)
|
|
|
|
err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewSelect().
|
|
Model(user).
|
|
Where("org_id = ?", orgID).
|
|
Where("id = ?", id).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user with id %s does not exist", id)
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (store *store) GetUsersByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) ([]*types.User, error) {
|
|
var users []*types.User
|
|
|
|
err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewSelect().
|
|
Model(&users).
|
|
Where("org_id = ?", orgID).
|
|
Where("email = ?", email).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (store *store) GetActiveUsersByRoleAndOrgID(ctx context.Context, role types.Role, orgID valuer.UUID) ([]*types.User, error) {
|
|
var users []*types.User
|
|
|
|
err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewSelect().
|
|
Model(&users).
|
|
Where("org_id = ?", orgID).
|
|
Where("role = ?", role).
|
|
Where("status = ?", types.UserStatusActive.StringValue()).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *types.User) error {
|
|
_, err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewUpdate().
|
|
Model(user).
|
|
Column("display_name").
|
|
Column("email").
|
|
Column("role").
|
|
Column("is_root").
|
|
Column("updated_at").
|
|
Column("status").
|
|
Where("org_id = ?", orgID).
|
|
Where("id = ?", user.ID).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user does not exist in org: %s", orgID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (store *store) ListUsersByOrgID(ctx context.Context, orgID valuer.UUID) ([]*types.GettableUser, error) {
|
|
users := []*types.User{}
|
|
|
|
err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewSelect().
|
|
Model(&users).
|
|
Where("org_id = ?", orgID).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (store *store) DeleteUser(ctx context.Context, orgID string, id string) error {
|
|
tx, err := store.sqlstore.BunDB().BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to start transaction")
|
|
}
|
|
|
|
defer func() {
|
|
_ = tx.Rollback()
|
|
}()
|
|
|
|
// get the password id
|
|
|
|
var password types.FactorPassword
|
|
err = tx.NewSelect().
|
|
Model(&password).
|
|
Where("user_id = ?", id).
|
|
Scan(ctx)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete password")
|
|
}
|
|
|
|
// delete reset password request
|
|
_, err = tx.NewDelete().
|
|
Model(new(types.ResetPasswordToken)).
|
|
Where("password_id = ?", password.ID.String()).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete reset password request")
|
|
}
|
|
|
|
// delete factor password
|
|
_, err = tx.NewDelete().
|
|
Model(new(types.FactorPassword)).
|
|
Where("user_id = ?", id).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete factor password")
|
|
}
|
|
|
|
// delete api keys
|
|
_, err = tx.NewDelete().
|
|
Model(&types.StorableAPIKey{}).
|
|
Where("user_id = ?", id).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete API keys")
|
|
}
|
|
|
|
// delete user_preference
|
|
_, err = tx.NewDelete().
|
|
Model(new(preferencetypes.StorableUserPreference)).
|
|
Where("user_id = ?", id).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete user preferences")
|
|
}
|
|
|
|
// delete tokens
|
|
_, err = tx.NewDelete().
|
|
Model(new(authtypes.StorableToken)).
|
|
Where("user_id = ?", id).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete tokens")
|
|
}
|
|
|
|
// delete user
|
|
_, err = tx.NewDelete().
|
|
Model(new(types.User)).
|
|
Where("org_id = ?", orgID).
|
|
Where("id = ?", id).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete user")
|
|
}
|
|
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to commit transaction")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string) error {
|
|
tx, err := store.sqlstore.BunDB().BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to start transaction")
|
|
}
|
|
|
|
defer func() {
|
|
_ = tx.Rollback()
|
|
}()
|
|
|
|
// get the password id
|
|
|
|
var password types.FactorPassword
|
|
err = tx.NewSelect().
|
|
Model(&password).
|
|
Where("user_id = ?", id).
|
|
Scan(ctx)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete password")
|
|
}
|
|
|
|
// delete reset password request
|
|
_, err = tx.NewDelete().
|
|
Model(new(types.ResetPasswordToken)).
|
|
Where("password_id = ?", password.ID.String()).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete reset password request")
|
|
}
|
|
|
|
// delete factor password
|
|
_, err = tx.NewDelete().
|
|
Model(new(types.FactorPassword)).
|
|
Where("user_id = ?", id).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete factor password")
|
|
}
|
|
|
|
// delete api keys
|
|
_, err = tx.NewDelete().
|
|
Model(&types.StorableAPIKey{}).
|
|
Where("user_id = ?", id).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete API keys")
|
|
}
|
|
|
|
// delete user_preference
|
|
_, err = tx.NewDelete().
|
|
Model(new(preferencetypes.StorableUserPreference)).
|
|
Where("user_id = ?", id).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete user preferences")
|
|
}
|
|
|
|
// delete tokens
|
|
_, err = tx.NewDelete().
|
|
Model(new(authtypes.StorableToken)).
|
|
Where("user_id = ?", id).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete tokens")
|
|
}
|
|
|
|
// soft delete user
|
|
now := time.Now()
|
|
_, err = tx.NewUpdate().
|
|
Model(new(types.User)).
|
|
Set("status = ?", types.UserStatusDeleted).
|
|
Set("deleted_at = ?", now).
|
|
Set("updated_at = ?", now).
|
|
Where("org_id = ?", orgID).
|
|
Where("id = ?", id).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete user")
|
|
}
|
|
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to commit transaction")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (store *store) CreateResetPasswordToken(ctx context.Context, resetPasswordToken *types.ResetPasswordToken) error {
|
|
_, err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewInsert().
|
|
Model(resetPasswordToken).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrResetPasswordTokenAlreadyExists, "reset password token for password %s already exists", resetPasswordToken.PasswordID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (store *store) GetPassword(ctx context.Context, id valuer.UUID) (*types.FactorPassword, error) {
|
|
password := new(types.FactorPassword)
|
|
|
|
err := store.
|
|
sqlstore.
|
|
BunDB().
|
|
NewSelect().
|
|
Model(password).
|
|
Where("id = ?", id).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with id: %s does not exist", id)
|
|
}
|
|
|
|
return password, nil
|
|
}
|
|
|
|
func (store *store) GetPasswordByUserID(ctx context.Context, userID valuer.UUID) (*types.FactorPassword, error) {
|
|
password := new(types.FactorPassword)
|
|
|
|
err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewSelect().
|
|
Model(password).
|
|
Where("user_id = ?", userID).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password for user %s does not exist", userID)
|
|
}
|
|
return password, nil
|
|
}
|
|
|
|
func (store *store) GetResetPasswordTokenByPasswordID(ctx context.Context, passwordID valuer.UUID) (*types.ResetPasswordToken, error) {
|
|
resetPasswordToken := new(types.ResetPasswordToken)
|
|
|
|
err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewSelect().
|
|
Model(resetPasswordToken).
|
|
Where("password_id = ?", passwordID).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token for password %s does not exist", passwordID)
|
|
}
|
|
|
|
return resetPasswordToken, nil
|
|
}
|
|
|
|
func (store *store) DeleteResetPasswordTokenByPasswordID(ctx context.Context, passwordID valuer.UUID) error {
|
|
_, err := store.sqlstore.BunDBCtx(ctx).NewDelete().
|
|
Model(&types.ResetPasswordToken{}).
|
|
Where("password_id = ?", passwordID).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete reset password token")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (store *store) GetResetPasswordToken(ctx context.Context, token string) (*types.ResetPasswordToken, error) {
|
|
resetPasswordRequest := new(types.ResetPasswordToken)
|
|
|
|
err := store.
|
|
sqlstore.
|
|
BunDB().
|
|
NewSelect().
|
|
Model(resetPasswordRequest).
|
|
Where("token = ?", token).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token does not exist")
|
|
}
|
|
|
|
return resetPasswordRequest, nil
|
|
}
|
|
|
|
func (store *store) UpdatePassword(ctx context.Context, factorPassword *types.FactorPassword) error {
|
|
_, err := store.sqlstore.BunDBCtx(ctx).
|
|
NewUpdate().
|
|
Model(factorPassword).
|
|
Where("user_id = ?", factorPassword.UserID).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password for user %s does not exist", factorPassword.UserID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// --- API KEY ---
|
|
func (store *store) CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error {
|
|
_, err := store.sqlstore.BunDB().NewInsert().
|
|
Model(apiKey).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrAPIKeyAlreadyExists, "API key with token: %s already exists", apiKey.Token)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (store *store) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *types.StorableAPIKey, updaterID valuer.UUID) error {
|
|
apiKey.UpdatedBy = updaterID.String()
|
|
apiKey.UpdatedAt = time.Now()
|
|
_, err := store.sqlstore.BunDB().NewUpdate().
|
|
Model(apiKey).
|
|
Column("role", "name", "updated_at", "updated_by").
|
|
Where("id = ?", id).
|
|
Where("revoked = false").
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return store.sqlstore.WrapNotFoundErrf(err, types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (store *store) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.StorableAPIKeyUser, error) {
|
|
orgUserAPIKeys := new(types.OrgUserAPIKey)
|
|
|
|
if err := store.sqlstore.BunDB().NewSelect().
|
|
Model(orgUserAPIKeys).
|
|
Relation("Users").
|
|
Relation("Users.APIKeys", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.Where("revoked = false")
|
|
},
|
|
).
|
|
Relation("Users.APIKeys.CreatedByUser").
|
|
Relation("Users.APIKeys.UpdatedByUser").
|
|
Where("id = ?", orgID).
|
|
Scan(ctx); err != nil {
|
|
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to fetch API keys")
|
|
}
|
|
|
|
// Flatten the API keys from all users
|
|
var allAPIKeys []*types.StorableAPIKeyUser
|
|
for _, user := range orgUserAPIKeys.Users {
|
|
if user.APIKeys != nil {
|
|
allAPIKeys = append(allAPIKeys, user.APIKeys...)
|
|
}
|
|
}
|
|
|
|
// sort the API keys by updated_at
|
|
sort.Slice(allAPIKeys, func(i, j int) bool {
|
|
return allAPIKeys[i].UpdatedAt.After(allAPIKeys[j].UpdatedAt)
|
|
})
|
|
|
|
return allAPIKeys, nil
|
|
}
|
|
|
|
func (store *store) RevokeAPIKey(ctx context.Context, id, revokedByUserID valuer.UUID) error {
|
|
updatedAt := time.Now().Unix()
|
|
_, err := store.sqlstore.BunDB().NewUpdate().
|
|
Model(&types.StorableAPIKey{}).
|
|
Set("revoked = ?", true).
|
|
Set("updated_by = ?", revokedByUserID).
|
|
Set("updated_at = ?", updatedAt).
|
|
Where("id = ?", id).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to revoke API key")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (store *store) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*types.StorableAPIKeyUser, error) {
|
|
apiKey := new(types.OrgUserAPIKey)
|
|
if err := store.sqlstore.BunDB().NewSelect().
|
|
Model(apiKey).
|
|
Relation("Users").
|
|
Relation("Users.APIKeys", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.Where("revoked = false").Where("storable_api_key.id = ?", id).
|
|
OrderExpr("storable_api_key.updated_at DESC").Limit(1)
|
|
},
|
|
).
|
|
Relation("Users.APIKeys.CreatedByUser").
|
|
Relation("Users.APIKeys.UpdatedByUser").
|
|
Scan(ctx); err != nil {
|
|
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
|
|
}
|
|
|
|
// flatten the API keys
|
|
flattenedAPIKeys := []*types.StorableAPIKeyUser{}
|
|
for _, user := range apiKey.Users {
|
|
if user.APIKeys != nil {
|
|
flattenedAPIKeys = append(flattenedAPIKeys, user.APIKeys...)
|
|
}
|
|
}
|
|
if len(flattenedAPIKeys) == 0 {
|
|
return nil, store.sqlstore.WrapNotFoundErrf(errors.New(errors.TypeNotFound, errors.CodeNotFound, "API key with id: %s does not exist"), types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
|
|
}
|
|
|
|
return flattenedAPIKeys[0], nil
|
|
}
|
|
|
|
func (store *store) CountByOrgID(ctx context.Context, orgID valuer.UUID) (int64, error) {
|
|
user := new(types.User)
|
|
|
|
count, err := store.
|
|
sqlstore.
|
|
BunDB().
|
|
NewSelect().
|
|
Model(user).
|
|
Where("org_id = ?", orgID).
|
|
Count(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return int64(count), nil
|
|
}
|
|
|
|
func (store *store) CountByOrgIDAndStatuses(ctx context.Context, orgID valuer.UUID, statuses []string) (map[valuer.String]int64, error) {
|
|
user := new(types.User)
|
|
var results []struct {
|
|
Status valuer.String `bun:"status"`
|
|
Count int64 `bun:"count"`
|
|
}
|
|
|
|
err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewSelect().
|
|
Model(user).
|
|
ColumnExpr("status").
|
|
ColumnExpr("COUNT(*) AS count").
|
|
Where("org_id = ?", orgID.StringValue()).
|
|
Where("status IN (?)", bun.In(statuses)).
|
|
GroupExpr("status").
|
|
Scan(ctx, &results)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
counts := make(map[valuer.String]int64, len(results))
|
|
for _, r := range results {
|
|
counts[r.Status] = r.Count
|
|
}
|
|
|
|
return counts, nil
|
|
}
|
|
|
|
func (store *store) CountAPIKeyByOrgID(ctx context.Context, orgID valuer.UUID) (int64, error) {
|
|
apiKey := new(types.StorableAPIKey)
|
|
|
|
count, err := store.
|
|
sqlstore.
|
|
BunDB().
|
|
NewSelect().
|
|
Model(apiKey).
|
|
Join("JOIN users ON users.id = storable_api_key.user_id").
|
|
Where("org_id = ?", orgID).
|
|
Count(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return int64(count), nil
|
|
}
|
|
|
|
func (store *store) RunInTx(ctx context.Context, cb func(ctx context.Context) error) error {
|
|
return store.sqlstore.RunInTxCtx(ctx, nil, func(ctx context.Context) error {
|
|
return cb(ctx)
|
|
})
|
|
}
|
|
|
|
func (store *store) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*types.User, error) {
|
|
user := new(types.User)
|
|
err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewSelect().
|
|
Model(user).
|
|
Where("org_id = ?", orgID).
|
|
Where("is_root = ?", true).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "root user for org %s not found", orgID)
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
func (store *store) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*types.User, error) {
|
|
users := []*types.User{}
|
|
err := store.
|
|
sqlstore.
|
|
BunDB().
|
|
NewSelect().
|
|
Model(&users).
|
|
Where("email = ?", email).
|
|
Where("org_id IN (?)", bun.In(orgIDs)).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (store *store) GetUserByResetPasswordToken(ctx context.Context, token string) (*types.User, error) {
|
|
user := new(types.User)
|
|
|
|
err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewSelect().
|
|
Model(user).
|
|
Join(`JOIN factor_password ON factor_password.user_id = "user".id`).
|
|
Join("JOIN reset_password_token ON reset_password_token.password_id = factor_password.id").
|
|
Where("reset_password_token.token = ?", token).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user not found for reset password token")
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (store *store) GetUsersByEmailsOrgIDAndStatuses(ctx context.Context, orgID valuer.UUID, emails []string, statuses []string) ([]*types.User, error) {
|
|
users := []*types.User{}
|
|
|
|
err := store.
|
|
sqlstore.
|
|
BunDBCtx(ctx).
|
|
NewSelect().
|
|
Model(&users).
|
|
Where("email IN (?)", bun.In(emails)).
|
|
Where("org_id = ?", orgID).
|
|
Where("status in (?)", bun.In(statuses)).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return users, nil
|
|
}
|