diff --git a/ee/http/middleware/pat.go b/ee/http/middleware/pat.go index cb7e8e0917..399d64aad5 100644 --- a/ee/http/middleware/pat.go +++ b/ee/http/middleware/pat.go @@ -4,6 +4,7 @@ import ( "net/http" "time" + eeTypes "github.com/SigNoz/signoz/ee/types" "github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/types/authtypes" @@ -24,7 +25,7 @@ func (p *Pat) Wrap(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var values []string var patToken string - var pat types.StorablePersonalAccessToken + var pat eeTypes.StorablePersonalAccessToken for _, header := range p.headers { values = append(values, r.Header.Get(header)) diff --git a/ee/query-service/app/api/cloudIntegrations.go b/ee/query-service/app/api/cloudIntegrations.go index 13ac372f65..41e0d58f85 100644 --- a/ee/query-service/app/api/cloudIntegrations.go +++ b/ee/query-service/app/api/cloudIntegrations.go @@ -11,7 +11,7 @@ import ( "time" "github.com/SigNoz/signoz/ee/query-service/constants" - "github.com/SigNoz/signoz/ee/query-service/model" + eeTypes "github.com/SigNoz/signoz/ee/types" "github.com/SigNoz/signoz/pkg/query-service/auth" baseconstants "github.com/SigNoz/signoz/pkg/query-service/constants" "github.com/SigNoz/signoz/pkg/query-service/dao" @@ -135,19 +135,12 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId zap.String("cloudProvider", cloudProvider), ) - newPAT := model.PAT{ - StorablePersonalAccessToken: types.StorablePersonalAccessToken{ - Token: generatePATToken(), - UserID: integrationUser.ID, - Name: integrationPATName, - Role: baseconstants.ViewerGroup, - ExpiresAt: 0, - TimeAuditable: types.TimeAuditable{ - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - }, - }, - } + newPAT := eeTypes.NewGettablePAT( + integrationPATName, + baseconstants.ViewerGroup, + integrationUser.ID, + 0, + ) integrationPAT, err := ah.AppDao().CreatePAT(ctx, orgId, newPAT) if err != nil { return "", basemodel.InternalError(fmt.Errorf( diff --git a/ee/query-service/app/api/pat.go b/ee/query-service/app/api/pat.go index 3b50fd0031..f89f85c67f 100644 --- a/ee/query-service/app/api/pat.go +++ b/ee/query-service/app/api/pat.go @@ -2,31 +2,21 @@ package api import ( "context" - "crypto/rand" - "encoding/base64" "encoding/json" "fmt" "net/http" "time" "github.com/SigNoz/signoz/ee/query-service/model" + "github.com/SigNoz/signoz/ee/types" + eeTypes "github.com/SigNoz/signoz/ee/types" "github.com/SigNoz/signoz/pkg/query-service/auth" baseconstants "github.com/SigNoz/signoz/pkg/query-service/constants" basemodel "github.com/SigNoz/signoz/pkg/query-service/model" - "github.com/SigNoz/signoz/pkg/types" "github.com/gorilla/mux" "go.uber.org/zap" ) -func generatePATToken() string { - // Generate a 32-byte random token. - token := make([]byte, 32) - rand.Read(token) - // Encode the token in base64. - encodedToken := base64.StdEncoding.EncodeToString(token) - return encodedToken -} - func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) { ctx := context.Background() @@ -43,31 +33,18 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) { }, nil) return } - pat := model.PAT{ - StorablePersonalAccessToken: types.StorablePersonalAccessToken{ - Name: req.Name, - Role: req.Role, - ExpiresAt: req.ExpiresInDays, - }, - } + pat := eeTypes.NewGettablePAT( + req.Name, + req.Role, + user.ID, + req.ExpiresInDays, + ) err = validatePATRequest(pat) if err != nil { RespondError(w, model.BadRequest(err), nil) return } - // All the PATs are associated with the user creating the PAT. - pat.UserID = user.ID - pat.CreatedAt = time.Now() - pat.UpdatedAt = time.Now() - pat.LastUsed = 0 - pat.Token = generatePATToken() - - if pat.ExpiresAt != 0 { - // convert expiresAt to unix timestamp from days - pat.ExpiresAt = time.Now().Unix() + (pat.ExpiresAt * 24 * 60 * 60) - } - zap.L().Info("Got Create PAT request", zap.Any("pat", pat)) var apierr basemodel.BaseApiError if pat, apierr = ah.AppDao().CreatePAT(ctx, user.OrgID, pat); apierr != nil { @@ -78,7 +55,7 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) { ah.Respond(w, &pat) } -func validatePATRequest(req model.PAT) error { +func validatePATRequest(req types.GettablePAT) error { if req.Role == "" || (req.Role != baseconstants.ViewerGroup && req.Role != baseconstants.EditorGroup && req.Role != baseconstants.AdminGroup) { return fmt.Errorf("valid role is required") } @@ -94,7 +71,7 @@ func validatePATRequest(req model.PAT) error { func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) { ctx := context.Background() - req := model.PAT{} + req := types.GettablePAT{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { RespondError(w, model.BadRequest(err), nil) return diff --git a/ee/query-service/dao/interface.go b/ee/query-service/dao/interface.go index bb2bc764d5..0ee500a327 100644 --- a/ee/query-service/dao/interface.go +++ b/ee/query-service/dao/interface.go @@ -4,7 +4,6 @@ import ( "context" "net/url" - "github.com/SigNoz/signoz/ee/query-service/model" "github.com/SigNoz/signoz/ee/types" basedao "github.com/SigNoz/signoz/pkg/query-service/dao" baseint "github.com/SigNoz/signoz/pkg/query-service/interfaces" @@ -36,11 +35,11 @@ type ModelDao interface { DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.BaseApiError GetDomainByEmail(ctx context.Context, email string) (*types.GettableOrgDomain, basemodel.BaseApiError) - CreatePAT(ctx context.Context, orgID string, p model.PAT) (model.PAT, basemodel.BaseApiError) - UpdatePAT(ctx context.Context, orgID string, p model.PAT, id string) basemodel.BaseApiError - GetPAT(ctx context.Context, pat string) (*model.PAT, basemodel.BaseApiError) - GetPATByID(ctx context.Context, orgID string, id string) (*model.PAT, basemodel.BaseApiError) + CreatePAT(ctx context.Context, orgID string, p types.GettablePAT) (types.GettablePAT, basemodel.BaseApiError) + UpdatePAT(ctx context.Context, orgID string, p types.GettablePAT, id string) basemodel.BaseApiError + GetPAT(ctx context.Context, pat string) (*types.GettablePAT, basemodel.BaseApiError) + GetPATByID(ctx context.Context, orgID string, id string) (*types.GettablePAT, basemodel.BaseApiError) GetUserByPAT(ctx context.Context, orgID string, token string) (*ossTypes.GettableUser, basemodel.BaseApiError) - ListPATs(ctx context.Context, orgID string) ([]model.PAT, basemodel.BaseApiError) + ListPATs(ctx context.Context, orgID string) ([]types.GettablePAT, basemodel.BaseApiError) RevokePAT(ctx context.Context, orgID string, id string, userID string) basemodel.BaseApiError } diff --git a/ee/query-service/dao/sqlite/pat.go b/ee/query-service/dao/sqlite/pat.go index 7cc5b96380..e1a08f8d40 100644 --- a/ee/query-service/dao/sqlite/pat.go +++ b/ee/query-service/dao/sqlite/pat.go @@ -6,41 +6,47 @@ import ( "time" "github.com/SigNoz/signoz/ee/query-service/model" + "github.com/SigNoz/signoz/ee/types" basemodel "github.com/SigNoz/signoz/pkg/query-service/model" - "github.com/SigNoz/signoz/pkg/types" + ossTypes "github.com/SigNoz/signoz/pkg/types" + "go.uber.org/zap" ) -func (m *modelDao) CreatePAT(ctx context.Context, orgID string, p model.PAT) (model.PAT, basemodel.BaseApiError) { +func (m *modelDao) CreatePAT(ctx context.Context, orgID string, p types.GettablePAT) (types.GettablePAT, basemodel.BaseApiError) { p.StorablePersonalAccessToken.OrgID = orgID _, err := m.DB().NewInsert(). Model(&p.StorablePersonalAccessToken). - Returning("id"). Exec(ctx) if err != nil { zap.L().Error("Failed to insert PAT in db, err: %v", zap.Error(err)) - return model.PAT{}, model.InternalError(fmt.Errorf("PAT insertion failed")) + return types.GettablePAT{}, model.InternalError(fmt.Errorf("PAT insertion failed")) } createdByUser, _ := m.GetUser(ctx, p.UserID) if createdByUser == nil { - p.CreatedByUser = model.User{ + p.CreatedByUser = types.PatUser{ NotFound: true, } } else { - p.CreatedByUser = model.User{ - Id: createdByUser.ID, - Name: createdByUser.Name, - Email: createdByUser.Email, - CreatedAt: createdByUser.CreatedAt.Unix(), - ProfilePictureURL: createdByUser.ProfilePictureURL, - NotFound: false, + p.CreatedByUser = types.PatUser{ + User: ossTypes.User{ + ID: createdByUser.ID, + Name: createdByUser.Name, + Email: createdByUser.Email, + TimeAuditable: ossTypes.TimeAuditable{ + CreatedAt: createdByUser.CreatedAt, + UpdatedAt: createdByUser.UpdatedAt, + }, + ProfilePictureURL: createdByUser.ProfilePictureURL, + }, + NotFound: false, } } return p, nil } -func (m *modelDao) UpdatePAT(ctx context.Context, orgID string, p model.PAT, id string) basemodel.BaseApiError { +func (m *modelDao) UpdatePAT(ctx context.Context, orgID string, p types.GettablePAT, id string) basemodel.BaseApiError { _, err := m.DB().NewUpdate(). Model(&p.StorablePersonalAccessToken). Column("role", "name", "updated_at", "updated_by_user_id"). @@ -55,7 +61,7 @@ func (m *modelDao) UpdatePAT(ctx context.Context, orgID string, p model.PAT, id return nil } -func (m *modelDao) ListPATs(ctx context.Context, orgID string) ([]model.PAT, basemodel.BaseApiError) { +func (m *modelDao) ListPATs(ctx context.Context, orgID string) ([]types.GettablePAT, basemodel.BaseApiError) { pats := []types.StorablePersonalAccessToken{} if err := m.DB().NewSelect(). @@ -68,41 +74,51 @@ func (m *modelDao) ListPATs(ctx context.Context, orgID string) ([]model.PAT, bas return nil, model.InternalError(fmt.Errorf("failed to fetch PATs")) } - patsWithUsers := []model.PAT{} + patsWithUsers := []types.GettablePAT{} for i := range pats { - patWithUser := model.PAT{ + patWithUser := types.GettablePAT{ StorablePersonalAccessToken: pats[i], } createdByUser, _ := m.GetUser(ctx, pats[i].UserID) if createdByUser == nil { - patWithUser.CreatedByUser = model.User{ + patWithUser.CreatedByUser = types.PatUser{ NotFound: true, } } else { - patWithUser.CreatedByUser = model.User{ - Id: createdByUser.ID, - Name: createdByUser.Name, - Email: createdByUser.Email, - CreatedAt: createdByUser.CreatedAt.Unix(), - ProfilePictureURL: createdByUser.ProfilePictureURL, - NotFound: false, + patWithUser.CreatedByUser = types.PatUser{ + User: ossTypes.User{ + ID: createdByUser.ID, + Name: createdByUser.Name, + Email: createdByUser.Email, + TimeAuditable: ossTypes.TimeAuditable{ + CreatedAt: createdByUser.CreatedAt, + UpdatedAt: createdByUser.UpdatedAt, + }, + ProfilePictureURL: createdByUser.ProfilePictureURL, + }, + NotFound: false, } } updatedByUser, _ := m.GetUser(ctx, pats[i].UpdatedByUserID) if updatedByUser == nil { - patWithUser.UpdatedByUser = model.User{ + patWithUser.UpdatedByUser = types.PatUser{ NotFound: true, } } else { - patWithUser.UpdatedByUser = model.User{ - Id: updatedByUser.ID, - Name: updatedByUser.Name, - Email: updatedByUser.Email, - CreatedAt: updatedByUser.CreatedAt.Unix(), - ProfilePictureURL: updatedByUser.ProfilePictureURL, - NotFound: false, + patWithUser.UpdatedByUser = types.PatUser{ + User: ossTypes.User{ + ID: updatedByUser.ID, + Name: updatedByUser.Name, + Email: updatedByUser.Email, + TimeAuditable: ossTypes.TimeAuditable{ + CreatedAt: updatedByUser.CreatedAt, + UpdatedAt: updatedByUser.UpdatedAt, + }, + ProfilePictureURL: updatedByUser.ProfilePictureURL, + }, + NotFound: false, } } @@ -128,7 +144,7 @@ func (m *modelDao) RevokePAT(ctx context.Context, orgID string, id string, userI return nil } -func (m *modelDao) GetPAT(ctx context.Context, token string) (*model.PAT, basemodel.BaseApiError) { +func (m *modelDao) GetPAT(ctx context.Context, token string) (*types.GettablePAT, basemodel.BaseApiError) { pats := []types.StorablePersonalAccessToken{} if err := m.DB().NewSelect(). @@ -146,14 +162,14 @@ func (m *modelDao) GetPAT(ctx context.Context, token string) (*model.PAT, basemo } } - patWithUser := model.PAT{ + patWithUser := types.GettablePAT{ StorablePersonalAccessToken: pats[0], } return &patWithUser, nil } -func (m *modelDao) GetPATByID(ctx context.Context, orgID string, id string) (*model.PAT, basemodel.BaseApiError) { +func (m *modelDao) GetPATByID(ctx context.Context, orgID string, id string) (*types.GettablePAT, basemodel.BaseApiError) { pats := []types.StorablePersonalAccessToken{} if err := m.DB().NewSelect(). @@ -172,7 +188,7 @@ func (m *modelDao) GetPATByID(ctx context.Context, orgID string, id string) (*mo } } - patWithUser := model.PAT{ + patWithUser := types.GettablePAT{ StorablePersonalAccessToken: pats[0], } @@ -180,8 +196,8 @@ func (m *modelDao) GetPATByID(ctx context.Context, orgID string, id string) (*mo } // deprecated -func (m *modelDao) GetUserByPAT(ctx context.Context, orgID string, token string) (*types.GettableUser, basemodel.BaseApiError) { - users := []types.GettableUser{} +func (m *modelDao) GetUserByPAT(ctx context.Context, orgID string, token string) (*ossTypes.GettableUser, basemodel.BaseApiError) { + users := []ossTypes.GettableUser{} if err := m.DB().NewSelect(). Model(&users). diff --git a/ee/query-service/model/pat.go b/ee/query-service/model/pat.go index 53003aa891..5eca7a8724 100644 --- a/ee/query-service/model/pat.go +++ b/ee/query-service/model/pat.go @@ -1,25 +1,7 @@ package model -import "github.com/SigNoz/signoz/pkg/types" - -type User struct { - Id string `json:"id" db:"id"` - Name string `json:"name" db:"name"` - Email string `json:"email" db:"email"` - CreatedAt int64 `json:"createdAt" db:"created_at"` - ProfilePictureURL string `json:"profilePictureURL" db:"profile_picture_url"` - NotFound bool `json:"notFound"` -} - type CreatePATRequestBody struct { Name string `json:"name"` Role string `json:"role"` ExpiresInDays int64 `json:"expiresInDays"` } - -type PAT struct { - CreatedByUser User `json:"createdByUser"` - UpdatedByUser User `json:"updatedByUser"` - - types.StorablePersonalAccessToken -} diff --git a/ee/sqlstore/postgressqlstore/dialect.go b/ee/sqlstore/postgressqlstore/dialect.go index 61619ca4c5..e751d8783e 100644 --- a/ee/sqlstore/postgressqlstore/dialect.go +++ b/ee/sqlstore/postgressqlstore/dialect.go @@ -2,6 +2,7 @@ package postgressqlstore import ( "context" + "fmt" "reflect" "github.com/uptrace/bun" @@ -209,3 +210,11 @@ func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.I return nil } + +func (dialect *dialect) AddNotNullDefaultToColumn(ctx context.Context, bun bun.IDB, table string, column, columnType, defaultValue string) error { + query := fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s SET DEFAULT %s, ALTER COLUMN %s SET NOT NULL", table, column, defaultValue, column) + if _, err := bun.ExecContext(ctx, query); err != nil { + return err + } + return nil +} diff --git a/ee/types/personal_access_token.go b/ee/types/personal_access_token.go new file mode 100644 index 0000000000..d791cba2a8 --- /dev/null +++ b/ee/types/personal_access_token.go @@ -0,0 +1,73 @@ +package types + +import ( + "crypto/rand" + "encoding/base64" + "time" + + "github.com/SigNoz/signoz/pkg/types" + "github.com/uptrace/bun" +) + +type GettablePAT struct { + CreatedByUser PatUser `json:"createdByUser"` + UpdatedByUser PatUser `json:"updatedByUser"` + + StorablePersonalAccessToken +} + +type PatUser struct { + types.User + NotFound bool `json:"notFound"` +} + +func NewGettablePAT(name, role, userID string, expiresAt int64) GettablePAT { + return GettablePAT{ + StorablePersonalAccessToken: NewStorablePersonalAccessToken(name, role, userID, expiresAt), + } +} + +type StorablePersonalAccessToken struct { + bun.BaseModel `bun:"table:personal_access_tokens"` + + types.TimeAuditable + OrgID string `json:"orgId" bun:"org_id,type:text,notnull"` + ID int `json:"id" bun:"id,pk,autoincrement"` + Role string `json:"role" bun:"role,type:text,notnull,default:'ADMIN'"` + UserID string `json:"userId" bun:"user_id,type:text,notnull"` + Token string `json:"token" bun:"token,type:text,notnull,unique"` + Name string `json:"name" bun:"name,type:text,notnull"` + ExpiresAt int64 `json:"expiresAt" bun:"expires_at,notnull,default:0"` + LastUsed int64 `json:"lastUsed" bun:"last_used,notnull,default:0"` + Revoked bool `json:"revoked" bun:"revoked,notnull,default:false"` + UpdatedByUserID string `json:"updatedByUserId" bun:"updated_by_user_id,type:text,notnull,default:''"` +} + +func NewStorablePersonalAccessToken(name, role, userID string, expiresAt int64) StorablePersonalAccessToken { + now := time.Now() + if expiresAt != 0 { + // convert expiresAt to unix timestamp from days + expiresAt = now.Unix() + (expiresAt * 24 * 60 * 60) + } + + // Generate a 32-byte random token. + token := make([]byte, 32) + rand.Read(token) + // Encode the token in base64. + encodedToken := base64.StdEncoding.EncodeToString(token) + + return StorablePersonalAccessToken{ + Token: encodedToken, + Name: name, + Role: role, + UserID: userID, + ExpiresAt: expiresAt, + LastUsed: 0, + Revoked: false, + UpdatedByUserID: "", + TimeAuditable: types.TimeAuditable{ + CreatedAt: now, + UpdatedAt: now, + }, + } +} diff --git a/pkg/signoz/provider.go b/pkg/signoz/provider.go index b4eb4acbbf..4dcac8a515 100644 --- a/pkg/signoz/provider.go +++ b/pkg/signoz/provider.go @@ -64,6 +64,7 @@ func NewSQLMigrationProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedM sqlmigration.NewUpdatePipelines(sqlstore), sqlmigration.NewDropLicensesSitesFactory(sqlstore), sqlmigration.NewUpdateInvitesFactory(sqlstore), + sqlmigration.NewUpdatePatFactory(sqlstore), ) } diff --git a/pkg/sqlmigration/014_add_alertmanager.go b/pkg/sqlmigration/014_add_alertmanager.go index af50ed078f..937e757b8a 100644 --- a/pkg/sqlmigration/014_add_alertmanager.go +++ b/pkg/sqlmigration/014_add_alertmanager.go @@ -5,7 +5,6 @@ import ( "database/sql" "encoding/json" "strconv" - "strings" "time" "github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver" @@ -50,12 +49,14 @@ func (migration *addAlertmanager) Up(ctx context.Context, db *bun.DB) error { defer tx.Rollback() //nolint:errcheck - if _, err := tx. - NewDropColumn(). - Table("notification_channels"). - ColumnExpr("deleted"). - Exec(ctx); err != nil { - if !strings.Contains(err.Error(), "no such column") { + if exists, err := migration.store.Dialect().ColumnExists(ctx, tx, "notification_channels", "deleted"); err != nil { + return err + } else if exists { + if _, err := tx. + NewDropColumn(). + Table("notification_channels"). + ColumnExpr("deleted"). + Exec(ctx); err != nil { return err } } diff --git a/pkg/sqlmigration/020_pat_update.go b/pkg/sqlmigration/020_pat_update.go new file mode 100644 index 0000000000..b4aeca243e --- /dev/null +++ b/pkg/sqlmigration/020_pat_update.go @@ -0,0 +1,68 @@ +package sqlmigration + +import ( + "context" + + "github.com/SigNoz/signoz/pkg/factory" + "github.com/SigNoz/signoz/pkg/sqlstore" + "github.com/uptrace/bun" + "github.com/uptrace/bun/migrate" +) + +type updatePat struct { + store sqlstore.SQLStore +} + +func NewUpdatePatFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] { + return factory.NewProviderFactory(factory.MustNewName("update_pat"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) { + return newUpdatePat(ctx, ps, c, sqlstore) + }) +} + +func newUpdatePat(_ context.Context, _ factory.ProviderSettings, _ Config, store sqlstore.SQLStore) (SQLMigration, error) { + return &updatePat{ + store: store, + }, nil +} + +func (migration *updatePat) Register(migrations *migrate.Migrations) error { + if err := migrations.Register(migration.Up, migration.Down); err != nil { + return err + } + + return nil +} + +func (migration *updatePat) Up(ctx context.Context, db *bun.DB) error { + + // begin transaction + tx, err := db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + + for _, column := range []string{"last_used", "expires_at"} { + if err := migration.store.Dialect().AddNotNullDefaultToColumn(ctx, tx, "personal_access_tokens", column, "INTEGER", "0"); err != nil { + return err + } + } + + if err := migration.store.Dialect().AddNotNullDefaultToColumn(ctx, tx, "personal_access_tokens", "revoked", "BOOLEAN", "false"); err != nil { + return err + } + + if err := migration.store.Dialect().AddNotNullDefaultToColumn(ctx, tx, "personal_access_tokens", "updated_by_user_id", "TEXT", "''"); err != nil { + return err + } + + if err := tx.Commit(); err != nil { + return err + } + + return nil +} + +func (migration *updatePat) Down(ctx context.Context, db *bun.DB) error { + return nil +} diff --git a/pkg/sqlstore/sqlitesqlstore/dialect.go b/pkg/sqlstore/sqlitesqlstore/dialect.go index b029501851..82e15ec865 100644 --- a/pkg/sqlstore/sqlitesqlstore/dialect.go +++ b/pkg/sqlstore/sqlitesqlstore/dialect.go @@ -2,6 +2,7 @@ package sqlitesqlstore import ( "context" + "fmt" "reflect" "github.com/uptrace/bun" @@ -201,3 +202,23 @@ func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.I return nil } + +func (dialect *dialect) AddNotNullDefaultToColumn(ctx context.Context, bun bun.IDB, table string, column, columnType, defaultValue string) error { + if _, err := bun.NewAddColumn().Table(table).ColumnExpr(fmt.Sprintf("%s_new %s NOT NULL DEFAULT %s ", column, columnType, defaultValue)).Exec(ctx); err != nil { + return err + } + + if _, err := bun.NewUpdate().Table(table).Set(fmt.Sprintf("%s_new = %s", column, column)).Where("1=1").Exec(ctx); err != nil { + return err + } + + if _, err := bun.NewDropColumn().Table(table).ColumnExpr(column).Exec(ctx); err != nil { + return err + } + + if _, err := bun.ExecContext(ctx, fmt.Sprintf("ALTER TABLE %s RENAME COLUMN %s_new TO %s", table, column, column)); err != nil { + return err + } + + return nil +} diff --git a/pkg/sqlstore/sqlstore.go b/pkg/sqlstore/sqlstore.go index 5dee974a55..4fa01a667a 100644 --- a/pkg/sqlstore/sqlstore.go +++ b/pkg/sqlstore/sqlstore.go @@ -39,6 +39,7 @@ type SQLStoreHook interface { type SQLDialect interface { MigrateIntToTimestamp(context.Context, bun.IDB, string, string) error MigrateIntToBoolean(context.Context, bun.IDB, string, string) error + AddNotNullDefaultToColumn(context.Context, bun.IDB, string, string, string, string) error 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) diff --git a/pkg/sqlstore/sqlstoretest/dialect.go b/pkg/sqlstore/sqlstoretest/dialect.go index 8e70488557..a441fd8a8f 100644 --- a/pkg/sqlstore/sqlstoretest/dialect.go +++ b/pkg/sqlstore/sqlstoretest/dialect.go @@ -32,3 +32,7 @@ func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table str func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, cb func(context.Context) error) error { return nil } + +func (dialect *dialect) AddNotNullDefaultToColumn(ctx context.Context, bun bun.IDB, table string, column, columnType, defaultValue string) error { + return nil +} diff --git a/pkg/types/personal_access_token.go b/pkg/types/personal_access_token.go deleted file mode 100644 index 5b5637e3a3..0000000000 --- a/pkg/types/personal_access_token.go +++ /dev/null @@ -1,21 +0,0 @@ -package types - -import ( - "github.com/uptrace/bun" -) - -type StorablePersonalAccessToken struct { - bun.BaseModel `bun:"table:personal_access_tokens"` - - TimeAuditable - OrgID string `json:"orgId" bun:"org_id,type:text,notnull"` - ID int `json:"id" bun:"id,pk,autoincrement"` - Role string `json:"role" bun:"role,type:text,notnull,default:'ADMIN'"` - UserID string `json:"userId" bun:"user_id,type:text,notnull"` - Token string `json:"token" bun:"token,type:text,notnull,unique"` - Name string `json:"name" bun:"name,type:text,notnull"` - ExpiresAt int64 `json:"expiresAt" bun:"expires_at,notnull,default:0"` - LastUsed int64 `json:"lastUsed" bun:"last_used,notnull,default:0"` - Revoked bool `json:"revoked" bun:"revoked,notnull,default:false"` - UpdatedByUserID string `json:"updatedByUserId" bun:"updated_by_user_id,type:text,notnull,default:''"` -}