diff --git a/pkg/query-service/auth/auth.go b/pkg/query-service/auth/auth.go index 032299432c..93101e9a5f 100644 --- a/pkg/query-service/auth/auth.go +++ b/pkg/query-service/auth/auth.go @@ -20,6 +20,7 @@ import ( smtpservice "github.com/SigNoz/signoz/pkg/query-service/utils/smtpService" "github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/types/authtypes" + "github.com/SigNoz/signoz/pkg/valuer" "go.uber.org/zap" "golang.org/x/crypto/bcrypt" ) @@ -87,12 +88,18 @@ func Invite(ctx context.Context, req *model.InviteRequest) (*model.InviteRespons } inv := &types.Invite{ - Name: req.Name, - Email: req.Email, - Token: token, - CreatedAt: time.Now(), - Role: req.Role, - OrgID: au.OrgID, + Identifiable: types.Identifiable{ + ID: valuer.GenerateUUID(), + }, + TimeAuditable: types.TimeAuditable{ + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + Name: req.Name, + Email: req.Email, + Token: token, + Role: req.Role, + OrgID: au.OrgID, } if err := dao.DB().CreateInviteEntry(ctx, inv); err != nil { @@ -188,12 +195,18 @@ func inviteUser(ctx context.Context, req *model.InviteRequest, au *types.Gettabl } inv := &types.Invite{ - Name: req.Name, - Email: req.Email, - Token: token, - CreatedAt: time.Now(), - Role: req.Role, - OrgID: au.OrgID, + Identifiable: types.Identifiable{ + ID: valuer.GenerateUUID(), + }, + TimeAuditable: types.TimeAuditable{ + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + Name: req.Name, + Email: req.Email, + Token: token, + Role: req.Role, + OrgID: au.OrgID, } if err := dao.DB().CreateInviteEntry(ctx, inv); err != nil { diff --git a/pkg/signoz/provider.go b/pkg/signoz/provider.go index 1aca7ebd9d..4dc9c71228 100644 --- a/pkg/signoz/provider.go +++ b/pkg/signoz/provider.go @@ -63,6 +63,7 @@ func NewSQLMigrationProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedM sqlmigration.NewUpdatePatAndOrgDomainsFactory(sqlstore), sqlmigration.NewUpdatePipelines(sqlstore), sqlmigration.NewDropLicensesSitesFactory(sqlstore), + sqlmigration.NewUpdateInvitesFactory(sqlstore), ) } diff --git a/pkg/sqlmigration/019_update_invites.go b/pkg/sqlmigration/019_update_invites.go new file mode 100644 index 0000000000..574216a67b --- /dev/null +++ b/pkg/sqlmigration/019_update_invites.go @@ -0,0 +1,139 @@ +package sqlmigration + +import ( + "context" + "database/sql" + "time" + + "github.com/SigNoz/signoz/pkg/factory" + "github.com/SigNoz/signoz/pkg/sqlstore" + "github.com/SigNoz/signoz/pkg/types" + "github.com/SigNoz/signoz/pkg/valuer" + "github.com/uptrace/bun" + "github.com/uptrace/bun/migrate" +) + +type updateInvites struct { + store sqlstore.SQLStore +} + +type existingInvite struct { + bun.BaseModel `bun:"table:invites"` + + OrgID string `bun:"org_id,type:text,notnull" json:"orgId"` + ID int `bun:"id,pk,autoincrement" json:"id"` + Name string `bun:"name,type:text,notnull" json:"name"` + Email string `bun:"email,type:text,notnull,unique" json:"email"` + Token string `bun:"token,type:text,notnull" json:"token"` + CreatedAt time.Time `bun:"created_at,notnull" json:"createdAt"` + Role string `bun:"role,type:text,notnull" json:"role"` +} + +type newInvite struct { + bun.BaseModel `bun:"table:user_invite"` + + types.Identifiable + types.TimeAuditable + Name string `bun:"name,type:text,notnull" json:"name"` + Email string `bun:"email,type:text,notnull,unique" json:"email"` + Token string `bun:"token,type:text,notnull" json:"token"` + Role string `bun:"role,type:text,notnull" json:"role"` + OrgID string `bun:"org_id,type:text,notnull" json:"orgId"` +} + +func NewUpdateInvitesFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] { + return factory. + NewProviderFactory( + factory.MustNewName("update_invites"), + func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) { + return newUpdateInvites(ctx, ps, c, sqlstore) + }) +} + +func newUpdateInvites(_ context.Context, _ factory.ProviderSettings, _ Config, store sqlstore.SQLStore) (SQLMigration, error) { + return &updateInvites{store: store}, nil +} + +func (migration *updateInvites) Register(migrations *migrate.Migrations) error { + if err := migrations. + Register(migration.Up, migration.Down); err != nil { + return err + } + + return nil +} + +func (migration *updateInvites) Up(ctx context.Context, db *bun.DB) error { + tx, err := db. + BeginTx(ctx, nil) + if err != nil { + return err + } + + defer tx.Rollback() + + err = migration. + store. + Dialect(). + RenameTableAndModifyModel(ctx, tx, new(existingInvite), new(newInvite), func(ctx context.Context) error { + existingInvites := make([]*existingInvite, 0) + err = tx. + NewSelect(). + Model(&existingInvites). + Scan(ctx) + if err != nil { + if err != sql.ErrNoRows { + return err + } + } + + if err == nil && len(existingInvites) > 0 { + newInvites := migration. + CopyOldInvitesToNewInvites(existingInvites) + _, err = tx. + NewInsert(). + Model(&newInvites). + Exec(ctx) + if err != nil { + return err + } + } + return nil + }) + if err != nil { + return err + } + + err = tx.Commit() + if err != nil { + return err + } + + return nil +} + +func (migration *updateInvites) Down(context.Context, *bun.DB) error { + return nil +} + +func (migration *updateInvites) CopyOldInvitesToNewInvites(existingInvites []*existingInvite) []*newInvite { + newInvites := make([]*newInvite, 0) + for _, invite := range existingInvites { + newInvites = append(newInvites, &newInvite{ + Identifiable: types.Identifiable{ + ID: valuer.GenerateUUID(), + }, + TimeAuditable: types.TimeAuditable{ + CreatedAt: invite.CreatedAt, + UpdatedAt: time.Now(), + }, + Name: invite.Name, + Email: invite.Email, + Token: invite.Token, + Role: invite.Role, + OrgID: invite.OrgID, + }) + } + + return newInvites +} diff --git a/pkg/sqlstore/postgressqlstore/dialect.go b/pkg/sqlstore/postgressqlstore/dialect.go index 49a07f160f..61619ca4c5 100644 --- a/pkg/sqlstore/postgressqlstore/dialect.go +++ b/pkg/sqlstore/postgressqlstore/dialect.go @@ -2,6 +2,7 @@ package postgressqlstore import ( "context" + "reflect" "github.com/uptrace/bun" ) @@ -151,3 +152,60 @@ func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table str } return true, nil } + +func (dialect *dialect) TableExists(ctx context.Context, bun bun.IDB, table interface{}) (bool, error) { + + count := 0 + err := bun. + NewSelect(). + ColumnExpr("count(*)"). + Table("pg_catalog.pg_tables"). + Where("tablename = ?", bun.Dialect().Tables().Get(reflect.TypeOf(table)).Name). + Scan(ctx, &count) + + if err != nil { + return false, err + } + + if count == 0 { + return false, nil + } + + return true, nil +} + +func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, cb func(context.Context) error) error { + exists, err := dialect.TableExists(ctx, bun, newModel) + if err != nil { + return err + } + if exists { + return nil + } + + _, err = bun. + NewCreateTable(). + IfNotExists(). + Model(newModel). + Exec(ctx) + + if err != nil { + return err + } + + err = cb(ctx) + if err != nil { + return err + } + + _, err = bun. + NewDropTable(). + IfExists(). + Model(oldModel). + Exec(ctx) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/sqlstore/sqlitesqlstore/dialect.go b/pkg/sqlstore/sqlitesqlstore/dialect.go index 08b87d1585..b029501851 100644 --- a/pkg/sqlstore/sqlitesqlstore/dialect.go +++ b/pkg/sqlstore/sqlitesqlstore/dialect.go @@ -2,6 +2,7 @@ package sqlitesqlstore import ( "context" + "reflect" "github.com/uptrace/bun" ) @@ -141,3 +142,62 @@ func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table str } return true, nil } + +func (dialect *dialect) TableExists(ctx context.Context, bun bun.IDB, table interface{}) (bool, error) { + + count := 0 + err := bun. + NewSelect(). + ColumnExpr("count(*)"). + Table("sqlite_master"). + Where("type = ?", "table"). + Where("name = ?", bun.Dialect().Tables().Get(reflect.TypeOf(table)).Name). + Scan(ctx, &count) + + if err != nil { + return false, err + } + + if count == 0 { + return false, nil + } + + return true, nil +} + +func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, cb func(context.Context) error) error { + exists, err := dialect.TableExists(ctx, bun, newModel) + if err != nil { + return err + } + if exists { + return nil + } + + _, err = bun. + NewCreateTable(). + IfNotExists(). + Model(newModel). + ForeignKey(`("org_id") REFERENCES "organizations" ("id")`). + Exec(ctx) + + if err != nil { + return err + } + + err = cb(ctx) + if err != nil { + return err + } + + _, err = bun. + NewDropTable(). + IfExists(). + Model(oldModel). + Exec(ctx) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/sqlstore/sqlstore.go b/pkg/sqlstore/sqlstore.go index 4e20e05240..5dee974a55 100644 --- a/pkg/sqlstore/sqlstore.go +++ b/pkg/sqlstore/sqlstore.go @@ -42,4 +42,5 @@ type SQLDialect interface { GetColumnType(context.Context, bun.IDB, string, string) (string, error) ColumnExists(context.Context, bun.IDB, string, string) (bool, error) RenameColumn(context.Context, bun.IDB, string, string, string) (bool, error) + RenameTableAndModifyModel(context.Context, bun.IDB, interface{}, interface{}, func(context.Context) error) error } diff --git a/pkg/sqlstore/sqlstoretest/dialect.go b/pkg/sqlstore/sqlstoretest/dialect.go index bc0820ec16..8e70488557 100644 --- a/pkg/sqlstore/sqlstoretest/dialect.go +++ b/pkg/sqlstore/sqlstoretest/dialect.go @@ -28,3 +28,7 @@ func (dialect *dialect) ColumnExists(ctx context.Context, bun bun.IDB, table str func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table string, oldColumnName string, newColumnName string) (bool, error) { return true, nil } + +func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, cb func(context.Context) error) error { + return nil +} diff --git a/pkg/types/user.go b/pkg/types/user.go index bf6fd59df1..33afceccb6 100644 --- a/pkg/types/user.go +++ b/pkg/types/user.go @@ -1,21 +1,19 @@ package types import ( - "time" - "github.com/uptrace/bun" ) type Invite struct { - bun.BaseModel `bun:"table:invites"` + bun.BaseModel `bun:"table:user_invite"` - OrgID string `bun:"org_id,type:text,notnull" json:"orgId"` - ID int `bun:"id,pk,autoincrement" json:"id"` - Name string `bun:"name,type:text,notnull" json:"name"` - Email string `bun:"email,type:text,notnull,unique" json:"email"` - Token string `bun:"token,type:text,notnull" json:"token"` - CreatedAt time.Time `bun:"created_at,notnull" json:"createdAt"` - Role string `bun:"role,type:text,notnull" json:"role"` + Identifiable + TimeAuditable + OrgID string `bun:"org_id,type:text,notnull" json:"orgId"` + Name string `bun:"name,type:text,notnull" json:"name"` + Email string `bun:"email,type:text,notnull,unique" json:"email"` + Token string `bun:"token,type:text,notnull" json:"token"` + Role string `bun:"role,type:text,notnull" json:"role"` } type Group struct {