2025-05-23 15:52:58 +05:30

599 lines
18 KiB
Go

package impluser
import (
"context"
"database/sql"
"sort"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
)
type Store struct {
sqlstore sqlstore.SQLStore
}
func NewStore(sqlstore sqlstore.SQLStore) types.UserStore {
return &Store{sqlstore: sqlstore}
}
// CreateBulkInvite implements types.InviteStore.
func (s *Store) CreateBulkInvite(ctx context.Context, invites []*types.Invite) error {
_, err := s.sqlstore.BunDB().NewInsert().
Model(&invites).
Exec(ctx)
if err != nil {
return s.sqlstore.WrapAlreadyExistsErrf(err, types.ErrInviteAlreadyExists, "invite with email: %s already exists in org: %s", invites[0].Email, invites[0].OrgID)
}
return nil
}
// Delete implements types.InviteStore.
func (s *Store) DeleteInvite(ctx context.Context, orgID string, id valuer.UUID) error {
_, err := s.sqlstore.BunDB().NewDelete().
Model(&types.Invite{}).
Where("org_id = ?", orgID).
Where("id = ?", id).
Exec(ctx)
if err != nil {
return s.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with id: %s does not exist in org: %s", id.StringValue(), orgID)
}
return nil
}
// GetInviteByEmailInOrg implements types.InviteStore.
func (s *Store) GetInviteByEmailInOrg(ctx context.Context, orgID string, email string) (*types.Invite, error) {
invite := new(types.Invite)
err := s.sqlstore.BunDB().NewSelect().
Model(invite).
Where("email = ?", email).
Where("org_id = ?", orgID).
Scan(ctx)
if err != nil {
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with email: %s does not exist in org: %s", email, orgID)
}
return invite, nil
}
func (s *Store) GetInviteByToken(ctx context.Context, token string) (*types.GettableInvite, error) {
invite := new(types.Invite)
err := s.sqlstore.BunDB().NewSelect().
Model(invite).
Where("token = ?", token).
Scan(ctx)
if err != nil {
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with token: %s does not exist", token)
}
orgName, err := s.getOrgNameByID(ctx, invite.OrgID)
if err != nil {
return nil, err
}
gettableInvite := &types.GettableInvite{
Invite: *invite,
Organization: orgName,
}
return gettableInvite, nil
}
func (s *Store) ListInvite(ctx context.Context, orgID string) ([]*types.Invite, error) {
invites := new([]*types.Invite)
err := s.sqlstore.BunDB().NewSelect().
Model(invites).
Where("org_id = ?", orgID).
Scan(ctx)
if err != nil {
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with org id: %s does not exist", orgID)
}
return *invites, nil
}
func (s *Store) CreatePassword(ctx context.Context, password *types.FactorPassword) (*types.FactorPassword, error) {
_, err := s.sqlstore.BunDB().NewInsert().
Model(password).
Exec(ctx)
if err != nil {
return nil, s.sqlstore.WrapAlreadyExistsErrf(err, types.ErrPasswordAlreadyExists, "password with user id: %s already exists", password.UserID)
}
return password, nil
}
func (s *Store) CreateUserWithPassword(ctx context.Context, user *types.User, password *types.FactorPassword) (*types.User, error) {
tx, err := s.sqlstore.BunDB().BeginTx(ctx, nil)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to start transaction")
}
defer func() {
_ = tx.Rollback()
}()
if _, err := tx.NewInsert().
Model(user).
Exec(ctx); err != nil {
return nil, s.sqlstore.WrapAlreadyExistsErrf(err, types.ErrUserAlreadyExists, "user with email: %s already exists in org: %s", user.Email, user.OrgID)
}
password.UserID = user.ID.StringValue()
if _, err := tx.NewInsert().
Model(password).
Exec(ctx); err != nil {
return nil, s.sqlstore.WrapAlreadyExistsErrf(err, types.ErrPasswordAlreadyExists, "password with email: %s already exists in org: %s", user.Email, user.OrgID)
}
err = tx.Commit()
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to commit transaction")
}
return user, nil
}
func (s *Store) CreateUser(ctx context.Context, user *types.User) error {
_, err := s.sqlstore.BunDB().NewInsert().
Model(user).
Exec(ctx)
if err != nil {
return s.sqlstore.WrapAlreadyExistsErrf(err, types.ErrUserAlreadyExists, "user with email: %s already exists in org: %s", user.Email, user.OrgID)
}
return nil
}
func (s *Store) GetDefaultOrgID(ctx context.Context) (string, error) {
org := new(types.Organization)
err := s.sqlstore.BunDB().NewSelect().
Model(org).
Limit(1).
Scan(ctx)
if err != nil {
return "", s.sqlstore.WrapNotFoundErrf(err, types.ErrOrganizationNotFound, "default org does not exist")
}
return org.ID.String(), nil
}
// this is temporary function, we plan to remove this in the next PR.
func (s *Store) getOrgNameByID(ctx context.Context, orgID string) (string, error) {
org := new(types.Organization)
err := s.sqlstore.BunDB().NewSelect().
Model(org).
Where("id = ?", orgID).
Scan(ctx)
if err != nil {
return "", s.sqlstore.WrapNotFoundErrf(err, types.ErrOrganizationNotFound, "org with id: %s does not exist", orgID)
}
return org.DisplayName, nil
}
func (s *Store) GetUserByID(ctx context.Context, orgID string, id string) (*types.GettableUser, error) {
user := new(types.User)
err := s.sqlstore.BunDB().NewSelect().
Model(user).
Where("org_id = ?", orgID).
Where("id = ?", id).
Scan(ctx)
if err != nil {
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "user with id: %s does not exist in org: %s", id, orgID)
}
// remove this in next PR
orgName, err := s.getOrgNameByID(ctx, orgID)
if err != nil {
return nil, err
}
return &types.GettableUser{User: *user, Organization: orgName}, nil
}
func (s *Store) GetUserByEmailInOrg(ctx context.Context, orgID string, email string) (*types.GettableUser, error) {
user := new(types.User)
err := s.sqlstore.BunDB().NewSelect().
Model(user).
Where("org_id = ?", orgID).
Where("email = ?", email).
Scan(ctx)
if err != nil {
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "user with email: %s does not exist in org: %s", email, orgID)
}
// remove this in next PR
orgName, err := s.getOrgNameByID(ctx, orgID)
if err != nil {
return nil, err
}
return &types.GettableUser{User: *user, Organization: orgName}, nil
}
func (s *Store) GetUsersByEmail(ctx context.Context, email string) ([]*types.GettableUser, error) {
users := new([]*types.User)
err := s.sqlstore.BunDB().NewSelect().
Model(users).
Where("email = ?", email).
Scan(ctx)
if err != nil {
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "user with email: %s does not exist", email)
}
// remove this in next PR
usersWithOrg := []*types.GettableUser{}
for _, user := range *users {
orgName, err := s.getOrgNameByID(ctx, user.OrgID)
if err != nil {
return nil, err
}
usersWithOrg = append(usersWithOrg, &types.GettableUser{User: *user, Organization: orgName})
}
return usersWithOrg, nil
}
func (s *Store) GetUsersByRoleInOrg(ctx context.Context, orgID string, role types.Role) ([]*types.GettableUser, error) {
users := new([]*types.User)
err := s.sqlstore.BunDB().NewSelect().
Model(users).
Where("org_id = ?", orgID).
Where("role = ?", role).
Scan(ctx)
if err != nil {
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "user with role: %s does not exist in org: %s", role, orgID)
}
// remove this in next PR
orgName, err := s.getOrgNameByID(ctx, orgID)
if err != nil {
return nil, err
}
usersWithOrg := []*types.GettableUser{}
for _, user := range *users {
usersWithOrg = append(usersWithOrg, &types.GettableUser{User: *user, Organization: orgName})
}
return usersWithOrg, nil
}
func (s *Store) UpdateUser(ctx context.Context, orgID string, id string, user *types.User) (*types.User, error) {
user.UpdatedAt = time.Now()
_, err := s.sqlstore.BunDB().NewUpdate().
Model(user).
Column("display_name").
Column("role").
Column("updated_at").
Where("id = ?", id).
Where("org_id = ?", orgID).
Exec(ctx)
if err != nil {
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "user with id: %s does not exist in org: %s", id, orgID)
}
return user, nil
}
func (s *Store) ListUsers(ctx context.Context, orgID string) ([]*types.GettableUser, error) {
users := []*types.User{}
err := s.sqlstore.BunDB().NewSelect().
Model(&users).
Where("org_id = ?", orgID).
Scan(ctx)
if err != nil {
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "users with org id: %s does not exist", orgID)
}
// remove this in next PR
orgName, err := s.getOrgNameByID(ctx, orgID)
if err != nil {
return nil, err
}
usersWithOrg := []*types.GettableUser{}
for _, user := range users {
usersWithOrg = append(usersWithOrg, &types.GettableUser{User: *user, Organization: orgName})
}
return usersWithOrg, nil
}
func (s *Store) DeleteUser(ctx context.Context, orgID string, id string) error {
tx, err := s.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.ResetPasswordRequest)).
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
_, 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 (s *Store) CreateResetPasswordToken(ctx context.Context, resetPasswordRequest *types.ResetPasswordRequest) error {
_, err := s.sqlstore.BunDB().NewInsert().
Model(resetPasswordRequest).
Exec(ctx)
if err != nil {
return s.sqlstore.WrapAlreadyExistsErrf(err, types.ErrResetPasswordTokenAlreadyExists, "reset password token with password id: %s already exists", resetPasswordRequest.PasswordID)
}
return nil
}
func (s *Store) GetPasswordByID(ctx context.Context, id string) (*types.FactorPassword, error) {
password := new(types.FactorPassword)
err := s.sqlstore.BunDB().NewSelect().
Model(password).
Where("id = ?", id).
Scan(ctx)
if err != nil {
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with id: %s does not exist", id)
}
return password, nil
}
func (s *Store) GetPasswordByUserID(ctx context.Context, id string) (*types.FactorPassword, error) {
password := new(types.FactorPassword)
err := s.sqlstore.BunDB().NewSelect().
Model(password).
Where("user_id = ?", id).
Scan(ctx)
if err != nil {
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with user id: %s does not exist", id)
}
return password, nil
}
func (s *Store) GetResetPasswordByPasswordID(ctx context.Context, passwordID string) (*types.ResetPasswordRequest, error) {
resetPasswordRequest := new(types.ResetPasswordRequest)
err := s.sqlstore.BunDB().NewSelect().
Model(resetPasswordRequest).
Where("password_id = ?", passwordID).
Scan(ctx)
if err != nil {
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token with password id: %s does not exist", passwordID)
}
return resetPasswordRequest, nil
}
func (s *Store) GetResetPassword(ctx context.Context, token string) (*types.ResetPasswordRequest, error) {
resetPasswordRequest := new(types.ResetPasswordRequest)
err := s.sqlstore.BunDB().NewSelect().
Model(resetPasswordRequest).
Where("token = ?", token).
Scan(ctx)
if err != nil {
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token with token: %s does not exist", token)
}
return resetPasswordRequest, nil
}
func (s *Store) UpdatePasswordAndDeleteResetPasswordEntry(ctx context.Context, userID string, password string) error {
tx, err := s.sqlstore.BunDB().BeginTx(ctx, nil)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to start transaction")
}
defer func() {
_ = tx.Rollback()
}()
factorPassword := &types.FactorPassword{
UserID: userID,
Password: password,
TimeAuditable: types.TimeAuditable{
UpdatedAt: time.Now(),
},
}
_, err = tx.NewUpdate().
Model(factorPassword).
Column("password").
Column("updated_at").
Where("user_id = ?", userID).
Exec(ctx)
if err != nil {
return s.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with user id: %s does not exist", userID)
}
_, err = tx.NewDelete().
Model(&types.ResetPasswordRequest{}).
Where("password_id = ?", userID).
Exec(ctx)
if err != nil {
return s.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token with password id: %s does not exist", userID)
}
err = tx.Commit()
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to commit transaction")
}
return nil
}
func (s *Store) UpdatePassword(ctx context.Context, userID string, password string) error {
factorPassword := &types.FactorPassword{
UserID: userID,
Password: password,
TimeAuditable: types.TimeAuditable{
UpdatedAt: time.Now(),
},
}
_, err := s.sqlstore.BunDB().NewUpdate().
Model(factorPassword).
Column("password").
Column("updated_at").
Where("user_id = ?", userID).
Exec(ctx)
if err != nil {
return s.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with user id: %s does not exist", userID)
}
return nil
}
func (s *Store) GetDomainByName(ctx context.Context, name string) (*types.StorableOrgDomain, error) {
return nil, errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "not supported")
}
// --- API KEY ---
func (s *Store) CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error {
_, err := s.sqlstore.BunDB().NewInsert().
Model(apiKey).
Exec(ctx)
if err != nil {
return s.sqlstore.WrapAlreadyExistsErrf(err, types.ErrAPIKeyAlreadyExists, "API key with token: %s already exists", apiKey.Token)
}
return nil
}
func (s *Store) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *types.StorableAPIKey, updaterID valuer.UUID) error {
apiKey.UpdatedBy = updaterID.String()
apiKey.UpdatedAt = time.Now()
_, err := s.sqlstore.BunDB().NewUpdate().
Model(apiKey).
Column("role", "name", "updated_at", "updated_by").
Where("id = ?", id).
Where("revoked = false").
Exec(ctx)
if err != nil {
return s.sqlstore.WrapNotFoundErrf(err, types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
}
return nil
}
func (s *Store) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.StorableAPIKeyUser, error) {
orgUserAPIKeys := new(types.OrgUserAPIKey)
if err := s.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 (s *Store) RevokeAPIKey(ctx context.Context, id, revokedByUserID valuer.UUID) error {
updatedAt := time.Now().Unix()
_, err := s.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 (s *Store) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*types.StorableAPIKeyUser, error) {
apiKey := new(types.OrgUserAPIKey)
if err := s.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, s.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, s.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
}