mirror of
https://github.com/SigNoz/signoz.git
synced 2026-03-18 02:32:13 +00:00
Compare commits
5 Commits
refactor/s
...
fix/cloudi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
871a4c74df | ||
|
|
49bbaccbe2 | ||
|
|
5bafdeb373 | ||
|
|
1502338f17 | ||
|
|
fdf75f03ac |
@@ -308,6 +308,9 @@ user:
|
||||
allow_self: true
|
||||
# The duration within which a user can reset their password.
|
||||
max_token_lifetime: 6h
|
||||
invite:
|
||||
# The duration within which a user can accept their invite.
|
||||
max_token_lifetime: 48h
|
||||
root:
|
||||
# Whether to enable the root user. When enabled, a root user is provisioned
|
||||
# on startup using the email and password below. The root user cannot be
|
||||
|
||||
@@ -186,7 +186,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Description: "This endpoint lists all users",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: make([]*types.User, 0),
|
||||
Response: make([]*types.GettableUser, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
@@ -203,7 +203,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Description: "This endpoint returns the user I belong to",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(types.User),
|
||||
Response: new(types.GettableUser),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
@@ -220,7 +220,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Description: "This endpoint returns the user by id",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(types.User),
|
||||
Response: new(types.GettableUser),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
@@ -237,7 +237,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Description: "This endpoint updates the user by id",
|
||||
Request: new(types.User),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(types.User),
|
||||
Response: new(types.GettableUser),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
|
||||
@@ -17,8 +17,8 @@ func NewStore(sqlstore sqlstore.SQLStore) authtypes.AuthNStore {
|
||||
return &store{sqlstore: sqlstore}
|
||||
}
|
||||
|
||||
func (store *store) GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx context.Context, email string, orgID valuer.UUID) (*types.StorableUser, *types.FactorPassword, error) {
|
||||
user := new(types.StorableUser)
|
||||
func (store *store) GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx context.Context, email string, orgID valuer.UUID) (*types.User, *types.FactorPassword, error) {
|
||||
user := new(types.User)
|
||||
factorPassword := new(types.FactorPassword)
|
||||
|
||||
err := store.
|
||||
|
||||
@@ -30,7 +30,7 @@ func (module *module) Create(ctx context.Context, timestamp int64, name string,
|
||||
funnel.CreatedBy = userID.String()
|
||||
|
||||
// Set up the user relationship
|
||||
funnel.CreatedByUser = &types.StorableUser{
|
||||
funnel.CreatedByUser = &types.User{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: userID,
|
||||
},
|
||||
|
||||
@@ -27,7 +27,12 @@ type OrgConfig struct {
|
||||
}
|
||||
|
||||
type PasswordConfig struct {
|
||||
Reset ResetConfig `mapstructure:"reset"`
|
||||
Invite InviteConfig `mapstructure:"invite"`
|
||||
Reset ResetConfig `mapstructure:"reset"`
|
||||
}
|
||||
|
||||
type InviteConfig struct {
|
||||
MaxTokenLifetime time.Duration `mapstructure:"max_token_lifetime"`
|
||||
}
|
||||
|
||||
type ResetConfig struct {
|
||||
@@ -46,6 +51,9 @@ func newConfig() factory.Config {
|
||||
AllowSelf: false,
|
||||
MaxTokenLifetime: 6 * time.Hour,
|
||||
},
|
||||
Invite: InviteConfig{
|
||||
MaxTokenLifetime: 48 * time.Hour,
|
||||
},
|
||||
},
|
||||
Root: RootConfig{
|
||||
Enabled: false,
|
||||
@@ -61,6 +69,10 @@ func (c Config) Validate() error {
|
||||
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::password::reset::max_token_lifetime must be positive")
|
||||
}
|
||||
|
||||
if c.Password.Invite.MaxTokenLifetime <= 0 {
|
||||
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::password::invite::max_token_lifetime must be positive")
|
||||
}
|
||||
|
||||
if c.Root.Enabled {
|
||||
if c.Root.Email.IsZero() {
|
||||
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::root::email is required when root user is enabled")
|
||||
|
||||
@@ -21,15 +21,11 @@ func NewGetter(store types.UserStore, flagger flagger.Flagger) user.Getter {
|
||||
}
|
||||
|
||||
func (module *getter) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*types.User, error) {
|
||||
storableUser, err := module.store.GetRootUserByOrgID(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return types.NewUserFromStorable(storableUser), nil
|
||||
return module.store.GetRootUserByOrgID(ctx, orgID)
|
||||
}
|
||||
|
||||
func (module *getter) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*types.User, error) {
|
||||
storableUsers, err := module.store.ListUsersByOrgID(ctx, orgID)
|
||||
users, err := module.store.ListUsersByOrgID(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -39,46 +35,46 @@ func (module *getter) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*ty
|
||||
hideRootUsers := module.flagger.BooleanOrEmpty(ctx, flagger.FeatureHideRootUser, evalCtx)
|
||||
|
||||
if hideRootUsers {
|
||||
storableUsers = slices.DeleteFunc(storableUsers, func(user *types.StorableUser) bool { return user.IsRoot })
|
||||
users = slices.DeleteFunc(users, func(user *types.User) bool { return user.IsRoot })
|
||||
}
|
||||
|
||||
return types.NewUsersFromStorables(storableUsers), nil
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (module *getter) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*types.User, error) {
|
||||
storableUsers, err := module.store.GetUsersByEmail(ctx, email)
|
||||
users, err := module.store.GetUsersByEmail(ctx, email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewUsersFromStorables(storableUsers), nil
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (module *getter) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.User, error) {
|
||||
storableUser, err := module.store.GetByOrgIDAndID(ctx, orgID, id)
|
||||
user, err := module.store.GetByOrgIDAndID(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewUserFromStorable(storableUser), nil
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (module *getter) Get(ctx context.Context, id valuer.UUID) (*types.User, error) {
|
||||
storableUser, err := module.store.GetUser(ctx, id)
|
||||
user, err := module.store.GetUser(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewUserFromStorable(storableUser), nil
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (module *getter) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*types.User, error) {
|
||||
storableUsers, err := module.store.ListUsersByEmailAndOrgIDs(ctx, email, orgIDs)
|
||||
users, err := module.store.ListUsersByEmailAndOrgIDs(ctx, email, orgIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewUsersFromStorables(storableUsers), nil
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (module *getter) CountByOrgID(ctx context.Context, orgID valuer.UUID) (int64, error) {
|
||||
|
||||
@@ -51,7 +51,7 @@ func NewModule(store types.UserStore, tokenizer tokenizer.Tokenizer, emailing em
|
||||
|
||||
func (m *Module) AcceptInvite(ctx context.Context, token string, password string) (*types.User, error) {
|
||||
// get the user by reset password token
|
||||
storableUser, err := m.store.GetUserByResetPasswordToken(ctx, token)
|
||||
user, err := m.store.GetUserByResetPasswordToken(ctx, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -63,23 +63,21 @@ func (m *Module) AcceptInvite(ctx context.Context, token string, password string
|
||||
}
|
||||
|
||||
// query the user again
|
||||
storableUser, err = m.store.GetByOrgIDAndID(ctx, storableUser.OrgID, storableUser.ID)
|
||||
user, err = m.store.GetByOrgIDAndID(ctx, user.OrgID, user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewUserFromStorable(storableUser), nil
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (m *Module) GetInviteByToken(ctx context.Context, token string) (*types.Invite, error) {
|
||||
// get the user
|
||||
storableUser, err := m.store.GetUserByResetPasswordToken(ctx, token)
|
||||
user, err := m.store.GetUserByResetPasswordToken(ctx, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := types.NewUserFromStorable(storableUser)
|
||||
|
||||
// create a dummy invite obj for backward compatibility
|
||||
invite := &types.Invite{
|
||||
Identifiable: types.Identifiable{
|
||||
@@ -111,11 +109,10 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
|
||||
for idx, invite := range bulkInvites.Invites {
|
||||
emails[idx] = invite.Email.StringValue()
|
||||
}
|
||||
storableUsers, err := m.store.GetUsersByEmailsOrgIDAndStatuses(ctx, orgID, emails, []string{types.UserStatusActive.StringValue(), types.UserStatusPendingInvite.StringValue()})
|
||||
users, err := m.store.GetUsersByEmailsOrgIDAndStatuses(ctx, orgID, emails, []string{types.UserStatusActive.StringValue(), types.UserStatusPendingInvite.StringValue()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users := types.NewUsersFromStorables(storableUsers)
|
||||
|
||||
if len(users) > 0 {
|
||||
if err := users[0].ErrIfRoot(); err != nil {
|
||||
@@ -206,7 +203,7 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
|
||||
|
||||
resetLink := userWithToken.ResetPasswordToken.FactorPasswordResetLink(frontendBaseUrl)
|
||||
|
||||
tokenLifetime := m.config.Password.Reset.MaxTokenLifetime
|
||||
tokenLifetime := m.config.Password.Invite.MaxTokenLifetime
|
||||
humanizedTokenLifetime := strings.TrimSpace(humanize.RelTime(time.Now(), time.Now().Add(tokenLifetime), "", ""))
|
||||
|
||||
if err := m.emailing.SendHTML(ctx, userWithToken.User.Email.String(), "You're Invited to Join SigNoz", emailtypes.TemplateNameInvitationEmail, map[string]any{
|
||||
@@ -223,13 +220,12 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
|
||||
|
||||
func (m *Module) ListInvite(ctx context.Context, orgID string) ([]*types.Invite, error) {
|
||||
// find all the users with pending_invite status
|
||||
storableUsers, err := m.store.ListUsersByOrgID(ctx, valuer.MustNewUUID(orgID))
|
||||
users, err := m.store.ListUsersByOrgID(ctx, valuer.MustNewUUID(orgID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pendingStorableUsers := slices.DeleteFunc(storableUsers, func(user *types.StorableUser) bool { return user.Status != types.UserStatusPendingInvite })
|
||||
pendingUsers := types.NewUsersFromStorables(pendingStorableUsers)
|
||||
pendingUsers := slices.DeleteFunc(users, func(user *types.User) bool { return user.Status != types.UserStatusPendingInvite })
|
||||
|
||||
var invites []*types.Invite
|
||||
|
||||
@@ -272,7 +268,7 @@ func (module *Module) CreateUser(ctx context.Context, input *types.User, opts ..
|
||||
}
|
||||
|
||||
if err := module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
if err := module.store.CreateUser(ctx, types.NewStorableUser(input)); err != nil {
|
||||
if err := module.store.CreateUser(ctx, input); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -295,13 +291,11 @@ func (module *Module) CreateUser(ctx context.Context, input *types.User, opts ..
|
||||
}
|
||||
|
||||
func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *types.User, updatedBy string) (*types.User, error) {
|
||||
existingStorableUser, err := m.store.GetUser(ctx, valuer.MustNewUUID(id))
|
||||
existingUser, err := m.store.GetUser(ctx, valuer.MustNewUUID(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingUser := types.NewUserFromStorable(existingStorableUser)
|
||||
|
||||
if err := existingUser.ErrIfRoot(); err != nil {
|
||||
return nil, errors.WithAdditionalf(err, "cannot update root user")
|
||||
}
|
||||
@@ -356,8 +350,7 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
|
||||
}
|
||||
|
||||
func (module *Module) UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *types.User) error {
|
||||
storableUser := types.NewStorableUser(user)
|
||||
if err := module.store.UpdateUser(ctx, orgID, storableUser); err != nil {
|
||||
if err := module.store.UpdateUser(ctx, orgID, user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -373,13 +366,11 @@ func (module *Module) UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user
|
||||
}
|
||||
|
||||
func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id string, deletedBy string) error {
|
||||
storableUser, err := module.store.GetUser(ctx, valuer.MustNewUUID(id))
|
||||
user, err := module.store.GetUser(ctx, valuer.MustNewUUID(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := types.NewUserFromStorable(storableUser)
|
||||
|
||||
if err := user.ErrIfRoot(); err != nil {
|
||||
return errors.WithAdditionalf(err, "cannot delete root user")
|
||||
}
|
||||
@@ -421,13 +412,11 @@ func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id stri
|
||||
}
|
||||
|
||||
func (module *Module) GetOrCreateResetPasswordToken(ctx context.Context, userID valuer.UUID) (*types.ResetPasswordToken, error) {
|
||||
storableUser, err := module.store.GetUser(ctx, userID)
|
||||
user, err := module.store.GetUser(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := types.NewUserFromStorable(storableUser)
|
||||
|
||||
if err := user.ErrIfRoot(); err != nil {
|
||||
return nil, errors.WithAdditionalf(err, "cannot reset password for root user")
|
||||
}
|
||||
@@ -471,7 +460,11 @@ func (module *Module) GetOrCreateResetPasswordToken(ctx context.Context, userID
|
||||
}
|
||||
|
||||
// create a new token
|
||||
resetPasswordToken, err := types.NewResetPasswordToken(password.ID, time.Now().Add(module.config.Password.Reset.MaxTokenLifetime))
|
||||
tokenLifetime := module.config.Password.Reset.MaxTokenLifetime
|
||||
if user.Status == types.UserStatusPendingInvite {
|
||||
tokenLifetime = module.config.Password.Invite.MaxTokenLifetime
|
||||
}
|
||||
resetPasswordToken, err := types.NewResetPasswordToken(password.ID, time.Now().Add(tokenLifetime))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -511,6 +504,9 @@ func (module *Module) ForgotPassword(ctx context.Context, orgID valuer.UUID, ema
|
||||
resetLink := token.FactorPasswordResetLink(frontendBaseURL)
|
||||
|
||||
tokenLifetime := module.config.Password.Reset.MaxTokenLifetime
|
||||
if user.Status == types.UserStatusPendingInvite {
|
||||
tokenLifetime = module.config.Password.Invite.MaxTokenLifetime
|
||||
}
|
||||
humanizedTokenLifetime := strings.TrimSpace(humanize.RelTime(time.Now(), time.Now().Add(tokenLifetime), "", ""))
|
||||
|
||||
if err := module.emailing.SendHTML(
|
||||
@@ -545,13 +541,11 @@ func (module *Module) UpdatePasswordByResetPasswordToken(ctx context.Context, to
|
||||
return err
|
||||
}
|
||||
|
||||
storableUser, err := module.store.GetUser(ctx, valuer.MustNewUUID(password.UserID))
|
||||
user, err := module.store.GetUser(ctx, valuer.MustNewUUID(password.UserID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := types.NewUserFromStorable(storableUser)
|
||||
|
||||
// handle deleted user
|
||||
if err := user.ErrIfDeleted(); err != nil {
|
||||
return errors.WithAdditionalf(err, "deleted users cannot reset their password")
|
||||
@@ -582,7 +576,7 @@ func (module *Module) UpdatePasswordByResetPasswordToken(ctx context.Context, to
|
||||
if err := user.UpdateStatus(types.UserStatusActive); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := module.store.UpdateUser(ctx, user.OrgID, types.NewStorableUser(user)); err != nil {
|
||||
if err := module.store.UpdateUser(ctx, user.OrgID, user); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -600,13 +594,11 @@ func (module *Module) UpdatePasswordByResetPasswordToken(ctx context.Context, to
|
||||
}
|
||||
|
||||
func (module *Module) UpdatePassword(ctx context.Context, userID valuer.UUID, oldpasswd string, passwd string) error {
|
||||
storableUser, err := module.store.GetUser(ctx, userID)
|
||||
user, err := module.store.GetUser(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := types.NewUserFromStorable(storableUser)
|
||||
|
||||
if err := user.ErrIfDeleted(); err != nil {
|
||||
return errors.WithAdditionalf(err, "cannot change password for deleted user")
|
||||
}
|
||||
@@ -758,13 +750,11 @@ func (module *Module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
|
||||
|
||||
// this function restricts that only one non-deleted user email can exist for an org ID, if found more, it throws an error
|
||||
func (module *Module) GetNonDeletedUserByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*types.User, error) {
|
||||
existingStorableUsers, err := module.store.GetUsersByEmailAndOrgID(ctx, email, orgID)
|
||||
existingUsers, err := module.store.GetUsersByEmailAndOrgID(ctx, email, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingUsers := types.NewUsersFromStorables(existingStorableUsers)
|
||||
|
||||
// filter out the deleted users
|
||||
existingUsers = slices.DeleteFunc(existingUsers, func(user *types.User) bool { return user.ErrIfDeleted() != nil })
|
||||
|
||||
@@ -783,7 +773,7 @@ func (module *Module) GetNonDeletedUserByEmailAndOrgID(ctx context.Context, emai
|
||||
func (module *Module) createUserWithoutGrant(ctx context.Context, input *types.User, opts ...root.CreateUserOption) error {
|
||||
createUserOpts := root.NewCreateUserOptions(opts...)
|
||||
if err := module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
if err := module.store.CreateUser(ctx, types.NewStorableUser(input)); err != nil {
|
||||
if err := module.store.CreateUser(ctx, input); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -819,7 +809,7 @@ func (module *Module) activatePendingUser(ctx context.Context, user *types.User)
|
||||
if err := user.UpdateStatus(types.UserStatusActive); err != nil {
|
||||
return err
|
||||
}
|
||||
err = module.store.UpdateUser(ctx, user.OrgID, types.NewStorableUser(user))
|
||||
err = module.store.UpdateUser(ctx, user.OrgID, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -129,11 +129,10 @@ func (s *service) reconcileByName(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (s *service) reconcileRootUser(ctx context.Context, orgID valuer.UUID) error {
|
||||
existingStorableRoot, err := s.store.GetRootUserByOrgID(ctx, orgID)
|
||||
existingRoot, err := s.store.GetRootUserByOrgID(ctx, orgID)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return err
|
||||
}
|
||||
existingRoot := types.NewUserFromStorable(existingStorableRoot)
|
||||
|
||||
if existingRoot == nil {
|
||||
return s.createOrPromoteRootUser(ctx, orgID)
|
||||
|
||||
@@ -39,7 +39,7 @@ func (store *store) CreatePassword(ctx context.Context, password *types.FactorPa
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) CreateUser(ctx context.Context, user *types.StorableUser) error {
|
||||
func (store *store) CreateUser(ctx context.Context, user *types.User) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -52,8 +52,8 @@ func (store *store) CreateUser(ctx context.Context, user *types.StorableUser) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*types.StorableUser, error) {
|
||||
var users []*types.StorableUser
|
||||
func (store *store) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*types.User, error) {
|
||||
var users []*types.User
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -69,8 +69,8 @@ func (store *store) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (store *store) GetUser(ctx context.Context, id valuer.UUID) (*types.StorableUser, error) {
|
||||
user := new(types.StorableUser)
|
||||
func (store *store) GetUser(ctx context.Context, id valuer.UUID) (*types.User, error) {
|
||||
user := new(types.User)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -86,8 +86,8 @@ func (store *store) GetUser(ctx context.Context, id valuer.UUID) (*types.Storabl
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (store *store) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.StorableUser, error) {
|
||||
user := new(types.StorableUser)
|
||||
func (store *store) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.User, error) {
|
||||
user := new(types.User)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -104,8 +104,8 @@ func (store *store) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id v
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (store *store) GetUsersByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) ([]*types.StorableUser, error) {
|
||||
var users []*types.StorableUser
|
||||
func (store *store) GetUsersByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) ([]*types.User, error) {
|
||||
var users []*types.User
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -122,8 +122,8 @@ func (store *store) GetUsersByEmailAndOrgID(ctx context.Context, email valuer.Em
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (store *store) GetActiveUsersByRoleAndOrgID(ctx context.Context, role types.Role, orgID valuer.UUID) ([]*types.StorableUser, error) {
|
||||
var users []*types.StorableUser
|
||||
func (store *store) GetActiveUsersByRoleAndOrgID(ctx context.Context, role types.Role, orgID valuer.UUID) ([]*types.User, error) {
|
||||
var users []*types.User
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -141,7 +141,7 @@ func (store *store) GetActiveUsersByRoleAndOrgID(ctx context.Context, role types
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *types.StorableUser) error {
|
||||
func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *types.User) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -162,8 +162,8 @@ func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *typ
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) ListUsersByOrgID(ctx context.Context, orgID valuer.UUID) ([]*types.StorableUser, error) {
|
||||
users := []*types.StorableUser{}
|
||||
func (store *store) ListUsersByOrgID(ctx context.Context, orgID valuer.UUID) ([]*types.GettableUser, error) {
|
||||
users := []*types.User{}
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -247,7 +247,7 @@ func (store *store) DeleteUser(ctx context.Context, orgID string, id string) err
|
||||
|
||||
// delete user
|
||||
_, err = tx.NewDelete().
|
||||
Model(new(types.StorableUser)).
|
||||
Model(new(types.User)).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("id = ?", id).
|
||||
Exec(ctx)
|
||||
@@ -332,7 +332,7 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
|
||||
// soft delete user
|
||||
now := time.Now()
|
||||
_, err = tx.NewUpdate().
|
||||
Model(new(types.StorableUser)).
|
||||
Model(new(types.User)).
|
||||
Set("status = ?", types.UserStatusDeleted).
|
||||
Set("deleted_at = ?", now).
|
||||
Set("updated_at = ?", now).
|
||||
@@ -580,7 +580,7 @@ func (store *store) CountByOrgID(ctx context.Context, orgID valuer.UUID) (int64,
|
||||
}
|
||||
|
||||
func (store *store) CountByOrgIDAndStatuses(ctx context.Context, orgID valuer.UUID, statuses []string) (map[valuer.String]int64, error) {
|
||||
user := new(types.StorableUser)
|
||||
user := new(types.User)
|
||||
var results []struct {
|
||||
Status valuer.String `bun:"status"`
|
||||
Count int64 `bun:"count"`
|
||||
@@ -633,8 +633,8 @@ func (store *store) RunInTx(ctx context.Context, cb func(ctx context.Context) er
|
||||
})
|
||||
}
|
||||
|
||||
func (store *store) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*types.StorableUser, error) {
|
||||
user := new(types.StorableUser)
|
||||
func (store *store) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*types.User, error) {
|
||||
user := new(types.User)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -649,8 +649,8 @@ func (store *store) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (store *store) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*types.StorableUser, error) {
|
||||
users := []*types.StorableUser{}
|
||||
func (store *store) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*types.User, error) {
|
||||
users := []*types.User{}
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
@@ -666,8 +666,8 @@ func (store *store) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (store *store) GetUserByResetPasswordToken(ctx context.Context, token string) (*types.StorableUser, error) {
|
||||
user := new(types.StorableUser)
|
||||
func (store *store) GetUserByResetPasswordToken(ctx context.Context, token string) (*types.User, error) {
|
||||
user := new(types.User)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -685,8 +685,8 @@ func (store *store) GetUserByResetPasswordToken(ctx context.Context, token strin
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (store *store) GetUsersByEmailsOrgIDAndStatuses(ctx context.Context, orgID valuer.UUID, emails []string, statuses []string) ([]*types.StorableUser, error) {
|
||||
users := []*types.StorableUser{}
|
||||
func (store *store) GetUsersByEmailsOrgIDAndStatuses(ctx context.Context, orgID valuer.UUID, emails []string, statuses []string) ([]*types.User, error) {
|
||||
users := []*types.User{}
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
|
||||
@@ -175,6 +175,7 @@ func NewSQLMigrationProviderFactories(
|
||||
sqlmigration.NewMigrateRulesV4ToV5Factory(sqlstore, telemetryStore),
|
||||
sqlmigration.NewAddStatusUserFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewDeprecateUserInviteFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewFixCloudIntegrationUniqueIndexFactory(sqlstore, sqlschema),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
@@ -17,12 +16,12 @@ type funnel struct {
|
||||
types.Identifiable // funnel id
|
||||
types.TimeAuditable
|
||||
types.UserAuditable
|
||||
Name string `json:"funnel_name" bun:"name,type:text,notnull"` // funnel name
|
||||
Description string `json:"description" bun:"description,type:text"` // funnel description
|
||||
OrgID valuer.UUID `json:"org_id" bun:"org_id,type:varchar,notnull"`
|
||||
Steps []funnelStep `json:"steps" bun:"steps,type:text,notnull"`
|
||||
Tags string `json:"tags" bun:"tags,type:text"`
|
||||
CreatedByUser *types.StorableUser `json:"user" bun:"rel:belongs-to,join:created_by=id"`
|
||||
Name string `json:"funnel_name" bun:"name,type:text,notnull"` // funnel name
|
||||
Description string `json:"description" bun:"description,type:text"` // funnel description
|
||||
OrgID valuer.UUID `json:"org_id" bun:"org_id,type:varchar,notnull"`
|
||||
Steps []funnelStep `json:"steps" bun:"steps,type:text,notnull"`
|
||||
Tags string `json:"tags" bun:"tags,type:text"`
|
||||
CreatedByUser *types.User `json:"user" bun:"rel:belongs-to,join:created_by=id"`
|
||||
}
|
||||
|
||||
type funnelStep struct {
|
||||
|
||||
264
pkg/sqlmigration/069_fix_cloud_integration_index.go
Normal file
264
pkg/sqlmigration/069_fix_cloud_integration_index.go
Normal file
@@ -0,0 +1,264 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
type fixCloudIntegrationUniqueIndex struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
sqlschema sqlschema.SQLSchema
|
||||
}
|
||||
|
||||
func NewFixCloudIntegrationUniqueIndexFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(
|
||||
factory.MustNewName("fix_cloud_integration_index"),
|
||||
func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||
return &fixCloudIntegrationUniqueIndex{
|
||||
sqlstore: sqlstore,
|
||||
sqlschema: sqlschema,
|
||||
}, nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (migration *fixCloudIntegrationUniqueIndex) Register(migrations *migrate.Migrations) error {
|
||||
if err := migrations.Register(migration.Up, migration.Down); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type cloudIntegrationRow struct {
|
||||
bun.BaseModel `bun:"table:cloud_integration"`
|
||||
|
||||
ID string `bun:"id"`
|
||||
AccountID string `bun:"account_id"`
|
||||
Provider string `bun:"provider"`
|
||||
OrgID string `bun:"org_id"`
|
||||
Config string `bun:"config"`
|
||||
UpdatedAt time.Time `bun:"updated_at"`
|
||||
}
|
||||
|
||||
type cloudIntegrationAccountConfig struct {
|
||||
Regions []string `json:"regions"`
|
||||
}
|
||||
|
||||
// duplicateGroup holds the keeper (first element) and losers (rest) for a duplicate (account_id, provider, org_id) group.
|
||||
type duplicateGroup struct {
|
||||
keeper *cloudIntegrationRow
|
||||
losers []*cloudIntegrationRow
|
||||
}
|
||||
|
||||
func (migration *fixCloudIntegrationUniqueIndex) Up(ctx context.Context, db *bun.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
// Step 1: Drop the wrong index on (id, provider, org_id)
|
||||
dropSqls := migration.sqlschema.Operator().DropIndex(
|
||||
(&sqlschema.UniqueIndex{
|
||||
TableName: "cloud_integration",
|
||||
ColumnNames: []sqlschema.ColumnName{"id", "provider", "org_id"},
|
||||
}).Named("unique_cloud_integration"),
|
||||
)
|
||||
for _, sql := range dropSqls {
|
||||
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Normalize empty-string account_id to NULL
|
||||
// Older table structure could store "" instead of NULL for unconnected accounts.
|
||||
// Empty strings would violate the partial unique index since '' = '' (unlike NULL != NULL).
|
||||
_, err = tx.NewUpdate().
|
||||
TableExpr("cloud_integration").
|
||||
Set("account_id = NULL").
|
||||
Where("account_id = ''").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Step 3: Fetch all active rows with non-null account_id, ordered for grouping
|
||||
var activeRows []*cloudIntegrationRow
|
||||
err = tx.NewSelect().
|
||||
Model(&activeRows).
|
||||
Where("removed_at IS NULL").
|
||||
Where("account_id IS NOT NULL").
|
||||
OrderExpr("account_id, provider, org_id, updated_at DESC").
|
||||
Scan(ctx)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Group by (account_id, provider, org_id)
|
||||
groups := groupCloudIntegrationRows(activeRows)
|
||||
|
||||
now := time.Now()
|
||||
var loserIDs []string
|
||||
|
||||
for _, group := range groups {
|
||||
if len(group.losers) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Step 4: Merge config from losers into keeper
|
||||
if err = mergeCloudIntegrationConfigs(ctx, tx, group); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Step 5: Reassign orphaned cloud_integration_service rows
|
||||
for _, loser := range group.losers {
|
||||
// Delete services from loser that would conflict with keeper's services (same type)
|
||||
_, err = tx.NewDelete().
|
||||
TableExpr("cloud_integration_service").
|
||||
Where("cloud_integration_id = ?", loser.ID).
|
||||
Where("type IN (?)",
|
||||
tx.NewSelect().
|
||||
TableExpr("cloud_integration_service").
|
||||
Column("type").
|
||||
Where("cloud_integration_id = ?", group.keeper.ID),
|
||||
).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reassign remaining services to the keeper
|
||||
_, err = tx.ExecContext(ctx,
|
||||
"UPDATE cloud_integration_service SET cloud_integration_id = ? WHERE cloud_integration_id = ?",
|
||||
group.keeper.ID, loser.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loserIDs = append(loserIDs, loser.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: Soft-delete all loser rows
|
||||
if len(loserIDs) > 0 {
|
||||
_, err = tx.NewUpdate().
|
||||
TableExpr("cloud_integration").
|
||||
Set("removed_at = ?", now).
|
||||
Set("updated_at = ?", now).
|
||||
Where("id IN (?)", bun.In(loserIDs)).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7: Create the correct partial unique index on (account_id, provider, org_id) WHERE removed_at IS NULL
|
||||
createSqls := migration.sqlschema.Operator().CreateIndex(
|
||||
&sqlschema.PartialUniqueIndex{
|
||||
TableName: "cloud_integration",
|
||||
ColumnNames: []sqlschema.ColumnName{"account_id", "provider", "org_id"},
|
||||
Where: "removed_at IS NULL",
|
||||
},
|
||||
)
|
||||
for _, sql := range createSqls {
|
||||
if _, err = tx.ExecContext(ctx, string(sql)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (migration *fixCloudIntegrationUniqueIndex) Down(ctx context.Context, db *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// groupCloudIntegrationRows groups rows by (account_id, provider, org_id).
|
||||
// Rows must be pre-sorted by account_id, provider, org_id, updated_at DESC
|
||||
// so the first row in each group is the keeper (most recently updated).
|
||||
func groupCloudIntegrationRows(rows []*cloudIntegrationRow) []duplicateGroup {
|
||||
if len(rows) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var groups []duplicateGroup
|
||||
var current duplicateGroup
|
||||
current.keeper = rows[0]
|
||||
|
||||
for i := 1; i < len(rows); i++ {
|
||||
row := rows[i]
|
||||
if row.AccountID == current.keeper.AccountID &&
|
||||
row.Provider == current.keeper.Provider &&
|
||||
row.OrgID == current.keeper.OrgID {
|
||||
current.losers = append(current.losers, row)
|
||||
} else {
|
||||
groups = append(groups, current)
|
||||
current = duplicateGroup{keeper: row}
|
||||
}
|
||||
}
|
||||
groups = append(groups, current)
|
||||
|
||||
return groups
|
||||
}
|
||||
|
||||
// mergeCloudIntegrationConfigs unions the EnabledRegions from all rows in the group into the keeper's config and updates
|
||||
func mergeCloudIntegrationConfigs(ctx context.Context, tx bun.Tx, group duplicateGroup) error {
|
||||
regionSet := make(map[string]struct{})
|
||||
|
||||
// Parse keeper's config
|
||||
parseRegions(group.keeper.Config, regionSet)
|
||||
|
||||
// Parse each loser's config
|
||||
for _, loser := range group.losers {
|
||||
parseRegions(loser.Config, regionSet)
|
||||
}
|
||||
|
||||
// Build merged config
|
||||
mergedRegions := make([]string, 0, len(regionSet))
|
||||
for region := range regionSet {
|
||||
mergedRegions = append(mergedRegions, region)
|
||||
}
|
||||
|
||||
merged := cloudIntegrationAccountConfig{Regions: mergedRegions}
|
||||
mergedJSON, err := json.Marshal(merged)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update keeper's config
|
||||
_, err = tx.NewUpdate().
|
||||
TableExpr("cloud_integration").
|
||||
Set("config = ?", string(mergedJSON)).
|
||||
Where("id = ?", group.keeper.ID).
|
||||
Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// parseRegions unmarshals a config JSON string and adds its regions to the set.
|
||||
func parseRegions(configJSON string, regionSet map[string]struct{}) {
|
||||
if configJSON == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var config cloudIntegrationAccountConfig
|
||||
if err := json.Unmarshal([]byte(configJSON), &config); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, region := range config.Regions {
|
||||
regionSet[region] = struct{}{}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ func (store *store) Create(ctx context.Context, token *authtypes.StorableToken)
|
||||
}
|
||||
|
||||
func (store *store) GetIdentityByUserID(ctx context.Context, userID valuer.UUID) (*authtypes.Identity, error) {
|
||||
user := new(types.StorableUser)
|
||||
user := new(types.User)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
|
||||
@@ -128,7 +128,7 @@ func (typ *Identity) ToClaims() Claims {
|
||||
|
||||
type AuthNStore interface {
|
||||
// Get user and factor password by email and orgID.
|
||||
GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx context.Context, email string, orgID valuer.UUID) (*types.StorableUser, *types.FactorPassword, error)
|
||||
GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx context.Context, email string, orgID valuer.UUID) (*types.User, *types.FactorPassword, error)
|
||||
|
||||
// Get org domain from id.
|
||||
GetAuthDomainFromID(ctx context.Context, domainID valuer.UUID) (*AuthDomain, error)
|
||||
|
||||
@@ -39,15 +39,15 @@ type OrgUserAPIKey struct {
|
||||
}
|
||||
|
||||
type UserWithAPIKey struct {
|
||||
*StorableUser `bun:",extend"`
|
||||
APIKeys []*StorableAPIKeyUser `bun:"rel:has-many,join:id=user_id"`
|
||||
*User `bun:",extend"`
|
||||
APIKeys []*StorableAPIKeyUser `bun:"rel:has-many,join:id=user_id"`
|
||||
}
|
||||
|
||||
type StorableAPIKeyUser struct {
|
||||
StorableAPIKey `bun:",extend"`
|
||||
|
||||
CreatedByUser *StorableUser `json:"createdByUser" bun:"created_by_user,rel:belongs-to,join:created_by=id"`
|
||||
UpdatedByUser *StorableUser `json:"updatedByUser" bun:"updated_by_user,rel:belongs-to,join:updated_by=id"`
|
||||
CreatedByUser *User `json:"createdByUser" bun:"created_by_user,rel:belongs-to,join:created_by=id"`
|
||||
UpdatedByUser *User `json:"updatedByUser" bun:"updated_by_user,rel:belongs-to,join:updated_by=id"`
|
||||
}
|
||||
|
||||
type StorableAPIKey struct {
|
||||
@@ -138,7 +138,7 @@ func NewGettableAPIKeyFromStorableAPIKey(storableAPIKey *StorableAPIKeyUser) *Ge
|
||||
LastUsed: lastUsed,
|
||||
Revoked: storableAPIKey.Revoked,
|
||||
UserID: storableAPIKey.UserID.String(),
|
||||
CreatedByUser: NewUserFromStorable(storableAPIKey.CreatedByUser),
|
||||
UpdatedByUser: NewUserFromStorable(storableAPIKey.UpdatedByUser),
|
||||
CreatedByUser: storableAPIKey.CreatedByUser,
|
||||
UpdatedByUser: storableAPIKey.UpdatedByUser,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@ type StorableFunnel struct {
|
||||
types.TimeAuditable
|
||||
types.UserAuditable
|
||||
bun.BaseModel `bun:"table:trace_funnel"`
|
||||
Name string `json:"funnel_name" bun:"name,type:text,notnull"`
|
||||
Description string `json:"description" bun:"description,type:text"`
|
||||
OrgID valuer.UUID `json:"org_id" bun:"org_id,type:varchar,notnull"`
|
||||
Steps []*FunnelStep `json:"steps" bun:"steps,type:text,notnull"`
|
||||
Tags string `json:"tags" bun:"tags,type:text"`
|
||||
CreatedByUser *types.StorableUser `json:"user" bun:"rel:belongs-to,join:created_by=id"`
|
||||
Name string `json:"funnel_name" bun:"name,type:text,notnull"`
|
||||
Description string `json:"description" bun:"description,type:text"`
|
||||
OrgID valuer.UUID `json:"org_id" bun:"org_id,type:varchar,notnull"`
|
||||
Steps []*FunnelStep `json:"steps" bun:"steps,type:text,notnull"`
|
||||
Tags string `json:"tags" bun:"tags,type:text"`
|
||||
CreatedByUser *types.User `json:"user" bun:"rel:belongs-to,join:created_by=id"`
|
||||
}
|
||||
|
||||
type FunnelStep struct {
|
||||
|
||||
@@ -443,7 +443,7 @@ func TestConstructFunnelResponse(t *testing.T) {
|
||||
},
|
||||
Name: "test-funnel",
|
||||
OrgID: orgID,
|
||||
CreatedByUser: &types.StorableUser{
|
||||
CreatedByUser: &types.User{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: userID,
|
||||
},
|
||||
|
||||
@@ -33,25 +33,15 @@ var (
|
||||
ValidUserStatus = []valuer.String{UserStatusPendingInvite, UserStatusActive, UserStatusDeleted}
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Identifiable
|
||||
DisplayName string `json:"displayName"`
|
||||
Email valuer.Email `json:"email"`
|
||||
Role Role `json:"role"` // this will be moved to roles
|
||||
OrgID valuer.UUID `json:"orgId"`
|
||||
IsRoot bool `json:"isRoot"`
|
||||
Status valuer.String `json:"status"`
|
||||
DeletedAt time.Time `json:"-"`
|
||||
TimeAuditable
|
||||
}
|
||||
type GettableUser = User
|
||||
|
||||
type StorableUser struct {
|
||||
type User struct {
|
||||
bun.BaseModel `bun:"table:users"`
|
||||
|
||||
Identifiable
|
||||
DisplayName string `bun:"display_name" json:"displayName"`
|
||||
Email valuer.Email `bun:"email" json:"email"`
|
||||
Role Role `bun:"role" json:"role"` // this will be removed as column from here
|
||||
Role Role `bun:"role" json:"role"`
|
||||
OrgID valuer.UUID `bun:"org_id" json:"orgId"`
|
||||
IsRoot bool `bun:"is_root" json:"isRoot"`
|
||||
Status valuer.String `bun:"status" json:"status"`
|
||||
@@ -67,57 +57,6 @@ type PostableRegisterOrgAndAdmin struct {
|
||||
OrgName string `json:"orgName"`
|
||||
}
|
||||
|
||||
func NewStorableUser(user *User) *StorableUser {
|
||||
if user == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &StorableUser{
|
||||
Identifiable: user.Identifiable,
|
||||
DisplayName: user.DisplayName,
|
||||
Email: user.Email,
|
||||
Role: user.Role,
|
||||
OrgID: user.OrgID,
|
||||
IsRoot: user.IsRoot,
|
||||
Status: user.Status,
|
||||
DeletedAt: user.DeletedAt,
|
||||
TimeAuditable: user.TimeAuditable,
|
||||
}
|
||||
}
|
||||
|
||||
func NewUserFromStorable(storableUser *StorableUser) *User {
|
||||
if storableUser == nil {
|
||||
return nil
|
||||
}
|
||||
return &User{
|
||||
Identifiable: storableUser.Identifiable,
|
||||
DisplayName: storableUser.DisplayName,
|
||||
Email: storableUser.Email,
|
||||
Role: storableUser.Role,
|
||||
OrgID: storableUser.OrgID,
|
||||
IsRoot: storableUser.IsRoot,
|
||||
Status: storableUser.Status,
|
||||
DeletedAt: storableUser.DeletedAt,
|
||||
TimeAuditable: storableUser.TimeAuditable,
|
||||
}
|
||||
}
|
||||
|
||||
func NewUsersFromStorables(storableUsers []*StorableUser) []*User {
|
||||
users := make([]*User, len(storableUsers))
|
||||
for i, s := range storableUsers {
|
||||
users[i] = NewUserFromStorable(s)
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
func NewStorableUsers(users []*User) []*StorableUser {
|
||||
storableUsers := make([]*StorableUser, len(users))
|
||||
for i, u := range users {
|
||||
storableUsers[i] = NewStorableUser(u)
|
||||
}
|
||||
return storableUsers
|
||||
}
|
||||
|
||||
func NewUser(displayName string, email valuer.Email, role Role, orgID valuer.UUID, status valuer.String) (*User, error) {
|
||||
if email.IsZero() {
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "email is required")
|
||||
@@ -276,33 +215,33 @@ func (request *PostableRegisterOrgAndAdmin) UnmarshalJSON(data []byte) error {
|
||||
|
||||
type UserStore interface {
|
||||
// Creates a user.
|
||||
CreateUser(ctx context.Context, user *StorableUser) error
|
||||
CreateUser(ctx context.Context, user *User) error
|
||||
|
||||
// Get user by id.
|
||||
GetUser(context.Context, valuer.UUID) (*StorableUser, error)
|
||||
GetUser(context.Context, valuer.UUID) (*User, error)
|
||||
|
||||
// Get user by orgID and id.
|
||||
GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*StorableUser, error)
|
||||
GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*User, error)
|
||||
|
||||
// Get user by email and orgID.
|
||||
GetUsersByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) ([]*StorableUser, error)
|
||||
GetUsersByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) ([]*User, error)
|
||||
|
||||
// Get users by email.
|
||||
GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*StorableUser, error)
|
||||
GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*User, error)
|
||||
|
||||
// Get users by role and org.
|
||||
GetActiveUsersByRoleAndOrgID(ctx context.Context, role Role, orgID valuer.UUID) ([]*StorableUser, error)
|
||||
GetActiveUsersByRoleAndOrgID(ctx context.Context, role Role, orgID valuer.UUID) ([]*User, error)
|
||||
|
||||
// List users by org.
|
||||
ListUsersByOrgID(ctx context.Context, orgID valuer.UUID) ([]*StorableUser, error)
|
||||
ListUsersByOrgID(ctx context.Context, orgID valuer.UUID) ([]*User, error)
|
||||
|
||||
// List users by email and org ids.
|
||||
ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*StorableUser, error)
|
||||
ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*User, error)
|
||||
|
||||
// Get users for an org id using emails and statuses
|
||||
GetUsersByEmailsOrgIDAndStatuses(context.Context, valuer.UUID, []string, []string) ([]*StorableUser, error)
|
||||
GetUsersByEmailsOrgIDAndStatuses(context.Context, valuer.UUID, []string, []string) ([]*User, error)
|
||||
|
||||
UpdateUser(ctx context.Context, orgID valuer.UUID, user *StorableUser) error
|
||||
UpdateUser(ctx context.Context, orgID valuer.UUID, user *User) error
|
||||
DeleteUser(ctx context.Context, orgID string, id string) error
|
||||
SoftDeleteUser(ctx context.Context, orgID string, id string) error
|
||||
|
||||
@@ -328,10 +267,10 @@ type UserStore interface {
|
||||
CountByOrgIDAndStatuses(ctx context.Context, orgID valuer.UUID, statuses []string) (map[valuer.String]int64, error)
|
||||
|
||||
// Get root user by org.
|
||||
GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*StorableUser, error)
|
||||
GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*User, error)
|
||||
|
||||
// Get user by reset password token
|
||||
GetUserByResetPasswordToken(ctx context.Context, token string) (*StorableUser, error)
|
||||
GetUserByResetPasswordToken(ctx context.Context, token string) (*User, error)
|
||||
|
||||
// Transaction
|
||||
RunInTx(ctx context.Context, cb func(ctx context.Context) error) error
|
||||
|
||||
Reference in New Issue
Block a user