From 71e7fbdd41143c32a7723a19d4ec375b7295a67a Mon Sep 17 00:00:00 2001 From: Tanish Date: Sat, 10 Jan 2026 19:16:24 +0530 Subject: [PATCH 01/16] basic gorm setup and automigrate --- server/db/main.go | 16 +++-- .../{migrations.go => migrations_archive.go} | 0 .../001_Create_User.sql | 0 .../002_Create_Projects.sql | 0 .../003_Create_Project_members.sql | 0 .../004_Create_GitProviders.sql | 0 .../005_Create_App.sql | 0 .../006_Create_Github_app.sql | 0 .../007_Create_Github_installations.sql | 0 .../008_Create_App_repositories.sql | 0 .../009_Create_deployments.sql | 0 .../010_Create_Envs.sql | 0 .../011_Create_domains.sql | 0 .../012_Create_Volumes.sql | 0 .../013_Create_Cron.sql | 0 .../014_Create_Registries.sql | 0 .../015_Create_System_settings.sql | 0 .../016_Create_Logs.sql | 0 .../017_Create_Audit_logs.sql | 0 .../018_Create_Service_templates.sql | 0 .../019_Create_Api_tokens.sql | 0 .../020_Create_Sessions.sql | 0 .../021_Create_Notifications.sql | 0 .../022_Create_Backups.sql | 0 .../023_Add_DNS_validation.sql | 0 .../024_Add_Wildcard_domain_settings.sql | 0 .../025_Add_Missing_Indexes.sql | 0 .../026_Add_Version_SystemSettings.sql | 0 .../027_Create_UpdateLogs.sql | 0 .../028_Version_bump_v1.0.1.sql | 0 .../029_Version_Bump_v1.0.2.sql | 0 server/go.mod | 7 ++- server/go.sum | 12 ++++ server/main.go | 27 ++++++++ server/models/appRepositories.go | 16 +++++ server/models/auditLog.go | 2 +- server/models/cron.go | 15 +++++ server/models/logs.go | 28 +++++++++ server/models/main.go | 9 ++- server/models/projectMembers.go | 10 +++ server/models/registries.go | 12 ++++ server/models/updateLog.go | 62 +++++++++++++------ 42 files changed, 186 insertions(+), 30 deletions(-) rename server/db/{migrations.go => migrations_archive.go} (100%) rename server/db/{migrations => migrations_archive}/001_Create_User.sql (100%) rename server/db/{migrations => migrations_archive}/002_Create_Projects.sql (100%) rename server/db/{migrations => migrations_archive}/003_Create_Project_members.sql (100%) rename server/db/{migrations => migrations_archive}/004_Create_GitProviders.sql (100%) rename server/db/{migrations => migrations_archive}/005_Create_App.sql (100%) rename server/db/{migrations => migrations_archive}/006_Create_Github_app.sql (100%) rename server/db/{migrations => migrations_archive}/007_Create_Github_installations.sql (100%) rename server/db/{migrations => migrations_archive}/008_Create_App_repositories.sql (100%) rename server/db/{migrations => migrations_archive}/009_Create_deployments.sql (100%) rename server/db/{migrations => migrations_archive}/010_Create_Envs.sql (100%) rename server/db/{migrations => migrations_archive}/011_Create_domains.sql (100%) rename server/db/{migrations => migrations_archive}/012_Create_Volumes.sql (100%) rename server/db/{migrations => migrations_archive}/013_Create_Cron.sql (100%) rename server/db/{migrations => migrations_archive}/014_Create_Registries.sql (100%) rename server/db/{migrations => migrations_archive}/015_Create_System_settings.sql (100%) rename server/db/{migrations => migrations_archive}/016_Create_Logs.sql (100%) rename server/db/{migrations => migrations_archive}/017_Create_Audit_logs.sql (100%) rename server/db/{migrations => migrations_archive}/018_Create_Service_templates.sql (100%) rename server/db/{migrations => migrations_archive}/019_Create_Api_tokens.sql (100%) rename server/db/{migrations => migrations_archive}/020_Create_Sessions.sql (100%) rename server/db/{migrations => migrations_archive}/021_Create_Notifications.sql (100%) rename server/db/{migrations => migrations_archive}/022_Create_Backups.sql (100%) rename server/db/{migrations => migrations_archive}/023_Add_DNS_validation.sql (100%) rename server/db/{migrations => migrations_archive}/024_Add_Wildcard_domain_settings.sql (100%) rename server/db/{migrations => migrations_archive}/025_Add_Missing_Indexes.sql (100%) rename server/db/{migrations => migrations_archive}/026_Add_Version_SystemSettings.sql (100%) rename server/db/{migrations => migrations_archive}/027_Create_UpdateLogs.sql (100%) rename server/db/{migrations => migrations_archive}/028_Version_bump_v1.0.1.sql (100%) rename server/db/{migrations => migrations_archive}/029_Version_Bump_v1.0.2.sql (100%) create mode 100644 server/models/appRepositories.go create mode 100644 server/models/cron.go create mode 100644 server/models/logs.go create mode 100644 server/models/projectMembers.go create mode 100644 server/models/registries.go diff --git a/server/db/main.go b/server/db/main.go index 4e66b46..584bd48 100644 --- a/server/db/main.go +++ b/server/db/main.go @@ -1,16 +1,18 @@ package db import ( - "database/sql" "fmt" "os" "path/filepath" "github.com/corecollectives/mist/fs" _ "github.com/mattn/go-sqlite3" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" ) -func InitDB() (*sql.DB, error) { +func InitDB() (*gorm.DB, error) { dbPath := "" if os.Getenv("ENV") == "dev" { dbPath = "./mist.db" @@ -22,12 +24,14 @@ func InitDB() (*sql.DB, error) { if err != nil { return nil, fmt.Errorf("failed to create database directory: %v", err) } - db, err := sql.Open("sqlite3", dbPath) + db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + }) if err != nil { return nil, fmt.Errorf("failed to open database: %v", err) } - if err := runMigrations(db); err != nil { - return nil, fmt.Errorf("failed to run migrations: %v", err) - } + // if err := runMigrations(db); err != nil { + // return nil, fmt.Errorf("failed to run migrations: %v", err) + // } return db, nil } diff --git a/server/db/migrations.go b/server/db/migrations_archive.go similarity index 100% rename from server/db/migrations.go rename to server/db/migrations_archive.go diff --git a/server/db/migrations/001_Create_User.sql b/server/db/migrations_archive/001_Create_User.sql similarity index 100% rename from server/db/migrations/001_Create_User.sql rename to server/db/migrations_archive/001_Create_User.sql diff --git a/server/db/migrations/002_Create_Projects.sql b/server/db/migrations_archive/002_Create_Projects.sql similarity index 100% rename from server/db/migrations/002_Create_Projects.sql rename to server/db/migrations_archive/002_Create_Projects.sql diff --git a/server/db/migrations/003_Create_Project_members.sql b/server/db/migrations_archive/003_Create_Project_members.sql similarity index 100% rename from server/db/migrations/003_Create_Project_members.sql rename to server/db/migrations_archive/003_Create_Project_members.sql diff --git a/server/db/migrations/004_Create_GitProviders.sql b/server/db/migrations_archive/004_Create_GitProviders.sql similarity index 100% rename from server/db/migrations/004_Create_GitProviders.sql rename to server/db/migrations_archive/004_Create_GitProviders.sql diff --git a/server/db/migrations/005_Create_App.sql b/server/db/migrations_archive/005_Create_App.sql similarity index 100% rename from server/db/migrations/005_Create_App.sql rename to server/db/migrations_archive/005_Create_App.sql diff --git a/server/db/migrations/006_Create_Github_app.sql b/server/db/migrations_archive/006_Create_Github_app.sql similarity index 100% rename from server/db/migrations/006_Create_Github_app.sql rename to server/db/migrations_archive/006_Create_Github_app.sql diff --git a/server/db/migrations/007_Create_Github_installations.sql b/server/db/migrations_archive/007_Create_Github_installations.sql similarity index 100% rename from server/db/migrations/007_Create_Github_installations.sql rename to server/db/migrations_archive/007_Create_Github_installations.sql diff --git a/server/db/migrations/008_Create_App_repositories.sql b/server/db/migrations_archive/008_Create_App_repositories.sql similarity index 100% rename from server/db/migrations/008_Create_App_repositories.sql rename to server/db/migrations_archive/008_Create_App_repositories.sql diff --git a/server/db/migrations/009_Create_deployments.sql b/server/db/migrations_archive/009_Create_deployments.sql similarity index 100% rename from server/db/migrations/009_Create_deployments.sql rename to server/db/migrations_archive/009_Create_deployments.sql diff --git a/server/db/migrations/010_Create_Envs.sql b/server/db/migrations_archive/010_Create_Envs.sql similarity index 100% rename from server/db/migrations/010_Create_Envs.sql rename to server/db/migrations_archive/010_Create_Envs.sql diff --git a/server/db/migrations/011_Create_domains.sql b/server/db/migrations_archive/011_Create_domains.sql similarity index 100% rename from server/db/migrations/011_Create_domains.sql rename to server/db/migrations_archive/011_Create_domains.sql diff --git a/server/db/migrations/012_Create_Volumes.sql b/server/db/migrations_archive/012_Create_Volumes.sql similarity index 100% rename from server/db/migrations/012_Create_Volumes.sql rename to server/db/migrations_archive/012_Create_Volumes.sql diff --git a/server/db/migrations/013_Create_Cron.sql b/server/db/migrations_archive/013_Create_Cron.sql similarity index 100% rename from server/db/migrations/013_Create_Cron.sql rename to server/db/migrations_archive/013_Create_Cron.sql diff --git a/server/db/migrations/014_Create_Registries.sql b/server/db/migrations_archive/014_Create_Registries.sql similarity index 100% rename from server/db/migrations/014_Create_Registries.sql rename to server/db/migrations_archive/014_Create_Registries.sql diff --git a/server/db/migrations/015_Create_System_settings.sql b/server/db/migrations_archive/015_Create_System_settings.sql similarity index 100% rename from server/db/migrations/015_Create_System_settings.sql rename to server/db/migrations_archive/015_Create_System_settings.sql diff --git a/server/db/migrations/016_Create_Logs.sql b/server/db/migrations_archive/016_Create_Logs.sql similarity index 100% rename from server/db/migrations/016_Create_Logs.sql rename to server/db/migrations_archive/016_Create_Logs.sql diff --git a/server/db/migrations/017_Create_Audit_logs.sql b/server/db/migrations_archive/017_Create_Audit_logs.sql similarity index 100% rename from server/db/migrations/017_Create_Audit_logs.sql rename to server/db/migrations_archive/017_Create_Audit_logs.sql diff --git a/server/db/migrations/018_Create_Service_templates.sql b/server/db/migrations_archive/018_Create_Service_templates.sql similarity index 100% rename from server/db/migrations/018_Create_Service_templates.sql rename to server/db/migrations_archive/018_Create_Service_templates.sql diff --git a/server/db/migrations/019_Create_Api_tokens.sql b/server/db/migrations_archive/019_Create_Api_tokens.sql similarity index 100% rename from server/db/migrations/019_Create_Api_tokens.sql rename to server/db/migrations_archive/019_Create_Api_tokens.sql diff --git a/server/db/migrations/020_Create_Sessions.sql b/server/db/migrations_archive/020_Create_Sessions.sql similarity index 100% rename from server/db/migrations/020_Create_Sessions.sql rename to server/db/migrations_archive/020_Create_Sessions.sql diff --git a/server/db/migrations/021_Create_Notifications.sql b/server/db/migrations_archive/021_Create_Notifications.sql similarity index 100% rename from server/db/migrations/021_Create_Notifications.sql rename to server/db/migrations_archive/021_Create_Notifications.sql diff --git a/server/db/migrations/022_Create_Backups.sql b/server/db/migrations_archive/022_Create_Backups.sql similarity index 100% rename from server/db/migrations/022_Create_Backups.sql rename to server/db/migrations_archive/022_Create_Backups.sql diff --git a/server/db/migrations/023_Add_DNS_validation.sql b/server/db/migrations_archive/023_Add_DNS_validation.sql similarity index 100% rename from server/db/migrations/023_Add_DNS_validation.sql rename to server/db/migrations_archive/023_Add_DNS_validation.sql diff --git a/server/db/migrations/024_Add_Wildcard_domain_settings.sql b/server/db/migrations_archive/024_Add_Wildcard_domain_settings.sql similarity index 100% rename from server/db/migrations/024_Add_Wildcard_domain_settings.sql rename to server/db/migrations_archive/024_Add_Wildcard_domain_settings.sql diff --git a/server/db/migrations/025_Add_Missing_Indexes.sql b/server/db/migrations_archive/025_Add_Missing_Indexes.sql similarity index 100% rename from server/db/migrations/025_Add_Missing_Indexes.sql rename to server/db/migrations_archive/025_Add_Missing_Indexes.sql diff --git a/server/db/migrations/026_Add_Version_SystemSettings.sql b/server/db/migrations_archive/026_Add_Version_SystemSettings.sql similarity index 100% rename from server/db/migrations/026_Add_Version_SystemSettings.sql rename to server/db/migrations_archive/026_Add_Version_SystemSettings.sql diff --git a/server/db/migrations/027_Create_UpdateLogs.sql b/server/db/migrations_archive/027_Create_UpdateLogs.sql similarity index 100% rename from server/db/migrations/027_Create_UpdateLogs.sql rename to server/db/migrations_archive/027_Create_UpdateLogs.sql diff --git a/server/db/migrations/028_Version_bump_v1.0.1.sql b/server/db/migrations_archive/028_Version_bump_v1.0.1.sql similarity index 100% rename from server/db/migrations/028_Version_bump_v1.0.1.sql rename to server/db/migrations_archive/028_Version_bump_v1.0.1.sql diff --git a/server/db/migrations/029_Version_Bump_v1.0.2.sql b/server/db/migrations_archive/029_Version_Bump_v1.0.2.sql similarity index 100% rename from server/db/migrations/029_Version_Bump_v1.0.2.sql rename to server/db/migrations_archive/029_Version_Bump_v1.0.2.sql diff --git a/server/go.mod b/server/go.mod index 5159166..14ba4ec 100644 --- a/server/go.mod +++ b/server/go.mod @@ -5,7 +5,7 @@ go 1.25.1 require ( github.com/golang-jwt/jwt v3.2.2+incompatible github.com/gorilla/websocket v1.5.3 - github.com/mattn/go-sqlite3 v1.14.32 + github.com/mattn/go-sqlite3 v1.14.33 github.com/rs/zerolog v1.34.0 github.com/shirou/gopsutil v3.21.11+incompatible golang.org/x/crypto v0.43.0 @@ -23,6 +23,8 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -40,6 +42,9 @@ require ( go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.33.0 // indirect + gorm.io/driver/sqlite v1.6.0 // indirect + gorm.io/gorm v1.31.1 // indirect ) replace github.com/docker/docker/api => github.com/moby/moby/api v1.52.0-beta.2 diff --git a/server/go.sum b/server/go.sum index db50e76..56684b1 100644 --- a/server/go.sum +++ b/server/go.sum @@ -27,6 +27,10 @@ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keL github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -34,6 +38,8 @@ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APP github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= +github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg= @@ -78,7 +84,13 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= +gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/server/main.go b/server/main.go index 69517e7..d943101 100644 --- a/server/main.go +++ b/server/main.go @@ -14,6 +14,33 @@ func main() { utils.InitLogger() log.Info().Msg("Starting Mist server") dbInstance, err := db.InitDB() + dbInstance.AutoMigrate( + &models.User{}, + &models.ApiToken{}, + &models.App{}, + &models.AuditLog{}, + &models.Backup{}, + &models.Deployment{}, + &models.EnvVariable{}, + &models.GithubApp{}, + &models.Project{}, + &models.ProjectMembers{}, + &models.GitProvider{}, + &models.GithubInstallation{}, + &models.AppRepositories{}, + &models.Domain{}, + &models.Volume{}, + &models.Cron{}, + &models.Registries{}, + &models.SystemSettings{}, + &models.Logs{}, + &models.AuditLog{}, + &models.ServiceTemplate{}, + &models.ApiToken{}, + &models.Session{}, + &models.Notification{}, + &models.UpdateLog{}, + ) _ = queue.InitQueue(dbInstance) if err != nil { log.Fatal().Err(err).Msg("Error initializing database") diff --git a/server/models/appRepositories.go b/server/models/appRepositories.go new file mode 100644 index 0000000..36bff21 --- /dev/null +++ b/server/models/appRepositories.go @@ -0,0 +1,16 @@ +package models + +import "time" + +type AppRepositories struct { + ID int64 `json:"id"` + AppID int64 `json:"app_id"` + SourceType string `json:"source_type"` + SourceID int64 `json:"source_id"` + RepoFullName string `json:"repo_full_name"` + RepoURL string `json:"repo_url"` + Branch string `json:"branch"` + WebhookID int64 `json:"webhook_id"` + AutoDeploy bool `json:"auto_deploy"` + LastSyncedAt time.Time `json:"last_synced_at"` +} diff --git a/server/models/auditLog.go b/server/models/auditLog.go index e8edacc..c72fdeb 100644 --- a/server/models/auditLog.go +++ b/server/models/auditLog.go @@ -108,7 +108,7 @@ func GetAuditLogsByUser(userID int64, limit, offset int) ([]AuditLog, error) { u.email, al.action, al.resource_type, - al.resource_id, + al.resource_id, SELECT al.details, al.created_at FROM audit_logs al diff --git a/server/models/cron.go b/server/models/cron.go new file mode 100644 index 0000000..1eb6aa2 --- /dev/null +++ b/server/models/cron.go @@ -0,0 +1,15 @@ +package models + +import "time" + +type Cron struct { + ID int64 `json:"id"` + AppID int64 `json:"app_id"` + Name string `json:"name"` + Schedule string `json:"schedule"` + Command string `json:"command"` + LastRun time.Time `json:"last_run"` + NextRun time.Time `json:"next_run"` + Enable bool `json:"enable"` + CreatedAt time.Time `json:"created_at"` +} diff --git a/server/models/logs.go b/server/models/logs.go new file mode 100644 index 0000000..87ecc1d --- /dev/null +++ b/server/models/logs.go @@ -0,0 +1,28 @@ +package models + +import "time" + +type LogSource string + +const ( + LogSourceApp LogSource = "app" + LogSourceSystem LogSource = "system" +) + +type LogLevel string + +const ( + LogLevelInfo LogLevel = "info" + LogLevelWarn LogLevel = "warn" + LogLevelError LogLevel = "error" + LogLevelDebug LogLevel = "debug" +) + +type Logs struct { + ID int64 + Source LogSource + SourceID *int64 + Message string + Level LogLevel + CreatedAt time.Time +} diff --git a/server/models/main.go b/server/models/main.go index 2520495..2785c70 100644 --- a/server/models/main.go +++ b/server/models/main.go @@ -1,9 +1,12 @@ package models -import "database/sql" +import ( -var db *sql.DB + "gorm.io/gorm" +) -func SetDB(database *sql.DB) { +var db *gorm.DB + +func SetDB(database *gorm.DB) { db = database } diff --git a/server/models/projectMembers.go b/server/models/projectMembers.go new file mode 100644 index 0000000..2d9fb85 --- /dev/null +++ b/server/models/projectMembers.go @@ -0,0 +1,10 @@ +package models + +import "time" + +type ProjectMembers struct{ + ID int64 `json:"id"` + ProjectID int64 `json:"project_id"` + USERID int64 `json:"user_id"` + AddedAt time.Time `json:"added_at"` +} \ No newline at end of file diff --git a/server/models/registries.go b/server/models/registries.go new file mode 100644 index 0000000..32b37b9 --- /dev/null +++ b/server/models/registries.go @@ -0,0 +1,12 @@ +package models + +import "time" + +type Registries struct { + ID int64 `json:"id"` + ProjectID int64 `json:"projectId"` + RegistryURL string `json:"registryUrl"` + Username string `json:"username"` + Password string `json:"password"` + CreatedAt time.Time `json:"createdAt"` +} diff --git a/server/models/updateLog.go b/server/models/updateLog.go index 181b3a6..9f5947d 100644 --- a/server/models/updateLog.go +++ b/server/models/updateLog.go @@ -8,17 +8,25 @@ import ( "github.com/rs/zerolog/log" ) +type UpdateStatus string + +const ( + UpdateStatusInProgress UpdateStatus = "in_progress" + UpdateStatusSuccess UpdateStatus = "success" + UpdateStatusFailed UpdateStatus = "failed" +) + type UpdateLog struct { - ID int64 `json:"id"` - VersionFrom string `json:"versionFrom"` - VersionTo string `json:"versionTo"` - Status string `json:"status"` // in_progress, success, failed - Logs string `json:"logs"` - ErrorMessage *string `json:"errorMessage"` - StartedBy int64 `json:"startedBy"` - StartedAt time.Time `json:"startedAt"` - CompletedAt *time.Time `json:"completedAt"` - Username string `json:"username"` + ID int64 + VersionFrom string + VersionTo string + Status UpdateStatus + Logs *string + ErrorMessage *string + StartedBy int64 + StartedAt time.Time + CompletedAt *time.Time + Username *string } func CreateUpdateLog(versionFrom, versionTo string, startedBy int64) (*UpdateLog, error) { @@ -56,7 +64,7 @@ func CreateUpdateLog(versionFrom, versionTo string, startedBy int64) (*UpdateLog return updateLog, nil } -func UpdateUpdateLogStatus(id int64, status string, logs string, errorMessage *string) error { +func UpdateUpdateLogStatus(id int64, status UpdateStatus, logs string, errorMessage *string) error { query := ` UPDATE update_logs SET status = ?, logs = ?, error_message = ?, completed_at = CURRENT_TIMESTAMP @@ -71,7 +79,7 @@ func UpdateUpdateLogStatus(id int64, status string, logs string, errorMessage *s log.Info(). Int64("update_log_id", id). - Str("status", status). + Str("status", string(status)). Msg("Update log status updated") return nil @@ -195,12 +203,17 @@ func GetUpdateLogsAsString() (string, error) { builder.WriteString(log.VersionTo) builder.WriteString("\n") builder.WriteString("Status: ") - builder.WriteString(log.Status) + builder.WriteString(string(log.Status)) builder.WriteString("\n") builder.WriteString("Started: ") builder.WriteString(log.StartedAt.Format("2006-01-02 15:04:05")) builder.WriteString(" by ") - builder.WriteString(log.Username) + if log.Username != nil { + builder.WriteString(*log.Username) + } else { + builder.WriteString("unknown") + } + builder.WriteString("\n") if log.CompletedAt != nil { builder.WriteString("Completed: ") @@ -230,7 +243,7 @@ func CheckAndCompletePendingUpdates() error { latestLog := logs[0] - if latestLog.Status != "in_progress" { + if latestLog.Status != UpdateStatusInProgress { return nil } @@ -257,8 +270,14 @@ func CheckAndCompletePendingUpdates() error { Str("version", currentVersion). Msg("Completing successful update that was interrupted by service restart") - completionLog := latestLog.Logs + "\n✅ Update completed successfully (verified on restart)\n" - err = UpdateUpdateLogStatus(latestLog.ID, "success", completionLog, nil) + existing := "" + if latestLog.Logs != nil { + existing = *latestLog.Logs + } + + completionLog := existing + "\n✅ Update completed successfully (verified on restart)\n" + + err = UpdateUpdateLogStatus(latestLog.ID, UpdateStatusSuccess, completionLog, nil) if err != nil { log.Error().Err(err).Int64("update_log_id", latestLog.ID).Msg("Failed to complete pending update") return err @@ -280,8 +299,13 @@ func CheckAndCompletePendingUpdates() error { Msg("Update appears to have failed (version mismatch detected on startup)") errMsg := "Update process was interrupted and version does not match target" - failureLog := latestLog.Logs + "\n❌ " + errMsg + "\n" - err = UpdateUpdateLogStatus(latestLog.ID, "failed", failureLog, &errMsg) + existing := "" + if latestLog.Logs != nil { + existing = *latestLog.Logs + } + + failureLog := existing + "\n❌ " + errMsg + "\n" + err = UpdateUpdateLogStatus(latestLog.ID, UpdateStatusFailed, failureLog, &errMsg) if err != nil { log.Error().Err(err).Int64("update_log_id", latestLog.ID).Msg("Failed to mark failed update") return err From 452a78b84578a027acc56d5e9aa2ac57dcf4ec0d Mon Sep 17 00:00:00 2001 From: Tanish Date: Sun, 11 Jan 2026 15:59:21 +0530 Subject: [PATCH 02/16] completed apiToken.go --- server/models/apiToken.go | 113 ++++++++++---------------------------- server/models/temp.go | 0 2 files changed, 29 insertions(+), 84 deletions(-) create mode 100644 server/models/temp.go diff --git a/server/models/apiToken.go b/server/models/apiToken.go index 75b457a..b902e04 100644 --- a/server/models/apiToken.go +++ b/server/models/apiToken.go @@ -4,21 +4,22 @@ import ( "time" "github.com/corecollectives/mist/utils" + "gorm.io/gorm" ) type ApiToken struct { - ID int64 `db:"id" json:"id"` - UserID int64 `db:"user_id" json:"userId"` - Name string `db:"name" json:"name"` - TokenHash string `db:"token_hash" json:"-"` // Never expose in JSON - TokenPrefix string `db:"token_prefix" json:"tokenPrefix"` - Scopes *string `db:"scopes" json:"scopes,omitempty"` // JSON array - LastUsedAt *time.Time `db:"last_used_at" json:"lastUsedAt,omitempty"` - LastUsedIP *string `db:"last_used_ip" json:"lastUsedIp,omitempty"` - UsageCount int `db:"usage_count" json:"usageCount"` - ExpiresAt *time.Time `db:"expires_at" json:"expiresAt,omitempty"` - CreatedAt time.Time `db:"created_at" json:"createdAt"` - RevokedAt *time.Time `db:"revoked_at" json:"revokedAt,omitempty"` + ID int64 `gorm:"primaryKey;autoIncrement:false" json:"id"` + UserID int64 `gorm:"index;not null" json:"userId"` + Name string `gorm:"type:varchar(255);not null" json:"name"` + TokenHash string `gorm:"uniqueIndex;type:varchar(255);not null" json:"-"` + TokenPrefix string `gorm:"index;type:varchar(50);not null" json:"tokenPrefix"` + Scopes *string `json:"scopes,omitempty"` + LastUsedAt *time.Time `json:"lastUsedAt,omitempty"` + LastUsedIP *string `json:"lastUsedIp,omitempty"` + UsageCount int `gorm:"default:0" json:"usageCount"` + ExpiresAt *time.Time `gorm:"index" json:"expiresAt,omitempty"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"createdAt"` + RevokedAt *time.Time `json:"revokedAt,omitempty"` } func (t *ApiToken) ToJson() map[string]interface{} { @@ -38,94 +39,38 @@ func (t *ApiToken) ToJson() map[string]interface{} { } func (t *ApiToken) InsertInDB() error { - id := utils.GenerateRandomId() - t.ID = id - query := ` - INSERT INTO api_tokens ( - id, user_id, name, token_hash, token_prefix, scopes, expires_at - ) VALUES (?, ?, ?, ?, ?, ?, ?) - RETURNING created_at - ` - err := db.QueryRow(query, t.ID, t.UserID, t.Name, t.TokenHash, t.TokenPrefix, t.Scopes, t.ExpiresAt).Scan(&t.CreatedAt) - return err + t.ID = utils.GenerateRandomId() + result := db.Create(t) + return result.Error } func GetApiTokensByUserID(userID int64) ([]ApiToken, error) { var tokens []ApiToken - query := ` - SELECT id, user_id, name, token_hash, token_prefix, scopes, - last_used_at, last_used_ip, usage_count, expires_at, - created_at, revoked_at - FROM api_tokens - WHERE user_id = ? AND revoked_at IS NULL - ORDER BY created_at DESC - ` - rows, err := db.Query(query, userID) - if err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - var token ApiToken - err := rows.Scan( - &token.ID, &token.UserID, &token.Name, &token.TokenHash, &token.TokenPrefix, - &token.Scopes, &token.LastUsedAt, &token.LastUsedIP, &token.UsageCount, - &token.ExpiresAt, &token.CreatedAt, &token.RevokedAt, - ) - if err != nil { - return nil, err - } - tokens = append(tokens, token) - } - - return tokens, rows.Err() + result := db.Where("user_id=? AND revoked_at IS NULL", userID).Order("created_at DESC").Find(&tokens) + return tokens, result.Error } func GetApiTokenByHash(tokenHash string) (*ApiToken, error) { var token ApiToken - query := ` - SELECT id, user_id, name, token_hash, token_prefix, scopes, - last_used_at, last_used_ip, usage_count, expires_at, - created_at, revoked_at - FROM api_tokens - WHERE token_hash = ? AND revoked_at IS NULL - ` - err := db.QueryRow(query, tokenHash).Scan( - &token.ID, &token.UserID, &token.Name, &token.TokenHash, &token.TokenPrefix, - &token.Scopes, &token.LastUsedAt, &token.LastUsedIP, &token.UsageCount, - &token.ExpiresAt, &token.CreatedAt, &token.RevokedAt, - ) - if err != nil { - return nil, err + result := db.Where("token_hash=? AND revoked_at IS NULL", tokenHash).First(&token) + if result.Error != nil { + return nil, result.Error } return &token, nil } -func (t *ApiToken) UpdateUsage(ipAddress string) error { - query := ` - UPDATE api_tokens - SET last_used_at = CURRENT_TIMESTAMP, - last_used_ip = ?, - usage_count = usage_count + 1 - WHERE id = ? - ` - _, err := db.Exec(query, ipAddress, t.ID) - return err +func (t *ApiToken) UpdateUsage(lastUsedIP string) error { + return db.Model(t).Updates(map[string]interface{}{ + "last_used_at": time.Now(), + "last_used_ip": lastUsedIP, + "usage_count": gorm.Expr("usage_count + ?", 1), + }).Error } func (t *ApiToken) Revoke() error { - query := ` - UPDATE api_tokens - SET revoked_at = CURRENT_TIMESTAMP - WHERE id = ? - ` - _, err := db.Exec(query, t.ID) - return err + return db.Model(t).Update("revoked_at", time.Now()).Error } func DeleteApiToken(tokenID int64) error { - query := `DELETE FROM api_tokens WHERE id = ?` - _, err := db.Exec(query, tokenID) - return err + return db.Delete(&ApiToken{}, tokenID).Error } diff --git a/server/models/temp.go b/server/models/temp.go new file mode 100644 index 0000000..e69de29 From e12ae05eb9810edeacf2838ae98e031b69d94a33 Mon Sep 17 00:00:00 2001 From: Tanish Date: Sun, 11 Jan 2026 18:48:58 +0530 Subject: [PATCH 03/16] refactored app.go --- server/models/app.go | 292 ++++++++++++------------------------------- 1 file changed, 83 insertions(+), 209 deletions(-) diff --git a/server/models/app.go b/server/models/app.go index 58ef5fc..4788877 100644 --- a/server/models/app.go +++ b/server/models/app.go @@ -1,7 +1,6 @@ package models import ( - "database/sql" "fmt" "time" @@ -34,39 +33,33 @@ const ( ) type App struct { - ID int64 `db:"id" json:"id"` - ProjectID int64 `db:"project_id" json:"project_id"` - CreatedBy int64 `db:"created_by" json:"created_by"` - Name string `db:"name" json:"name"` - Description *string `db:"description" json:"description,omitempty"` - - AppType AppType `db:"app_type" json:"app_type"` - TemplateName *string `db:"template_name" json:"template_name,omitempty"` - - GitProviderID *int64 `db:"git_provider_id" json:"git_provider_id,omitempty"` - GitRepository *string `db:"git_repository" json:"git_repository,omitempty"` - GitBranch string `db:"git_branch" json:"git_branch,omitempty"` - GitCloneURL *string `db:"git_clone_url" json:"git_clone_url,omitempty"` - - DeploymentStrategy DeploymentStrategy `db:"deployment_strategy" json:"deployment_strategy"` - Port *int64 `db:"port" json:"port,omitempty"` - RootDirectory string `db:"root_directory" json:"root_directory,omitempty"` - BuildCommand *string `db:"build_command" json:"build_command,omitempty"` - StartCommand *string `db:"start_command" json:"start_command,omitempty"` - DockerfilePath *string `db:"dockerfile_path" json:"dockerfile_path,omitempty"` - - CPULimit *float64 `db:"cpu_limit" json:"cpu_limit,omitempty"` - MemoryLimit *int `db:"memory_limit" json:"memory_limit,omitempty"` - RestartPolicy RestartPolicy `db:"restart_policy" json:"restart_policy"` - - HealthcheckPath *string `db:"healthcheck_path" json:"healthcheck_path,omitempty"` - HealthcheckInterval int `db:"healthcheck_interval" json:"healthcheck_interval"` - HealthcheckTimeout int `db:"healthcheck_timeout" json:"healthcheck_timeout"` - HealthcheckRetries int `db:"healthcheck_retries" json:"healthcheck_retries"` - Status AppStatus `db:"status" json:"status"` - - CreatedAt time.Time `db:"created_at" json:"created_at"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + ID int64 `gorm:"primaryKey;autoIncrement:false" json:"id"` + ProjectID int64 `gorm:"uniqueIndex:idx_project_app_name;index;not null" json:"project_id"` + Name string `gorm:"uniqueIndex:idx_project_app_name;not null" json:"name"` + CreatedBy int64 `gorm:"index" json:"created_by"` + Description *string `json:"description,omitempty"` + AppType AppType `gorm:"default:'web';index" json:"app_type"` + TemplateName *string `json:"template_name,omitempty"` + GitProviderID *int64 `json:"git_provider_id,omitempty"` + GitRepository *string `json:"git_repository,omitempty"` + GitBranch string `gorm:"default:'main'" json:"git_branch,omitempty"` + GitCloneURL *string `json:"git_clone_url,omitempty"` + DeploymentStrategy DeploymentStrategy `gorm:"default:'auto'" json:"deployment_strategy"` + Port *int64 `json:"port,omitempty"` + RootDirectory string `gorm:"default:'.'" json:"root_directory,omitempty"` + BuildCommand *string `json:"build_command,omitempty"` + StartCommand *string `json:"start_command,omitempty"` + DockerfilePath *string `gorm:"default:'DOCKERFILE'" json:"dockerfile_path,omitempty"` + CPULimit *float64 `json:"cpu_limit,omitempty"` + MemoryLimit *int `json:"memory_limit,omitempty"` + RestartPolicy RestartPolicy `gorm:"default:'unless-stopped'" json:"restart_policy"` + HealthcheckPath *string `json:"healthcheck_path,omitempty"` + HealthcheckInterval int `gorm:"default:30" json:"healthcheck_interval"` + HealthcheckTimeout int `gorm:"default:10" json:"healthcheck_timeout"` + HealthcheckRetries int `gorm:"default:3" json:"healthcheck_retries"` + Status AppStatus `gorm:"default:'stopped';index" json:"status"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` } func (a *App) ToJson() map[string]interface{} { @@ -102,9 +95,7 @@ func (a *App) ToJson() map[string]interface{} { } func (a *App) InsertInDB() error { - id := utils.GenerateRandomId() - a.ID = id - + a.ID = utils.GenerateRandomId() if a.AppType == "" { a.AppType = AppTypeWeb } @@ -118,229 +109,112 @@ func (a *App) InsertInDB() error { a.Status = StatusStopped } - query := ` - INSERT INTO apps ( - id, name, description, project_id, created_by, app_type, template_name, - port, deployment_strategy, restart_policy, git_provider_id, git_repository, - git_branch, git_clone_url, root_directory, build_command, start_command, - dockerfile_path, cpu_limit, memory_limit, healthcheck_path, - healthcheck_interval, healthcheck_timeout, healthcheck_retries, status - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - RETURNING - created_at, updated_at - ` - err := db.QueryRow(query, - a.ID, a.Name, a.Description, a.ProjectID, a.CreatedBy, a.AppType, a.TemplateName, - a.Port, a.DeploymentStrategy, a.RestartPolicy, a.GitProviderID, a.GitRepository, - a.GitBranch, a.GitCloneURL, a.RootDirectory, a.BuildCommand, a.StartCommand, - a.DockerfilePath, a.CPULimit, a.MemoryLimit, a.HealthcheckPath, - a.HealthcheckInterval, a.HealthcheckTimeout, a.HealthcheckRetries, a.Status, - ).Scan(&a.CreatedAt, &a.UpdatedAt) - if err != nil { - return err - } - return nil + return db.Create(a).Error } func GetApplicationByProjectID(projectId int64) ([]App, error) { var apps []App - query := ` - SELECT id, project_id, created_by, name, description, app_type, template_name, - git_provider_id, git_repository, git_branch, git_clone_url, - deployment_strategy, port, root_directory, build_command, start_command, - dockerfile_path, cpu_limit, memory_limit, restart_policy, - healthcheck_path, healthcheck_interval, healthcheck_timeout, healthcheck_retries, - status, created_at, updated_at - FROM apps - WHERE project_id = ? - ` - rows, err := db.Query(query, projectId) - if err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - var app App - err := rows.Scan( - &app.ID, &app.ProjectID, &app.CreatedBy, &app.Name, &app.Description, - &app.AppType, &app.TemplateName, &app.GitProviderID, &app.GitRepository, - &app.GitBranch, &app.GitCloneURL, &app.DeploymentStrategy, &app.Port, - &app.RootDirectory, &app.BuildCommand, &app.StartCommand, &app.DockerfilePath, - &app.CPULimit, &app.MemoryLimit, &app.RestartPolicy, - &app.HealthcheckPath, &app.HealthcheckInterval, &app.HealthcheckTimeout, - &app.HealthcheckRetries, &app.Status, &app.CreatedAt, &app.UpdatedAt, - ) - if err != nil { - return nil, err - } - apps = append(apps, app) - } - if err = rows.Err(); err != nil { - return nil, err - } - return apps, nil + result := db.Where("project_id=?", projectId).Find(&apps) + return apps, result.Error } func GetApplicationByID(appId int64) (*App, error) { var app App - query := ` - SELECT id, project_id, created_by, name, description, app_type, template_name, - git_provider_id, git_repository, git_branch, git_clone_url, - deployment_strategy, port, root_directory, build_command, start_command, - dockerfile_path, cpu_limit, memory_limit, restart_policy, - healthcheck_path, healthcheck_interval, healthcheck_timeout, healthcheck_retries, - status, created_at, updated_at - FROM apps - WHERE id = ? - ` - err := db.QueryRow(query, appId).Scan( - &app.ID, &app.ProjectID, &app.CreatedBy, &app.Name, &app.Description, - &app.AppType, &app.TemplateName, &app.GitProviderID, &app.GitRepository, - &app.GitBranch, &app.GitCloneURL, &app.DeploymentStrategy, &app.Port, - &app.RootDirectory, &app.BuildCommand, &app.StartCommand, &app.DockerfilePath, - &app.CPULimit, &app.MemoryLimit, &app.RestartPolicy, - &app.HealthcheckPath, &app.HealthcheckInterval, &app.HealthcheckTimeout, - &app.HealthcheckRetries, &app.Status, &app.CreatedAt, &app.UpdatedAt, - ) - if err != nil { - return nil, err + result := db.First(&app, "id=?", appId) + if result.Error != nil { + return nil, result.Error } return &app, nil } -func (app *App) UpdateApplication() error { - query := ` - UPDATE apps - SET - name = ?, - description = ?, - app_type = ?, - template_name = ?, - git_provider_id = ?, - git_repository = ?, - git_branch = ?, - git_clone_url = ?, - deployment_strategy = ?, - port = ?, - root_directory = ?, - build_command = ?, - start_command = ?, - dockerfile_path = ?, - cpu_limit = ?, - memory_limit = ?, - restart_policy = ?, - healthcheck_path = ?, - healthcheck_interval = ?, - healthcheck_timeout = ?, - healthcheck_retries = ?, - status = ?, - updated_at = CURRENT_TIMESTAMP - WHERE id = ? - ` - _, err := db.Exec(query, - app.Name, app.Description, app.AppType, app.TemplateName, - app.GitProviderID, app.GitRepository, app.GitBranch, app.GitCloneURL, - app.DeploymentStrategy, app.Port, app.RootDirectory, - app.BuildCommand, app.StartCommand, app.DockerfilePath, - app.CPULimit, app.MemoryLimit, app.RestartPolicy, - app.HealthcheckPath, app.HealthcheckInterval, app.HealthcheckTimeout, - app.HealthcheckRetries, app.Status, app.ID, - ) - return err +func (a *App) UpdateApplication() error { + return db.Model(a).Select("Name", "Description", "AppType", "TemplateName", + "GitProviderID", "GitRepository", "GitBranch", "GitCloneURL", + "DeploymentStrategy", "Port", "RootDirectory", + "BuildCommand", "StartCommand", "DockerfilePath", + "CPULimit", "MemoryLimit", "RestartPolicy", + "HealthcheckPath", "HealthcheckInterval", "HealthcheckTimeout", "HealthcheckRetries", + "Status", "UpdatedAt").Updates(a).Error } func IsUserApplicationOwner(userId int64, appId int64) (bool, error) { - var createdBy int64 - err := db.QueryRow(` - SELECT created_by FROM apps WHERE id = ? - `, appId).Scan(&createdBy) - if err != nil { - return false, err - } - return createdBy == userId, nil + var count int64 + err := db.Model(&App{}). + Where("id = ? AND created_by = ?", appId, userId). + Count(&count).Error + + return count > 0, err } func FindApplicationIDByGitRepoAndBranch(gitRepo string, gitBranch string) (int64, error) { - var appId int64 - err := db.QueryRow(` - SELECT id FROM apps WHERE git_repository = ? AND git_branch = ? - `, gitRepo, gitBranch).Scan(&appId) + var app App + err := db.Select("id"). + Where("git_repository = ? AND git_branch = ?", gitRepo, gitBranch). + First(&app).Error + if err != nil { return 0, err } - return appId, nil + return app.ID, nil } func GetUserIDByAppID(appID int64) (*int64, error) { - query := ` - SELECT created_by FROM apps WHERE id = ? - ` - var userID int64 - err := db.QueryRow(query, appID).Scan(&userID) + var app App + err := db.Select("created_by").First(&app, appID).Error if err != nil { return nil, err } - return &userID, nil + return &app.CreatedBy, nil } -func GetAppIDByDeploymentID(depID int64) (int64, error) { - query := ` - SELECT app_id FROM deployments WHERE id = ? - ` - var appID int64 - err := db.QueryRow(query, depID).Scan(&appID) +func GetAppIDByDeploymentID(depId int64) (int64, error) { + var result struct { + AppID int64 + } + err := db.Table("deployments").Select("app_id").Where("id=?", depId).Scan(&result).Error if err != nil { return 0, err } - return appID, nil + return result.AppID, nil } func GetAppRepoInfo(appId int64) (string, string, int64, string, error) { - var repo sql.NullString - var branch sql.NullString - var name string - var projectId int64 - - err := db.QueryRow(` - SELECT git_repository, git_branch, project_id, name - FROM apps WHERE id = ? - `, appId).Scan(&repo, &branch, &projectId, &name) + var app App + err := db.Select("git_repository, git_branch, project_id, name"). + First(&app, appId).Error + if err != nil { return "", "", 0, "", err } - repoStr := "" - if repo.Valid { - repoStr = repo.String + repo := "" + if app.GitRepository != nil { + repo = *app.GitRepository } - branchStr := "" - if branch.Valid { - branchStr = branch.String - } - - return repoStr, branchStr, projectId, name, nil + return repo, app.GitBranch, app.ProjectID, app.Name, nil } -func GetAppRepoAndBranch(appID int64) (string, string, error) { - var repoName sql.NullString - var branch string - err := db.QueryRow(`SELECT git_repository, COALESCE(git_branch, 'main') FROM apps WHERE id = ?`, appID). - Scan(&repoName, &branch) +func getAppRepoAndBranch(appId int64) (string, string, error) { + var app App + err := db.Select("git_repository, git_branch").First(&app, appId).Error if err != nil { return "", "", err } - if !repoName.Valid || repoName.String == "" { + if app.GitRepository == nil || *app.GitRepository == "" { return "", "", fmt.Errorf("app has no git repository configured") } - return repoName.String, branch, nil + + branch := app.GitBranch + if branch == "" { + branch = "main" + } + + return *app.GitRepository, branch, nil } func DeleteApplication(appID int64) error { - query := `DELETE FROM apps WHERE id = ?` - _, err := db.Exec(query, appID) - return err + return db.Delete(&App{}, appID).Error } // if git_clone_url is set, it uses that From 37694b0981e973a76356c40b4aebc3d412616455 Mon Sep 17 00:00:00 2001 From: Tanish Date: Sun, 11 Jan 2026 20:35:47 +0530 Subject: [PATCH 04/16] auditlogs.go refactored --- server/models/appRepositories.go | 31 +-- server/models/auditLog.go | 322 ++++++------------------------- server/models/temp.go | 2 + 3 files changed, 86 insertions(+), 269 deletions(-) diff --git a/server/models/appRepositories.go b/server/models/appRepositories.go index 36bff21..477748a 100644 --- a/server/models/appRepositories.go +++ b/server/models/appRepositories.go @@ -1,16 +1,25 @@ package models -import "time" +import ( + "time" +) + +type AppRepositorySourceType string + +const ( + SourceGitProvider AppRepositorySourceType = "git_provider" + SourceGithubApp AppRepositorySourceType = "github_app" +) type AppRepositories struct { - ID int64 `json:"id"` - AppID int64 `json:"app_id"` - SourceType string `json:"source_type"` - SourceID int64 `json:"source_id"` - RepoFullName string `json:"repo_full_name"` - RepoURL string `json:"repo_url"` - Branch string `json:"branch"` - WebhookID int64 `json:"webhook_id"` - AutoDeploy bool `json:"auto_deploy"` - LastSyncedAt time.Time `json:"last_synced_at"` + ID int64 `gorm:"primaryKey;autoIncrement:true" json:"id"` + AppID int64 `gorm:"uniqueIndex:idx_app_repo_unique;not null;constraint:OnDelete:CASCADE" json:"app_id"` + SourceType AppRepositorySourceType `gorm:"not null" json:"source_type"` + SourceID int64 `gorm:"not null" json:"source_id"` + RepoFullName string `gorm:"uniqueIndex:idx_app_repo_unique;not null" json:"repo_full_name"` + RepoURL string `gorm:"not null" json:"repo_url"` + Branch string `gorm:"default:'main'" json:"branch"` + WebhookID int64 `json:"webhook_id"` + AutoDeploy bool `gorm:"default:false" json:"auto_deploy"` + LastSyncedAt *time.Time `json:"last_synced_at,omitempty"` } diff --git a/server/models/auditLog.go b/server/models/auditLog.go index c72fdeb..8850a80 100644 --- a/server/models/auditLog.go +++ b/server/models/auditLog.go @@ -1,25 +1,26 @@ package models import ( - "database/sql" "encoding/json" "time" + + "gorm.io/gorm" ) type AuditLog struct { - ID int64 `json:"id"` - UserID *int64 `json:"userId"` - Username *string `json:"username"` - Email *string `json:"email"` - Action string `json:"action"` - ResourceType string `json:"resourceType"` + ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` + UserID *int64 `gorm:"index" json:"user_id"` + Username *string `gorm:"-" json:"username"` + Email *string `gorm:"-" json:"email"` + Action string `gorm:"not null" json:"action"` + ResourceType string `gorm:"not null" json:"resourceType"` ResourceID *int64 `json:"resourceId"` - ResourceName *string `json:"resourceName"` + ResourceName *string `gorm:"-" json:"resourceName"` Details *string `json:"details"` - IPAddress *string `json:"ipAddress"` - UserAgent *string `json:"userAgent"` - TriggerType string `json:"triggerType"` // "user", "webhook", "system" - CreatedAt time.Time `json:"createdAt"` + IPAddress *string `gorm:"-" json:"ipAddress"` + UserAgent *string `gorm:"-" json:"userAgent"` + TriggerType string `gorm:"-" json:"triggerType"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"createdAt"` } type AuditLogDetails struct { @@ -30,56 +31,28 @@ type AuditLogDetails struct { } func (a *AuditLog) Create() error { - query := ` - INSERT INTO audit_logs (user_id, action, resource_type, resource_id, details, created_at) - VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP) - RETURNING id, created_at - ` - return db.QueryRow(query, a.UserID, a.Action, a.ResourceType, a.ResourceID, a.Details). - Scan(&a.ID, &a.CreatedAt) + return db.Create(a).Error } -func GetAllAuditLogs(limit, offset int) ([]AuditLog, error) { - query := ` - SELECT - al.id, - al.user_id, - u.username, - u.email, - al.action, - al.resource_type, - al.resource_id, - al.details, - al.created_at - FROM audit_logs al - LEFT JOIN users u ON al.user_id = u.id - ORDER BY al.created_at DESC - LIMIT ? OFFSET ? - ` - rows, err := db.Query(query, limit, offset) - if err != nil { - return nil, err - } - defer rows.Close() +//hlper function for join logic - var logs []AuditLog - for rows.Next() { - var log AuditLog - err := rows.Scan( - &log.ID, - &log.UserID, - &log.Username, - &log.Email, - &log.Action, - &log.ResourceType, - &log.ResourceID, - &log.Details, - &log.CreatedAt, - ) - if err != nil { - return nil, err - } +func getAuditLogsQuery() *gorm.DB { + return db.Table("audit_logs").Select(`audit_logs.id, + audit_logs.user_id, + users.username, + users.email, + audit_logs.action, + audit_logs.resource_type, + audit_logs.resource_id, + audit_logs.details, + audit_logs.created_at`).Joins("LEFT JOIN users ON audit_logs.user_id=users.id").Order("audit_logs.created_at DESC") + +} +// helper function to reduce repetition +func logHelper(logs []AuditLog) []AuditLog { + for i := range logs { + log := &logs[i] if log.UserID == nil { log.TriggerType = "system" if log.Details != nil && (*log.Details != "") { @@ -93,124 +66,44 @@ func GetAllAuditLogs(limit, offset int) ([]AuditLog, error) { } else { log.TriggerType = "user" } - - logs = append(logs, log) } - return logs, nil + return logs } -func GetAuditLogsByUser(userID int64, limit, offset int) ([]AuditLog, error) { - query := ` - SELECT - al.id, - al.user_id, - u.username, - u.email, - al.action, - al.resource_type, - al.resource_id, SELECT - al.details, - al.created_at - FROM audit_logs al - LEFT JOIN users u ON al.user_id = u.id - WHERE al.user_id = ? - ORDER BY al.created_at DESC - LIMIT ? OFFSET ? - ` - rows, err := db.Query(query, userID, limit, offset) +func GetAllAuditLogs(limit, offset int) ([]AuditLog, error) { + var logs []AuditLog + err := getAuditLogsQuery().Limit(limit).Offset(offset).Scan(&logs).Error if err != nil { return nil, err } - defer rows.Close() + return logHelper(logs), nil +} +func GetAuditLogsByUserID(userID int64, limit, offset int) ([]AuditLog, error) { var logs []AuditLog - for rows.Next() { - var log AuditLog - err := rows.Scan( - &log.ID, - &log.UserID, - &log.Username, - &log.Email, - &log.Action, - &log.ResourceType, - &log.ResourceID, - &log.Details, - &log.CreatedAt, - ) - if err != nil { - return nil, err - } - log.TriggerType = "user" - logs = append(logs, log) + err := getAuditLogsQuery().Where("audit_logs.user_id = ?", userID).Limit(limit).Offset(offset).Scan(&logs).Error + if err != nil { + return nil, err + } + for i := range logs { + logs[i].TriggerType = "user" } return logs, nil } func GetAuditLogsByResource(resourceType string, resourceID int64, limit, offset int) ([]AuditLog, error) { - query := ` - SELECT - al.id, - al.user_id, - u.username, - u.email, - al.action, - al.resource_type, - al.resource_id, - al.details, - al.created_at - FROM audit_logs al - LEFT JOIN users u ON al.user_id = u.id - WHERE al.resource_type = ? AND al.resource_id = ? - ORDER BY al.created_at DESC - LIMIT ? OFFSET ? - ` - rows, err := db.Query(query, resourceType, resourceID, limit, offset) + var logs []AuditLog + err := getAuditLogsQuery().Where("audit_logs.resource_type = ? AND audit_logs.resource_id = ?", resourceType, resourceID). + Limit(limit).Offset(offset).Scan(&logs).Error if err != nil { return nil, err } - defer rows.Close() - - var logs []AuditLog - for rows.Next() { - var log AuditLog - err := rows.Scan( - &log.ID, - &log.UserID, - &log.Username, - &log.Email, - &log.Action, - &log.ResourceType, - &log.ResourceID, - &log.Details, - &log.CreatedAt, - ) - if err != nil { - return nil, err - } - - if log.UserID == nil { - log.TriggerType = "system" - if log.Details != nil && (*log.Details != "") { - var detailsMap map[string]interface{} - if err := json.Unmarshal([]byte(*log.Details), &detailsMap); err == nil { - if triggerType, ok := detailsMap["trigger_type"].(string); ok { - log.TriggerType = triggerType - } - } - } - } else { - log.TriggerType = "user" - } - - logs = append(logs, log) - } - return logs, nil + return logHelper(logs), nil } -func GetAuditLogsCount() (int, error) { - query := `SELECT COUNT(*) FROM audit_logs` - var count int - err := db.QueryRow(query).Scan(&count) +func GetAuditLogsCount() (int64, error) { + var count int64 + err := db.Model(&AuditLog{}).Count(&count).Error return count, err } @@ -257,116 +150,29 @@ func LogSystemAudit(action, resourceType string, resourceID *int64, details inte } func GetAuditLogsByResourceType(resourceType string, limit, offset int) ([]AuditLog, error) { - query := ` - SELECT - al.id, - al.user_id, - u.username, - u.email, - al.action, - al.resource_type, - al.resource_id, - al.details, - al.created_at - FROM audit_logs al - LEFT JOIN users u ON al.user_id = u.id - WHERE al.resource_type = ? - ORDER BY al.created_at DESC - LIMIT ? OFFSET ? - ` - rows, err := db.Query(query, resourceType, limit, offset) + var logs []AuditLog + err := getAuditLogsQuery().Where("audit_logs.resource_type = ?", resourceType). + Limit(limit).Offset(offset).Scan(&logs).Error if err != nil { return nil, err } - defer rows.Close() - - var logs []AuditLog - for rows.Next() { - var log AuditLog - err := rows.Scan( - &log.ID, - &log.UserID, - &log.Username, - &log.Email, - &log.Action, - &log.ResourceType, - &log.ResourceID, - &log.Details, - &log.CreatedAt, - ) - if err != nil { - return nil, err - } - - if log.UserID == nil { - log.TriggerType = "system" - if log.Details != nil && (*log.Details != "") { - var detailsMap map[string]interface{} - if err := json.Unmarshal([]byte(*log.Details), &detailsMap); err == nil { - if triggerType, ok := detailsMap["trigger_type"].(string); ok { - log.TriggerType = triggerType - } - } - } - } else { - log.TriggerType = "user" - } - - logs = append(logs, log) - } - return logs, nil + return logHelper(logs), nil } func GetAuditLogByID(id int64) (*AuditLog, error) { - query := ` - SELECT - al.id, - al.user_id, - u.username, - u.email, - al.action, - al.resource_type, - al.resource_id, - al.details, - al.created_at - FROM audit_logs al - LEFT JOIN users u ON al.user_id = u.id - WHERE al.id = ? - ` - - var log AuditLog - err := db.QueryRow(query, id).Scan( - &log.ID, - &log.UserID, - &log.Username, - &log.Email, - &log.Action, - &log.ResourceType, - &log.ResourceID, - &log.Details, - &log.CreatedAt, - ) + var logs []AuditLog + err := getAuditLogsQuery(). + Where("audit_logs.id = ?", id). + Limit(1). + Scan(&logs).Error - if err == sql.ErrNoRows { - return nil, nil - } if err != nil { return nil, err } - - if log.UserID == nil { - log.TriggerType = "system" - if log.Details != nil && (*log.Details != "") { - var detailsMap map[string]interface{} - if err := json.Unmarshal([]byte(*log.Details), &detailsMap); err == nil { - if triggerType, ok := detailsMap["trigger_type"].(string); ok { - log.TriggerType = triggerType - } - } - } - } else { - log.TriggerType = "user" + if len(logs) == 0 { + return nil, nil } - return &log, nil + enrichedLogs := logHelper(logs) + return &enrichedLogs[0], nil } diff --git a/server/models/temp.go b/server/models/temp.go index e69de29..d6a05a4 100644 --- a/server/models/temp.go +++ b/server/models/temp.go @@ -0,0 +1,2 @@ +package models + From c7203d855086498650b556547be22cbfe425edde Mon Sep 17 00:00:00 2001 From: Tanish Date: Tue, 13 Jan 2026 21:44:02 +0530 Subject: [PATCH 05/16] rewritten backup cron and deployment files --- server/models/backup.go | 423 +++++++++++++++++-------- server/models/cron.go | 18 +- server/models/deployment.go | 611 ++++++++++++++++++++++++------------ server/models/temp.go | 2 - 4 files changed, 714 insertions(+), 340 deletions(-) diff --git a/server/models/backup.go b/server/models/backup.go index 0e3e535..6edd4cc 100644 --- a/server/models/backup.go +++ b/server/models/backup.go @@ -4,6 +4,7 @@ import ( "time" "github.com/corecollectives/mist/utils" + "gorm.io/gorm" ) type BackupType string @@ -30,34 +31,49 @@ const ( ) type Backup struct { - ID int64 `db:"id" json:"id"` - AppID int64 `db:"app_id" json:"appId"` - BackupType BackupType `db:"backup_type" json:"backupType"` - BackupName string `db:"backup_name" json:"backupName"` - FilePath string `db:"file_path" json:"filePath"` - FileSize *int64 `db:"file_size" json:"fileSize,omitempty"` - CompressionType string `db:"compression_type" json:"compressionType"` - DatabaseType *string `db:"database_type" json:"databaseType,omitempty"` - DatabaseVersion *string `db:"database_version" json:"databaseVersion,omitempty"` - StorageType StorageType `db:"storage_type" json:"storageType"` - StoragePath *string `db:"storage_path" json:"storagePath,omitempty"` - Status BackupStatus `db:"status" json:"status"` - Progress int `db:"progress" json:"progress"` - ErrorMessage *string `db:"error_message" json:"errorMessage,omitempty"` - Checksum *string `db:"checksum" json:"checksum,omitempty"` - ChecksumAlgorithm string `db:"checksum_algorithm" json:"checksumAlgorithm"` - IsVerified bool `db:"is_verified" json:"isVerified"` - VerifiedAt *time.Time `db:"verified_at" json:"verifiedAt,omitempty"` - CanRestore bool `db:"can_restore" json:"canRestore"` - LastRestoreAt *time.Time `db:"last_restore_at" json:"lastRestoreAt,omitempty"` - RestoreCount int `db:"restore_count" json:"restoreCount"` - RetentionDays *int `db:"retention_days" json:"retentionDays,omitempty"` - AutoDeleteAt *time.Time `db:"auto_delete_at" json:"autoDeleteAt,omitempty"` - CreatedBy *int64 `db:"created_by" json:"createdBy,omitempty"` - CreatedAt time.Time `db:"created_at" json:"createdAt"` - CompletedAt *time.Time `db:"completed_at" json:"completedAt,omitempty"` - Duration *int `db:"duration" json:"duration,omitempty"` - Notes *string `db:"notes" json:"notes,omitempty"` + ID int64 `gorm:"primaryKey;autoIncrement:false" json:"id"` + + AppID int64 `gorm:"index;not null;constraint:OnDelete:CASCADE" json:"appId"` + + BackupType BackupType `gorm:"index;not null" json:"backupType"` + BackupName string `gorm:"not null" json:"backupName"` + FilePath string `gorm:"not null" json:"filePath"` + FileSize *int64 `json:"fileSize,omitempty"` + + CompressionType string `gorm:"default:'gzip'" json:"compressionType"` + + DatabaseType *string `json:"databaseType,omitempty"` + DatabaseVersion *string `json:"databaseVersion,omitempty"` + + StorageType StorageType `gorm:"default:'local'" json:"storageType"` + StoragePath *string `json:"storagePath,omitempty"` + + Status BackupStatus `gorm:"default:'pending';index" json:"status"` + + Progress int `gorm:"default:0" json:"progress"` + ErrorMessage *string `json:"errorMessage,omitempty"` + Checksum *string `json:"checksum,omitempty"` + + ChecksumAlgorithm string `gorm:"default:'sha256'" json:"checksumAlgorithm"` + + IsVerified bool `gorm:"default:false" json:"isVerified"` + VerifiedAt *time.Time `json:"verifiedAt,omitempty"` + + CanRestore bool `gorm:"default:true" json:"canRestore"` + LastRestoreAt *time.Time `json:"lastRestoreAt,omitempty"` + + RestoreCount int `gorm:"default:0" json:"restoreCount"` + + RetentionDays *int `json:"retentionDays,omitempty"` + + AutoDeleteAt *time.Time `gorm:"index" json:"autoDeleteAt,omitempty"` + + CreatedBy *int64 `gorm:"constraint:OnDelete:SET NULL" json:"createdBy,omitempty"` + + CreatedAt time.Time `gorm:"autoCreateTime;index:,sort:desc" json:"createdAt"` + CompletedAt *time.Time `json:"completedAt,omitempty"` + Duration *int `json:"duration,omitempty"` + Notes *string `json:"notes,omitempty"` } func (b *Backup) ToJson() map[string]interface{} { @@ -94,127 +110,272 @@ func (b *Backup) ToJson() map[string]interface{} { } func (b *Backup) InsertInDB() error { - id := utils.GenerateRandomId() - b.ID = id - query := ` - INSERT INTO backups ( - id, app_id, backup_type, backup_name, file_path, - file_size, compression_type, database_type, database_version, - storage_type, storage_path, status, checksum_algorithm, - retention_days, auto_delete_at, created_by, notes - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - RETURNING created_at - ` - err := db.QueryRow(query, b.ID, b.AppID, b.BackupType, b.BackupName, b.FilePath, - b.FileSize, b.CompressionType, b.DatabaseType, b.DatabaseVersion, - b.StorageType, b.StoragePath, b.Status, b.ChecksumAlgorithm, - b.RetentionDays, b.AutoDeleteAt, b.CreatedBy, b.Notes).Scan(&b.CreatedAt) - return err + b.ID = utils.GenerateRandomId() + b.CanRestore = true + + return db.Create(b).Error } -func GetBackupsByAppID(appID int64) ([]Backup, error) { +func GetBackupsByAppID(appId int64) ([]Backup, error) { var backups []Backup - query := ` - SELECT id, app_id, backup_type, backup_name, file_path, file_size, - compression_type, database_type, database_version, - storage_type, storage_path, status, progress, error_message, - checksum, checksum_algorithm, is_verified, verified_at, - can_restore, last_restore_at, restore_count, - retention_days, auto_delete_at, created_by, created_at, - completed_at, duration, notes - FROM backups - WHERE app_id = ? AND status != 'deleted' - ORDER BY created_at DESC - ` - rows, err := db.Query(query, appID) - if err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - var backup Backup - err := rows.Scan( - &backup.ID, &backup.AppID, &backup.BackupType, &backup.BackupName, - &backup.FilePath, &backup.FileSize, &backup.CompressionType, - &backup.DatabaseType, &backup.DatabaseVersion, &backup.StorageType, - &backup.StoragePath, &backup.Status, &backup.Progress, &backup.ErrorMessage, - &backup.Checksum, &backup.ChecksumAlgorithm, &backup.IsVerified, &backup.VerifiedAt, - &backup.CanRestore, &backup.LastRestoreAt, &backup.RestoreCount, - &backup.RetentionDays, &backup.AutoDeleteAt, &backup.CreatedBy, &backup.CreatedAt, - &backup.CompletedAt, &backup.Duration, &backup.Notes, - ) - if err != nil { - return nil, err - } - backups = append(backups, backup) - } - - return backups, rows.Err() + result := db.Where("app_id=? AND status != ?", appId, BackupStatusDeleted).Order("created_at DESC").Find(&backups) + return backups, result.Error } -func GetBackupByID(backupID int64) (*Backup, error) { +func GetBackupByID(backupId int64) (*Backup, error) { var backup Backup - query := ` - SELECT id, app_id, backup_type, backup_name, file_path, file_size, - compression_type, database_type, database_version, - storage_type, storage_path, status, progress, error_message, - checksum, checksum_algorithm, is_verified, verified_at, - can_restore, last_restore_at, restore_count, - retention_days, auto_delete_at, created_by, created_at, - completed_at, duration, notes - FROM backups - WHERE id = ? - ` - err := db.QueryRow(query, backupID).Scan( - &backup.ID, &backup.AppID, &backup.BackupType, &backup.BackupName, - &backup.FilePath, &backup.FileSize, &backup.CompressionType, - &backup.DatabaseType, &backup.DatabaseVersion, &backup.StorageType, - &backup.StoragePath, &backup.Status, &backup.Progress, &backup.ErrorMessage, - &backup.Checksum, &backup.ChecksumAlgorithm, &backup.IsVerified, &backup.VerifiedAt, - &backup.CanRestore, &backup.LastRestoreAt, &backup.RestoreCount, - &backup.RetentionDays, &backup.AutoDeleteAt, &backup.CreatedBy, &backup.CreatedAt, - &backup.CompletedAt, &backup.Duration, &backup.Notes, - ) - if err != nil { - return nil, err + result := db.First(&backup, backupId) + if result.Error != nil { + return nil, result.Error } return &backup, nil } func (b *Backup) UpdateStatus(status BackupStatus, errorMsg *string) error { - query := ` - UPDATE backups - SET status = ?, error_message = ?, completed_at = CURRENT_TIMESTAMP - WHERE id = ? - ` - _, err := db.Exec(query, status, errorMsg, b.ID) - return err + update := map[string]interface{}{ + "status": status, + "error_message": errorMsg, + "completed_at": time.Now(), + } + return db.Model(b).Updates(update).Error } func (b *Backup) UpdateProgress(progress int) error { - query := `UPDATE backups SET progress = ? WHERE id = ?` - _, err := db.Exec(query, progress, b.ID) - return err + return db.Model(b).Update("progress", progress).Error } func (b *Backup) MarkAsRestored() error { - query := ` - UPDATE backups - SET last_restore_at = CURRENT_TIMESTAMP, - restore_count = restore_count + 1 - WHERE id = ? - ` - _, err := db.Exec(query, b.ID) - return err + return db.Model(b).Updates(map[string]interface{}{ + "last_restore_at": time.Now(), + "restore_count": gorm.Expr("restore_count + ?", 1), + }).Error } func DeleteExpiredBackups() error { - query := ` - UPDATE backups - SET status = 'deleted' - WHERE auto_delete_at IS NOT NULL AND auto_delete_at < CURRENT_TIMESTAMP AND status != 'deleted' - ` - _, err := db.Exec(query) - return err + return db.Where("auto_delete_at IS NOT NULL AND auto_delete_at < ? AND status != ?", time.Now(), BackupStatusDeleted).Update("status", BackupStatusDeleted).Error } + +//########################################################################################################################## + +//ARCHIVED CODE BELOW---------------------> + +// package models + +// import ( +// "time" + +// "github.com/corecollectives/mist/utils" +// ) + +// type BackupType string +// type BackupStatus string +// type StorageType string + +// const ( +// BackupTypeManual BackupType = "manual" +// BackupTypeScheduled BackupType = "scheduled" +// BackupTypePreDeployment BackupType = "pre_deployment" +// BackupTypeAutomatic BackupType = "automatic" + +// BackupStatusPending BackupStatus = "pending" +// BackupStatusInProgress BackupStatus = "in_progress" +// BackupStatusCompleted BackupStatus = "completed" +// BackupStatusFailed BackupStatus = "failed" +// BackupStatusDeleted BackupStatus = "deleted" + +// StorageTypeLocal StorageType = "local" +// StorageTypeS3 StorageType = "s3" +// StorageTypeGCS StorageType = "gcs" +// StorageTypeAzure StorageType = "azure" +// StorageTypeFTP StorageType = "ftp" +// ) + +// type Backup struct { +// ID int64 `db:"id" json:"id"` +// AppID int64 `db:"app_id" json:"appId"` +// BackupType BackupType `db:"backup_type" json:"backupType"` +// BackupName string `db:"backup_name" json:"backupName"` +// FilePath string `db:"file_path" json:"filePath"` +// FileSize *int64 `db:"file_size" json:"fileSize,omitempty"` +// CompressionType string `db:"compression_type" json:"compressionType"` +// DatabaseType *string `db:"database_type" json:"databaseType,omitempty"` +// DatabaseVersion *string `db:"database_version" json:"databaseVersion,omitempty"` +// StorageType StorageType `db:"storage_type" json:"storageType"` +// StoragePath *string `db:"storage_path" json:"storagePath,omitempty"` +// Status BackupStatus `db:"status" json:"status"` +// Progress int `db:"progress" json:"progress"` +// ErrorMessage *string `db:"error_message" json:"errorMessage,omitempty"` +// Checksum *string `db:"checksum" json:"checksum,omitempty"` +// ChecksumAlgorithm string `db:"checksum_algorithm" json:"checksumAlgorithm"` +// IsVerified bool `db:"is_verified" json:"isVerified"` +// VerifiedAt *time.Time `db:"verified_at" json:"verifiedAt,omitempty"` +// CanRestore bool `db:"can_restore" json:"canRestore"` +// LastRestoreAt *time.Time `db:"last_restore_at" json:"lastRestoreAt,omitempty"` +// RestoreCount int `db:"restore_count" json:"restoreCount"` +// RetentionDays *int `db:"retention_days" json:"retentionDays,omitempty"` +// AutoDeleteAt *time.Time `db:"auto_delete_at" json:"autoDeleteAt,omitempty"` +// CreatedBy *int64 `db:"created_by" json:"createdBy,omitempty"` +// CreatedAt time.Time `db:"created_at" json:"createdAt"` +// CompletedAt *time.Time `db:"completed_at" json:"completedAt,omitempty"` +// Duration *int `db:"duration" json:"duration,omitempty"` +// Notes *string `db:"notes" json:"notes,omitempty"` +// } + +// func (b *Backup) ToJson() map[string]interface{} { +// return map[string]interface{}{ +// "id": b.ID, +// "appId": b.AppID, +// "backupType": b.BackupType, +// "backupName": b.BackupName, +// "filePath": b.FilePath, +// "fileSize": b.FileSize, +// "compressionType": b.CompressionType, +// "databaseType": b.DatabaseType, +// "databaseVersion": b.DatabaseVersion, +// "storageType": b.StorageType, +// "storagePath": b.StoragePath, +// "status": b.Status, +// "progress": b.Progress, +// "errorMessage": b.ErrorMessage, +// "checksum": b.Checksum, +// "checksumAlgorithm": b.ChecksumAlgorithm, +// "isVerified": b.IsVerified, +// "verifiedAt": b.VerifiedAt, +// "canRestore": b.CanRestore, +// "lastRestoreAt": b.LastRestoreAt, +// "restoreCount": b.RestoreCount, +// "retentionDays": b.RetentionDays, +// "autoDeleteAt": b.AutoDeleteAt, +// "createdBy": b.CreatedBy, +// "createdAt": b.CreatedAt, +// "completedAt": b.CompletedAt, +// "duration": b.Duration, +// "notes": b.Notes, +// } +// } + +// func (b *Backup) InsertInDB() error { +// id := utils.GenerateRandomId() +// b.ID = id +// query := ` +// INSERT INTO backups ( +// id, app_id, backup_type, backup_name, file_path, +// file_size, compression_type, database_type, database_version, +// storage_type, storage_path, status, checksum_algorithm, +// retention_days, auto_delete_at, created_by, notes +// ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +// RETURNING created_at +// ` +// err := db.QueryRow(query, b.ID, b.AppID, b.BackupType, b.BackupName, b.FilePath, +// b.FileSize, b.CompressionType, b.DatabaseType, b.DatabaseVersion, +// b.StorageType, b.StoragePath, b.Status, b.ChecksumAlgorithm, +// b.RetentionDays, b.AutoDeleteAt, b.CreatedBy, b.Notes).Scan(&b.CreatedAt) +// return err +// } + +// func GetBackupsByAppID(appID int64) ([]Backup, error) { +// var backups []Backup +// query := ` +// SELECT id, app_id, backup_type, backup_name, file_path, file_size, +// compression_type, database_type, database_version, +// storage_type, storage_path, status, progress, error_message, +// checksum, checksum_algorithm, is_verified, verified_at, +// can_restore, last_restore_at, restore_count, +// retention_days, auto_delete_at, created_by, created_at, +// completed_at, duration, notes +// FROM backups +// WHERE app_id = ? AND status != 'deleted' +// ORDER BY created_at DESC +// ` +// rows, err := db.Query(query, appID) +// if err != nil { +// return nil, err +// } +// defer rows.Close() + +// for rows.Next() { +// var backup Backup +// err := rows.Scan( +// &backup.ID, &backup.AppID, &backup.BackupType, &backup.BackupName, +// &backup.FilePath, &backup.FileSize, &backup.CompressionType, +// &backup.DatabaseType, &backup.DatabaseVersion, &backup.StorageType, +// &backup.StoragePath, &backup.Status, &backup.Progress, &backup.ErrorMessage, +// &backup.Checksum, &backup.ChecksumAlgorithm, &backup.IsVerified, &backup.VerifiedAt, +// &backup.CanRestore, &backup.LastRestoreAt, &backup.RestoreCount, +// &backup.RetentionDays, &backup.AutoDeleteAt, &backup.CreatedBy, &backup.CreatedAt, +// &backup.CompletedAt, &backup.Duration, &backup.Notes, +// ) +// if err != nil { +// return nil, err +// } +// backups = append(backups, backup) +// } + +// return backups, rows.Err() +// } + +// func GetBackupByID(backupID int64) (*Backup, error) { +// var backup Backup +// query := ` +// SELECT id, app_id, backup_type, backup_name, file_path, file_size, +// compression_type, database_type, database_version, +// storage_type, storage_path, status, progress, error_message, +// checksum, checksum_algorithm, is_verified, verified_at, +// can_restore, last_restore_at, restore_count, +// retention_days, auto_delete_at, created_by, created_at, +// completed_at, duration, notes +// FROM backups +// WHERE id = ? +// ` +// err := db.QueryRow(query, backupID).Scan( +// &backup.ID, &backup.AppID, &backup.BackupType, &backup.BackupName, +// &backup.FilePath, &backup.FileSize, &backup.CompressionType, +// &backup.DatabaseType, &backup.DatabaseVersion, &backup.StorageType, +// &backup.StoragePath, &backup.Status, &backup.Progress, &backup.ErrorMessage, +// &backup.Checksum, &backup.ChecksumAlgorithm, &backup.IsVerified, &backup.VerifiedAt, +// &backup.CanRestore, &backup.LastRestoreAt, &backup.RestoreCount, +// &backup.RetentionDays, &backup.AutoDeleteAt, &backup.CreatedBy, &backup.CreatedAt, +// &backup.CompletedAt, &backup.Duration, &backup.Notes, +// ) +// if err != nil { +// return nil, err +// } +// return &backup, nil +// } + +// func (b *Backup) UpdateStatus(status BackupStatus, errorMsg *string) error { +// query := ` +// UPDATE backups +// SET status = ?, error_message = ?, completed_at = CURRENT_TIMESTAMP +// WHERE id = ? +// ` +// _, err := db.Exec(query, status, errorMsg, b.ID) +// return err +// } + +// func (b *Backup) UpdateProgress(progress int) error { +// query := `UPDATE backups SET progress = ? WHERE id = ?` +// _, err := db.Exec(query, progress, b.ID) +// return err +// } + +// func (b *Backup) MarkAsRestored() error { +// query := ` +// UPDATE backups +// SET last_restore_at = CURRENT_TIMESTAMP, +// restore_count = restore_count + 1 +// WHERE id = ? +// ` +// _, err := db.Exec(query, b.ID) +// return err +// } + +// func DeleteExpiredBackups() error { +// query := ` +// UPDATE backups +// SET status = 'deleted' +// WHERE auto_delete_at IS NOT NULL AND auto_delete_at < CURRENT_TIMESTAMP AND status != 'deleted' +// ` +// _, err := db.Exec(query) +// return err +// } diff --git a/server/models/cron.go b/server/models/cron.go index 1eb6aa2..262a4db 100644 --- a/server/models/cron.go +++ b/server/models/cron.go @@ -3,13 +3,13 @@ package models import "time" type Cron struct { - ID int64 `json:"id"` - AppID int64 `json:"app_id"` - Name string `json:"name"` - Schedule string `json:"schedule"` - Command string `json:"command"` - LastRun time.Time `json:"last_run"` - NextRun time.Time `json:"next_run"` - Enable bool `json:"enable"` - CreatedAt time.Time `json:"created_at"` + ID int64 `gorm:"primaryKey;autoIncrement:true" json:"id"` + AppID int64 `gorm:"index;constraint:OnDelete:CASCADE;not null" json:"app_id"` + Name string `gorm:"index;not null" json:"name"` + Schedule string `gorm:"not null" json:"schedule"` + Command string `gorm:"not null" json:"command"` + LastRun *time.Time `gorm:"type:timestamp" json:"last_run"` + NextRun *time.Time `gorm:"type:timestamp" json:"next_run"` + Enable bool `gorm:"default:true" json:"enable"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` } diff --git a/server/models/deployment.go b/server/models/deployment.go index 1572cba..e79c41b 100644 --- a/server/models/deployment.go +++ b/server/models/deployment.go @@ -4,6 +4,7 @@ import ( "time" "github.com/corecollectives/mist/utils" + "gorm.io/gorm" ) type DeploymentStatus string @@ -19,28 +20,42 @@ const ( ) type Deployment struct { - ID int64 `db:"id" json:"id"` - AppID int64 `db:"app_id" json:"app_id"` - CommitHash string `db:"commit_hash" json:"commit_hash"` - CommitMessage *string `db:"commit_message" json:"commit_message,omitempty"` - CommitAuthor *string `db:"commit_author" json:"commit_author,omitempty"` - TriggeredBy *int64 `db:"triggered_by" json:"triggered_by,omitempty"` - DeploymentNumber *int `db:"deployment_number" json:"deployment_number,omitempty"` - ContainerID *string `db:"container_id" json:"container_id,omitempty"` - ContainerName *string `db:"container_name" json:"container_name,omitempty"` - ImageTag *string `db:"image_tag" json:"image_tag,omitempty"` - Logs *string `db:"logs" json:"logs,omitempty"` - BuildLogsPath *string `db:"build_logs_path" json:"build_logs_path,omitempty"` - Status DeploymentStatus `db:"status" json:"status"` - Stage string `db:"stage" json:"stage"` - Progress int `db:"progress" json:"progress"` - ErrorMessage *string `db:"error_message" json:"error_message,omitempty"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - StartedAt *time.Time `db:"started_at" json:"started_at,omitempty"` - FinishedAt *time.Time `db:"finished_at" json:"finished_at,omitempty"` - Duration *int `db:"duration" json:"duration,omitempty"` - IsActive bool `db:"is_active" json:"is_active"` - RolledBackFrom *int64 `db:"rolled_back_from" json:"rolled_back_from,omitempty"` + ID int64 `gorm:"primaryKey;autoIncrement:false" json:"id"` + + AppID int64 `gorm:"index:idx_deployments_app_id;not null;constraint:OnDelete:CASCADE" json:"app_id"` + + CommitHash string `gorm:"index:idx_deployments_commit_hash;not null" json:"commit_hash"` + + CommitMessage *string `json:"commit_message,omitempty"` + CommitAuthor *string `json:"commit_author,omitempty"` + + TriggeredBy *int64 `gorm:"constraint:OnDelete:SET NULL" json:"triggered_by,omitempty"` + + DeploymentNumber *int `json:"deployment_number,omitempty"` + + ContainerID *string `json:"container_id,omitempty"` + ContainerName *string `json:"container_name,omitempty"` + ImageTag *string `json:"image_tag,omitempty"` + + Logs *string `json:"logs,omitempty"` + BuildLogsPath *string `json:"build_logs_path,omitempty"` + + Status DeploymentStatus `gorm:"default:'pending';index:idx_deployments_status" json:"status"` + + Stage string `gorm:"default:'pending'" json:"stage"` + Progress int `gorm:"default:0" json:"progress"` + + ErrorMessage *string `json:"error_message,omitempty"` + + CreatedAt time.Time `gorm:"autoCreateTime;index:idx_deployments_created_at,sort:desc" json:"created_at"` + + StartedAt *time.Time `json:"started_at,omitempty"` + FinishedAt *time.Time `json:"finished_at,omitempty"` + Duration *int `json:"duration,omitempty"` + + IsActive bool `gorm:"default:false;index:idx_deployments_is_active" json:"is_active"` + + RolledBackFrom *int64 `gorm:"constraint:OnDelete:SET NULL" json:"rolled_back_from,omitempty"` } func (d *Deployment) ToJson() map[string]interface{} { @@ -71,219 +86,419 @@ func (d *Deployment) ToJson() map[string]interface{} { } func GetDeploymentsByAppID(appID int64) ([]Deployment, error) { - query := ` - SELECT id, app_id, commit_hash, commit_message, commit_author, triggered_by, - deployment_number, container_id, container_name, image_tag, - logs, build_logs_path, status, stage, progress, error_message, - created_at, started_at, finished_at, duration, is_active, rolled_back_from - FROM deployments - WHERE app_id = ? - ORDER BY created_at DESC - ` - - rows, err := db.Query(query, appID) - if err != nil { - return nil, err - } - defer rows.Close() - var deployments []Deployment - for rows.Next() { - var d Deployment - if err := rows.Scan( - &d.ID, &d.AppID, &d.CommitHash, &d.CommitMessage, &d.CommitAuthor, - &d.TriggeredBy, &d.DeploymentNumber, &d.ContainerID, &d.ContainerName, - &d.ImageTag, &d.Logs, &d.BuildLogsPath, &d.Status, &d.Stage, &d.Progress, - &d.ErrorMessage, &d.CreatedAt, &d.StartedAt, &d.FinishedAt, &d.Duration, - &d.IsActive, &d.RolledBackFrom, - ); err != nil { - return nil, err - } - deployments = append(deployments, d) - } - - if err := rows.Err(); err != nil { - return nil, err - } - - return deployments, nil + result := db.Where("app_id = ?", appID).Order("created_at DESC").Find(&deployments) + return deployments, result.Error } func (d *Deployment) CreateDeployment() error { - id := utils.GenerateRandomId() - d.ID = id - - var maxDeploymentNum int - err := db.QueryRow(`SELECT COALESCE(MAX(deployment_number), 0) FROM deployments WHERE app_id = ?`, d.AppID).Scan(&maxDeploymentNum) - if err == nil { - deploymentNum := maxDeploymentNum + 1 - d.DeploymentNumber = &deploymentNum - } - - query := ` - INSERT INTO deployments ( - id, app_id, commit_hash, commit_message, commit_author, triggered_by, - deployment_number, status, stage, progress - ) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', 'pending', 0) - RETURNING created_at - ` - err = db.QueryRow(query, d.ID, d.AppID, d.CommitHash, d.CommitMessage, - d.CommitAuthor, d.TriggeredBy, d.DeploymentNumber).Scan(&d.CreatedAt) - if err != nil { - return err + d.ID = utils.GenerateRandomId() + var maxDepNum *int + result := db.Model(&Deployment{}). + Where("app_id = ?", d.AppID). + Pluck("MAX(deployment_number)", &maxDepNum) + + currentNum := 0 + if result.Error == nil && maxDepNum != nil { + currentNum = *maxDepNum } + newNum := currentNum + 1 + d.DeploymentNumber = &newNum d.Status = DeploymentStatusPending d.Stage = "pending" d.Progress = 0 d.IsActive = false - return nil + + return db.Create(d).Error } func GetDeploymentByID(depID int64) (*Deployment, error) { - query := ` - SELECT id, app_id, commit_hash, commit_message, commit_author, triggered_by, - deployment_number, container_id, container_name, image_tag, - logs, build_logs_path, status, stage, progress, error_message, - created_at, started_at, finished_at, duration, is_active, rolled_back_from - FROM deployments - WHERE id = ? - ` - - var d Deployment - if err := db.QueryRow(query, depID).Scan( - &d.ID, &d.AppID, &d.CommitHash, &d.CommitMessage, &d.CommitAuthor, - &d.TriggeredBy, &d.DeploymentNumber, &d.ContainerID, &d.ContainerName, - &d.ImageTag, &d.Logs, &d.BuildLogsPath, &d.Status, &d.Stage, &d.Progress, - &d.ErrorMessage, &d.CreatedAt, &d.StartedAt, &d.FinishedAt, &d.Duration, - &d.IsActive, &d.RolledBackFrom, - ); err != nil { - return nil, err + var deployment Deployment + result := db.First(&deployment, "id = ?", depID) + if result.Error != nil { + return nil, result.Error } - - return &d, nil + return &deployment, nil } func GetDeploymentByCommitHash(commitHash string) (*Deployment, error) { - query := ` - SELECT id, app_id, commit_hash, commit_message, commit_author, triggered_by, - deployment_number, container_id, container_name, image_tag, - logs, build_logs_path, status, stage, progress, error_message, - created_at, started_at, finished_at, duration, is_active, rolled_back_from - FROM deployments - WHERE commit_hash = ? - LIMIT 1 - ` - var d Deployment - if err := db.QueryRow(query, commitHash).Scan( - &d.ID, &d.AppID, &d.CommitHash, &d.CommitMessage, &d.CommitAuthor, - &d.TriggeredBy, &d.DeploymentNumber, &d.ContainerID, &d.ContainerName, - &d.ImageTag, &d.Logs, &d.BuildLogsPath, &d.Status, &d.Stage, &d.Progress, - &d.ErrorMessage, &d.CreatedAt, &d.StartedAt, &d.FinishedAt, &d.Duration, - &d.IsActive, &d.RolledBackFrom, - ); err != nil { - return nil, err + var deployment Deployment + result := db.First(&deployment, "commit_hash = ?", commitHash) + if result.Error != nil { + return nil, result.Error } - - return &d, nil - + return &deployment, nil } func GetDeploymentByAppIDAndCommitHash(appID int64, commitHash string) (*Deployment, error) { - query := ` - SELECT id, app_id, commit_hash, commit_message, commit_author, triggered_by, - deployment_number, container_id, container_name, image_tag, - logs, build_logs_path, status, stage, progress, error_message, - created_at, started_at, finished_at, duration, is_active, rolled_back_from - FROM deployments - WHERE app_id = ? AND commit_hash = ? - LIMIT 1 - ` - var d Deployment - if err := db.QueryRow(query, appID, commitHash).Scan( - &d.ID, &d.AppID, &d.CommitHash, &d.CommitMessage, &d.CommitAuthor, - &d.TriggeredBy, &d.DeploymentNumber, &d.ContainerID, &d.ContainerName, - &d.ImageTag, &d.Logs, &d.BuildLogsPath, &d.Status, &d.Stage, &d.Progress, - &d.ErrorMessage, &d.CreatedAt, &d.StartedAt, &d.FinishedAt, &d.Duration, - &d.IsActive, &d.RolledBackFrom, - ); err != nil { - return nil, err + var deployment Deployment + result := db.First(&deployment, "app_id = ? AND commit_hash = ?", appID, commitHash) + if result.Error != nil { + return nil, result.Error } - - return &d, nil - + return &deployment, nil } - func GetActiveDeploymentByAppID(appID int64) (*Deployment, error) { - query := ` - SELECT id, app_id, commit_hash, commit_message, commit_author, triggered_by, - deployment_number, container_id, container_name, image_tag, - logs, build_logs_path, status, stage, progress, error_message, - created_at, started_at, finished_at, duration, is_active, rolled_back_from - FROM deployments - WHERE app_id = ? AND is_active = 1 - LIMIT 1 - ` - - var d Deployment - if err := db.QueryRow(query, appID).Scan( - &d.ID, &d.AppID, &d.CommitHash, &d.CommitMessage, &d.CommitAuthor, - &d.TriggeredBy, &d.DeploymentNumber, &d.ContainerID, &d.ContainerName, - &d.ImageTag, &d.Logs, &d.BuildLogsPath, &d.Status, &d.Stage, &d.Progress, - &d.ErrorMessage, &d.CreatedAt, &d.StartedAt, &d.FinishedAt, &d.Duration, - &d.IsActive, &d.RolledBackFrom, - ); err != nil { - return nil, err + var deployment Deployment + err := db.Where("app_id = ? AND is_active = ?", appID, true).First(&deployment) + if err.Error != nil { + return nil, err.Error } - - return &d, nil + return &deployment, nil } func GetCommitHashByDeploymentID(depID int64) (string, error) { - var commitHash string - err := db.QueryRow(`SELECT commit_hash FROM deployments WHERE id = ?`, depID).Scan(&commitHash) - if err != nil { - return "", err + var d Deployment + result := db.Select("commit_hash").First(&d, "id = ?", depID) + if result.Error != nil { + return "", result.Error } - return commitHash, nil + return d.CommitHash, nil } func UpdateDeploymentStatus(depID int64, status, stage string, progress int, errorMsg *string) error { - query := ` - UPDATE deployments - SET status = ?, stage = ?, progress = ?, error_message = ?, - finished_at = CASE WHEN ? IN ('success', 'failed', 'stopped') THEN CURRENT_TIMESTAMP ELSE finished_at END, - duration = CASE WHEN ? IN ('success', 'failed', 'stopped') THEN - CAST((julianday(CURRENT_TIMESTAMP) - julianday(started_at)) * 86400 AS INTEGER) - ELSE duration END - WHERE id = ? - ` - _, err := db.Exec(query, status, stage, progress, errorMsg, status, status, depID) - return err -} + var d Deployment + result := db.First(&d, "id = ?", depID) + if result.Error != nil { + return result.Error + } + updates := map[string]interface{}{ + "status": status, + "stage": stage, + "progress": progress, + "error_message": errorMsg, + } + if status == string(DeploymentStatusFailed) || status == string(DeploymentStatusSuccess) || status == string(DeploymentStatusStopped) { + now := time.Now() + updates["finished_at"] = &now + if d.StartedAt != nil { + duration := int(now.Sub(*d.StartedAt).Seconds()) + updates["duration"] = &duration + } + } + return db.Model(d).Updates(updates).Error +} func MarkDeploymentStarted(depID int64) error { - query := `UPDATE deployments SET started_at = CURRENT_TIMESTAMP WHERE id = ?` - _, err := db.Exec(query, depID) - return err + now := time.Now() + return db.Model(&Deployment{}).Where("id = ?", depID).Update("started_at", now).Error } func MarkDeploymentActive(depID int64, appID int64) error { - _, err := db.Exec(`UPDATE deployments SET is_active = 0 WHERE app_id = ?`, appID) - if err != nil { - return err - } - - _, err = db.Exec(`UPDATE deployments SET is_active = 1 WHERE id = ?`, depID) - return err + return db.Transaction(func(tx *gorm.DB) error { + err := tx.Model(&Deployment{}).Where("app_id=?", appID).Update("is_active", false).Error + if err != nil { + return err + } + err = tx.Model(&Deployment{}).Where("id = ?", depID).Update("is_active", true).Error + if err != nil { + return err + } + return nil + }) } - func UpdateContainerInfo(depID int64, containerID, containerName, imageTag string) error { - query := ` - UPDATE deployments - SET container_id = ?, container_name = ?, image_tag = ? - WHERE id = ? - ` - _, err := db.Exec(query, containerID, containerName, imageTag, depID) - return err + updates := map[string]interface{}{ + "container_id": containerID, + "container_name": containerName, + "image_tag": imageTag, + } + return db.Model(&Deployment{}).Where("id = ?", depID).Updates(updates).Error } + + + + +//############################################################################################################# +//ARCHIVED CODE BELOW------> + +// package models + +// import ( +// "time" + +// "github.com/corecollectives/mist/utils" +// ) + +// type DeploymentStatus string + +// const ( +// DeploymentStatusPending DeploymentStatus = "pending" +// DeploymentStatusBuilding DeploymentStatus = "building" +// DeploymentStatusDeploying DeploymentStatus = "deploying" +// DeploymentStatusSuccess DeploymentStatus = "success" +// DeploymentStatusFailed DeploymentStatus = "failed" +// DeploymentStatusStopped DeploymentStatus = "stopped" +// DeploymentStatusRolledBack DeploymentStatus = "rolled_back" +// ) + +// type Deployment struct { +// ID int64 `db:"id" json:"id"` +// AppID int64 `db:"app_id" json:"app_id"` +// CommitHash string `db:"commit_hash" json:"commit_hash"` +// CommitMessage *string `db:"commit_message" json:"commit_message,omitempty"` +// CommitAuthor *string `db:"commit_author" json:"commit_author,omitempty"` +// TriggeredBy *int64 `db:"triggered_by" json:"triggered_by,omitempty"` +// DeploymentNumber *int `db:"deployment_number" json:"deployment_number,omitempty"` +// ContainerID *string `db:"container_id" json:"container_id,omitempty"` +// ContainerName *string `db:"container_name" json:"container_name,omitempty"` +// ImageTag *string `db:"image_tag" json:"image_tag,omitempty"` +// Logs *string `db:"logs" json:"logs,omitempty"` +// BuildLogsPath *string `db:"build_logs_path" json:"build_logs_path,omitempty"` +// Status DeploymentStatus `db:"status" json:"status"` +// Stage string `db:"stage" json:"stage"` +// Progress int `db:"progress" json:"progress"` +// ErrorMessage *string `db:"error_message" json:"error_message,omitempty"` +// CreatedAt time.Time `db:"created_at" json:"created_at"` +// StartedAt *time.Time `db:"started_at" json:"started_at,omitempty"` +// FinishedAt *time.Time `db:"finished_at" json:"finished_at,omitempty"` +// Duration *int `db:"duration" json:"duration,omitempty"` +// IsActive bool `db:"is_active" json:"is_active"` +// RolledBackFrom *int64 `db:"rolled_back_from" json:"rolled_back_from,omitempty"` +// } + +// func (d *Deployment) ToJson() map[string]interface{} { +// return map[string]interface{}{ +// "id": d.ID, +// "appId": d.AppID, +// "commitHash": d.CommitHash, +// "commitMessage": d.CommitMessage, +// "commitAuthor": d.CommitAuthor, +// "triggeredBy": d.TriggeredBy, +// "deploymentNumber": d.DeploymentNumber, +// "containerId": d.ContainerID, +// "containerName": d.ContainerName, +// "imageTag": d.ImageTag, +// "logs": d.Logs, +// "buildLogsPath": d.BuildLogsPath, +// "status": d.Status, +// "stage": d.Stage, +// "progress": d.Progress, +// "errorMessage": d.ErrorMessage, +// "createdAt": d.CreatedAt, +// "startedAt": d.StartedAt, +// "finishedAt": d.FinishedAt, +// "duration": d.Duration, +// "isActive": d.IsActive, +// "rolledBackFrom": d.RolledBackFrom, +// } +// } + +// func GetDeploymentsByAppID(appID int64) ([]Deployment, error) { +// query := ` +// SELECT id, app_id, commit_hash, commit_message, commit_author, triggered_by, +// deployment_number, container_id, container_name, image_tag, +// logs, build_logs_path, status, stage, progress, error_message, +// created_at, started_at, finished_at, duration, is_active, rolled_back_from +// FROM deployments +// WHERE app_id = ? +// ORDER BY created_at DESC +// ` + +// rows, err := db.Query(query, appID) +// if err != nil { +// return nil, err +// } +// defer rows.Close() + +// var deployments []Deployment +// for rows.Next() { +// var d Deployment +// if err := rows.Scan( +// &d.ID, &d.AppID, &d.CommitHash, &d.CommitMessage, &d.CommitAuthor, +// &d.TriggeredBy, &d.DeploymentNumber, &d.ContainerID, &d.ContainerName, +// &d.ImageTag, &d.Logs, &d.BuildLogsPath, &d.Status, &d.Stage, &d.Progress, +// &d.ErrorMessage, &d.CreatedAt, &d.StartedAt, &d.FinishedAt, &d.Duration, +// &d.IsActive, &d.RolledBackFrom, +// ); err != nil { +// return nil, err +// } +// deployments = append(deployments, d) +// } + +// if err := rows.Err(); err != nil { +// return nil, err +// } + +// return deployments, nil +// } + +// func (d *Deployment) CreateDeployment() error { +// id := utils.GenerateRandomId() +// d.ID = id + +// var maxDeploymentNum int +// err := db.QueryRow(`SELECT COALESCE(MAX(deployment_number), 0) FROM deployments WHERE app_id = ?`, d.AppID).Scan(&maxDeploymentNum) +// if err == nil { +// deploymentNum := maxDeploymentNum + 1 +// d.DeploymentNumber = &deploymentNum +// } + +// query := ` +// INSERT INTO deployments ( +// id, app_id, commit_hash, commit_message, commit_author, triggered_by, +// deployment_number, status, stage, progress +// ) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', 'pending', 0) +// RETURNING created_at +// ` +// err = db.QueryRow(query, d.ID, d.AppID, d.CommitHash, d.CommitMessage, +// d.CommitAuthor, d.TriggeredBy, d.DeploymentNumber).Scan(&d.CreatedAt) +// if err != nil { +// return err +// } + +// d.Status = DeploymentStatusPending +// d.Stage = "pending" +// d.Progress = 0 +// d.IsActive = false +// return nil +// } + +// func GetDeploymentByID(depID int64) (*Deployment, error) { +// query := ` +// SELECT id, app_id, commit_hash, commit_message, commit_author, triggered_by, +// deployment_number, container_id, container_name, image_tag, +// logs, build_logs_path, status, stage, progress, error_message, +// created_at, started_at, finished_at, duration, is_active, rolled_back_from +// FROM deployments +// WHERE id = ? +// ` + +// var d Deployment +// if err := db.QueryRow(query, depID).Scan( +// &d.ID, &d.AppID, &d.CommitHash, &d.CommitMessage, &d.CommitAuthor, +// &d.TriggeredBy, &d.DeploymentNumber, &d.ContainerID, &d.ContainerName, +// &d.ImageTag, &d.Logs, &d.BuildLogsPath, &d.Status, &d.Stage, &d.Progress, +// &d.ErrorMessage, &d.CreatedAt, &d.StartedAt, &d.FinishedAt, &d.Duration, +// &d.IsActive, &d.RolledBackFrom, +// ); err != nil { +// return nil, err +// } + +// return &d, nil +// } + +// func GetDeploymentByCommitHash(commitHash string) (*Deployment, error) { +// query := ` +// SELECT id, app_id, commit_hash, commit_message, commit_author, triggered_by, +// deployment_number, container_id, container_name, image_tag, +// logs, build_logs_path, status, stage, progress, error_message, +// created_at, started_at, finished_at, duration, is_active, rolled_back_from +// FROM deployments +// WHERE commit_hash = ? +// LIMIT 1 +// ` +// var d Deployment +// if err := db.QueryRow(query, commitHash).Scan( +// &d.ID, &d.AppID, &d.CommitHash, &d.CommitMessage, &d.CommitAuthor, +// &d.TriggeredBy, &d.DeploymentNumber, &d.ContainerID, &d.ContainerName, +// &d.ImageTag, &d.Logs, &d.BuildLogsPath, &d.Status, &d.Stage, &d.Progress, +// &d.ErrorMessage, &d.CreatedAt, &d.StartedAt, &d.FinishedAt, &d.Duration, +// &d.IsActive, &d.RolledBackFrom, +// ); err != nil { +// return nil, err +// } + +// return &d, nil + +// } + +// func GetDeploymentByAppIDAndCommitHash(appID int64, commitHash string) (*Deployment, error) { +// query := ` +// SELECT id, app_id, commit_hash, commit_message, commit_author, triggered_by, +// deployment_number, container_id, container_name, image_tag, +// logs, build_logs_path, status, stage, progress, error_message, +// created_at, started_at, finished_at, duration, is_active, rolled_back_from +// FROM deployments +// WHERE app_id = ? AND commit_hash = ? +// LIMIT 1 +// ` +// var d Deployment +// if err := db.QueryRow(query, appID, commitHash).Scan( +// &d.ID, &d.AppID, &d.CommitHash, &d.CommitMessage, &d.CommitAuthor, +// &d.TriggeredBy, &d.DeploymentNumber, &d.ContainerID, &d.ContainerName, +// &d.ImageTag, &d.Logs, &d.BuildLogsPath, &d.Status, &d.Stage, &d.Progress, +// &d.ErrorMessage, &d.CreatedAt, &d.StartedAt, &d.FinishedAt, &d.Duration, +// &d.IsActive, &d.RolledBackFrom, +// ); err != nil { +// return nil, err +// } + +// return &d, nil + +// } + +// func GetActiveDeploymentByAppID(appID int64) (*Deployment, error) { +// query := ` +// SELECT id, app_id, commit_hash, commit_message, commit_author, triggered_by, +// deployment_number, container_id, container_name, image_tag, +// logs, build_logs_path, status, stage, progress, error_message, +// created_at, started_at, finished_at, duration, is_active, rolled_back_from +// FROM deployments +// WHERE app_id = ? AND is_active = 1 +// LIMIT 1 +// ` + +// var d Deployment +// if err := db.QueryRow(query, appID).Scan( +// &d.ID, &d.AppID, &d.CommitHash, &d.CommitMessage, &d.CommitAuthor, +// &d.TriggeredBy, &d.DeploymentNumber, &d.ContainerID, &d.ContainerName, +// &d.ImageTag, &d.Logs, &d.BuildLogsPath, &d.Status, &d.Stage, &d.Progress, +// &d.ErrorMessage, &d.CreatedAt, &d.StartedAt, &d.FinishedAt, &d.Duration, +// &d.IsActive, &d.RolledBackFrom, +// ); err != nil { +// return nil, err +// } + +// return &d, nil +// } + +// func GetCommitHashByDeploymentID(depID int64) (string, error) { +// var commitHash string +// err := db.QueryRow(`SELECT commit_hash FROM deployments WHERE id = ?`, depID).Scan(&commitHash) +// if err != nil { +// return "", err +// } +// return commitHash, nil +// } + +// func UpdateDeploymentStatus(depID int64, status, stage string, progress int, errorMsg *string) error { +// query := ` +// UPDATE deployments +// SET status = ?, stage = ?, progress = ?, error_message = ?, +// finished_at = CASE WHEN ? IN ('success', 'failed', 'stopped') THEN CURRENT_TIMESTAMP ELSE finished_at END, +// duration = CASE WHEN ? IN ('success', 'failed', 'stopped') THEN +// CAST((julianday(CURRENT_TIMESTAMP) - julianday(started_at)) * 86400 AS INTEGER) +// ELSE duration END +// WHERE id = ? +// ` +// _, err := db.Exec(query, status, stage, progress, errorMsg, status, status, depID) +// return err +// } + +// func MarkDeploymentStarted(depID int64) error { +// query := `UPDATE deployments SET started_at = CURRENT_TIMESTAMP WHERE id = ?` +// _, err := db.Exec(query, depID) +// return err +// } + +// func MarkDeploymentActive(depID int64, appID int64) error { +// _, err := db.Exec(`UPDATE deployments SET is_active = 0 WHERE app_id = ?`, appID) +// if err != nil { +// return err +// } + +// _, err = db.Exec(`UPDATE deployments SET is_active = 1 WHERE id = ?`, depID) +// return err +// } + +// func UpdateContainerInfo(depID int64, containerID, containerName, imageTag string) error { +// query := ` +// UPDATE deployments +// SET container_id = ?, container_name = ?, image_tag = ? +// WHERE id = ? +// ` +// _, err := db.Exec(query, containerID, containerName, imageTag, depID) +// return err +// } diff --git a/server/models/temp.go b/server/models/temp.go index d6a05a4..e69de29 100644 --- a/server/models/temp.go +++ b/server/models/temp.go @@ -1,2 +0,0 @@ -package models - From bd396dd6ed2da0a8adfb4b0416864dbc1d912f13 Mon Sep 17 00:00:00 2001 From: Tanish Date: Tue, 13 Jan 2026 22:13:21 +0530 Subject: [PATCH 06/16] rewrote domain file --- server/models/domain.go | 320 ++++++++++++++++++++++++++++------------ 1 file changed, 229 insertions(+), 91 deletions(-) diff --git a/server/models/domain.go b/server/models/domain.go index b569686..24a5f78 100644 --- a/server/models/domain.go +++ b/server/models/domain.go @@ -6,133 +6,271 @@ import ( "github.com/corecollectives/mist/utils" ) +type sslStatus string +type sslProvider string +type acmeChallengeType string + +const ( + SSLStatusPending sslStatus = "pending" + SSLStatusActive sslStatus = "active" + SSLStatusFailed sslStatus = "failed" + SSLStatusDisabled sslStatus = "disabled" + SSLStatusExpired sslStatus = "expired" + + SSLProvider sslProvider = "letsencrypt" + SSLProviderCustom sslProvider = "custom" + SSLProviderNone sslProvider = "none" + + AcmeChallengeTypeHttp01 acmeChallengeType = "http-01" + AcmeChallengeTypeDns01 acmeChallengeType = "dns-01" + AcmeChallengeTypeTlsAlpn01 acmeChallengeType = "tls-alpn-01" +) + type Domain struct { - ID int64 `json:"id"` - AppID int64 `json:"appId"` - Domain string `json:"domain"` - SslStatus string `json:"sslStatus"` - DnsConfigured bool `json:"dnsConfigured"` - DnsVerifiedAt *time.Time `json:"dnsVerifiedAt"` - LastDnsCheck *time.Time `json:"lastDnsCheck"` - DnsCheckError *string `json:"dnsCheckError"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID int64 `gorm:"primaryKey;autoIncrement:false" json:"id"` + + AppID int64 `gorm:"index;not null;constraint:OnDelete:CASCADE" json:"appId"` + + Domain string `gorm:"column:domain_name;uniqueIndex;not null" json:"domain"` + + SslStatus sslStatus `gorm:"default:'pending';index" json:"sslStatus"` + SslProvider sslProvider `gorm:"default:'letsencrypt'" json:"sslProvider,omitempty"` + + CertificatePath *string `json:"certificatePath,omitempty"` + CertificateData *string `json:"-"` + KeyPath *string `json:"keyPath,omitempty"` + KeyData *string `json:"-"` + ChainPath *string `json:"chainPath,omitempty"` + + AcmeAccountUrl *string `json:"acmeAccountUrl,omitempty"` + AcmeChallengeType acmeChallengeType `json:"acmeChallengeType,omitempty"` + + Issuer *string `json:"issuer,omitempty"` + IssuedAt *time.Time `json:"issuedAt,omitempty"` + ExpiresAt *time.Time `gorm:"index" json:"expiresAt,omitempty"` + LastRenewalAttempt *time.Time `json:"lastRenewalAttempt,omitempty"` + RenewalError *string `json:"renewalError,omitempty"` + + AutoRenew bool `gorm:"default:true" json:"autoRenew"` + ForceHttps bool `gorm:"default:false" json:"forceHttps"` + HstsEnabled bool `gorm:"default:false" json:"hstsEnabled"` + HstsMaxAge int `gorm:"default:31536000" json:"hstsMaxAge"` + RedirectWww bool `gorm:"default:false" json:"redirectWww"` + RedirectWwwToRoot bool `gorm:"default:true" json:"redirectWwwToRoot"` + + DnsConfigured bool `gorm:"default:false" json:"dnsConfigured"` + DnsVerifiedAt *time.Time `json:"dnsVerifiedAt,omitempty"` + LastDnsCheck *time.Time `json:"lastDnsCheck,omitempty"` + DnsCheckError *string `json:"dnsCheckError,omitempty"` + + CreatedAt time.Time `gorm:"autoCreateTime" json:"createdAt"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updatedAt"` } func CreateDomain(appID int64, domain string) (*Domain, error) { - id := utils.GenerateRandomId() - query := ` - INSERT INTO domains (id, app_id, domain_name, ssl_status, dns_configured, created_at, updated_at) - VALUES (?, ?, ?, 'pending', 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) - RETURNING id, app_id, domain_name, ssl_status, dns_configured, dns_verified_at, last_dns_check, dns_check_error, created_at, updated_at - ` var d Domain - err := db.QueryRow(query, id, appID, domain).Scan( - &d.ID, &d.AppID, &d.Domain, &d.SslStatus, &d.DnsConfigured, &d.DnsVerifiedAt, &d.LastDnsCheck, &d.DnsCheckError, &d.CreatedAt, &d.UpdatedAt, - ) - if err != nil { - return nil, err + id := utils.GenerateRandomId() + d.ID = id + d.AppID = appID + d.Domain = domain + result := db.Create(&d) + if result.Error != nil { + return nil, result.Error } return &d, nil } - func GetDomainsByAppID(appID int64) ([]Domain, error) { - query := ` - SELECT id, app_id, domain_name, ssl_status, dns_configured, dns_verified_at, last_dns_check, dns_check_error, created_at, updated_at - FROM domains - WHERE app_id = ? - ORDER BY created_at ASC - ` - rows, err := db.Query(query, appID) - if err != nil { - return nil, err - } - defer rows.Close() - var domains []Domain - for rows.Next() { - var d Domain - err := rows.Scan(&d.ID, &d.AppID, &d.Domain, &d.SslStatus, &d.DnsConfigured, &d.DnsVerifiedAt, &d.LastDnsCheck, &d.DnsCheckError, &d.CreatedAt, &d.UpdatedAt) - if err != nil { - return nil, err - } - domains = append(domains, d) + result := db.Where("app_id = ?", appID).Order("created_at ASC").Find(&domains) + if result.Error != nil { + return nil, result.Error } return domains, nil } func GetPrimaryDomainByAppID(appID int64) (*Domain, error) { - query := ` - SELECT id, app_id, domain_name, ssl_status, dns_configured, dns_verified_at, last_dns_check, dns_check_error, created_at, updated_at - FROM domains - WHERE app_id = ? - ORDER BY created_at ASC - LIMIT 1 - ` var d Domain - err := db.QueryRow(query, appID).Scan(&d.ID, &d.AppID, &d.Domain, &d.SslStatus, &d.DnsConfigured, &d.DnsVerifiedAt, &d.LastDnsCheck, &d.DnsCheckError, &d.CreatedAt, &d.UpdatedAt) - if err != nil { - return nil, err + result := db.Where("app_id = ?", appID).Order("created_at ASC").First(&d) + if result.Error != nil { + return nil, result.Error } return &d, nil } func UpdateDomain(id int64, domain string) error { - query := ` - UPDATE domains - SET domain_name = ?, updated_at = CURRENT_TIMESTAMP - WHERE id = ? - ` - _, err := db.Exec(query, domain, id) - return err + result := db.Model(&Domain{}).Where("id=?", id).Update("domain_name", domain) + return result.Error } func DeleteDomain(id int64) error { - query := `DELETE FROM domains WHERE id = ?` - _, err := db.Exec(query, id) - return err + result := db.Delete(&Domain{}, id) + return result.Error } func GetDomainByID(id int64) (*Domain, error) { - query := ` - SELECT id, app_id, domain_name, ssl_status, dns_configured, dns_verified_at, last_dns_check, dns_check_error, created_at, updated_at - FROM domains - WHERE id = ? - ` var d Domain - err := db.QueryRow(query, id).Scan(&d.ID, &d.AppID, &d.Domain, &d.SslStatus, &d.DnsConfigured, &d.DnsVerifiedAt, &d.LastDnsCheck, &d.DnsCheckError, &d.CreatedAt, &d.UpdatedAt) - if err != nil { - return nil, err + result := db.First(&d, id) + if result.Error != nil { + return nil, result.Error } return &d, nil } func UpdateDomainDnsStatus(id int64, configured bool, errorMsg *string) error { - var query string - var err error + updates := map[string]interface{}{ + "last_dns_check": time.Now(), + } if configured { - query = ` - UPDATE domains - SET dns_configured = 1, - dns_verified_at = CURRENT_TIMESTAMP, - last_dns_check = CURRENT_TIMESTAMP, - dns_check_error = NULL, - updated_at = CURRENT_TIMESTAMP - WHERE id = ? - ` - _, err = db.Exec(query, id) + updates["dns_configured"] = true + updates["dns_verified_at"] = time.Now() + updates["dns_check_error"] = nil } else { - query = ` - UPDATE domains - SET dns_configured = 0, - last_dns_check = CURRENT_TIMESTAMP, - dns_check_error = ?, - updated_at = CURRENT_TIMESTAMP - WHERE id = ? - ` - _, err = db.Exec(query, errorMsg, id) + updates["dns_configured"] = false + updates["dns_check_error"] = errorMsg } - return err + return db.Model(&Domain{ID: id}).Updates(updates).Error } + +//############################################################################################################################################# +//ARCHIVED CODE BELOW-------> + +// package models + +// import ( +// "time" + +// "github.com/corecollectives/mist/utils" +// ) + +// type Domain struct { +// ID int64 `json:"id"` +// AppID int64 `json:"appId"` +// Domain string `json:"domain"` +// SslStatus string `json:"sslStatus"` +// DnsConfigured bool `json:"dnsConfigured"` +// DnsVerifiedAt *time.Time `json:"dnsVerifiedAt"` +// LastDnsCheck *time.Time `json:"lastDnsCheck"` +// DnsCheckError *string `json:"dnsCheckError"` +// CreatedAt time.Time `json:"createdAt"` +// UpdatedAt time.Time `json:"updatedAt"` +// } + +// func CreateDomain(appID int64, domain string) (*Domain, error) { +// id := utils.GenerateRandomId() +// query := ` +// INSERT INTO domains (id, app_id, domain_name, ssl_status, dns_configured, created_at, updated_at) +// VALUES (?, ?, ?, 'pending', 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) +// RETURNING id, app_id, domain_name, ssl_status, dns_configured, dns_verified_at, last_dns_check, dns_check_error, created_at, updated_at +// ` +// var d Domain +// err := db.QueryRow(query, id, appID, domain).Scan( +// &d.ID, &d.AppID, &d.Domain, &d.SslStatus, &d.DnsConfigured, &d.DnsVerifiedAt, &d.LastDnsCheck, &d.DnsCheckError, &d.CreatedAt, &d.UpdatedAt, +// ) +// if err != nil { +// return nil, err +// } +// return &d, nil +// } + +// func GetDomainsByAppID(appID int64) ([]Domain, error) { +// query := ` +// SELECT id, app_id, domain_name, ssl_status, dns_configured, dns_verified_at, last_dns_check, dns_check_error, created_at, updated_at +// FROM domains +// WHERE app_id = ? +// ORDER BY created_at ASC +// ` +// rows, err := db.Query(query, appID) +// if err != nil { +// return nil, err +// } +// defer rows.Close() + +// var domains []Domain +// for rows.Next() { +// var d Domain +// err := rows.Scan(&d.ID, &d.AppID, &d.Domain, &d.SslStatus, &d.DnsConfigured, &d.DnsVerifiedAt, &d.LastDnsCheck, &d.DnsCheckError, &d.CreatedAt, &d.UpdatedAt) +// if err != nil { +// return nil, err +// } +// domains = append(domains, d) +// } +// return domains, nil +// } + +// func GetPrimaryDomainByAppID(appID int64) (*Domain, error) { +// query := ` +// SELECT id, app_id, domain_name, ssl_status, dns_configured, dns_verified_at, last_dns_check, dns_check_error, created_at, updated_at +// FROM domains +// WHERE app_id = ? +// ORDER BY created_at ASC +// LIMIT 1 +// ` +// var d Domain +// err := db.QueryRow(query, appID).Scan(&d.ID, &d.AppID, &d.Domain, &d.SslStatus, &d.DnsConfigured, &d.DnsVerifiedAt, &d.LastDnsCheck, &d.DnsCheckError, &d.CreatedAt, &d.UpdatedAt) +// if err != nil { +// return nil, err +// } +// return &d, nil +// } + +// func UpdateDomain(id int64, domain string) error { +// query := ` +// UPDATE domains +// SET domain_name = ?, updated_at = CURRENT_TIMESTAMP +// WHERE id = ? +// ` +// _, err := db.Exec(query, domain, id) +// return err +// } + +// func DeleteDomain(id int64) error { +// query := `DELETE FROM domains WHERE id = ?` +// _, err := db.Exec(query, id) +// return err +// } + +// func GetDomainByID(id int64) (*Domain, error) { +// query := ` +// SELECT id, app_id, domain_name, ssl_status, dns_configured, dns_verified_at, last_dns_check, dns_check_error, created_at, updated_at +// FROM domains +// WHERE id = ? +// ` +// var d Domain +// err := db.QueryRow(query, id).Scan(&d.ID, &d.AppID, &d.Domain, &d.SslStatus, &d.DnsConfigured, &d.DnsVerifiedAt, &d.LastDnsCheck, &d.DnsCheckError, &d.CreatedAt, &d.UpdatedAt) +// if err != nil { +// return nil, err +// } +// return &d, nil +// } + +// func UpdateDomainDnsStatus(id int64, configured bool, errorMsg *string) error { +// var query string +// var err error + +// if configured { +// query = ` +// UPDATE domains +// SET dns_configured = 1, +// dns_verified_at = CURRENT_TIMESTAMP, +// last_dns_check = CURRENT_TIMESTAMP, +// dns_check_error = NULL, +// updated_at = CURRENT_TIMESTAMP +// WHERE id = ? +// ` +// _, err = db.Exec(query, id) +// } else { +// query = ` +// UPDATE domains +// SET dns_configured = 0, +// last_dns_check = CURRENT_TIMESTAMP, +// dns_check_error = ?, +// updated_at = CURRENT_TIMESTAMP +// WHERE id = ? +// ` +// _, err = db.Exec(query, errorMsg, id) +// } + +// return err +// } From 7e3da066774a8463229106e6afc08652d13bced6 Mon Sep 17 00:00:00 2001 From: Tanish Date: Tue, 13 Jan 2026 22:33:34 +0530 Subject: [PATCH 07/16] gormified envVariable table --- server/models/envVariable.go | 188 ++++++++++++++++++++++++----------- 1 file changed, 130 insertions(+), 58 deletions(-) diff --git a/server/models/envVariable.go b/server/models/envVariable.go index 20f33c3..c37bafa 100644 --- a/server/models/envVariable.go +++ b/server/models/envVariable.go @@ -7,82 +7,154 @@ import ( ) type EnvVariable struct { - ID int64 `json:"id"` - AppID int64 `json:"appId"` - Key string `json:"key"` - Value string `json:"value"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID int64 `gorm:"primaryKey;autoIncrement:false" json:"id"` + + AppID int64 `gorm:"uniqueIndex:idx_app_key;index;not null;constraint:OnDelete:CASCADE" json:"appId"` + + Key string `gorm:"uniqueIndex:idx_app_key;not null" json:"key"` + + Value string `gorm:"not null" json:"value"` + + IsSecret bool `gorm:"default:false" json:"isSecret,omitempty"` + + CreatedAt time.Time `gorm:"autoCreateTime" json:"createdAt"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updatedAt"` +} + +func (EnvVariable) TableName() string { + return "envs" } func CreateEnvVariable(appID int64, key, value string) (*EnvVariable, error) { - id := utils.GenerateRandomId() - query := ` - INSERT INTO envs (id, app_id, key, value, created_at, updated_at) - VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) - RETURNING id, app_id, key, value, created_at, updated_at - ` - var env EnvVariable - err := db.QueryRow(query, id, appID, key, value).Scan( - &env.ID, &env.AppID, &env.Key, &env.Value, &env.CreatedAt, &env.UpdatedAt, - ) - if err != nil { - return nil, err + env := &EnvVariable{ + ID: utils.GenerateRandomId(), + AppID: appID, + Key: key, + Value: value, } - return &env, nil -} -func GetEnvVariablesByAppID(appID int64) ([]EnvVariable, error) { - query := ` - SELECT id, app_id, key, value, created_at, updated_at - FROM envs - WHERE app_id = ? - ORDER BY key ASC - ` - rows, err := db.Query(query, appID) - if err != nil { - return nil, err + result := db.Create(env) + if result.Error != nil { + return nil, result.Error } - defer rows.Close() + return env, nil +} +func GetEnvVariablesByAppID(appID int64) ([]EnvVariable, error) { var envs []EnvVariable - for rows.Next() { - var env EnvVariable - err := rows.Scan(&env.ID, &env.AppID, &env.Key, &env.Value, &env.CreatedAt, &env.UpdatedAt) - if err != nil { - return nil, err - } - envs = append(envs, env) - } - return envs, nil + result := db.Where("app_id = ?", appID).Order("key ASC").Find(&envs) + return envs, result.Error } func UpdateEnvVariable(id int64, key, value string) error { - query := ` - UPDATE envs - SET key = ?, value = ?, updated_at = CURRENT_TIMESTAMP - WHERE id = ? - ` - _, err := db.Exec(query, key, value, id) - return err + updates := map[string]interface{}{ + "key": key, + "value": value, + } + return db.Model(&EnvVariable{ID: id}).Updates(updates).Error } func DeleteEnvVariable(id int64) error { - query := `DELETE FROM envs WHERE id = ?` - _, err := db.Exec(query, id) - return err + return db.Delete(&EnvVariable{}, id).Error } func GetEnvVariableByID(id int64) (*EnvVariable, error) { - query := ` - SELECT id, app_id, key, value, created_at, updated_at - FROM envs - WHERE id = ? - ` var env EnvVariable - err := db.QueryRow(query, id).Scan(&env.ID, &env.AppID, &env.Key, &env.Value, &env.CreatedAt, &env.UpdatedAt) - if err != nil { - return nil, err + result := db.First(&env, id) + if result.Error != nil { + return nil, result.Error } return &env, nil } + +//########################################################################################################## +//ARCHIVED CODE BELOW + +// package models + +// import ( +// "time" + +// "github.com/corecollectives/mist/utils" +// ) + +// type EnvVariable struct { +// ID int64 `json:"id"` +// AppID int64 `json:"appId"` +// Key string `json:"key"` +// Value string `json:"value"` +// CreatedAt time.Time `json:"createdAt"` +// UpdatedAt time.Time `json:"updatedAt"` +// } + +// func CreateEnvVariable(appID int64, key, value string) (*EnvVariable, error) { +// id := utils.GenerateRandomId() +// query := ` +// INSERT INTO envs (id, app_id, key, value, created_at, updated_at) +// VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) +// RETURNING id, app_id, key, value, created_at, updated_at +// ` +// var env EnvVariable +// err := db.QueryRow(query, id, appID, key, value).Scan( +// &env.ID, &env.AppID, &env.Key, &env.Value, &env.CreatedAt, &env.UpdatedAt, +// ) +// if err != nil { +// return nil, err +// } +// return &env, nil +// } + +// func GetEnvVariablesByAppID(appID int64) ([]EnvVariable, error) { +// query := ` +// SELECT id, app_id, key, value, created_at, updated_at +// FROM envs +// WHERE app_id = ? +// ORDER BY key ASC +// ` +// rows, err := db.Query(query, appID) +// if err != nil { +// return nil, err +// } +// defer rows.Close() + +// var envs []EnvVariable +// for rows.Next() { +// var env EnvVariable +// err := rows.Scan(&env.ID, &env.AppID, &env.Key, &env.Value, &env.CreatedAt, &env.UpdatedAt) +// if err != nil { +// return nil, err +// } +// envs = append(envs, env) +// } +// return envs, nil +// } + +// func UpdateEnvVariable(id int64, key, value string) error { +// query := ` +// UPDATE envs +// SET key = ?, value = ?, updated_at = CURRENT_TIMESTAMP +// WHERE id = ? +// ` +// _, err := db.Exec(query, key, value, id) +// return err +// } + +// func DeleteEnvVariable(id int64) error { +// query := `DELETE FROM envs WHERE id = ?` +// _, err := db.Exec(query, id) +// return err +// } + +// func GetEnvVariableByID(id int64) (*EnvVariable, error) { +// query := ` +// SELECT id, app_id, key, value, created_at, updated_at +// FROM envs +// WHERE id = ? +// ` +// var env EnvVariable +// err := db.QueryRow(query, id).Scan(&env.ID, &env.AppID, &env.Key, &env.Value, &env.CreatedAt, &env.UpdatedAt) +// if err != nil { +// return nil, err +// } +// return &env, nil +// } From ccaee7b9f83c5ea19c736ba6946300a03903c788 Mon Sep 17 00:00:00 2001 From: Tanish Date: Tue, 13 Jan 2026 23:20:50 +0530 Subject: [PATCH 08/16] gormified githubApps --- server/models/github.go | 395 ++++++++++++++++++++++++++++------------ 1 file changed, 279 insertions(+), 116 deletions(-) diff --git a/server/models/github.go b/server/models/github.go index d857c02..848db59 100644 --- a/server/models/github.go +++ b/server/models/github.go @@ -1,33 +1,53 @@ package models import ( - "database/sql" + "fmt" "time" + + "gorm.io/gorm" + "gorm.io/gorm/clause" ) type GithubApp struct { - ID int64 `json:"id"` - AppID int64 `json:"appId"` - Name *string `json:"name"` - Slug string `json:"slug"` - ClientID string `json:"clientId"` - ClientSecret string `json:"clientSecret"` - WebhookSecret string `json:"webhookSecret"` - PrivateKey string `json:"privateKey"` - CreatedAt time.Time `json:"createdAt"` + ID int64 `gorm:"primaryKey;autoIncrement:true" json:"id"` + + AppID int64 `gorm:"not null" json:"appId"` + ClientID string `gorm:"not null" json:"clientId"` + ClientSecret string `gorm:"not null" json:"clientSecret"` + WebhookSecret string `gorm:"not null" json:"webhookSecret"` + PrivateKey string `gorm:"not null" json:"privateKey"` + Name *string `json:"name"` + Slug string `gorm:"not null" json:"slug"` + + CreatedAt time.Time `gorm:"autoCreateTime" json:"createdAt"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updatedAt"` +} + +func (GithubApp) TableName() string { + return "github_app" +} + +type GithubInstallation struct { + InstallationID int64 `gorm:"primaryKey;autoIncrement:false" json:"installation_id"` + + AccountLogin string `json:"account_login"` + AccountType string `json:"account_type"` + AccessToken string `json:"access_token"` + TokenExpiresAt time.Time `json:"token_expires_at"` + + UserID int `gorm:"index" json:"user_id"` + + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` } func (app *GithubApp) InsertInDB() error { - _, err := db.Exec(` - INSERT INTO github_app (app_id, client_id, client_secret, webhook_secret, private_key, name, slug) - VALUES (?, ?, ?, ?, ?, ?, ?) - `, app.AppID, app.ClientID, app.ClientSecret, app.WebhookSecret, app.PrivateKey, app.Name, app.Slug) - return err + return db.Create(app).Error } func CheckIfAppExists() (bool, error) { - var count int - err := db.QueryRow(`SELECT COUNT(*) FROM github_app`).Scan(&count) + var count int64 + err := db.Model(&GithubApp{}).Count(&count).Error if err != nil { return false, err } @@ -35,92 +55,45 @@ func CheckIfAppExists() (bool, error) { } func GetApp(userID int) (GithubApp, bool, error) { - query := ` - SELECT - a.id, - a.name, - a.app_id, - a.client_id, - a.slug, - a.created_at, - CASE WHEN i.installation_id IS NOT NULL THEN 1 ELSE 0 END AS is_installed - FROM github_app a - LEFT JOIN github_installations i ON i.user_id = ? - WHERE a.id = 1 - ` - - row := db.QueryRow(query, userID) - var app GithubApp - var isInstalled bool - - err := row.Scan( - &app.ID, - &app.Name, - &app.AppID, - &app.ClientID, - &app.Slug, - &app.CreatedAt, - &isInstalled, - ) + err := db.First(&app, 1).Error if err != nil { - if err == sql.ErrNoRows { + if err == gorm.ErrRecordNotFound { return GithubApp{}, false, nil } return GithubApp{}, false, err } - return app, isInstalled, nil -} + var count int64 + err = db.Model(&GithubInstallation{}). + Where("user_id = ?", userID). + Count(&count).Error -type GithubInstallation struct { - InstallationID int64 `json:"installation_id"` - AccountLogin string `json:"account_login"` - AccountType string `json:"account_type"` - AccessToken string `json:"access_token"` - TokenExpiresAt time.Time `json:"token_expires_at"` - UserID int `json:"user_id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + isInstalled := count > 0 + return app, isInstalled, err } func (i *GithubInstallation) InsertOrReplace() error { - _, err := db.Exec(` - INSERT OR REPLACE INTO github_installations - (installation_id, account_login, account_type, access_token, token_expires_at, user_id, updated_at) - VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) - `, i.InstallationID, i.AccountLogin, i.AccountType, i.AccessToken, i.TokenExpiresAt, i.UserID) - return err + return db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "installation_id"}}, + UpdateAll: true, + }).Create(i).Error } func GetInstallationID(userID int) (int64, error) { - var installationID int64 - err := db.QueryRow(` - SELECT installation_id FROM github_installations - WHERE user_id = ? - `, userID).Scan(&installationID) + var inst GithubInstallation + err := db.Select("installation_id"). + Where("user_id = ?", userID). + First(&inst).Error if err != nil { return 0, err } - return installationID, nil + return inst.InstallationID, nil } func GetInstallationByUserID(userID int) (GithubInstallation, error) { var inst GithubInstallation - err := db.QueryRow(` - SELECT installation_id, account_login, account_type, access_token, token_expires_at, user_id, created_at, updated_at - FROM github_installations - WHERE user_id = ? - `, userID).Scan( - &inst.InstallationID, - &inst.AccountLogin, - &inst.AccountType, - &inst.AccessToken, - &inst.TokenExpiresAt, - &inst.UserID, - &inst.CreatedAt, - &inst.UpdatedAt, - ) + err := db.Where("user_id = ?", userID).First(&inst).Error if err != nil { return GithubInstallation{}, err } @@ -128,63 +101,253 @@ func GetInstallationByUserID(userID int) (GithubInstallation, error) { } func GetInstallationToken(installationID int64) (string, string, int, error) { - var ( - token string - tokenExpires string - appID int - ) - err := db.QueryRow(` - SELECT i.access_token, i.token_expires_at, a.app_id - FROM github_installations i - JOIN github_app a ON a.id = 1 - WHERE i.installation_id = ? - `, installationID).Scan(&token, &tokenExpires, &appID) + type Result struct { + AccessToken string + TokenExpiresAt time.Time + AppID int + } + + var res Result + err := db.Table("github_installations as i"). + Select("i.access_token, i.token_expires_at, a.app_id"). + Joins("JOIN github_app a ON a.id = 1"). + Where("i.installation_id = ?", installationID). + Scan(&res).Error + if err != nil { return "", "", 0, err } - return token, tokenExpires, appID, nil + + return res.AccessToken, res.TokenExpiresAt.Format(time.RFC3339), res.AppID, nil } func UpdateInstallationToken(installationID int64, token string, newExpiry time.Time) error { - _, err := db.Exec(` - UPDATE github_installations - SET access_token = ?, token_expires_at = ?, updated_at = CURRENT_TIMESTAMP - WHERE installation_id = ? - `, token, newExpiry.Format(time.RFC3339), installationID) - return err + return db.Model(&GithubInstallation{InstallationID: installationID}). + Updates(map[string]interface{}{ + "access_token": token, + "token_expires_at": newExpiry, + "updated_at": time.Now(), + }).Error } func GetGithubAppCredentials(appID int) (string, string, error) { - var appNumericId string - var appPrivateKeyPEM string - - err := db.QueryRow(` - SELECT app_id, private_key FROM github_app WHERE id = 1 - `).Scan(&appNumericId, &appPrivateKeyPEM) - + var app GithubApp + err := db.Select("app_id, private_key").First(&app, 1).Error if err != nil { return "", "", err } - return appNumericId, appPrivateKeyPEM, nil + return fmt.Sprintf("%d", app.AppID), app.PrivateKey, nil } func GetInstallationIDByUserID(userID int64) (int64, error) { - var installationID int64 - err := db.QueryRow(`SELECT installation_id FROM github_installations WHERE user_id = ?`, userID). - Scan(&installationID) + var inst GithubInstallation + err := db.Select("installation_id").Where("user_id = ?", userID).First(&inst).Error if err != nil { return 0, err } - return installationID, nil + return inst.InstallationID, nil } func GetGithubAppIDAndPrivateKey() (int64, string, error) { - var appAppID int64 - var privateKey string - err := db.QueryRow(`SELECT app_id, private_key FROM github_app LIMIT 1`).Scan(&appAppID, &privateKey) + var app GithubApp + err := db.Select("app_id, private_key").First(&app).Error if err != nil { return 0, "", err } - return appAppID, privateKey, nil + return app.AppID, app.PrivateKey, nil } + +//######################################################################################################################## +//ARCHIVED CODE BELOW--------------------------------> + +// package models + +// import ( +// "database/sql" +// "time" +// ) + +// type GithubApp struct { +// ID int64 `json:"id"` +// AppID int64 `json:"appId"` +// Name *string `json:"name"` +// Slug string `json:"slug"` +// ClientID string `json:"clientId"` +// ClientSecret string `json:"clientSecret"` +// WebhookSecret string `json:"webhookSecret"` +// PrivateKey string `json:"privateKey"` +// CreatedAt time.Time `json:"createdAt"` +// } + +// func (app *GithubApp) InsertInDB() error { +// _, err := db.Exec(` +// INSERT INTO github_app (app_id, client_id, client_secret, webhook_secret, private_key, name, slug) +// VALUES (?, ?, ?, ?, ?, ?, ?) +// `, app.AppID, app.ClientID, app.ClientSecret, app.WebhookSecret, app.PrivateKey, app.Name, app.Slug) +// return err +// } + +// func CheckIfAppExists() (bool, error) { +// var count int +// err := db.QueryRow(`SELECT COUNT(*) FROM github_app`).Scan(&count) +// if err != nil { +// return false, err +// } +// return count > 0, nil +// } + +// func GetApp(userID int) (GithubApp, bool, error) { +// query := ` +// SELECT +// a.id, +// a.name, +// a.app_id, +// a.client_id, +// a.slug, +// a.created_at, +// CASE WHEN i.installation_id IS NOT NULL THEN 1 ELSE 0 END AS is_installed +// FROM github_app a +// LEFT JOIN github_installations i ON i.user_id = ? +// WHERE a.id = 1 +// ` + +// row := db.QueryRow(query, userID) + +// var app GithubApp +// var isInstalled bool + +// err := row.Scan( +// &app.ID, +// &app.Name, +// &app.AppID, +// &app.ClientID, +// &app.Slug, +// &app.CreatedAt, +// &isInstalled, +// ) +// if err != nil { +// if err == sql.ErrNoRows { +// return GithubApp{}, false, nil +// } +// return GithubApp{}, false, err +// } + +// return app, isInstalled, nil +// } + +// type GithubInstallation struct { +// InstallationID int64 `json:"installation_id"` +// AccountLogin string `json:"account_login"` +// AccountType string `json:"account_type"` +// AccessToken string `json:"access_token"` +// TokenExpiresAt time.Time `json:"token_expires_at"` +// UserID int `json:"user_id"` +// CreatedAt time.Time `json:"created_at"` +// UpdatedAt time.Time `json:"updated_at"` +// } + +// func (i *GithubInstallation) InsertOrReplace() error { +// _, err := db.Exec(` +// INSERT OR REPLACE INTO github_installations +// (installation_id, account_login, account_type, access_token, token_expires_at, user_id, updated_at) +// VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) +// `, i.InstallationID, i.AccountLogin, i.AccountType, i.AccessToken, i.TokenExpiresAt, i.UserID) +// return err +// } + +// func GetInstallationID(userID int) (int64, error) { +// var installationID int64 +// err := db.QueryRow(` +// SELECT installation_id FROM github_installations +// WHERE user_id = ? +// `, userID).Scan(&installationID) +// if err != nil { +// return 0, err +// } +// return installationID, nil +// } + +// func GetInstallationByUserID(userID int) (GithubInstallation, error) { +// var inst GithubInstallation +// err := db.QueryRow(` +// SELECT installation_id, account_login, account_type, access_token, token_expires_at, user_id, created_at, updated_at +// FROM github_installations +// WHERE user_id = ? +// `, userID).Scan( +// &inst.InstallationID, +// &inst.AccountLogin, +// &inst.AccountType, +// &inst.AccessToken, +// &inst.TokenExpiresAt, +// &inst.UserID, +// &inst.CreatedAt, +// &inst.UpdatedAt, +// ) +// if err != nil { +// return GithubInstallation{}, err +// } +// return inst, nil +// } + +// func GetInstallationToken(installationID int64) (string, string, int, error) { +// var ( +// token string +// tokenExpires string +// appID int +// ) +// err := db.QueryRow(` +// SELECT i.access_token, i.token_expires_at, a.app_id +// FROM github_installations i +// JOIN github_app a ON a.id = 1 +// WHERE i.installation_id = ? +// `, installationID).Scan(&token, &tokenExpires, &appID) +// if err != nil { +// return "", "", 0, err +// } +// return token, tokenExpires, appID, nil +// } + +// func UpdateInstallationToken(installationID int64, token string, newExpiry time.Time) error { +// _, err := db.Exec(` +// UPDATE github_installations +// SET access_token = ?, token_expires_at = ?, updated_at = CURRENT_TIMESTAMP +// WHERE installation_id = ? +// `, token, newExpiry.Format(time.RFC3339), installationID) +// return err +// } + +// func GetGithubAppCredentials(appID int) (string, string, error) { +// var appNumericId string +// var appPrivateKeyPEM string + +// err := db.QueryRow(` +// SELECT app_id, private_key FROM github_app WHERE id = 1 +// `).Scan(&appNumericId, &appPrivateKeyPEM) + +// if err != nil { +// return "", "", err +// } + +// return appNumericId, appPrivateKeyPEM, nil +// } + +// func GetInstallationIDByUserID(userID int64) (int64, error) { +// var installationID int64 +// err := db.QueryRow(`SELECT installation_id FROM github_installations WHERE user_id = ?`, userID). +// Scan(&installationID) +// if err != nil { +// return 0, err +// } +// return installationID, nil +// } + +// func GetGithubAppIDAndPrivateKey() (int64, string, error) { +// var appAppID int64 +// var privateKey string +// err := db.QueryRow(`SELECT app_id, private_key FROM github_app LIMIT 1`).Scan(&appAppID, &privateKey) +// if err != nil { +// return 0, "", err +// } +// return appAppID, privateKey, nil +// } From 4e5562bc0d7e23562a86d25f6708d7b3d85e646d Mon Sep 17 00:00:00 2001 From: Tanish Date: Wed, 14 Jan 2026 23:30:29 +0530 Subject: [PATCH 09/16] gormified gitProvider logs and notification files --- server/models/gitProvider.go | 459 +++++++++++++++++++++++++--------- server/models/logs.go | 12 +- server/models/notification.go | 330 +++++++++++++++++------- 3 files changed, 585 insertions(+), 216 deletions(-) diff --git a/server/models/gitProvider.go b/server/models/gitProvider.go index fbf05f7..a2db968 100644 --- a/server/models/gitProvider.go +++ b/server/models/gitProvider.go @@ -1,7 +1,6 @@ package models import ( - "database/sql" "encoding/json" "encoding/pem" "fmt" @@ -21,36 +20,23 @@ const ( ) type GitProvider struct { - ID int64 `db:"id" json:"id"` - UserID int64 `db:"user_id" json:"user_id"` - Provider GitProviderType `db:"provider" json:"provider"` - AccessToken string `db:"access_token" json:"access_token"` - RefreshToken *string `db:"refresh_token" json:"refresh_token,omitempty"` - ExpiresAt *time.Time `db:"expires_at" json:"expires_at,omitempty"` - Username *string `db:"username" json:"username,omitempty"` - Email *string `db:"email" json:"email,omitempty"` + ID int64 `gorm:"primaryKey;autoIncrement:true" json:"id"` + UserID int64 `gorm:"uniqueIndex:idx_user_provider;index;not null;constraint:OnDelete:CASCADE" json:"user_id"` + Provider GitProviderType `gorm:"uniqueIndex:idx_user_provider;not null" json:"provider"` + AccessToken string `gorm:"not null" json:"access_token"` + RefreshToken *string `json:"refresh_token,omitempty"` + ExpiresAt *time.Time `json:"expires_at,omitempty"` + Username *string `json:"username,omitempty"` + Email *string `json:"email,omitempty"` } func (gp *GitProvider) InsertInDB() error { - query := ` - INSERT INTO git_providers (user_id, provider, access_token, refresh_token, expires_at, username, email) - VALUES (?, ?, ?, ?, ?, ?, ?) - RETURNING id - ` - err := db.QueryRow(query, gp.UserID, gp.Provider, gp.AccessToken, gp.RefreshToken, gp.ExpiresAt, gp.Username, gp.Email).Scan(&gp.ID) - return err + return db.Create(gp).Error } func GetGitProviderByID(id int64) (*GitProvider, error) { var gp GitProvider - query := ` - SELECT id, user_id, provider, access_token, refresh_token, expires_at, username, email - FROM git_providers - WHERE id = ? - ` - err := db.QueryRow(query, id).Scan( - &gp.ID, &gp.UserID, &gp.Provider, &gp.AccessToken, &gp.RefreshToken, &gp.ExpiresAt, &gp.Username, &gp.Email, - ) + err := db.First(&gp, id).Error if err != nil { return nil, err } @@ -59,14 +45,7 @@ func GetGitProviderByID(id int64) (*GitProvider, error) { func GetGitProviderByUserAndProvider(userID int64, provider GitProviderType) (*GitProvider, error) { var gp GitProvider - query := ` - SELECT id, user_id, provider, access_token, refresh_token, expires_at, username, email - FROM git_providers - WHERE user_id = ? AND provider = ? - ` - err := db.QueryRow(query, userID, provider).Scan( - &gp.ID, &gp.UserID, &gp.Provider, &gp.AccessToken, &gp.RefreshToken, &gp.ExpiresAt, &gp.Username, &gp.Email, - ) + err := db.Where("user_id = ? AND provider = ?", userID, provider).First(&gp).Error if err != nil { return nil, err } @@ -75,113 +54,61 @@ func GetGitProviderByUserAndProvider(userID int64, provider GitProviderType) (*G func GetGitProvidersByUser(userID int64) ([]GitProvider, error) { var providers []GitProvider - query := ` - SELECT id, user_id, provider, access_token, refresh_token, expires_at, username, email - FROM git_providers - WHERE user_id = ? - ` - rows, err := db.Query(query, userID) - if err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - var gp GitProvider - err := rows.Scan( - &gp.ID, &gp.UserID, &gp.Provider, &gp.AccessToken, &gp.RefreshToken, &gp.ExpiresAt, &gp.Username, &gp.Email, - ) - if err != nil { - return nil, err - } - providers = append(providers, gp) - } - return providers, rows.Err() + err := db.Where("user_id = ?", userID).Find(&providers).Error + return providers, err } func (gp *GitProvider) UpdateToken(accessToken string, refreshToken *string, expiresAt *time.Time) error { - query := ` - UPDATE git_providers - SET access_token = ?, refresh_token = ?, expires_at = ? - WHERE id = ? - ` - _, err := db.Exec(query, accessToken, refreshToken, expiresAt, gp.ID) - return err + return db.Model(gp).Updates(map[string]interface{}{ + "access_token": accessToken, + "refresh_token": refreshToken, + "expires_at": expiresAt, + }).Error } func DeleteGitProvider(id int64) error { - query := `DELETE FROM git_providers WHERE id = ?` - _, err := db.Exec(query, id) - return err + return db.Delete(&GitProvider{}, id).Error } func GetGitProviderAccessToken(providerID int64) (string, GitProviderType, bool, error) { - var token string - var provider GitProviderType - var expiresAt sql.NullTime - query := `SELECT access_token, provider, expires_at FROM git_providers WHERE id = ?` - err := db.QueryRow(query, providerID).Scan(&token, &provider, &expiresAt) + var gp GitProvider + err := db.Select("access_token, provider, expires_at").First(&gp, providerID).Error if err != nil { return "", "", false, err } - // check if token is expired needsRefresh := false - if expiresAt.Valid && time.Now().After(expiresAt.Time) { + if gp.ExpiresAt != nil && time.Now().After(*gp.ExpiresAt) { needsRefresh = true } - return token, provider, needsRefresh, nil + return gp.AccessToken, gp.Provider, needsRefresh, nil } func GetAppGitInfo(appID int64) (*int64, *string, string, *string, int64, string, error) { - var gitProviderID sql.NullInt64 - var gitRepository sql.NullString - var gitBranch string - var gitCloneURL sql.NullString - var projectID int64 - var appName string - - query := ` - SELECT git_provider_id, git_repository, COALESCE(git_branch, 'main'), git_clone_url, project_id, name - FROM apps - WHERE id = ? - ` - err := db.QueryRow(query, appID).Scan(&gitProviderID, &gitRepository, &gitBranch, &gitCloneURL, &projectID, &appName) + var app App + err := db.Select("git_provider_id, git_repository, git_branch, git_clone_url, project_id, name"). + First(&app, appID).Error + if err != nil { return nil, nil, "", nil, 0, "", err } - var gitProviderIDPtr *int64 - if gitProviderID.Valid { - gitProviderIDPtr = &gitProviderID.Int64 - } - - var gitRepositoryPtr *string - if gitRepository.Valid { - gitRepositoryPtr = &gitRepository.String - } - - var gitCloneURLPtr *string - if gitCloneURL.Valid { - gitCloneURLPtr = &gitCloneURL.String + gitBranch := app.GitBranch + if gitBranch == "" { + gitBranch = "main" } - return gitProviderIDPtr, gitRepositoryPtr, gitBranch, gitCloneURLPtr, projectID, appName, nil + return app.GitProviderID, app.GitRepository, gitBranch, app.GitCloneURL, app.ProjectID, app.Name, nil } -// this is used for migrating old apps that only have git_repository set func UpdateAppGitCloneURL(appID int64, gitCloneURL string, gitProviderID *int64) error { - query := ` - UPDATE apps - SET git_clone_url = ?, git_provider_id = ? - WHERE id = ? - ` - _, err := db.Exec(query, gitCloneURL, gitProviderID, appID) - return err + return db.Model(&App{ID: appID}).Updates(map[string]interface{}{ + "git_clone_url": gitCloneURL, + "git_provider_id": gitProviderID, + }).Error } -// currently only supports GitHub via GitHub App installations func RefreshGitProviderToken(providerID int64) (string, error) { provider, err := GetGitProviderByID(providerID) if err != nil { @@ -191,7 +118,6 @@ func RefreshGitProviderToken(providerID int64) (string, error) { switch provider.Provider { case GitProviderGitHub: return refreshGitHubToken(provider.UserID) - // will be implemented as we add more git-providers case GitProviderGitLab, GitProviderBitbucket, GitProviderGitea: return "", fmt.Errorf("token refresh not implemented for %s", provider.Provider) default: @@ -210,12 +136,12 @@ func refreshGitHubToken(userID int64) (string, error) { return "", fmt.Errorf("failed to get GitHub App credentials: %w", err) } - jwt, err := generateGithubJwt(appID, privateKey) + jwtToken, err := generateGithubJwt(appID, privateKey) if err != nil { return "", fmt.Errorf("failed to generate GitHub JWT: %w", err) } - newToken, newExpiry, err := regenerateGithubInstallationToken(jwt, installation.InstallationID) + newToken, newExpiry, err := regenerateGithubInstallationToken(jwtToken, installation.InstallationID) if err != nil { return "", fmt.Errorf("failed to regenerate installation token: %w", err) } @@ -225,10 +151,9 @@ func refreshGitHubToken(userID int64) (string, error) { return "", fmt.Errorf("failed to update installation token: %w", err) } - // update the git_provider token - err = (&GitProvider{ID: installation.InstallationID}).UpdateToken(newToken, nil, &newExpiry) - if err != nil { - return newToken, nil + var gp GitProvider + if err := db.Where("user_id = ? AND provider = ?", userID, GitProviderGitHub).First(&gp).Error; err == nil { + _ = gp.UpdateToken(newToken, nil, &newExpiry) } return newToken, nil @@ -251,12 +176,7 @@ func generateGithubJwt(appID int64, privateKeyPEM string) (string, error) { "iss": fmt.Sprintf("%d", appID), }) - signedToken, err := token.SignedString(privateKey) - if err != nil { - return "", err - } - - return signedToken, nil + return token.SignedString(privateKey) } func regenerateGithubInstallationToken(appJWT string, installationID int64) (string, time.Time, error) { @@ -266,6 +186,7 @@ func regenerateGithubInstallationToken(appJWT string, installationID int64) (str if err != nil { return "", time.Time{}, err } + req.Header.Set("Authorization", "Bearer "+appJWT) req.Header.Set("Accept", "application/vnd.github+json") @@ -290,3 +211,299 @@ func regenerateGithubInstallationToken(appJWT string, installationID int64) (str return tokenResp.Token, tokenResp.ExpiresAt, nil } + +//######################################################################################################## +//ARCHIVED CODE BELOW + +// package models + +// import ( +// "database/sql" +// "encoding/json" +// "encoding/pem" +// "fmt" +// "net/http" +// "time" + +// "github.com/golang-jwt/jwt" +// ) + +// type GitProviderType string + +// const ( +// GitProviderGitHub GitProviderType = "github" +// GitProviderGitLab GitProviderType = "gitlab" +// GitProviderBitbucket GitProviderType = "bitbucket" +// GitProviderGitea GitProviderType = "gitea" +// ) + +// type GitProvider struct { +// ID int64 `db:"id" json:"id"` +// UserID int64 `db:"user_id" json:"user_id"` +// Provider GitProviderType `db:"provider" json:"provider"` +// AccessToken string `db:"access_token" json:"access_token"` +// RefreshToken *string `db:"refresh_token" json:"refresh_token,omitempty"` +// ExpiresAt *time.Time `db:"expires_at" json:"expires_at,omitempty"` +// Username *string `db:"username" json:"username,omitempty"` +// Email *string `db:"email" json:"email,omitempty"` +// } + +// func (gp *GitProvider) InsertInDB() error { +// query := ` +// INSERT INTO git_providers (user_id, provider, access_token, refresh_token, expires_at, username, email) +// VALUES (?, ?, ?, ?, ?, ?, ?) +// RETURNING id +// ` +// err := db.QueryRow(query, gp.UserID, gp.Provider, gp.AccessToken, gp.RefreshToken, gp.ExpiresAt, gp.Username, gp.Email).Scan(&gp.ID) +// return err +// } + +// func GetGitProviderByID(id int64) (*GitProvider, error) { +// var gp GitProvider +// query := ` +// SELECT id, user_id, provider, access_token, refresh_token, expires_at, username, email +// FROM git_providers +// WHERE id = ? +// ` +// err := db.QueryRow(query, id).Scan( +// &gp.ID, &gp.UserID, &gp.Provider, &gp.AccessToken, &gp.RefreshToken, &gp.ExpiresAt, &gp.Username, &gp.Email, +// ) +// if err != nil { +// return nil, err +// } +// return &gp, nil +// } + +// func GetGitProviderByUserAndProvider(userID int64, provider GitProviderType) (*GitProvider, error) { +// var gp GitProvider +// query := ` +// SELECT id, user_id, provider, access_token, refresh_token, expires_at, username, email +// FROM git_providers +// WHERE user_id = ? AND provider = ? +// ` +// err := db.QueryRow(query, userID, provider).Scan( +// &gp.ID, &gp.UserID, &gp.Provider, &gp.AccessToken, &gp.RefreshToken, &gp.ExpiresAt, &gp.Username, &gp.Email, +// ) +// if err != nil { +// return nil, err +// } +// return &gp, nil +// } + +// func GetGitProvidersByUser(userID int64) ([]GitProvider, error) { +// var providers []GitProvider +// query := ` +// SELECT id, user_id, provider, access_token, refresh_token, expires_at, username, email +// FROM git_providers +// WHERE user_id = ? +// ` +// rows, err := db.Query(query, userID) +// if err != nil { +// return nil, err +// } +// defer rows.Close() + +// for rows.Next() { +// var gp GitProvider +// err := rows.Scan( +// &gp.ID, &gp.UserID, &gp.Provider, &gp.AccessToken, &gp.RefreshToken, &gp.ExpiresAt, &gp.Username, &gp.Email, +// ) +// if err != nil { +// return nil, err +// } +// providers = append(providers, gp) +// } +// return providers, rows.Err() +// } + +// func (gp *GitProvider) UpdateToken(accessToken string, refreshToken *string, expiresAt *time.Time) error { +// query := ` +// UPDATE git_providers +// SET access_token = ?, refresh_token = ?, expires_at = ? +// WHERE id = ? +// ` +// _, err := db.Exec(query, accessToken, refreshToken, expiresAt, gp.ID) +// return err +// } + +// func DeleteGitProvider(id int64) error { +// query := `DELETE FROM git_providers WHERE id = ?` +// _, err := db.Exec(query, id) +// return err +// } + +// func GetGitProviderAccessToken(providerID int64) (string, GitProviderType, bool, error) { +// var token string +// var provider GitProviderType +// var expiresAt sql.NullTime +// query := `SELECT access_token, provider, expires_at FROM git_providers WHERE id = ?` +// err := db.QueryRow(query, providerID).Scan(&token, &provider, &expiresAt) +// if err != nil { +// return "", "", false, err +// } + +// // check if token is expired +// needsRefresh := false +// if expiresAt.Valid && time.Now().After(expiresAt.Time) { +// needsRefresh = true +// } + +// return token, provider, needsRefresh, nil +// } + +// func GetAppGitInfo(appID int64) (*int64, *string, string, *string, int64, string, error) { +// var gitProviderID sql.NullInt64 +// var gitRepository sql.NullString +// var gitBranch string +// var gitCloneURL sql.NullString +// var projectID int64 +// var appName string + +// query := ` +// SELECT git_provider_id, git_repository, COALESCE(git_branch, 'main'), git_clone_url, project_id, name +// FROM apps +// WHERE id = ? +// ` +// err := db.QueryRow(query, appID).Scan(&gitProviderID, &gitRepository, &gitBranch, &gitCloneURL, &projectID, &appName) +// if err != nil { +// return nil, nil, "", nil, 0, "", err +// } + +// var gitProviderIDPtr *int64 +// if gitProviderID.Valid { +// gitProviderIDPtr = &gitProviderID.Int64 +// } + +// var gitRepositoryPtr *string +// if gitRepository.Valid { +// gitRepositoryPtr = &gitRepository.String +// } + +// var gitCloneURLPtr *string +// if gitCloneURL.Valid { +// gitCloneURLPtr = &gitCloneURL.String +// } + +// return gitProviderIDPtr, gitRepositoryPtr, gitBranch, gitCloneURLPtr, projectID, appName, nil +// } + +// // this is used for migrating old apps that only have git_repository set +// func UpdateAppGitCloneURL(appID int64, gitCloneURL string, gitProviderID *int64) error { +// query := ` +// UPDATE apps +// SET git_clone_url = ?, git_provider_id = ? +// WHERE id = ? +// ` +// _, err := db.Exec(query, gitCloneURL, gitProviderID, appID) +// return err +// } + +// // currently only supports GitHub via GitHub App installations +// func RefreshGitProviderToken(providerID int64) (string, error) { +// provider, err := GetGitProviderByID(providerID) +// if err != nil { +// return "", err +// } + +// switch provider.Provider { +// case GitProviderGitHub: +// return refreshGitHubToken(provider.UserID) +// // will be implemented as we add more git-providers +// case GitProviderGitLab, GitProviderBitbucket, GitProviderGitea: +// return "", fmt.Errorf("token refresh not implemented for %s", provider.Provider) +// default: +// return "", fmt.Errorf("unknown provider type: %s", provider.Provider) +// } +// } + +// func refreshGitHubToken(userID int64) (string, error) { +// installation, err := GetInstallationByUserID(int(userID)) +// if err != nil { +// return "", fmt.Errorf("failed to get GitHub installation: %w", err) +// } + +// appID, privateKey, err := GetGithubAppIDAndPrivateKey() +// if err != nil { +// return "", fmt.Errorf("failed to get GitHub App credentials: %w", err) +// } + +// jwt, err := generateGithubJwt(appID, privateKey) +// if err != nil { +// return "", fmt.Errorf("failed to generate GitHub JWT: %w", err) +// } + +// newToken, newExpiry, err := regenerateGithubInstallationToken(jwt, installation.InstallationID) +// if err != nil { +// return "", fmt.Errorf("failed to regenerate installation token: %w", err) +// } + +// err = UpdateInstallationToken(installation.InstallationID, newToken, newExpiry) +// if err != nil { +// return "", fmt.Errorf("failed to update installation token: %w", err) +// } + +// // update the git_provider token +// err = (&GitProvider{ID: installation.InstallationID}).UpdateToken(newToken, nil, &newExpiry) +// if err != nil { +// return newToken, nil +// } + +// return newToken, nil +// } + +// func generateGithubJwt(appID int64, privateKeyPEM string) (string, error) { +// block, _ := pem.Decode([]byte(privateKeyPEM)) +// if block == nil { +// return "", fmt.Errorf("failed to decode PEM block") +// } + +// privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(privateKeyPEM)) +// if err != nil { +// return "", err +// } + +// token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ +// "iat": time.Now().Unix(), +// "exp": time.Now().Add(10 * time.Minute).Unix(), +// "iss": fmt.Sprintf("%d", appID), +// }) + +// signedToken, err := token.SignedString(privateKey) +// if err != nil { +// return "", err +// } + +// return signedToken, nil +// } + +// func regenerateGithubInstallationToken(appJWT string, installationID int64) (string, time.Time, error) { +// url := fmt.Sprintf("https://api.github.com/app/installations/%d/access_tokens", installationID) + +// req, err := http.NewRequest("POST", url, nil) +// if err != nil { +// return "", time.Time{}, err +// } +// req.Header.Set("Authorization", "Bearer "+appJWT) +// req.Header.Set("Accept", "application/vnd.github+json") + +// resp, err := http.DefaultClient.Do(req) +// if err != nil { +// return "", time.Time{}, err +// } +// defer resp.Body.Close() + +// if resp.StatusCode != http.StatusCreated { +// return "", time.Time{}, fmt.Errorf("failed to create token, status %d", resp.StatusCode) +// } + +// var tokenResp struct { +// Token string `json:"token"` +// ExpiresAt time.Time `json:"expires_at"` +// } + +// if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil { +// return "", time.Time{}, err +// } + +// return tokenResp.Token, tokenResp.ExpiresAt, nil +// } diff --git a/server/models/logs.go b/server/models/logs.go index 87ecc1d..662572d 100644 --- a/server/models/logs.go +++ b/server/models/logs.go @@ -19,10 +19,10 @@ const ( ) type Logs struct { - ID int64 - Source LogSource - SourceID *int64 - Message string - Level LogLevel - CreatedAt time.Time + ID int64 `gorm:"primaryKey;autoIncrement:true"` + Source LogSource `gorm:"not null"` + SourceID *int64 `gorm:"index"` + Message string `gorm:"not null"` + Level LogLevel `gorm:"not null;default:'info'"` + CreatedAt time.Time `gorm:"autoCreateTime"` } diff --git a/server/models/notification.go b/server/models/notification.go index a0b5775..6a234d4 100644 --- a/server/models/notification.go +++ b/server/models/notification.go @@ -36,28 +36,39 @@ const ( ) type Notification struct { - ID int64 `db:"id" json:"id"` - UserID *int64 `db:"user_id" json:"userId,omitempty"` - Type NotificationType `db:"type" json:"type"` - Title string `db:"title" json:"title"` - Message string `db:"message" json:"message"` - Link *string `db:"link" json:"link,omitempty"` - ResourceType *string `db:"resource_type" json:"resourceType,omitempty"` - ResourceID *int64 `db:"resource_id" json:"resourceId,omitempty"` - EmailSent bool `db:"email_sent" json:"emailSent"` - EmailSentAt *time.Time `db:"email_sent_at" json:"emailSentAt,omitempty"` - SlackSent bool `db:"slack_sent" json:"slackSent"` - SlackSentAt *time.Time `db:"slack_sent_at" json:"slackSentAt,omitempty"` - DiscordSent bool `db:"discord_sent" json:"discordSent"` - DiscordSentAt *time.Time `db:"discord_sent_at" json:"discordSentAt,omitempty"` - WebhookSent bool `db:"webhook_sent" json:"webhookSent"` - WebhookSentAt *time.Time `db:"webhook_sent_at" json:"webhookSentAt,omitempty"` - IsRead bool `db:"is_read" json:"isRead"` - ReadAt *time.Time `db:"read_at" json:"readAt,omitempty"` - Priority NotificationPriority `db:"priority" json:"priority"` - Metadata *string `db:"metadata" json:"metadata,omitempty"` // JSON - CreatedAt time.Time `db:"created_at" json:"createdAt"` - ExpiresAt *time.Time `db:"expires_at" json:"expiresAt,omitempty"` + ID int64 `gorm:"primaryKey;autoIncrement:false" json:"id"` + + UserID *int64 `gorm:"index;constraint:OnDelete:CASCADE" json:"userId,omitempty"` + + Type NotificationType `gorm:"index;not null" json:"type"` + Title string `gorm:"not null" json:"title"` + Message string `gorm:"not null" json:"message"` + + Link *string `json:"link,omitempty"` + ResourceType *string `json:"resourceType,omitempty"` + ResourceID *int64 `json:"resourceId,omitempty"` + + EmailSent bool `gorm:"default:false" json:"emailSent"` + EmailSentAt *time.Time `json:"emailSentAt,omitempty"` + + SlackSent bool `gorm:"default:false" json:"slackSent"` + SlackSentAt *time.Time `json:"slackSentAt,omitempty"` + + DiscordSent bool `gorm:"default:false" json:"discordSent"` + DiscordSentAt *time.Time `json:"discordSentAt,omitempty"` + + WebhookSent bool `gorm:"default:false" json:"webhookSent"` + WebhookSentAt *time.Time `json:"webhookSentAt,omitempty"` + + IsRead bool `gorm:"default:false;index" json:"isRead"` + ReadAt *time.Time `json:"readAt,omitempty"` + + Priority NotificationPriority `gorm:"default:'normal';index" json:"priority"` + + Metadata *string `json:"metadata,omitempty"` + + CreatedAt time.Time `gorm:"autoCreateTime;index:,sort:desc" json:"createdAt"` + ExpiresAt *time.Time `json:"expiresAt,omitempty"` } func (n *Notification) ToJson() map[string]interface{} { @@ -88,91 +99,232 @@ func (n *Notification) ToJson() map[string]interface{} { } func (n *Notification) InsertInDB() error { - id := utils.GenerateRandomId() - n.ID = id - query := ` - INSERT INTO notifications ( - id, user_id, type, title, message, link, - resource_type, resource_id, priority, metadata, expires_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - RETURNING created_at - ` - err := db.QueryRow(query, n.ID, n.UserID, n.Type, n.Title, n.Message, n.Link, - n.ResourceType, n.ResourceID, n.Priority, n.Metadata, n.ExpiresAt).Scan(&n.CreatedAt) - return err + n.ID = utils.GenerateRandomId() + + if n.Priority == "" { + n.Priority = PriorityNormal + } + + return db.Create(n).Error } func GetNotificationsByUserID(userID int64, unreadOnly bool) ([]Notification, error) { var notifications []Notification - query := ` - SELECT id, user_id, type, title, message, link, resource_type, resource_id, - email_sent, email_sent_at, slack_sent, slack_sent_at, - discord_sent, discord_sent_at, webhook_sent, webhook_sent_at, - is_read, read_at, priority, metadata, created_at, expires_at - FROM notifications - WHERE user_id = ? OR user_id IS NULL - ` + + query := db.Where(db.Where("user_id = ?", userID).Or("user_id IS NULL")) + if unreadOnly { - query += " AND is_read = 0" + query = query.Where("is_read = ?", false) } - query += " ORDER BY created_at DESC LIMIT 100" - rows, err := db.Query(query, userID) - if err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - var notif Notification - err := rows.Scan( - ¬if.ID, ¬if.UserID, ¬if.Type, ¬if.Title, ¬if.Message, - ¬if.Link, ¬if.ResourceType, ¬if.ResourceID, - ¬if.EmailSent, ¬if.EmailSentAt, ¬if.SlackSent, ¬if.SlackSentAt, - ¬if.DiscordSent, ¬if.DiscordSentAt, ¬if.WebhookSent, ¬if.WebhookSentAt, - ¬if.IsRead, ¬if.ReadAt, ¬if.Priority, ¬if.Metadata, - ¬if.CreatedAt, ¬if.ExpiresAt, - ) - if err != nil { - return nil, err - } - notifications = append(notifications, notif) - } + result := query.Order("created_at DESC").Limit(100).Find(¬ifications) - return notifications, rows.Err() + return notifications, result.Error } func (n *Notification) MarkAsRead() error { - query := ` - UPDATE notifications - SET is_read = 1, read_at = CURRENT_TIMESTAMP - WHERE id = ? - ` - _, err := db.Exec(query, n.ID) - return err + return db.Model(n).Updates(map[string]interface{}{ + "is_read": true, + "read_at": time.Now(), + }).Error } func MarkAllAsRead(userID int64) error { - query := ` - UPDATE notifications - SET is_read = 1, read_at = CURRENT_TIMESTAMP - WHERE user_id = ? AND is_read = 0 - ` - _, err := db.Exec(query, userID) - return err + return db.Model(&Notification{}). + Where("user_id = ? AND is_read = ?", userID, false). + Updates(map[string]interface{}{ + "is_read": true, + "read_at": time.Now(), + }).Error } func DeleteNotification(notificationID int64) error { - query := `DELETE FROM notifications WHERE id = ?` - _, err := db.Exec(query, notificationID) - return err + return db.Delete(&Notification{}, notificationID).Error } func DeleteExpiredNotifications() error { - query := ` - DELETE FROM notifications - WHERE expires_at IS NOT NULL AND expires_at < CURRENT_TIMESTAMP - ` - _, err := db.Exec(query) - return err + return db.Where("expires_at IS NOT NULL AND expires_at < ?", time.Now()). + Delete(&Notification{}).Error } + +//###################################################################################################### +//ARCHIVED CODE BELOW + +// package models + +// import ( +// "time" + +// "github.com/corecollectives/mist/utils" +// ) + +// type NotificationType string + +// const ( +// NotificationDeploymentSuccess NotificationType = "deployment_success" +// NotificationDeploymentFailed NotificationType = "deployment_failed" +// NotificationDeploymentStarted NotificationType = "deployment_started" +// NotificationSSLExpiryWarning NotificationType = "ssl_expiry_warning" +// NotificationSSLRenewalSuccess NotificationType = "ssl_renewal_success" +// NotificationSSLRenewalFailed NotificationType = "ssl_renewal_failed" +// NotificationResourceAlert NotificationType = "resource_alert" +// NotificationAppError NotificationType = "app_error" +// NotificationAppStopped NotificationType = "app_stopped" +// NotificationBackupSuccess NotificationType = "backup_success" +// NotificationBackupFailed NotificationType = "backup_failed" +// NotificationUserInvited NotificationType = "user_invited" +// NotificationMemberAdded NotificationType = "member_added" +// NotificationSystemUpdate NotificationType = "system_update" +// NotificationCustom NotificationType = "custom" +// ) + +// type NotificationPriority string + +// const ( +// PriorityLow NotificationPriority = "low" +// PriorityNormal NotificationPriority = "normal" +// PriorityHigh NotificationPriority = "high" +// PriorityUrgent NotificationPriority = "urgent" +// ) + +// type Notification struct { +// ID int64 `db:"id" json:"id"` +// UserID *int64 `db:"user_id" json:"userId,omitempty"` +// Type NotificationType `db:"type" json:"type"` +// Title string `db:"title" json:"title"` +// Message string `db:"message" json:"message"` +// Link *string `db:"link" json:"link,omitempty"` +// ResourceType *string `db:"resource_type" json:"resourceType,omitempty"` +// ResourceID *int64 `db:"resource_id" json:"resourceId,omitempty"` +// EmailSent bool `db:"email_sent" json:"emailSent"` +// EmailSentAt *time.Time `db:"email_sent_at" json:"emailSentAt,omitempty"` +// SlackSent bool `db:"slack_sent" json:"slackSent"` +// SlackSentAt *time.Time `db:"slack_sent_at" json:"slackSentAt,omitempty"` +// DiscordSent bool `db:"discord_sent" json:"discordSent"` +// DiscordSentAt *time.Time `db:"discord_sent_at" json:"discordSentAt,omitempty"` +// WebhookSent bool `db:"webhook_sent" json:"webhookSent"` +// WebhookSentAt *time.Time `db:"webhook_sent_at" json:"webhookSentAt,omitempty"` +// IsRead bool `db:"is_read" json:"isRead"` +// ReadAt *time.Time `db:"read_at" json:"readAt,omitempty"` +// Priority NotificationPriority `db:"priority" json:"priority"` +// Metadata *string `db:"metadata" json:"metadata,omitempty"` // JSON +// CreatedAt time.Time `db:"created_at" json:"createdAt"` +// ExpiresAt *time.Time `db:"expires_at" json:"expiresAt,omitempty"` +// } + +// func (n *Notification) ToJson() map[string]interface{} { +// return map[string]interface{}{ +// "id": n.ID, +// "userId": n.UserID, +// "type": n.Type, +// "title": n.Title, +// "message": n.Message, +// "link": n.Link, +// "resourceType": n.ResourceType, +// "resourceId": n.ResourceID, +// "emailSent": n.EmailSent, +// "emailSentAt": n.EmailSentAt, +// "slackSent": n.SlackSent, +// "slackSentAt": n.SlackSentAt, +// "discordSent": n.DiscordSent, +// "discordSentAt": n.DiscordSentAt, +// "webhookSent": n.WebhookSent, +// "webhookSentAt": n.WebhookSentAt, +// "isRead": n.IsRead, +// "readAt": n.ReadAt, +// "priority": n.Priority, +// "metadata": n.Metadata, +// "createdAt": n.CreatedAt, +// "expiresAt": n.ExpiresAt, +// } +// } + +// func (n *Notification) InsertInDB() error { +// id := utils.GenerateRandomId() +// n.ID = id +// query := ` +// INSERT INTO notifications ( +// id, user_id, type, title, message, link, +// resource_type, resource_id, priority, metadata, expires_at +// ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +// RETURNING created_at +// ` +// err := db.QueryRow(query, n.ID, n.UserID, n.Type, n.Title, n.Message, n.Link, +// n.ResourceType, n.ResourceID, n.Priority, n.Metadata, n.ExpiresAt).Scan(&n.CreatedAt) +// return err +// } + +// func GetNotificationsByUserID(userID int64, unreadOnly bool) ([]Notification, error) { +// var notifications []Notification +// query := ` +// SELECT id, user_id, type, title, message, link, resource_type, resource_id, +// email_sent, email_sent_at, slack_sent, slack_sent_at, +// discord_sent, discord_sent_at, webhook_sent, webhook_sent_at, +// is_read, read_at, priority, metadata, created_at, expires_at +// FROM notifications +// WHERE user_id = ? OR user_id IS NULL +// ` +// if unreadOnly { +// query += " AND is_read = 0" +// } +// query += " ORDER BY created_at DESC LIMIT 100" + +// rows, err := db.Query(query, userID) +// if err != nil { +// return nil, err +// } +// defer rows.Close() + +// for rows.Next() { +// var notif Notification +// err := rows.Scan( +// ¬if.ID, ¬if.UserID, ¬if.Type, ¬if.Title, ¬if.Message, +// ¬if.Link, ¬if.ResourceType, ¬if.ResourceID, +// ¬if.EmailSent, ¬if.EmailSentAt, ¬if.SlackSent, ¬if.SlackSentAt, +// ¬if.DiscordSent, ¬if.DiscordSentAt, ¬if.WebhookSent, ¬if.WebhookSentAt, +// ¬if.IsRead, ¬if.ReadAt, ¬if.Priority, ¬if.Metadata, +// ¬if.CreatedAt, ¬if.ExpiresAt, +// ) +// if err != nil { +// return nil, err +// } +// notifications = append(notifications, notif) +// } + +// return notifications, rows.Err() +// } + +// func (n *Notification) MarkAsRead() error { +// query := ` +// UPDATE notifications +// SET is_read = 1, read_at = CURRENT_TIMESTAMP +// WHERE id = ? +// ` +// _, err := db.Exec(query, n.ID) +// return err +// } + +// func MarkAllAsRead(userID int64) error { +// query := ` +// UPDATE notifications +// SET is_read = 1, read_at = CURRENT_TIMESTAMP +// WHERE user_id = ? AND is_read = 0 +// ` +// _, err := db.Exec(query, userID) +// return err +// } + +// func DeleteNotification(notificationID int64) error { +// query := `DELETE FROM notifications WHERE id = ?` +// _, err := db.Exec(query, notificationID) +// return err +// } + +// func DeleteExpiredNotifications() error { +// query := ` +// DELETE FROM notifications +// WHERE expires_at IS NOT NULL AND expires_at < CURRENT_TIMESTAMP +// ` +// _, err := db.Exec(query) +// return err +// } From b275bf79da5000d03b73b3da8a76823d864d71a4 Mon Sep 17 00:00:00 2001 From: Tanish Date: Thu, 15 Jan 2026 00:45:31 +0530 Subject: [PATCH 10/16] gormified project projectMembers registries serviceTemplate files --- server/models/project.go | 686 +++++++++++++++++++------------ server/models/projectMembers.go | 17 +- server/models/registries.go | 22 +- server/models/serviceTemplate.go | 395 ++++++++++++------ 4 files changed, 717 insertions(+), 403 deletions(-) diff --git a/server/models/project.go b/server/models/project.go index ed1c3ff..6c1fb70 100644 --- a/server/models/project.go +++ b/server/models/project.go @@ -1,40 +1,63 @@ package models import ( - "database/sql" "strings" "time" "github.com/corecollectives/mist/utils" + "gorm.io/gorm" ) type Project struct { - ID int64 `json:"id"` - Name string `json:"name"` - Description sql.NullString `json:"description"` - Tags []sql.NullString `json:"tags"` - OwnerID int64 `json:"ownerId"` - Owner *User `json:"owner,omitempty"` - ProjectMembers []User `json:"projectMembers"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID int64 `gorm:"primaryKey;autoIncrement:false" json:"id"` + Name string `gorm:"not null" json:"name"` + Description *string `json:"description"` + + TagsString string `gorm:"column:tags" json:"-"` + Tags []string `gorm:"-" json:"tags"` + + OwnerID int64 `gorm:"not null" json:"ownerId"` + Owner *User `gorm:"foreignKey:OwnerID" json:"owner,omitempty"` + + ProjectMembers []User `gorm:"many2many:project_members;" json:"projectMembers"` + + CreatedAt time.Time `gorm:"autoCreateTime" json:"createdAt"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updatedAt"` } -func (p *Project) ToJSON() map[string]interface{} { - tags := []string{} - for _, tag := range p.Tags { - if tag.Valid { - tags = append(tags, tag.String) - } +func (p *Project) BeforeSave(tx *gorm.DB) (err error) { + if len(p.Tags) > 0 { + p.TagsString = strings.Join(p.Tags, ",") + } else { + p.TagsString = "" } - if len(tags) == 0 { + return +} + +func (p *Project) AfterFind(tx *gorm.DB) (err error) { + if p.TagsString != "" { + p.Tags = strings.Split(p.TagsString, ",") + } else { + p.Tags = []string{} + } + return +} + +func (p *Project) ToJSON() map[string]interface{} { + tags := p.Tags + if tags == nil { tags = []string{} } + desc := "" + if p.Description != nil { + desc = *p.Description + } + return map[string]interface{}{ "id": p.ID, "name": p.Name, - "description": p.Description.String, + "description": desc, "tags": tags, "ownerId": p.OwnerID, "owner": p.Owner, @@ -47,285 +70,436 @@ func (p *Project) ToJSON() map[string]interface{} { func (p *Project) InsertInDB() error { p.ID = utils.GenerateRandomId() - tagsStr := "" - if len(p.Tags) > 0 { - for i, tag := range p.Tags { - if i > 0 { - tagsStr += "," - } - tagsStr += tag.String - } - } - - var desc interface{} - if p.Description.Valid { - desc = p.Description.String - } else { - desc = nil - } - - query := ` - INSERT INTO projects (id, name, description, tags, owner_id) - VALUES ($1, $2, $3, $4, $5) - RETURNING id, created_at, updated_at - ` - err := db.QueryRow(query, p.ID, p.Name, desc, tagsStr, p.OwnerID).Scan( - &p.ID, &p.CreatedAt, &p.UpdatedAt, - ) - if err != nil { + if err := db.Create(p).Error; err != nil { return err } - _, err = db.Exec(` - INSERT INTO project_members (project_id, user_id) - VALUES ($1, $2) - ON CONFLICT DO NOTHING - `, p.ID, p.OwnerID) - if err != nil { - return err - } - - user, err := GetUserByID(p.OwnerID) - if err != nil { + owner := User{ID: p.OwnerID} + if err := db.Model(p).Association("ProjectMembers").Append(&owner); err != nil { return err } - p.Owner = user - p.ProjectMembers = []User{*user} + p.Owner = &owner + p.ProjectMembers = []User{owner} return nil } func GetProjectByID(projectID int64) (*Project, error) { - query := ` - SELECT - p.id, p.name, p.description, p.tags, p.owner_id, p.created_at, p.updated_at, - u.id, u.username, u.email, u.role, u.avatar_url, u.created_at - FROM projects p - JOIN users u ON p.owner_id = u.id - WHERE p.id = $1 - ` - - project := &Project{} - owner := &User{} - var tagsStr sql.NullString - - err := db.QueryRow(query, projectID).Scan( - &project.ID, - &project.Name, - &project.Description, - &tagsStr, - &project.OwnerID, - &project.CreatedAt, - &project.UpdatedAt, - &owner.ID, - &owner.Username, - &owner.Email, - &owner.Role, - &owner.AvatarURL, - &owner.CreatedAt, - ) - if err != nil { - return nil, err - } - - if tagsStr.Valid { - strTags := strings.Split(tagsStr.String, ",") - tags := make([]sql.NullString, len(strTags)) - for i, s := range strTags { - tags[i] = sql.NullString{ - String: s, - Valid: true, - } - } - project.Tags = tags - } - - project.Owner = owner + var p Project + err := db.Preload("Owner"). + Preload("ProjectMembers"). + First(&p, projectID).Error - memberQuery := ` - SELECT u.id, u.username, u.email, u.role, u.avatar_url, u.created_at - FROM users u - JOIN project_members pm ON u.id = pm.user_id - WHERE pm.project_id = $1 - ` - rows, err := db.Query(memberQuery, projectID) if err != nil { return nil, err } - defer rows.Close() - - var members []User - for rows.Next() { - var member User - if err := rows.Scan( - &member.ID, - &member.Username, - &member.Email, - &member.Role, - &member.AvatarURL, - &member.CreatedAt, - ); err != nil { - return nil, err - } - members = append(members, member) - } - project.ProjectMembers = members - - return project, nil + return &p, nil } func DeleteProjectByID(projectID int64) error { - query := `DELETE FROM projects WHERE id = $1` - _, err := db.Exec(query, projectID) - return err + return db.Delete(&Project{}, projectID).Error } func UpdateProject(p *Project) error { - query := ` - UPDATE projects - SET name = $1, description = $2, tags = $3, updated_at = CURRENT_TIMESTAMP - WHERE id = $4 - RETURNING updated_at - ` - tagsStr := "" - if len(p.Tags) > 0 { - for i, tag := range p.Tags { - if i > 0 { - tagsStr += "," - } - tagsStr += tag.String - } - } - - return db.QueryRow(query, p.Name, p.Description, tagsStr, p.ID).Scan(&p.UpdatedAt) + return db.Model(p).Updates(map[string]interface{}{ + "name": p.Name, + "description": p.Description, + "tags": strings.Join(p.Tags, ","), + "updated_at": time.Now(), + }).Error } func GetProjectsUserIsPartOf(userID int64) ([]Project, error) { - query := ` - SELECT - p.id, p.name, p.description, p.tags, p.owner_id, p.created_at, p.updated_at, - u.id, u.username, u.email, u.role, u.avatar_url, u.created_at - FROM projects p - JOIN project_members pm ON p.id = pm.project_id - JOIN users u ON p.owner_id = u.id - WHERE pm.user_id = $1 - ` - - rows, err := db.Query(query, userID) - if err != nil { - return nil, err - } - defer rows.Close() - var projects []Project - for rows.Next() { - var project Project - var owner User - var tagsStr sql.NullString - - err := rows.Scan( - &project.ID, - &project.Name, - &project.Description, - &tagsStr, - &project.OwnerID, - &project.CreatedAt, - &project.UpdatedAt, - &owner.ID, - &owner.Username, - &owner.Email, - &owner.Role, - &owner.AvatarURL, - &owner.CreatedAt, - ) - if err != nil { - return nil, err - } - if tagsStr.Valid { - strTags := strings.Split(tagsStr.String, ",") - tags := make([]sql.NullString, len(strTags)) - for i, s := range strTags { - tags[i] = sql.NullString{ - String: s, - Valid: true, - } - } - project.Tags = tags - } - - project.Owner = &owner - projects = append(projects, project) - } + err := db.Preload("Owner"). + Joins("JOIN project_members pm ON pm.project_id = projects.id"). + Where("pm.user_id = ?", userID). + Find(&projects).Error - return projects, nil + return projects, err } func HasUserAccessToProject(userID, projectID int64) (bool, error) { - query := ` - SELECT COUNT(1) - FROM project_members - WHERE project_id = $1 AND user_id = $2 - ` - var count int - err := db.QueryRow(query, projectID, userID).Scan(&count) - if err != nil { - return false, err - } - return count > 0, nil + var count int64 + err := db.Table("project_members"). + Where("project_id = ? AND user_id = ?", projectID, userID). + Count(&count).Error + + return count > 0, err } func IsUserProjectOwner(userID, projectID int64) (bool, error) { - query := `SELECT owner_id FROM projects WHERE id = $1` - var ownerID int64 - err := db.QueryRow(query, projectID).Scan(&ownerID) - if err != nil { - return false, err - } - return ownerID == userID, nil + var count int64 + err := db.Model(&Project{}). + Where("id = ? AND owner_id = ?", projectID, userID). + Count(&count).Error + return count > 0, err } func UpdateProjectMembers(projectID int64, userIDs []int64) error { - query := `SELECT owner_id FROM projects WHERE id = $1` - var ownerID int64 - err := db.QueryRow(query, projectID).Scan(&ownerID) - if err != nil { - return err - } - - tx, err := db.Begin() - if err != nil { - return err - } - defer tx.Rollback() + return db.Transaction(func(tx *gorm.DB) error { + var project Project + if err := tx.Select("owner_id").First(&project, projectID).Error; err != nil { + return err + } - _, err = tx.Exec(`DELETE FROM project_members WHERE project_id = $1 AND user_id != $2`, projectID, ownerID) - if err != nil { - return err - } + ownerIncluded := false + for _, uid := range userIDs { + if uid == project.OwnerID { + ownerIncluded = true + break + } + } + if !ownerIncluded { + userIDs = append(userIDs, project.OwnerID) + } - ownerIncluded := false - for _, userID := range userIDs { - if userID == ownerID { - ownerIncluded = true - break + var users []User + for _, uid := range userIDs { + users = append(users, User{ID: uid}) } - } - if !ownerIncluded { - userIDs = append(userIDs, ownerID) - } - for _, userID := range userIDs { - _, err = tx.Exec(` - INSERT INTO project_members (project_id, user_id) - VALUES ($1, $2) - ON CONFLICT (project_id, user_id) DO NOTHING - `, projectID, userID) - if err != nil { + if err := tx.Model(&Project{ID: projectID}).Association("ProjectMembers").Replace(users); err != nil { return err } - } - return tx.Commit() + return nil + }) } + +//############################################################################################################ +//ARCHIVED CODE BELOW + +// package models + +// import ( +// "database/sql" +// "strings" +// "time" + +// "github.com/corecollectives/mist/utils" +// ) + +// type Project struct { +// ID int64 `json:"id"` +// Name string `json:"name"` +// Description sql.NullString `json:"description"` +// Tags []sql.NullString `json:"tags"` +// OwnerID int64 `json:"ownerId"` +// Owner *User `json:"owner,omitempty"` +// ProjectMembers []User `json:"projectMembers"` +// CreatedAt time.Time `json:"createdAt"` +// UpdatedAt time.Time `json:"updatedAt"` +// } + +// func (p *Project) ToJSON() map[string]interface{} { +// tags := []string{} +// for _, tag := range p.Tags { +// if tag.Valid { +// tags = append(tags, tag.String) +// } +// } +// if len(tags) == 0 { +// tags = []string{} +// } + +// return map[string]interface{}{ +// "id": p.ID, +// "name": p.Name, +// "description": p.Description.String, +// "tags": tags, +// "ownerId": p.OwnerID, +// "owner": p.Owner, +// "projectMembers": p.ProjectMembers, +// "createdAt": p.CreatedAt, +// "updatedAt": p.UpdatedAt, +// } +// } + +// func (p *Project) InsertInDB() error { +// p.ID = utils.GenerateRandomId() + +// tagsStr := "" +// if len(p.Tags) > 0 { +// for i, tag := range p.Tags { +// if i > 0 { +// tagsStr += "," +// } +// tagsStr += tag.String +// } +// } + +// var desc interface{} +// if p.Description.Valid { +// desc = p.Description.String +// } else { +// desc = nil +// } + +// query := ` +// INSERT INTO projects (id, name, description, tags, owner_id) +// VALUES ($1, $2, $3, $4, $5) +// RETURNING id, created_at, updated_at +// ` +// err := db.QueryRow(query, p.ID, p.Name, desc, tagsStr, p.OwnerID).Scan( +// &p.ID, &p.CreatedAt, &p.UpdatedAt, +// ) +// if err != nil { +// return err +// } + +// _, err = db.Exec(` +// INSERT INTO project_members (project_id, user_id) +// VALUES ($1, $2) +// ON CONFLICT DO NOTHING +// `, p.ID, p.OwnerID) +// if err != nil { +// return err +// } + +// user, err := GetUserByID(p.OwnerID) +// if err != nil { +// return err +// } +// p.Owner = user + +// p.ProjectMembers = []User{*user} + +// return nil +// } + +// func GetProjectByID(projectID int64) (*Project, error) { +// query := ` +// SELECT +// p.id, p.name, p.description, p.tags, p.owner_id, p.created_at, p.updated_at, +// u.id, u.username, u.email, u.role, u.avatar_url, u.created_at +// FROM projects p +// JOIN users u ON p.owner_id = u.id +// WHERE p.id = $1 +// ` + +// project := &Project{} +// owner := &User{} +// var tagsStr sql.NullString + +// err := db.QueryRow(query, projectID).Scan( +// &project.ID, +// &project.Name, +// &project.Description, +// &tagsStr, +// &project.OwnerID, +// &project.CreatedAt, +// &project.UpdatedAt, +// &owner.ID, +// &owner.Username, +// &owner.Email, +// &owner.Role, +// &owner.AvatarURL, +// &owner.CreatedAt, +// ) +// if err != nil { +// return nil, err +// } + +// if tagsStr.Valid { +// strTags := strings.Split(tagsStr.String, ",") +// tags := make([]sql.NullString, len(strTags)) +// for i, s := range strTags { +// tags[i] = sql.NullString{ +// String: s, +// Valid: true, +// } +// } +// project.Tags = tags +// } + +// project.Owner = owner + +// memberQuery := ` +// SELECT u.id, u.username, u.email, u.role, u.avatar_url, u.created_at +// FROM users u +// JOIN project_members pm ON u.id = pm.user_id +// WHERE pm.project_id = $1 +// ` +// rows, err := db.Query(memberQuery, projectID) +// if err != nil { +// return nil, err +// } +// defer rows.Close() + +// var members []User +// for rows.Next() { +// var member User +// if err := rows.Scan( +// &member.ID, +// &member.Username, +// &member.Email, +// &member.Role, +// &member.AvatarURL, +// &member.CreatedAt, +// ); err != nil { +// return nil, err +// } +// members = append(members, member) +// } +// project.ProjectMembers = members + +// return project, nil +// } + +// func DeleteProjectByID(projectID int64) error { +// query := `DELETE FROM projects WHERE id = $1` +// _, err := db.Exec(query, projectID) +// return err +// } + +// func UpdateProject(p *Project) error { +// query := ` +// UPDATE projects +// SET name = $1, description = $2, tags = $3, updated_at = CURRENT_TIMESTAMP +// WHERE id = $4 +// RETURNING updated_at +// ` +// tagsStr := "" +// if len(p.Tags) > 0 { +// for i, tag := range p.Tags { +// if i > 0 { +// tagsStr += "," +// } +// tagsStr += tag.String +// } +// } + +// return db.QueryRow(query, p.Name, p.Description, tagsStr, p.ID).Scan(&p.UpdatedAt) +// } + +// func GetProjectsUserIsPartOf(userID int64) ([]Project, error) { +// query := ` +// SELECT +// p.id, p.name, p.description, p.tags, p.owner_id, p.created_at, p.updated_at, +// u.id, u.username, u.email, u.role, u.avatar_url, u.created_at +// FROM projects p +// JOIN project_members pm ON p.id = pm.project_id +// JOIN users u ON p.owner_id = u.id +// WHERE pm.user_id = $1 +// ` + +// rows, err := db.Query(query, userID) +// if err != nil { +// return nil, err +// } +// defer rows.Close() + +// var projects []Project +// for rows.Next() { +// var project Project +// var owner User +// var tagsStr sql.NullString + +// err := rows.Scan( +// &project.ID, +// &project.Name, +// &project.Description, +// &tagsStr, +// &project.OwnerID, +// &project.CreatedAt, +// &project.UpdatedAt, +// &owner.ID, +// &owner.Username, +// &owner.Email, +// &owner.Role, +// &owner.AvatarURL, +// &owner.CreatedAt, +// ) +// if err != nil { +// return nil, err +// } + +// if tagsStr.Valid { +// strTags := strings.Split(tagsStr.String, ",") +// tags := make([]sql.NullString, len(strTags)) +// for i, s := range strTags { +// tags[i] = sql.NullString{ +// String: s, +// Valid: true, +// } +// } +// project.Tags = tags +// } + +// project.Owner = &owner +// projects = append(projects, project) +// } + +// return projects, nil +// } + +// func HasUserAccessToProject(userID, projectID int64) (bool, error) { +// query := ` +// SELECT COUNT(1) +// FROM project_members +// WHERE project_id = $1 AND user_id = $2 +// ` +// var count int +// err := db.QueryRow(query, projectID, userID).Scan(&count) +// if err != nil { +// return false, err +// } +// return count > 0, nil +// } + +// func IsUserProjectOwner(userID, projectID int64) (bool, error) { +// query := `SELECT owner_id FROM projects WHERE id = $1` +// var ownerID int64 +// err := db.QueryRow(query, projectID).Scan(&ownerID) +// if err != nil { +// return false, err +// } +// return ownerID == userID, nil +// } + +// func UpdateProjectMembers(projectID int64, userIDs []int64) error { +// query := `SELECT owner_id FROM projects WHERE id = $1` +// var ownerID int64 +// err := db.QueryRow(query, projectID).Scan(&ownerID) +// if err != nil { +// return err +// } + +// tx, err := db.Begin() +// if err != nil { +// return err +// } +// defer tx.Rollback() + +// _, err = tx.Exec(`DELETE FROM project_members WHERE project_id = $1 AND user_id != $2`, projectID, ownerID) +// if err != nil { +// return err +// } + +// ownerIncluded := false +// for _, userID := range userIDs { +// if userID == ownerID { +// ownerIncluded = true +// break +// } +// } +// if !ownerIncluded { +// userIDs = append(userIDs, ownerID) +// } + +// for _, userID := range userIDs { +// _, err = tx.Exec(` +// INSERT INTO project_members (project_id, user_id) +// VALUES ($1, $2) +// ON CONFLICT (project_id, user_id) DO NOTHING +// `, projectID, userID) +// if err != nil { +// return err +// } +// } + +// return tx.Commit() +// } diff --git a/server/models/projectMembers.go b/server/models/projectMembers.go index 2d9fb85..cda414d 100644 --- a/server/models/projectMembers.go +++ b/server/models/projectMembers.go @@ -2,9 +2,14 @@ package models import "time" -type ProjectMembers struct{ - ID int64 `json:"id"` - ProjectID int64 `json:"project_id"` - USERID int64 `json:"user_id"` - AddedAt time.Time `json:"added_at"` -} \ No newline at end of file +type ProjectMember struct { + UserID int64 `gorm:"primaryKey;autoIncrement:false" json:"user_id"` + + ProjectID int64 `gorm:"primaryKey;autoIncrement:false" json:"project_id"` + + AddedAt time.Time `gorm:"autoCreateTime" json:"added_at"` +} + +func (ProjectMember) TableName() string { + return "project_members" +} diff --git a/server/models/registries.go b/server/models/registries.go index 32b37b9..509885e 100644 --- a/server/models/registries.go +++ b/server/models/registries.go @@ -2,11 +2,19 @@ package models import "time" -type Registries struct { - ID int64 `json:"id"` - ProjectID int64 `json:"projectId"` - RegistryURL string `json:"registryUrl"` - Username string `json:"username"` - Password string `json:"password"` - CreatedAt time.Time `json:"createdAt"` +type Registry struct { + ID int64 `gorm:"primaryKey;autoIncrement:true" json:"id"` + + ProjectID int64 `gorm:"uniqueIndex:idx_project_registry;not null;constraint:OnDelete:CASCADE" json:"projectId"` + + RegistryURL string `gorm:"uniqueIndex:idx_project_registry;not null" json:"registryUrl"` + + Username string `json:"username"` + Password string `json:"password"` + + CreatedAt time.Time `gorm:"autoCreateTime" json:"createdAt"` +} + +func (Registry) TableName() string { + return "registries" } diff --git a/server/models/serviceTemplate.go b/server/models/serviceTemplate.go index 36be0c1..f75c9ae 100644 --- a/server/models/serviceTemplate.go +++ b/server/models/serviceTemplate.go @@ -1,8 +1,9 @@ package models import ( - "database/sql" "time" + + "gorm.io/gorm" ) type ServiceTemplateCategory string @@ -16,32 +17,48 @@ const ( ) type ServiceTemplate struct { - ID int64 `db:"id" json:"id"` - Name string `db:"name" json:"name"` - DisplayName string `db:"display_name" json:"displayName"` - Category ServiceTemplateCategory `db:"category" json:"category"` - Description *string `db:"description" json:"description,omitempty"` - IconURL *string `db:"icon_url" json:"iconUrl,omitempty"` - DockerImage string `db:"docker_image" json:"dockerImage"` - DockerImageVersion *string `db:"docker_image_version" json:"dockerImageVersion,omitempty"` - DefaultPort int `db:"default_port" json:"defaultPort"` - DefaultEnvVars *string `db:"default_env_vars" json:"defaultEnvVars,omitempty"` - RequiredEnvVars *string `db:"required_env_vars" json:"requiredEnvVars,omitempty"` - DefaultVolumePath *string `db:"default_volume_path" json:"defaultVolumePath,omitempty"` - VolumeRequired bool `db:"volume_required" json:"volumeRequired"` - RecommendedCPU *float64 `db:"recommended_cpu" json:"recommendedCpu,omitempty"` - RecommendedMemory *int `db:"recommended_memory" json:"recommendedMemory,omitempty"` - MinMemory *int `db:"min_memory" json:"minMemory,omitempty"` - HealthcheckCommand *string `db:"healthcheck_command" json:"healthcheckCommand,omitempty"` - HealthcheckInterval int `db:"healthcheck_interval" json:"healthcheckInterval"` - AdminUIImage *string `db:"admin_ui_image" json:"adminUiImage,omitempty"` - AdminUIPort *int `db:"admin_ui_port" json:"adminUiPort,omitempty"` - SetupInstructions *string `db:"setup_instructions" json:"setupInstructions,omitempty"` - IsActive bool `db:"is_active" json:"isActive"` - IsFeatured bool `db:"is_featured" json:"isFeatured"` - SortOrder int `db:"sort_order" json:"sortOrder"` - CreatedAt time.Time `db:"created_at" json:"createdAt"` - UpdatedAt time.Time `db:"updated_at" json:"updatedAt"` + ID int64 `gorm:"primaryKey;autoIncrement:true" json:"id"` + + Name string `gorm:"uniqueIndex;not null" json:"name"` + + DisplayName string `gorm:"not null" json:"displayName"` + + Category ServiceTemplateCategory `gorm:"default:'database';index" json:"category"` + + Description *string `json:"description,omitempty"` + IconURL *string `json:"iconUrl,omitempty"` + + DockerImage string `gorm:"not null" json:"dockerImage"` + DockerImageVersion *string `json:"dockerImageVersion,omitempty"` + + DefaultPort int `gorm:"not null" json:"defaultPort"` + DefaultEnvVars *string `json:"defaultEnvVars,omitempty"` + RequiredEnvVars *string `json:"requiredEnvVars,omitempty"` + + DefaultVolumePath *string `json:"defaultVolumePath,omitempty"` + + VolumeRequired bool `gorm:"default:true" json:"volumeRequired"` + + RecommendedCPU *float64 `json:"recommendedCpu,omitempty"` + RecommendedMemory *int `json:"recommendedMemory,omitempty"` + MinMemory *int `json:"minMemory,omitempty"` + + HealthcheckCommand *string `json:"healthcheckCommand,omitempty"` + + HealthcheckInterval int `gorm:"default:30" json:"healthcheckInterval"` + + AdminUIImage *string `json:"adminUiImage,omitempty"` + AdminUIPort *int `json:"adminUiPort,omitempty"` + SetupInstructions *string `json:"setupInstructions,omitempty"` + + IsActive bool `gorm:"default:true;index" json:"isActive"` + + IsFeatured bool `gorm:"default:false" json:"isFeatured"` + + SortOrder int `gorm:"default:0;index" json:"sortOrder"` + + CreatedAt time.Time `gorm:"autoCreateTime" json:"createdAt"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updatedAt"` } func (st *ServiceTemplate) ToJson() map[string]interface{} { @@ -77,77 +94,21 @@ func (st *ServiceTemplate) ToJson() map[string]interface{} { func GetAllServiceTemplates() ([]ServiceTemplate, error) { var templates []ServiceTemplate - query := ` - SELECT id, name, display_name, category, description, icon_url, - docker_image, docker_image_version, default_port, default_env_vars, - required_env_vars, default_volume_path, volume_required, - recommended_cpu, recommended_memory, min_memory, - healthcheck_command, healthcheck_interval, - admin_ui_image, admin_ui_port, setup_instructions, - is_active, is_featured, sort_order, created_at, updated_at - FROM service_templates - WHERE is_active = 1 - ORDER BY sort_order, display_name - ` - rows, err := db.Query(query) - if err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - var template ServiceTemplate - err := rows.Scan( - &template.ID, &template.Name, &template.DisplayName, &template.Category, - &template.Description, &template.IconURL, &template.DockerImage, - &template.DockerImageVersion, &template.DefaultPort, &template.DefaultEnvVars, - &template.RequiredEnvVars, &template.DefaultVolumePath, &template.VolumeRequired, - &template.RecommendedCPU, &template.RecommendedMemory, &template.MinMemory, - &template.HealthcheckCommand, &template.HealthcheckInterval, - &template.AdminUIImage, &template.AdminUIPort, &template.SetupInstructions, - &template.IsActive, &template.IsFeatured, &template.SortOrder, - &template.CreatedAt, &template.UpdatedAt, - ) - if err != nil { - return nil, err - } - templates = append(templates, template) - } - - if err = rows.Err(); err != nil { - return nil, err - } - return templates, nil + err := db.Where("is_active = ?", true). + Order("sort_order, display_name"). + Find(&templates).Error + return templates, err } func GetServiceTemplateByName(name string) (*ServiceTemplate, error) { var template ServiceTemplate - query := ` - SELECT id, name, display_name, category, description, icon_url, - docker_image, docker_image_version, default_port, default_env_vars, - required_env_vars, default_volume_path, volume_required, - recommended_cpu, recommended_memory, min_memory, - healthcheck_command, healthcheck_interval, - admin_ui_image, admin_ui_port, setup_instructions, - is_active, is_featured, sort_order, created_at, updated_at - FROM service_templates - WHERE name = ? AND is_active = 1 - ` - err := db.QueryRow(query, name).Scan( - &template.ID, &template.Name, &template.DisplayName, &template.Category, - &template.Description, &template.IconURL, &template.DockerImage, - &template.DockerImageVersion, &template.DefaultPort, &template.DefaultEnvVars, - &template.RequiredEnvVars, &template.DefaultVolumePath, &template.VolumeRequired, - &template.RecommendedCPU, &template.RecommendedMemory, &template.MinMemory, - &template.HealthcheckCommand, &template.HealthcheckInterval, - &template.AdminUIImage, &template.AdminUIPort, &template.SetupInstructions, - &template.IsActive, &template.IsFeatured, &template.SortOrder, - &template.CreatedAt, &template.UpdatedAt, - ) - if err == sql.ErrNoRows { - return nil, nil - } + err := db.Where("name = ? AND is_active = ?", name, true). + First(&template).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } return nil, err } return &template, nil @@ -155,45 +116,211 @@ func GetServiceTemplateByName(name string) (*ServiceTemplate, error) { func GetServiceTemplatesByCategory(category ServiceTemplateCategory) ([]ServiceTemplate, error) { var templates []ServiceTemplate - query := ` - SELECT id, name, display_name, category, description, icon_url, - docker_image, docker_image_version, default_port, default_env_vars, - required_env_vars, default_volume_path, volume_required, - recommended_cpu, recommended_memory, min_memory, - healthcheck_command, healthcheck_interval, - admin_ui_image, admin_ui_port, setup_instructions, - is_active, is_featured, sort_order, created_at, updated_at - FROM service_templates - WHERE category = ? AND is_active = 1 - ORDER BY sort_order, display_name - ` - rows, err := db.Query(query, category) - if err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - var template ServiceTemplate - err := rows.Scan( - &template.ID, &template.Name, &template.DisplayName, &template.Category, - &template.Description, &template.IconURL, &template.DockerImage, - &template.DockerImageVersion, &template.DefaultPort, &template.DefaultEnvVars, - &template.RequiredEnvVars, &template.DefaultVolumePath, &template.VolumeRequired, - &template.RecommendedCPU, &template.RecommendedMemory, &template.MinMemory, - &template.HealthcheckCommand, &template.HealthcheckInterval, - &template.AdminUIImage, &template.AdminUIPort, &template.SetupInstructions, - &template.IsActive, &template.IsFeatured, &template.SortOrder, - &template.CreatedAt, &template.UpdatedAt, - ) - if err != nil { - return nil, err - } - templates = append(templates, template) - } - - if err = rows.Err(); err != nil { - return nil, err - } - return templates, nil + err := db.Where("category = ? AND is_active = ?", category, true). + Order("sort_order, display_name"). + Find(&templates).Error + return templates, err } + +//############################################################################################################### +//ARCHIVED CODE BELOW + +// package models + +// import ( +// "database/sql" +// "time" +// ) + +// type ServiceTemplateCategory string + +// const ( +// CategoryDatabase ServiceTemplateCategory = "database" +// CategoryCache ServiceTemplateCategory = "cache" +// CategoryQueue ServiceTemplateCategory = "queue" +// CategoryStorage ServiceTemplateCategory = "storage" +// CategoryOther ServiceTemplateCategory = "other" +// ) + +// type ServiceTemplate struct { +// ID int64 `db:"id" json:"id"` +// Name string `db:"name" json:"name"` +// DisplayName string `db:"display_name" json:"displayName"` +// Category ServiceTemplateCategory `db:"category" json:"category"` +// Description *string `db:"description" json:"description,omitempty"` +// IconURL *string `db:"icon_url" json:"iconUrl,omitempty"` +// DockerImage string `db:"docker_image" json:"dockerImage"` +// DockerImageVersion *string `db:"docker_image_version" json:"dockerImageVersion,omitempty"` +// DefaultPort int `db:"default_port" json:"defaultPort"` +// DefaultEnvVars *string `db:"default_env_vars" json:"defaultEnvVars,omitempty"` +// RequiredEnvVars *string `db:"required_env_vars" json:"requiredEnvVars,omitempty"` +// DefaultVolumePath *string `db:"default_volume_path" json:"defaultVolumePath,omitempty"` +// VolumeRequired bool `db:"volume_required" json:"volumeRequired"` +// RecommendedCPU *float64 `db:"recommended_cpu" json:"recommendedCpu,omitempty"` +// RecommendedMemory *int `db:"recommended_memory" json:"recommendedMemory,omitempty"` +// MinMemory *int `db:"min_memory" json:"minMemory,omitempty"` +// HealthcheckCommand *string `db:"healthcheck_command" json:"healthcheckCommand,omitempty"` +// HealthcheckInterval int `db:"healthcheck_interval" json:"healthcheckInterval"` +// AdminUIImage *string `db:"admin_ui_image" json:"adminUiImage,omitempty"` +// AdminUIPort *int `db:"admin_ui_port" json:"adminUiPort,omitempty"` +// SetupInstructions *string `db:"setup_instructions" json:"setupInstructions,omitempty"` +// IsActive bool `db:"is_active" json:"isActive"` +// IsFeatured bool `db:"is_featured" json:"isFeatured"` +// SortOrder int `db:"sort_order" json:"sortOrder"` +// CreatedAt time.Time `db:"created_at" json:"createdAt"` +// UpdatedAt time.Time `db:"updated_at" json:"updatedAt"` +// } + +// func (st *ServiceTemplate) ToJson() map[string]interface{} { +// return map[string]interface{}{ +// "id": st.ID, +// "name": st.Name, +// "displayName": st.DisplayName, +// "category": st.Category, +// "description": st.Description, +// "iconUrl": st.IconURL, +// "dockerImage": st.DockerImage, +// "dockerImageVersion": st.DockerImageVersion, +// "defaultPort": st.DefaultPort, +// "defaultEnvVars": st.DefaultEnvVars, +// "requiredEnvVars": st.RequiredEnvVars, +// "defaultVolumePath": st.DefaultVolumePath, +// "volumeRequired": st.VolumeRequired, +// "recommendedCpu": st.RecommendedCPU, +// "recommendedMemory": st.RecommendedMemory, +// "minMemory": st.MinMemory, +// "healthcheckCommand": st.HealthcheckCommand, +// "healthcheckInterval": st.HealthcheckInterval, +// "adminUiImage": st.AdminUIImage, +// "adminUiPort": st.AdminUIPort, +// "setupInstructions": st.SetupInstructions, +// "isActive": st.IsActive, +// "isFeatured": st.IsFeatured, +// "sortOrder": st.SortOrder, +// "createdAt": st.CreatedAt, +// "updatedAt": st.UpdatedAt, +// } +// } + +// func GetAllServiceTemplates() ([]ServiceTemplate, error) { +// var templates []ServiceTemplate +// query := ` +// SELECT id, name, display_name, category, description, icon_url, +// docker_image, docker_image_version, default_port, default_env_vars, +// required_env_vars, default_volume_path, volume_required, +// recommended_cpu, recommended_memory, min_memory, +// healthcheck_command, healthcheck_interval, +// admin_ui_image, admin_ui_port, setup_instructions, +// is_active, is_featured, sort_order, created_at, updated_at +// FROM service_templates +// WHERE is_active = 1 +// ORDER BY sort_order, display_name +// ` +// rows, err := db.Query(query) +// if err != nil { +// return nil, err +// } +// defer rows.Close() + +// for rows.Next() { +// var template ServiceTemplate +// err := rows.Scan( +// &template.ID, &template.Name, &template.DisplayName, &template.Category, +// &template.Description, &template.IconURL, &template.DockerImage, +// &template.DockerImageVersion, &template.DefaultPort, &template.DefaultEnvVars, +// &template.RequiredEnvVars, &template.DefaultVolumePath, &template.VolumeRequired, +// &template.RecommendedCPU, &template.RecommendedMemory, &template.MinMemory, +// &template.HealthcheckCommand, &template.HealthcheckInterval, +// &template.AdminUIImage, &template.AdminUIPort, &template.SetupInstructions, +// &template.IsActive, &template.IsFeatured, &template.SortOrder, +// &template.CreatedAt, &template.UpdatedAt, +// ) +// if err != nil { +// return nil, err +// } +// templates = append(templates, template) +// } + +// if err = rows.Err(); err != nil { +// return nil, err +// } +// return templates, nil +// } + +// func GetServiceTemplateByName(name string) (*ServiceTemplate, error) { +// var template ServiceTemplate +// query := ` +// SELECT id, name, display_name, category, description, icon_url, +// docker_image, docker_image_version, default_port, default_env_vars, +// required_env_vars, default_volume_path, volume_required, +// recommended_cpu, recommended_memory, min_memory, +// healthcheck_command, healthcheck_interval, +// admin_ui_image, admin_ui_port, setup_instructions, +// is_active, is_featured, sort_order, created_at, updated_at +// FROM service_templates +// WHERE name = ? AND is_active = 1 +// ` +// err := db.QueryRow(query, name).Scan( +// &template.ID, &template.Name, &template.DisplayName, &template.Category, +// &template.Description, &template.IconURL, &template.DockerImage, +// &template.DockerImageVersion, &template.DefaultPort, &template.DefaultEnvVars, +// &template.RequiredEnvVars, &template.DefaultVolumePath, &template.VolumeRequired, +// &template.RecommendedCPU, &template.RecommendedMemory, &template.MinMemory, +// &template.HealthcheckCommand, &template.HealthcheckInterval, +// &template.AdminUIImage, &template.AdminUIPort, &template.SetupInstructions, +// &template.IsActive, &template.IsFeatured, &template.SortOrder, +// &template.CreatedAt, &template.UpdatedAt, +// ) +// if err == sql.ErrNoRows { +// return nil, nil +// } +// if err != nil { +// return nil, err +// } +// return &template, nil +// } + +// func GetServiceTemplatesByCategory(category ServiceTemplateCategory) ([]ServiceTemplate, error) { +// var templates []ServiceTemplate +// query := ` +// SELECT id, name, display_name, category, description, icon_url, +// docker_image, docker_image_version, default_port, default_env_vars, +// required_env_vars, default_volume_path, volume_required, +// recommended_cpu, recommended_memory, min_memory, +// healthcheck_command, healthcheck_interval, +// admin_ui_image, admin_ui_port, setup_instructions, +// is_active, is_featured, sort_order, created_at, updated_at +// FROM service_templates +// WHERE category = ? AND is_active = 1 +// ORDER BY sort_order, display_name +// ` +// rows, err := db.Query(query, category) +// if err != nil { +// return nil, err +// } +// defer rows.Close() + +// for rows.Next() { +// var template ServiceTemplate +// err := rows.Scan( +// &template.ID, &template.Name, &template.DisplayName, &template.Category, +// &template.Description, &template.IconURL, &template.DockerImage, +// &template.DockerImageVersion, &template.DefaultPort, &template.DefaultEnvVars, +// &template.RequiredEnvVars, &template.DefaultVolumePath, &template.VolumeRequired, +// &template.RecommendedCPU, &template.RecommendedMemory, &template.MinMemory, +// &template.HealthcheckCommand, &template.HealthcheckInterval, +// &template.AdminUIImage, &template.AdminUIPort, &template.SetupInstructions, +// &template.IsActive, &template.IsFeatured, &template.SortOrder, +// &template.CreatedAt, &template.UpdatedAt, +// ) +// if err != nil { +// return nil, err +// } +// templates = append(templates, template) +// } + +// if err = rows.Err(); err != nil { +// return nil, err +// } +// return templates, nil +// } From 07f26a448eb764448d18764a47fca44725b76180 Mon Sep 17 00:00:00 2001 From: Tanish Date: Thu, 15 Jan 2026 01:58:56 +0530 Subject: [PATCH 11/16] gormified session systemSettings updateLog user volume --- .../015_Create_System_settings.sql | 4 +- server/models/session.go | 303 +++++++---- server/models/systemSettings.go | 352 +++++++++++-- server/models/systemSettingsDbVer.go | 7 + server/models/updateLog.go | 477 ++++++++++++++---- server/models/user.go | 384 ++++++++++---- server/models/volume.go | 199 +++++--- 7 files changed, 1313 insertions(+), 413 deletions(-) create mode 100644 server/models/systemSettingsDbVer.go diff --git a/server/db/migrations_archive/015_Create_System_settings.sql b/server/db/migrations_archive/015_Create_System_settings.sql index 3f58c6e..9270aa8 100644 --- a/server/db/migrations_archive/015_Create_System_settings.sql +++ b/server/db/migrations_archive/015_Create_System_settings.sql @@ -1,5 +1,7 @@ -CREATE TABLE IF NOT EXISTS system_settings ( +CREATE TABLE IF NOT EXISTS system_settings_ ( key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); + + diff --git a/server/models/session.go b/server/models/session.go index 68c0ab4..dbdad14 100644 --- a/server/models/session.go +++ b/server/models/session.go @@ -4,22 +4,31 @@ import ( "time" ) +type DeviceType string + +const ( + DeviceDesktop DeviceType = "desktop" + DeviceMobile DeviceType = "mobile" + DeviceTablet DeviceType = "tablet" + DeviceUnknown DeviceType = "unknown" +) + type Session struct { - ID string `db:"id" json:"id"` - UserID int64 `db:"user_id" json:"userId"` - SessionData *string `db:"session_data" json:"sessionData,omitempty"` // JSON - IPAddress *string `db:"ip_address" json:"ipAddress,omitempty"` - UserAgent *string `db:"user_agent" json:"userAgent,omitempty"` - DeviceType *string `db:"device_type" json:"deviceType,omitempty"` - Browser *string `db:"browser" json:"browser,omitempty"` - OS *string `db:"os" json:"os,omitempty"` - Location *string `db:"location" json:"location,omitempty"` - IsActive bool `db:"is_active" json:"isActive"` - LastActivityAt time.Time `db:"last_activity_at" json:"lastActivityAt"` - RevokedAt *time.Time `db:"revoked_at" json:"revokedAt,omitempty"` - RevokedReason *string `db:"revoked_reason" json:"revokedReason,omitempty"` - CreatedAt time.Time `db:"created_at" json:"createdAt"` - ExpiresAt time.Time `db:"expires_at" json:"expiresAt"` + ID string `gorm:"primaryKey" json:"id"` + UserID int64 `gorm:"index;not null;constraint:OnDelete:CASCADE" json:"userId"` + SessionData *string `json:"sessionData,omitempty"` + IPAddress *string `json:"ipAddress,omitempty"` + UserAgent *string `json:"userAgent,omitempty"` + DeviceType *string `json:"deviceType,omitempty"` + Browser *string `json:"browser,omitempty"` + OS *string `json:"os,omitempty"` + Location *string `json:"location,omitempty"` + IsActive bool `gorm:"default:true;index" json:"isActive"` + LastActivityAt time.Time `gorm:"autoUpdateTime" json:"lastActivityAt"` + RevokedAt *time.Time `json:"revokedAt,omitempty"` + RevokedReason *string `json:"revokedReason,omitempty"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"createdAt"` + ExpiresAt time.Time `gorm:"not null;index" json:"expiresAt"` } func (s *Session) ToJson() map[string]interface{} { @@ -43,34 +52,12 @@ func (s *Session) ToJson() map[string]interface{} { } func (s *Session) InsertInDB() error { - query := ` - INSERT INTO sessions ( - id, user_id, session_data, ip_address, user_agent, - device_type, browser, os, location, expires_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - RETURNING created_at, last_activity_at - ` - err := db.QueryRow(query, s.ID, s.UserID, s.SessionData, s.IPAddress, s.UserAgent, - s.DeviceType, s.Browser, s.OS, s.Location, s.ExpiresAt).Scan(&s.CreatedAt, &s.LastActivityAt) - return err + return db.Create(s).Error } func GetSessionByID(sessionID string) (*Session, error) { var session Session - query := ` - SELECT id, user_id, session_data, ip_address, user_agent, - device_type, browser, os, location, is_active, - last_activity_at, revoked_at, revoked_reason, - created_at, expires_at - FROM sessions - WHERE id = ? AND is_active = 1 - ` - err := db.QueryRow(query, sessionID).Scan( - &session.ID, &session.UserID, &session.SessionData, &session.IPAddress, - &session.UserAgent, &session.DeviceType, &session.Browser, &session.OS, - &session.Location, &session.IsActive, &session.LastActivityAt, - &session.RevokedAt, &session.RevokedReason, &session.CreatedAt, &session.ExpiresAt, - ) + err := db.Where("id = ? AND is_active = ?", sessionID, true).First(&session).Error if err != nil { return nil, err } @@ -79,77 +66,193 @@ func GetSessionByID(sessionID string) (*Session, error) { func GetSessionsByUserID(userID int64) ([]Session, error) { var sessions []Session - query := ` - SELECT id, user_id, session_data, ip_address, user_agent, - device_type, browser, os, location, is_active, - last_activity_at, revoked_at, revoked_reason, - created_at, expires_at - FROM sessions - WHERE user_id = ? AND is_active = 1 - ORDER BY last_activity_at DESC - ` - rows, err := db.Query(query, userID) - if err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - var session Session - err := rows.Scan( - &session.ID, &session.UserID, &session.SessionData, &session.IPAddress, - &session.UserAgent, &session.DeviceType, &session.Browser, &session.OS, - &session.Location, &session.IsActive, &session.LastActivityAt, - &session.RevokedAt, &session.RevokedReason, &session.CreatedAt, &session.ExpiresAt, - ) - if err != nil { - return nil, err - } - sessions = append(sessions, session) - } - - return sessions, rows.Err() + err := db.Where("user_id = ? AND is_active = ?", userID, true). + Order("last_activity_at DESC"). + Find(&sessions).Error + return sessions, err } func (s *Session) UpdateActivity() error { - query := ` - UPDATE sessions - SET last_activity_at = CURRENT_TIMESTAMP - WHERE id = ? - ` - _, err := db.Exec(query, s.ID) - return err + return db.Model(s).Update("last_activity_at", time.Now()).Error } func (s *Session) Revoke(reason string) error { - query := ` - UPDATE sessions - SET is_active = 0, - revoked_at = CURRENT_TIMESTAMP, - revoked_reason = ? - WHERE id = ? - ` - _, err := db.Exec(query, reason, s.ID) - return err + return db.Model(s).Updates(map[string]interface{}{ + "is_active": false, + "revoked_at": time.Now(), + "revoked_reason": reason, + }).Error } func RevokeAllUserSessions(userID int64, reason string) error { - query := ` - UPDATE sessions - SET is_active = 0, - revoked_at = CURRENT_TIMESTAMP, - revoked_reason = ? - WHERE user_id = ? AND is_active = 1 - ` - _, err := db.Exec(query, reason, userID) - return err + return db.Model(&Session{}). + Where("user_id = ? AND is_active = ?", userID, true). + Updates(map[string]interface{}{ + "is_active": false, + "revoked_at": time.Now(), + "revoked_reason": reason, + }).Error } func DeleteExpiredSessions() error { - query := ` - DELETE FROM sessions - WHERE expires_at < CURRENT_TIMESTAMP - ` - _, err := db.Exec(query) - return err + return db.Where("expires_at < ?", time.Now()).Delete(&Session{}).Error } + +//################################################################################################ +//ARCHIVED CODE BELOW + +// package models + +// import ( +// "time" +// ) + +// type Session struct { +// ID string `db:"id" json:"id"` +// UserID int64 `db:"user_id" json:"userId"` +// SessionData *string `db:"session_data" json:"sessionData,omitempty"` // JSON +// IPAddress *string `db:"ip_address" json:"ipAddress,omitempty"` +// UserAgent *string `db:"user_agent" json:"userAgent,omitempty"` +// DeviceType *string `db:"device_type" json:"deviceType,omitempty"` +// Browser *string `db:"browser" json:"browser,omitempty"` +// OS *string `db:"os" json:"os,omitempty"` +// Location *string `db:"location" json:"location,omitempty"` +// IsActive bool `db:"is_active" json:"isActive"` +// LastActivityAt time.Time `db:"last_activity_at" json:"lastActivityAt"` +// RevokedAt *time.Time `db:"revoked_at" json:"revokedAt,omitempty"` +// RevokedReason *string `db:"revoked_reason" json:"revokedReason,omitempty"` +// CreatedAt time.Time `db:"created_at" json:"createdAt"` +// ExpiresAt time.Time `db:"expires_at" json:"expiresAt"` +// } + +// func (s *Session) ToJson() map[string]interface{} { +// return map[string]interface{}{ +// "id": s.ID, +// "userId": s.UserID, +// "sessionData": s.SessionData, +// "ipAddress": s.IPAddress, +// "userAgent": s.UserAgent, +// "deviceType": s.DeviceType, +// "browser": s.Browser, +// "os": s.OS, +// "location": s.Location, +// "isActive": s.IsActive, +// "lastActivityAt": s.LastActivityAt, +// "revokedAt": s.RevokedAt, +// "revokedReason": s.RevokedReason, +// "createdAt": s.CreatedAt, +// "expiresAt": s.ExpiresAt, +// } +// } + +// func (s *Session) InsertInDB() error { +// query := ` +// INSERT INTO sessions ( +// id, user_id, session_data, ip_address, user_agent, +// device_type, browser, os, location, expires_at +// ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +// RETURNING created_at, last_activity_at +// ` +// err := db.QueryRow(query, s.ID, s.UserID, s.SessionData, s.IPAddress, s.UserAgent, +// s.DeviceType, s.Browser, s.OS, s.Location, s.ExpiresAt).Scan(&s.CreatedAt, &s.LastActivityAt) +// return err +// } + +// func GetSessionByID(sessionID string) (*Session, error) { +// var session Session +// query := ` +// SELECT id, user_id, session_data, ip_address, user_agent, +// device_type, browser, os, location, is_active, +// last_activity_at, revoked_at, revoked_reason, +// created_at, expires_at +// FROM sessions +// WHERE id = ? AND is_active = 1 +// ` +// err := db.QueryRow(query, sessionID).Scan( +// &session.ID, &session.UserID, &session.SessionData, &session.IPAddress, +// &session.UserAgent, &session.DeviceType, &session.Browser, &session.OS, +// &session.Location, &session.IsActive, &session.LastActivityAt, +// &session.RevokedAt, &session.RevokedReason, &session.CreatedAt, &session.ExpiresAt, +// ) +// if err != nil { +// return nil, err +// } +// return &session, nil +// } + +// func GetSessionsByUserID(userID int64) ([]Session, error) { +// var sessions []Session +// query := ` +// SELECT id, user_id, session_data, ip_address, user_agent, +// device_type, browser, os, location, is_active, +// last_activity_at, revoked_at, revoked_reason, +// created_at, expires_at +// FROM sessions +// WHERE user_id = ? AND is_active = 1 +// ORDER BY last_activity_at DESC +// ` +// rows, err := db.Query(query, userID) +// if err != nil { +// return nil, err +// } +// defer rows.Close() + +// for rows.Next() { +// var session Session +// err := rows.Scan( +// &session.ID, &session.UserID, &session.SessionData, &session.IPAddress, +// &session.UserAgent, &session.DeviceType, &session.Browser, &session.OS, +// &session.Location, &session.IsActive, &session.LastActivityAt, +// &session.RevokedAt, &session.RevokedReason, &session.CreatedAt, &session.ExpiresAt, +// ) +// if err != nil { +// return nil, err +// } +// sessions = append(sessions, session) +// } + +// return sessions, rows.Err() +// } + +// func (s *Session) UpdateActivity() error { +// query := ` +// UPDATE sessions +// SET last_activity_at = CURRENT_TIMESTAMP +// WHERE id = ? +// ` +// _, err := db.Exec(query, s.ID) +// return err +// } + +// func (s *Session) Revoke(reason string) error { +// query := ` +// UPDATE sessions +// SET is_active = 0, +// revoked_at = CURRENT_TIMESTAMP, +// revoked_reason = ? +// WHERE id = ? +// ` +// _, err := db.Exec(query, reason, s.ID) +// return err +// } + +// func RevokeAllUserSessions(userID int64, reason string) error { +// query := ` +// UPDATE sessions +// SET is_active = 0, +// revoked_at = CURRENT_TIMESTAMP, +// revoked_reason = ? +// WHERE user_id = ? AND is_active = 1 +// ` +// _, err := db.Exec(query, reason, userID) +// return err +// } + +// func DeleteExpiredSessions() error { +// query := ` +// DELETE FROM sessions +// WHERE expires_at < CURRENT_TIMESTAMP +// ` +// _, err := db.Exec(query) +// return err +// } diff --git a/server/models/systemSettings.go b/server/models/systemSettings.go index 1e61c0e..262a9bf 100644 --- a/server/models/systemSettings.go +++ b/server/models/systemSettings.go @@ -2,11 +2,13 @@ package models import ( "crypto/rand" - "database/sql" "encoding/base64" "fmt" + "time" "github.com/rs/zerolog/log" + "gorm.io/gorm" + "gorm.io/gorm/clause" ) type SystemSettings struct { @@ -21,6 +23,16 @@ type SystemSettings struct { AutoCleanupImages bool `json:"autoCleanupImages"` } +type systemSettingEntry struct { + Key string `gorm:"primaryKey" json:"key"` + Value string `json:"value"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` +} + +func (systemSettingEntry) TableName() string { + return "system_settings" +} + func generateRandomSecret(length int) (string, error) { bytes := make([]byte, length) if _, err := rand.Read(bytes); err != nil { @@ -30,41 +42,43 @@ func generateRandomSecret(length int) (string, error) { } func GetSystemSetting(key string) (string, error) { - var value string - err := db.QueryRow(`SELECT value FROM system_settings WHERE key = ?`, key).Scan(&value) - if err == sql.ErrNoRows { - return "", nil - } + var entry systemSettingEntry + err := db.Where("key = ?", key).First(&entry).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return "", nil + } return "", err } - return value, nil + return entry.Value, nil } func SetSystemSetting(key, value string) error { - _, err := db.Exec(` - INSERT INTO system_settings (key, value, updated_at) - VALUES (?, ?, CURRENT_TIMESTAMP) - ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = CURRENT_TIMESTAMP - `, key, value, value) - return err + entry := systemSettingEntry{ + Key: key, + Value: value, + } + + return db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "key"}}, + DoUpdates: clause.AssignmentColumns([]string{"value", "updated_at"}), + }).Create(&entry).Error } func GetSystemSettings() (*SystemSettings, error) { var settings SystemSettings - var wildcardDomain sql.NullString - err := db.QueryRow(`SELECT value FROM system_settings WHERE key = ?`, "wildcard_domain").Scan(&wildcardDomain) - if err != nil && err != sql.ErrNoRows { + wildcardVal, err := GetSystemSetting("wildcard_domain") + if err != nil { return nil, err } - if wildcardDomain.Valid && wildcardDomain.String != "" { - settings.WildcardDomain = &wildcardDomain.String + if wildcardVal != "" { + settings.WildcardDomain = &wildcardVal } - var mistAppName string - err = db.QueryRow(`SELECT value FROM system_settings WHERE key = ?`, "mist_app_name").Scan(&mistAppName) - if err != nil && err != sql.ErrNoRows { + mistAppName, err := GetSystemSetting("mist_app_name") + if err != nil { return nil, err } if mistAppName == "" { @@ -98,8 +112,6 @@ func GetSystemSettings() (*SystemSettings, error) { if err != nil { return nil, err } - // Default to empty string - same-origin requests are always allowed - // Users only need to configure this for cross-origin requests settings.AllowedOrigins = allowedOrigins prodMode, err := GetSystemSetting("production_mode") @@ -138,21 +150,12 @@ func UpdateSystemSettings(wildcardDomain *string, mistAppName string) (*SystemSe if wildcardDomain != nil { wildcardValue = *wildcardDomain } - _, err := db.Exec(` - INSERT INTO system_settings (key, value, updated_at) - VALUES (?, ?, CURRENT_TIMESTAMP) - ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = CURRENT_TIMESTAMP - `, "wildcard_domain", wildcardValue, wildcardValue) - if err != nil { + + if err := SetSystemSetting("wildcard_domain", wildcardValue); err != nil { return nil, err } - _, err = db.Exec(` - INSERT INTO system_settings (key, value, updated_at) - VALUES (?, ?, CURRENT_TIMESTAMP) - ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = CURRENT_TIMESTAMP - `, "mist_app_name", mistAppName, mistAppName) - if err != nil { + if err := SetSystemSetting("mist_app_name", mistAppName); err != nil { return nil, err } @@ -203,7 +206,6 @@ func UpdateDockerSettings(autoCleanupContainers, autoCleanupImages bool) error { return nil } -// UpdateSystemSettings updates all settings from the SystemSettings struct func (s *SystemSettings) UpdateSystemSettings() error { wildcardValue := "" if s.WildcardDomain != nil { @@ -272,3 +274,281 @@ func GenerateAutoDomain(projectName, appName string) (string, error) { return projectName + "-" + appName + "." + wildcardDomain, nil } + +//############################################################################################################ +//ARCHIVED CODE BELOW + +// package models + +// import ( +// "crypto/rand" +// "database/sql" +// "encoding/base64" +// "fmt" + +// "github.com/rs/zerolog/log" +// ) + +// type SystemSettings struct { +// WildcardDomain *string `json:"wildcardDomain"` +// MistAppName string `json:"mistAppName"` +// JwtSecret string `json:"-"` +// GithubWebhookSecret string `json:"-"` +// AllowedOrigins string `json:"allowedOrigins"` +// ProductionMode bool `json:"productionMode"` +// SecureCookies bool `json:"secureCookies"` +// AutoCleanupContainers bool `json:"autoCleanupContainers"` +// AutoCleanupImages bool `json:"autoCleanupImages"` +// } + +// func generateRandomSecret(length int) (string, error) { +// bytes := make([]byte, length) +// if _, err := rand.Read(bytes); err != nil { +// return "", err +// } +// return base64.URLEncoding.EncodeToString(bytes), nil +// } + +// func GetSystemSetting(key string) (string, error) { +// var value string +// err := db.QueryRow(`SELECT value FROM system_settings WHERE key = ?`, key).Scan(&value) +// if err == sql.ErrNoRows { +// return "", nil +// } +// if err != nil { +// return "", err +// } +// return value, nil +// } + +// func SetSystemSetting(key, value string) error { +// _, err := db.Exec(` +// INSERT INTO system_settings (key, value, updated_at) +// VALUES (?, ?, CURRENT_TIMESTAMP) +// ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = CURRENT_TIMESTAMP +// `, key, value, value) +// return err +// } + +// func GetSystemSettings() (*SystemSettings, error) { +// var settings SystemSettings + +// var wildcardDomain sql.NullString +// err := db.QueryRow(`SELECT value FROM system_settings WHERE key = ?`, "wildcard_domain").Scan(&wildcardDomain) +// if err != nil && err != sql.ErrNoRows { +// return nil, err +// } +// if wildcardDomain.Valid && wildcardDomain.String != "" { +// settings.WildcardDomain = &wildcardDomain.String +// } + +// var mistAppName string +// err = db.QueryRow(`SELECT value FROM system_settings WHERE key = ?`, "mist_app_name").Scan(&mistAppName) +// if err != nil && err != sql.ErrNoRows { +// return nil, err +// } +// if mistAppName == "" { +// mistAppName = "mist" +// } +// settings.MistAppName = mistAppName + +// jwtSecret, err := GetSystemSetting("jwt_secret") +// if err != nil { +// return nil, err +// } +// if jwtSecret == "" { +// jwtSecret, err = generateRandomSecret(64) +// if err != nil { +// return nil, fmt.Errorf("failed to generate JWT secret: %w", err) +// } +// if err := SetSystemSetting("jwt_secret", jwtSecret); err != nil { +// return nil, fmt.Errorf("failed to save JWT secret: %w", err) +// } +// log.Info().Msg("Auto-generated JWT secret and saved to database") +// } +// settings.JwtSecret = jwtSecret + +// githubSecret, err := GetSystemSetting("github_webhook_secret") +// if err != nil { +// return nil, err +// } +// settings.GithubWebhookSecret = githubSecret + +// allowedOrigins, err := GetSystemSetting("allowed_origins") +// if err != nil { +// return nil, err +// } +// // Default to empty string - same-origin requests are always allowed +// // Users only need to configure this for cross-origin requests +// settings.AllowedOrigins = allowedOrigins + +// prodMode, err := GetSystemSetting("production_mode") +// if err != nil { +// return nil, err +// } +// settings.ProductionMode = prodMode == "true" + +// secureCookies, err := GetSystemSetting("secure_cookies") +// if err != nil { +// return nil, err +// } +// if secureCookies == "" { +// settings.SecureCookies = settings.ProductionMode +// } else { +// settings.SecureCookies = secureCookies == "true" +// } + +// autoCleanupContainers, err := GetSystemSetting("auto_cleanup_containers") +// if err != nil { +// return nil, err +// } +// settings.AutoCleanupContainers = autoCleanupContainers == "true" + +// autoCleanupImages, err := GetSystemSetting("auto_cleanup_images") +// if err != nil { +// return nil, err +// } +// settings.AutoCleanupImages = autoCleanupImages == "true" + +// return &settings, nil +// } + +// func UpdateSystemSettings(wildcardDomain *string, mistAppName string) (*SystemSettings, error) { +// wildcardValue := "" +// if wildcardDomain != nil { +// wildcardValue = *wildcardDomain +// } +// _, err := db.Exec(` +// INSERT INTO system_settings (key, value, updated_at) +// VALUES (?, ?, CURRENT_TIMESTAMP) +// ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = CURRENT_TIMESTAMP +// `, "wildcard_domain", wildcardValue, wildcardValue) +// if err != nil { +// return nil, err +// } + +// _, err = db.Exec(` +// INSERT INTO system_settings (key, value, updated_at) +// VALUES (?, ?, CURRENT_TIMESTAMP) +// ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = CURRENT_TIMESTAMP +// `, "mist_app_name", mistAppName, mistAppName) +// if err != nil { +// return nil, err +// } + +// return GetSystemSettings() +// } + +// func UpdateSecuritySettings(allowedOrigins string, productionMode, secureCookies bool) error { +// if err := SetSystemSetting("allowed_origins", allowedOrigins); err != nil { +// return err +// } + +// prodModeStr := "false" +// if productionMode { +// prodModeStr = "true" +// } +// if err := SetSystemSetting("production_mode", prodModeStr); err != nil { +// return err +// } + +// secureCookiesStr := "false" +// if secureCookies { +// secureCookiesStr = "true" +// } +// if err := SetSystemSetting("secure_cookies", secureCookiesStr); err != nil { +// return err +// } + +// return nil +// } + +// func UpdateDockerSettings(autoCleanupContainers, autoCleanupImages bool) error { +// cleanupContainersStr := "false" +// if autoCleanupContainers { +// cleanupContainersStr = "true" +// } +// if err := SetSystemSetting("auto_cleanup_containers", cleanupContainersStr); err != nil { +// return err +// } + +// cleanupImagesStr := "false" +// if autoCleanupImages { +// cleanupImagesStr = "true" +// } +// if err := SetSystemSetting("auto_cleanup_images", cleanupImagesStr); err != nil { +// return err +// } + +// return nil +// } + +// // UpdateSystemSettings updates all settings from the SystemSettings struct +// func (s *SystemSettings) UpdateSystemSettings() error { +// wildcardValue := "" +// if s.WildcardDomain != nil { +// wildcardValue = *s.WildcardDomain +// } +// if err := SetSystemSetting("wildcard_domain", wildcardValue); err != nil { +// return err +// } + +// if err := SetSystemSetting("mist_app_name", s.MistAppName); err != nil { +// return err +// } + +// prodModeStr := "false" +// if s.ProductionMode { +// prodModeStr = "true" +// } +// if err := SetSystemSetting("production_mode", prodModeStr); err != nil { +// return err +// } + +// secureCookiesStr := "false" +// if s.SecureCookies { +// secureCookiesStr = "true" +// } +// if err := SetSystemSetting("secure_cookies", secureCookiesStr); err != nil { +// return err +// } + +// cleanupContainersStr := "false" +// if s.AutoCleanupContainers { +// cleanupContainersStr = "true" +// } +// if err := SetSystemSetting("auto_cleanup_containers", cleanupContainersStr); err != nil { +// return err +// } + +// cleanupImagesStr := "false" +// if s.AutoCleanupImages { +// cleanupImagesStr = "true" +// } +// if err := SetSystemSetting("auto_cleanup_images", cleanupImagesStr); err != nil { +// return err +// } + +// return nil +// } + +// func GenerateAutoDomain(projectName, appName string) (string, error) { +// settings, err := GetSystemSettings() +// if err != nil { +// return "", err +// } + +// if settings.WildcardDomain == nil || *settings.WildcardDomain == "" { +// return "", nil +// } + +// wildcardDomain := *settings.WildcardDomain +// if len(wildcardDomain) > 0 && wildcardDomain[0] == '*' { +// wildcardDomain = wildcardDomain[1:] +// } +// if len(wildcardDomain) > 0 && wildcardDomain[0] == '.' { +// wildcardDomain = wildcardDomain[1:] +// } + +// return projectName + "-" + appName + "." + wildcardDomain, nil +// } diff --git a/server/models/systemSettingsDbVer.go b/server/models/systemSettingsDbVer.go new file mode 100644 index 0000000..b285fdc --- /dev/null +++ b/server/models/systemSettingsDbVer.go @@ -0,0 +1,7 @@ +package models + +type SystemSettingsDbVer struct { + key string `gorm:"primaryKey"` + value string `gorm:"not null"` + updatedAt int64 `gorm:"autoUpdateTime"` +} diff --git a/server/models/updateLog.go b/server/models/updateLog.go index 9f5947d..1d9da56 100644 --- a/server/models/updateLog.go +++ b/server/models/updateLog.go @@ -1,11 +1,11 @@ package models import ( - "database/sql" "strings" "time" "github.com/rs/zerolog/log" + "gorm.io/gorm" ) type UpdateStatus string @@ -17,39 +17,29 @@ const ( ) type UpdateLog struct { - ID int64 - VersionFrom string - VersionTo string - Status UpdateStatus - Logs *string - ErrorMessage *string - StartedBy int64 - StartedAt time.Time - CompletedAt *time.Time - Username *string + ID int64 `gorm:"primaryKey;autoIncrement:true" json:"id"` + VersionFrom string `gorm:"not null" json:"version_from"` + VersionTo string `gorm:"not null" json:"version_to"` + Status UpdateStatus `gorm:"index;not null" json:"status"` + Logs *string `json:"logs"` + ErrorMessage *string `json:"error_message"` + StartedBy int64 `gorm:"not null;constraint:OnDelete:CASCADE" json:"started_by"` + StartedAt time.Time `gorm:"autoCreateTime;index:,sort:desc" json:"started_at"` + CompletedAt *time.Time `json:"completed_at"` + Username *string `gorm:"-" json:"username"` } func CreateUpdateLog(versionFrom, versionTo string, startedBy int64) (*UpdateLog, error) { - query := ` - INSERT INTO update_logs (version_from, version_to, status, logs, started_by, started_at) - VALUES (?, ?, 'in_progress', '', ?, CURRENT_TIMESTAMP) - RETURNING id, version_from, version_to, status, logs, error_message, started_by, started_at, completed_at - ` - - updateLog := &UpdateLog{} - err := db.QueryRow(query, versionFrom, versionTo, startedBy).Scan( - &updateLog.ID, - &updateLog.VersionFrom, - &updateLog.VersionTo, - &updateLog.Status, - &updateLog.Logs, - &updateLog.ErrorMessage, - &updateLog.StartedBy, - &updateLog.StartedAt, - &updateLog.CompletedAt, - ) + emptyLogs := "" + updateLog := &UpdateLog{ + VersionFrom: versionFrom, + VersionTo: versionTo, + Status: UpdateStatusInProgress, + Logs: &emptyLogs, + StartedBy: startedBy, + } - if err != nil { + if err := db.Create(updateLog).Error; err != nil { log.Error().Err(err).Msg("Failed to create update log") return nil, err } @@ -65,13 +55,13 @@ func CreateUpdateLog(versionFrom, versionTo string, startedBy int64) (*UpdateLog } func UpdateUpdateLogStatus(id int64, status UpdateStatus, logs string, errorMessage *string) error { - query := ` - UPDATE update_logs - SET status = ?, logs = ?, error_message = ?, completed_at = CURRENT_TIMESTAMP - WHERE id = ? - ` + err := db.Model(&UpdateLog{ID: id}).Updates(map[string]interface{}{ + "status": status, + "logs": logs, + "error_message": errorMessage, + "completed_at": time.Now(), + }).Error - _, err := db.Exec(query, status, logs, errorMessage, id) if err != nil { log.Error().Err(err).Int64("update_log_id", id).Msg("Failed to update log status") return err @@ -86,13 +76,9 @@ func UpdateUpdateLogStatus(id int64, status UpdateStatus, logs string, errorMess } func AppendUpdateLog(id int64, logLine string) error { - query := ` - UPDATE update_logs - SET logs = logs || ? - WHERE id = ? - ` + err := db.Model(&UpdateLog{ID: id}). + Update("logs", gorm.Expr("logs || ?", logLine+"\n")).Error - _, err := db.Exec(query, logLine+"\n", id) if err != nil { log.Error().Err(err).Int64("update_log_id", id).Msg("Failed to append log line") return err @@ -102,84 +88,42 @@ func AppendUpdateLog(id int64, logLine string) error { } func GetUpdateLogs(limit int) ([]UpdateLog, error) { - query := ` - SELECT - ul.id, ul.version_from, ul.version_to, ul.status, - ul.logs, ul.error_message, ul.started_by, ul.started_at, - ul.completed_at, u.username - FROM update_logs ul - LEFT JOIN users u ON ul.started_by = u.id - ORDER BY ul.started_at DESC - LIMIT ? - ` - - rows, err := db.Query(query, limit) + var logs []UpdateLog + + err := db.Table("update_logs"). + Select("update_logs.*, users.username"). + Joins("LEFT JOIN users ON update_logs.started_by = users.id"). + Order("update_logs.started_at DESC"). + Limit(limit). + Scan(&logs).Error + if err != nil { log.Error().Err(err).Msg("Failed to query update logs") return nil, err } - defer rows.Close() - - var logs []UpdateLog - for rows.Next() { - var updateLog UpdateLog - err := rows.Scan( - &updateLog.ID, - &updateLog.VersionFrom, - &updateLog.VersionTo, - &updateLog.Status, - &updateLog.Logs, - &updateLog.ErrorMessage, - &updateLog.StartedBy, - &updateLog.StartedAt, - &updateLog.CompletedAt, - &updateLog.Username, - ) - if err != nil { - log.Error().Err(err).Msg("Failed to scan update log row") - return nil, err - } - logs = append(logs, updateLog) - } return logs, nil } func GetUpdateLogByID(id int64) (*UpdateLog, error) { - query := ` - SELECT - ul.id, ul.version_from, ul.version_to, ul.status, - ul.logs, ul.error_message, ul.started_by, ul.started_at, - ul.completed_at, u.username - FROM update_logs ul - LEFT JOIN users u ON ul.started_by = u.id - WHERE ul.id = ? - ` - - updateLog := &UpdateLog{} - err := db.QueryRow(query, id).Scan( - &updateLog.ID, - &updateLog.VersionFrom, - &updateLog.VersionTo, - &updateLog.Status, - &updateLog.Logs, - &updateLog.ErrorMessage, - &updateLog.StartedBy, - &updateLog.StartedAt, - &updateLog.CompletedAt, - &updateLog.Username, - ) - - if err == sql.ErrNoRows { - return nil, nil - } + var updateLog UpdateLog + + err := db.Table("update_logs"). + Select("update_logs.*, users.username"). + Joins("LEFT JOIN users ON update_logs.started_by = users.id"). + Where("update_logs.id = ?", id). + Scan(&updateLog).Error if err != nil { log.Error().Err(err).Int64("update_log_id", id).Msg("Failed to get update log by ID") return nil, err } - return updateLog, nil + if updateLog.ID == 0 { + return nil, nil + } + + return &updateLog, nil } func GetUpdateLogsAsString() (string, error) { @@ -317,3 +261,326 @@ func CheckAndCompletePendingUpdates() error { return nil } + +//############################################################################################################################ +//ARCHIVED CODE BELOW + +// package models + +// import ( +// "database/sql" +// "strings" +// "time" + +// "github.com/rs/zerolog/log" +// ) + +// type UpdateStatus string + +// const ( +// UpdateStatusInProgress UpdateStatus = "in_progress" +// UpdateStatusSuccess UpdateStatus = "success" +// UpdateStatusFailed UpdateStatus = "failed" +// ) + +// type UpdateLog struct { +// ID int64 +// VersionFrom string +// VersionTo string +// Status UpdateStatus +// Logs *string +// ErrorMessage *string +// StartedBy int64 +// StartedAt time.Time +// CompletedAt *time.Time +// Username *string +// } + +// func CreateUpdateLog(versionFrom, versionTo string, startedBy int64) (*UpdateLog, error) { +// query := ` +// INSERT INTO update_logs (version_from, version_to, status, logs, started_by, started_at) +// VALUES (?, ?, 'in_progress', '', ?, CURRENT_TIMESTAMP) +// RETURNING id, version_from, version_to, status, logs, error_message, started_by, started_at, completed_at +// ` + +// updateLog := &UpdateLog{} +// err := db.QueryRow(query, versionFrom, versionTo, startedBy).Scan( +// &updateLog.ID, +// &updateLog.VersionFrom, +// &updateLog.VersionTo, +// &updateLog.Status, +// &updateLog.Logs, +// &updateLog.ErrorMessage, +// &updateLog.StartedBy, +// &updateLog.StartedAt, +// &updateLog.CompletedAt, +// ) + +// if err != nil { +// log.Error().Err(err).Msg("Failed to create update log") +// return nil, err +// } + +// log.Info(). +// Int64("update_log_id", updateLog.ID). +// Str("from", versionFrom). +// Str("to", versionTo). +// Int64("started_by", startedBy). +// Msg("Update log created") + +// return updateLog, nil +// } + +// func UpdateUpdateLogStatus(id int64, status UpdateStatus, logs string, errorMessage *string) error { +// query := ` +// UPDATE update_logs +// SET status = ?, logs = ?, error_message = ?, completed_at = CURRENT_TIMESTAMP +// WHERE id = ? +// ` + +// _, err := db.Exec(query, status, logs, errorMessage, id) +// if err != nil { +// log.Error().Err(err).Int64("update_log_id", id).Msg("Failed to update log status") +// return err +// } + +// log.Info(). +// Int64("update_log_id", id). +// Str("status", string(status)). +// Msg("Update log status updated") + +// return nil +// } + +// func AppendUpdateLog(id int64, logLine string) error { +// query := ` +// UPDATE update_logs +// SET logs = logs || ? +// WHERE id = ? +// ` + +// _, err := db.Exec(query, logLine+"\n", id) +// if err != nil { +// log.Error().Err(err).Int64("update_log_id", id).Msg("Failed to append log line") +// return err +// } + +// return nil +// } + +// func GetUpdateLogs(limit int) ([]UpdateLog, error) { +// query := ` +// SELECT +// ul.id, ul.version_from, ul.version_to, ul.status, +// ul.logs, ul.error_message, ul.started_by, ul.started_at, +// ul.completed_at, u.username +// FROM update_logs ul +// LEFT JOIN users u ON ul.started_by = u.id +// ORDER BY ul.started_at DESC +// LIMIT ? +// ` + +// rows, err := db.Query(query, limit) +// if err != nil { +// log.Error().Err(err).Msg("Failed to query update logs") +// return nil, err +// } +// defer rows.Close() + +// var logs []UpdateLog +// for rows.Next() { +// var updateLog UpdateLog +// err := rows.Scan( +// &updateLog.ID, +// &updateLog.VersionFrom, +// &updateLog.VersionTo, +// &updateLog.Status, +// &updateLog.Logs, +// &updateLog.ErrorMessage, +// &updateLog.StartedBy, +// &updateLog.StartedAt, +// &updateLog.CompletedAt, +// &updateLog.Username, +// ) +// if err != nil { +// log.Error().Err(err).Msg("Failed to scan update log row") +// return nil, err +// } +// logs = append(logs, updateLog) +// } + +// return logs, nil +// } + +// func GetUpdateLogByID(id int64) (*UpdateLog, error) { +// query := ` +// SELECT +// ul.id, ul.version_from, ul.version_to, ul.status, +// ul.logs, ul.error_message, ul.started_by, ul.started_at, +// ul.completed_at, u.username +// FROM update_logs ul +// LEFT JOIN users u ON ul.started_by = u.id +// WHERE ul.id = ? +// ` + +// updateLog := &UpdateLog{} +// err := db.QueryRow(query, id).Scan( +// &updateLog.ID, +// &updateLog.VersionFrom, +// &updateLog.VersionTo, +// &updateLog.Status, +// &updateLog.Logs, +// &updateLog.ErrorMessage, +// &updateLog.StartedBy, +// &updateLog.StartedAt, +// &updateLog.CompletedAt, +// &updateLog.Username, +// ) + +// if err == sql.ErrNoRows { +// return nil, nil +// } + +// if err != nil { +// log.Error().Err(err).Int64("update_log_id", id).Msg("Failed to get update log by ID") +// return nil, err +// } + +// return updateLog, nil +// } + +// func GetUpdateLogsAsString() (string, error) { +// logs, err := GetUpdateLogs(10) +// if err != nil { +// return "", err +// } + +// if len(logs) == 0 { +// return "No update history available", nil +// } + +// var builder strings.Builder +// builder.WriteString("Recent Update History:\n") +// builder.WriteString("======================\n\n") + +// for _, log := range logs { +// builder.WriteString("Version: ") +// builder.WriteString(log.VersionFrom) +// builder.WriteString(" → ") +// builder.WriteString(log.VersionTo) +// builder.WriteString("\n") +// builder.WriteString("Status: ") +// builder.WriteString(string(log.Status)) +// builder.WriteString("\n") +// builder.WriteString("Started: ") +// builder.WriteString(log.StartedAt.Format("2006-01-02 15:04:05")) +// builder.WriteString(" by ") +// if log.Username != nil { +// builder.WriteString(*log.Username) +// } else { +// builder.WriteString("unknown") +// } + +// builder.WriteString("\n") +// if log.CompletedAt != nil { +// builder.WriteString("Completed: ") +// builder.WriteString(log.CompletedAt.Format("2006-01-02 15:04:05")) +// builder.WriteString("\n") +// } +// if log.ErrorMessage != nil && *log.ErrorMessage != "" { +// builder.WriteString("Error: ") +// builder.WriteString(*log.ErrorMessage) +// builder.WriteString("\n") +// } +// builder.WriteString("\n") +// } + +// return builder.String(), nil +// } + +// func CheckAndCompletePendingUpdates() error { +// logs, err := GetUpdateLogs(1) +// if err != nil { +// return err +// } + +// if len(logs) == 0 { +// return nil +// } + +// latestLog := logs[0] + +// if latestLog.Status != UpdateStatusInProgress { +// return nil +// } + +// log.Info(). +// Int64("update_log_id", latestLog.ID). +// Str("from_version", latestLog.VersionFrom). +// Str("to_version", latestLog.VersionTo). +// Str("age", time.Since(latestLog.StartedAt).String()). +// Msg("Found in-progress update on startup, checking status") + +// currentVersion, err := GetSystemSetting("version") +// if err != nil { +// log.Error().Err(err).Msg("Failed to get current version for update completion check") +// return err +// } + +// if currentVersion == "" { +// currentVersion = "1.0.0" +// } + +// if currentVersion == latestLog.VersionTo { +// log.Info(). +// Int64("update_log_id", latestLog.ID). +// Str("version", currentVersion). +// Msg("Completing successful update that was interrupted by service restart") + +// existing := "" +// if latestLog.Logs != nil { +// existing = *latestLog.Logs +// } + +// completionLog := existing + "\n✅ Update completed successfully (verified on restart)\n" + +// err = UpdateUpdateLogStatus(latestLog.ID, UpdateStatusSuccess, completionLog, nil) +// if err != nil { +// log.Error().Err(err).Int64("update_log_id", latestLog.ID).Msg("Failed to complete pending update") +// return err +// } + +// log.Info(). +// Int64("update_log_id", latestLog.ID). +// Str("from", latestLog.VersionFrom). +// Str("to", latestLog.VersionTo). +// Msg("Successfully completed pending update") +// return nil +// } + +// log.Warn(). +// Int64("update_log_id", latestLog.ID). +// Str("expected_version", latestLog.VersionTo). +// Str("current_version", currentVersion). +// Str("age", time.Since(latestLog.StartedAt).String()). +// Msg("Update appears to have failed (version mismatch detected on startup)") + +// errMsg := "Update process was interrupted and version does not match target" +// existing := "" +// if latestLog.Logs != nil { +// existing = *latestLog.Logs +// } + +// failureLog := existing + "\n❌ " + errMsg + "\n" +// err = UpdateUpdateLogStatus(latestLog.ID, UpdateStatusFailed, failureLog, &errMsg) +// if err != nil { +// log.Error().Err(err).Int64("update_log_id", latestLog.ID).Msg("Failed to mark failed update") +// return err +// } + +// log.Info(). +// Int64("update_log_id", latestLog.ID). +// Msg("Marked failed update as failed") + +// return nil +// } diff --git a/server/models/user.go b/server/models/user.go index 2eefe21..3f13a17 100644 --- a/server/models/user.go +++ b/server/models/user.go @@ -5,30 +5,53 @@ import ( "github.com/corecollectives/mist/utils" "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" ) type User struct { - ID int64 `json:"id"` - Username string `json:"username"` - Email string `json:"email"` - PasswordHash string `json:"-"` - Role string `json:"role"` - AvatarURL *string `json:"avatarUrl"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID int64 `gorm:"primaryKey;autoIncrement:false" json:"id"` + Username string `gorm:"uniqueIndex;not null" json:"username"` + Email string `gorm:"uniqueIndex;not null" json:"email"` + PasswordHash string `gorm:"not null" json:"-"` + Role string `gorm:"default:'user';index" json:"role"` + + FullName *string `json:"fullName,omitempty"` + AvatarURL *string `json:"avatarUrl,omitempty"` + Bio *string `json:"bio,omitempty"` + + EmailVerified bool `gorm:"default:false" json:"emailVerified"` + EmailVerificationToken *string `json:"-"` + EmailVerificationSentAt *time.Time `json:"-"` + + PasswordResetToken *string `json:"-"` + PasswordResetExpiresAt *time.Time `json:"-"` + PasswordChangedAt *time.Time `json:"-"` + + TwoFactorEnabled bool `gorm:"default:false" json:"twoFactorEnabled"` + TwoFactorSecret *string `json:"-"` + TwoFactorBackupCodes *string `json:"-"` + + LastLoginAt *time.Time `json:"lastLoginAt,omitempty"` + LastLoginIP *string `json:"lastLoginIp,omitempty"` + FailedLoginAttempts int `gorm:"default:0" json:"failedLoginAttempts"` + AccountLockedUntil *time.Time `json:"accountLockedUntil,omitempty"` + + Timezone string `gorm:"default:'UTC'" json:"timezone"` + Language string `gorm:"default:'en'" json:"language"` + NotificationPreferences *string `json:"notificationPreferences,omitempty"` + + IsActive bool `gorm:"default:true;index" json:"isActive"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"createdAt"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updatedAt"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt,omitempty"` } func (u *User) Create() error { - query := ` - INSERT INTO users (id, username, email, password_hash, role, avatar_url) - VALUES ($1, $2, $3, $4, $5, $6) - RETURNING id, username, email, role, avatar_url, created_at, updated_at - ` u.ID = utils.GenerateRandomId() - err := db.QueryRow(query, u.ID, u.Username, u.Email, u.PasswordHash, u.Role, u.AvatarURL).Scan( - &u.ID, &u.Username, &u.Email, &u.Role, &u.AvatarURL, &u.CreatedAt, &u.UpdatedAt, - ) - return err + if u.Role == "" { + u.Role = "user" + } + return db.Create(u).Error } func (u *User) SetPassword(password string) error { @@ -41,45 +64,35 @@ func (u *User) SetPassword(password string) error { } func GetUserByID(userID int64) (*User, error) { - query := ` - SELECT id, username, email, role, avatar_url, created_at, updated_at - FROM users - WHERE id = $1 - ` - user := &User{} - err := db.QueryRow(query, userID).Scan( - &user.ID, &user.Username, &user.Email, &user.Role, &user.AvatarURL, &user.CreatedAt, &user.UpdatedAt, - ) + var user User + err := db.First(&user, userID).Error if err != nil { return nil, err } - return user, nil + return &user, nil } func DeleteUserByID(userID int64) error { - query := `DELETE FROM users WHERE id = $1` - _, err := db.Exec(query, userID) - return err + return db.Delete(&User{}, userID).Error } func UpdateUser(u *User) error { - query := ` - UPDATE users - SET username = $1, email = $2, role = $3, avatar_url = $4, updated_at = CURRENT_TIMESTAMP - WHERE id = $5 - RETURNING updated_at - ` - return db.QueryRow(query, u.Username, u.Email, u.Role, u.AvatarURL, u.ID).Scan(&u.UpdatedAt) + return db.Model(u).Updates(map[string]interface{}{ + "username": u.Username, + "email": u.Email, + "role": u.Role, + "avatar_url": u.AvatarURL, + "updated_at": time.Now(), + }).Error } func (u *User) MatchPassword(password string) bool { - query := ` - SELECT password_hash - FROM users - WHERE id = $1 - ` var storedHash string - err := db.QueryRow(query, u.ID).Scan(&storedHash) + err := db.Model(&User{}). + Select("password_hash"). + Where("id = ?", u.ID). + Scan(&storedHash).Error + if err != nil { return false } @@ -88,101 +101,250 @@ func (u *User) MatchPassword(password string) bool { } func (u *User) UpdatePassword() error { - query := ` - UPDATE users - SET password_hash = $1, updated_at = CURRENT_TIMESTAMP - WHERE id = $2 - RETURNING updated_at - ` - return db.QueryRow(query, u.PasswordHash, u.ID).Scan(&u.UpdatedAt) - + return db.Model(u).Updates(map[string]interface{}{ + "password_hash": u.PasswordHash, + "updated_at": time.Now(), + }).Error } func GetUserByEmail(email string) (*User, error) { - query := ` - SELECT id, username, email, role, avatar_url, created_at, updated_at - FROM users - WHERE email = $1 - ` - user := &User{} - err := db.QueryRow(query, email).Scan( - &user.ID, &user.Username, &user.Email, &user.Role, &user.AvatarURL, &user.CreatedAt, &user.UpdatedAt, - ) + var user User + err := db.Where("email = ?", email).First(&user).Error if err != nil { return nil, err } - return user, nil + return &user, nil } func GetUserByUsername(username string) (*User, error) { - query := ` - SELECT id, username, email, role, avatar_url, created_at, updated_at - FROM users - WHERE username = $1 - ` - user := &User{} - err := db.QueryRow(query, username).Scan( - &user.ID, &user.Username, &user.Email, &user.Role, &user.AvatarURL, &user.CreatedAt, &user.UpdatedAt, - ) + var user User + err := db.Where("username = ?", username).First(&user).Error if err != nil { return nil, err } - return user, nil + return &user, nil } func UpdateUserPassword(userID int64, passwordHash string) error { - query := ` - UPDATE users - SET password_hash = $1, updated_at = CURRENT_TIMESTAMP - WHERE id = $2 - ` - _, err := db.Exec(query, passwordHash, userID) - return err + return db.Model(&User{ID: userID}).Updates(map[string]interface{}{ + "password_hash": passwordHash, + "updated_at": time.Now(), + }).Error } func GetAllUsers() ([]User, error) { - query := ` - SELECT id, username, email, role, avatar_url, created_at, updated_at - FROM users - ` - rows, err := db.Query(query) - if err != nil { - return nil, err - } - defer rows.Close() - var users []User - for rows.Next() { - user := User{} - err := rows.Scan(&user.ID, &user.Username, &user.Email, &user.Role, &user.AvatarURL, &user.CreatedAt, &user.UpdatedAt) - if err != nil { - return nil, err - } - users = append(users, user) - } - return users, nil + err := db.Find(&users).Error + return users, err } func GetUserRole(userID int64) (string, error) { - query := ` - SELECT role - FROM users - WHERE id = $1 - ` var role string - err := db.QueryRow(query, userID).Scan(&role) + err := db.Model(&User{}). + Select("role"). + Where("id = ?", userID). + Scan(&role).Error + if err != nil { return "", err } return role, nil } -func GetUserCount() (int, error) { - query := `SELECT COUNT(*) FROM users` - var count int - err := db.QueryRow(query).Scan(&count) - if err != nil { - return 0, err - } - return count, nil +func GetUserCount() (int64, error) { + var count int64 + err := db.Model(&User{}).Count(&count).Error + return count, err } + +//############################################################################################################## +//ARCHIVED CODE BELOW + +// package models + +// import ( +// "time" + +// "github.com/corecollectives/mist/utils" +// "golang.org/x/crypto/bcrypt" +// ) + +// type User struct { +// ID int64 `json:"id"` +// Username string `json:"username"` +// Email string `json:"email"` +// PasswordHash string `json:"-"` +// Role string `json:"role"` +// AvatarURL *string `json:"avatarUrl"` +// CreatedAt time.Time `json:"createdAt"` +// UpdatedAt time.Time `json:"updatedAt"` +// } + +// func (u *User) Create() error { +// query := ` +// INSERT INTO users (id, username, email, password_hash, role, avatar_url) +// VALUES ($1, $2, $3, $4, $5, $6) +// RETURNING id, username, email, role, avatar_url, created_at, updated_at +// ` +// u.ID = utils.GenerateRandomId() +// err := db.QueryRow(query, u.ID, u.Username, u.Email, u.PasswordHash, u.Role, u.AvatarURL).Scan( +// &u.ID, &u.Username, &u.Email, &u.Role, &u.AvatarURL, &u.CreatedAt, &u.UpdatedAt, +// ) +// return err +// } + +// func (u *User) SetPassword(password string) error { +// hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) +// if err != nil { +// return err +// } +// u.PasswordHash = string(hashedPassword) +// return nil +// } + +// func GetUserByID(userID int64) (*User, error) { +// query := ` +// SELECT id, username, email, role, avatar_url, created_at, updated_at +// FROM users +// WHERE id = $1 +// ` +// user := &User{} +// err := db.QueryRow(query, userID).Scan( +// &user.ID, &user.Username, &user.Email, &user.Role, &user.AvatarURL, &user.CreatedAt, &user.UpdatedAt, +// ) +// if err != nil { +// return nil, err +// } +// return user, nil +// } + +// func DeleteUserByID(userID int64) error { +// query := `DELETE FROM users WHERE id = $1` +// _, err := db.Exec(query, userID) +// return err +// } + +// func UpdateUser(u *User) error { +// query := ` +// UPDATE users +// SET username = $1, email = $2, role = $3, avatar_url = $4, updated_at = CURRENT_TIMESTAMP +// WHERE id = $5 +// RETURNING updated_at +// ` +// return db.QueryRow(query, u.Username, u.Email, u.Role, u.AvatarURL, u.ID).Scan(&u.UpdatedAt) +// } + +// func (u *User) MatchPassword(password string) bool { +// query := ` +// SELECT password_hash +// FROM users +// WHERE id = $1 +// ` +// var storedHash string +// err := db.QueryRow(query, u.ID).Scan(&storedHash) +// if err != nil { +// return false +// } + +// return bcrypt.CompareHashAndPassword([]byte(storedHash), []byte(password)) == nil +// } + +// func (u *User) UpdatePassword() error { +// query := ` +// UPDATE users +// SET password_hash = $1, updated_at = CURRENT_TIMESTAMP +// WHERE id = $2 +// RETURNING updated_at +// ` +// return db.QueryRow(query, u.PasswordHash, u.ID).Scan(&u.UpdatedAt) + +// } + +// func GetUserByEmail(email string) (*User, error) { +// query := ` +// SELECT id, username, email, role, avatar_url, created_at, updated_at +// FROM users +// WHERE email = $1 +// ` +// user := &User{} +// err := db.QueryRow(query, email).Scan( +// &user.ID, &user.Username, &user.Email, &user.Role, &user.AvatarURL, &user.CreatedAt, &user.UpdatedAt, +// ) +// if err != nil { +// return nil, err +// } +// return user, nil +// } + +// func GetUserByUsername(username string) (*User, error) { +// query := ` +// SELECT id, username, email, role, avatar_url, created_at, updated_at +// FROM users +// WHERE username = $1 +// ` +// user := &User{} +// err := db.QueryRow(query, username).Scan( +// &user.ID, &user.Username, &user.Email, &user.Role, &user.AvatarURL, &user.CreatedAt, &user.UpdatedAt, +// ) +// if err != nil { +// return nil, err +// } +// return user, nil +// } + +// func UpdateUserPassword(userID int64, passwordHash string) error { +// query := ` +// UPDATE users +// SET password_hash = $1, updated_at = CURRENT_TIMESTAMP +// WHERE id = $2 +// ` +// _, err := db.Exec(query, passwordHash, userID) +// return err +// } + +// func GetAllUsers() ([]User, error) { +// query := ` +// SELECT id, username, email, role, avatar_url, created_at, updated_at +// FROM users +// ` +// rows, err := db.Query(query) +// if err != nil { +// return nil, err +// } +// defer rows.Close() + +// var users []User +// for rows.Next() { +// user := User{} +// err := rows.Scan(&user.ID, &user.Username, &user.Email, &user.Role, &user.AvatarURL, &user.CreatedAt, &user.UpdatedAt) +// if err != nil { +// return nil, err +// } +// users = append(users, user) +// } +// return users, nil +// } + +// func GetUserRole(userID int64) (string, error) { +// query := ` +// SELECT role +// FROM users +// WHERE id = $1 +// ` +// var role string +// err := db.QueryRow(query, userID).Scan(&role) +// if err != nil { +// return "", err +// } +// return role, nil +// } + +// func GetUserCount() (int, error) { +// query := `SELECT COUNT(*) FROM users` +// var count int +// err := db.QueryRow(query).Scan(&count) +// if err != nil { +// return 0, err +// } +// return count, nil +// } diff --git a/server/models/volume.go b/server/models/volume.go index e2e88d8..cc3fc74 100644 --- a/server/models/volume.go +++ b/server/models/volume.go @@ -1,18 +1,19 @@ package models import ( - "database/sql" "time" + + "gorm.io/gorm" ) type Volume struct { - ID int64 `db:"id" json:"id"` - AppID int64 `db:"app_id" json:"appId"` - Name string `db:"name" json:"name"` - HostPath string `db:"host_path" json:"hostPath"` - ContainerPath string `db:"container_path" json:"containerPath"` - ReadOnly bool `db:"read_only" json:"readOnly"` - CreatedAt time.Time `db:"created_at" json:"createdAt"` + ID int64 `gorm:"primaryKey;autoIncrement:true" json:"id"` + AppID int64 `gorm:"uniqueIndex:idx_app_vol_name;not null;constraint:OnDelete:CASCADE" json:"appId"` + Name string `gorm:"uniqueIndex:idx_app_vol_name;not null" json:"name"` + HostPath string `gorm:"not null" json:"hostPath"` + ContainerPath string `gorm:"not null" json:"containerPath"` + ReadOnly bool `gorm:"default:false" json:"readOnly"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"createdAt"` } func (v *Volume) ToJson() map[string]interface{} { @@ -28,78 +29,156 @@ func (v *Volume) ToJson() map[string]interface{} { } func GetVolumesByAppID(appID int64) ([]Volume, error) { - query := ` - SELECT id, app_id, name, host_path, container_path, read_only, created_at - FROM volumes - WHERE app_id = ? - ORDER BY created_at DESC - ` - rows, err := db.Query(query, appID) - if err != nil { - return nil, err - } - defer rows.Close() - var volumes []Volume - for rows.Next() { - var vol Volume - err := rows.Scan(&vol.ID, &vol.AppID, &vol.Name, &vol.HostPath, &vol.ContainerPath, &vol.ReadOnly, &vol.CreatedAt) - if err != nil { - return nil, err - } - volumes = append(volumes, vol) - } - - return volumes, rows.Err() + err := db.Where("app_id = ?", appID).Order("created_at DESC").Find(&volumes).Error + return volumes, err } func GetVolumeByID(id int64) (*Volume, error) { - query := ` - SELECT id, app_id, name, host_path, container_path, read_only, created_at - FROM volumes - WHERE id = ? - ` var vol Volume - err := db.QueryRow(query, id).Scan(&vol.ID, &vol.AppID, &vol.Name, &vol.HostPath, &vol.ContainerPath, &vol.ReadOnly, &vol.CreatedAt) - if err == sql.ErrNoRows { - return nil, nil - } + err := db.First(&vol, id).Error if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } return nil, err } return &vol, nil } func CreateVolume(appID int64, name, hostPath, containerPath string, readOnly bool) (*Volume, error) { - query := ` - INSERT INTO volumes (app_id, name, host_path, container_path, read_only, created_at) - VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP) - ` - result, err := db.Exec(query, appID, name, hostPath, containerPath, readOnly) - if err != nil { - return nil, err + vol := &Volume{ + AppID: appID, + Name: name, + HostPath: hostPath, + ContainerPath: containerPath, + ReadOnly: readOnly, } - - id, err := result.LastInsertId() + err := db.Create(vol).Error if err != nil { return nil, err } - - return GetVolumeByID(id) + return vol, nil } func UpdateVolume(id int64, name, hostPath, containerPath string, readOnly bool) error { - query := ` - UPDATE volumes - SET name = ?, host_path = ?, container_path = ?, read_only = ? - WHERE id = ? - ` - _, err := db.Exec(query, name, hostPath, containerPath, readOnly, id) - return err + return db.Model(&Volume{ID: id}).Updates(map[string]interface{}{ + "name": name, + "host_path": hostPath, + "container_path": containerPath, + "read_only": readOnly, + }).Error } func DeleteVolume(id int64) error { - query := `DELETE FROM volumes WHERE id = ?` - _, err := db.Exec(query, id) - return err + return db.Delete(&Volume{}, id).Error } + +//############################################################################################################## +//ARCHIVED CODE BELOW + +// package models + +// import ( +// "database/sql" +// "time" +// ) + +// type Volume struct { +// ID int64 `db:"id" json:"id"` +// AppID int64 `db:"app_id" json:"appId"` +// Name string `db:"name" json:"name"` +// HostPath string `db:"host_path" json:"hostPath"` +// ContainerPath string `db:"container_path" json:"containerPath"` +// ReadOnly bool `db:"read_only" json:"readOnly"` +// CreatedAt time.Time `db:"created_at" json:"createdAt"` +// } + +// func (v *Volume) ToJson() map[string]interface{} { +// return map[string]interface{}{ +// "id": v.ID, +// "appId": v.AppID, +// "name": v.Name, +// "hostPath": v.HostPath, +// "containerPath": v.ContainerPath, +// "readOnly": v.ReadOnly, +// "createdAt": v.CreatedAt, +// } +// } + +// func GetVolumesByAppID(appID int64) ([]Volume, error) { +// query := ` +// SELECT id, app_id, name, host_path, container_path, read_only, created_at +// FROM volumes +// WHERE app_id = ? +// ORDER BY created_at DESC +// ` +// rows, err := db.Query(query, appID) +// if err != nil { +// return nil, err +// } +// defer rows.Close() + +// var volumes []Volume +// for rows.Next() { +// var vol Volume +// err := rows.Scan(&vol.ID, &vol.AppID, &vol.Name, &vol.HostPath, &vol.ContainerPath, &vol.ReadOnly, &vol.CreatedAt) +// if err != nil { +// return nil, err +// } +// volumes = append(volumes, vol) +// } + +// return volumes, rows.Err() +// } + +// func GetVolumeByID(id int64) (*Volume, error) { +// query := ` +// SELECT id, app_id, name, host_path, container_path, read_only, created_at +// FROM volumes +// WHERE id = ? +// ` +// var vol Volume +// err := db.QueryRow(query, id).Scan(&vol.ID, &vol.AppID, &vol.Name, &vol.HostPath, &vol.ContainerPath, &vol.ReadOnly, &vol.CreatedAt) +// if err == sql.ErrNoRows { +// return nil, nil +// } +// if err != nil { +// return nil, err +// } +// return &vol, nil +// } + +// func CreateVolume(appID int64, name, hostPath, containerPath string, readOnly bool) (*Volume, error) { +// query := ` +// INSERT INTO volumes (app_id, name, host_path, container_path, read_only, created_at) +// VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP) +// ` +// result, err := db.Exec(query, appID, name, hostPath, containerPath, readOnly) +// if err != nil { +// return nil, err +// } + +// id, err := result.LastInsertId() +// if err != nil { +// return nil, err +// } + +// return GetVolumeByID(id) +// } + +// func UpdateVolume(id int64, name, hostPath, containerPath string, readOnly bool) error { +// query := ` +// UPDATE volumes +// SET name = ?, host_path = ?, container_path = ?, read_only = ? +// WHERE id = ? +// ` +// _, err := db.Exec(query, name, hostPath, containerPath, readOnly, id) +// return err +// } + +// func DeleteVolume(id int64) error { +// query := `DELETE FROM volumes WHERE id = ?` +// _, err := db.Exec(query, id) +// return err +// } From 4a6ae98739c64f6cd39601331bcf5d66fefaf2e4 Mon Sep 17 00:00:00 2001 From: Tanish Date: Thu, 15 Jan 2026 13:58:23 +0530 Subject: [PATCH 12/16] fixed errors and removed old sql dependencies --- server/api/handlers/projects/createProject.go | 10 +- server/api/handlers/projects/updateProject.go | 20 +- server/api/handlers/updates/update.go | 9 +- server/docker/deployer.go | 325 ++++++++++++++++-- server/docker/deployerMain.go | 8 +- server/docker/loadDeployment.go | 19 +- server/main.go | 11 +- server/models/app.go | 2 +- server/models/temp.go | 1 + server/queue/deployQueue.go | 6 +- server/queue/handleWork.go | 4 +- server/queue/main.go | 6 +- 12 files changed, 353 insertions(+), 68 deletions(-) diff --git a/server/api/handlers/projects/createProject.go b/server/api/handlers/projects/createProject.go index d80ce04..2ab25a4 100644 --- a/server/api/handlers/projects/createProject.go +++ b/server/api/handlers/projects/createProject.go @@ -1,7 +1,6 @@ package projects import ( - "database/sql" "encoding/json" "net/http" @@ -42,12 +41,13 @@ func CreateProject(w http.ResponseWriter, r *http.Request) { OwnerID: userData.ID, } if input.Description != "" { - project.Description = sql.NullString{String: input.Description, Valid: true} + desc := input.Description + project.Description = &desc + } else { + project.Description = nil } if input.Tags != nil { - for _, tag := range input.Tags { - project.Tags = append(project.Tags, sql.NullString{String: tag, Valid: true}) - } + project.Tags = input.Tags } err := project.InsertInDB() diff --git a/server/api/handlers/projects/updateProject.go b/server/api/handlers/projects/updateProject.go index efe1a53..7eb8a9b 100644 --- a/server/api/handlers/projects/updateProject.go +++ b/server/api/handlers/projects/updateProject.go @@ -57,19 +57,23 @@ func UpdateProject(w http.ResponseWriter, r *http.Request) { return } - tags := make([]sql.NullString, len(input.Tags)) - for i, tag := range input.Tags { - tags[i] = sql.NullString{ - String: tag, - Valid: true, - } + // tags := make([]string, len(input.Tags)) + // for i, tag := range input.Tags { + // tags[i] = tag + // } + var desc *string + if input.Description != "" { + desc = &input.Description + + } else { + desc = nil } project := &models.Project{ ID: projectId, Name: input.Name, - Description: sql.NullString{String: input.Description, Valid: input.Description != ""}, - Tags: tags, + Description: desc, + Tags: input.Tags, } err = models.UpdateProject(project) diff --git a/server/api/handlers/updates/update.go b/server/api/handlers/updates/update.go index 3e84aa2..8cce9dd 100644 --- a/server/api/handlers/updates/update.go +++ b/server/api/handlers/updates/update.go @@ -270,13 +270,16 @@ func ClearStuckUpdate(w http.ResponseWriter, r *http.Request) { // Only allow clearing in_progress updates if updateLog.Status != "in_progress" { - handlers.SendResponse(w, http.StatusBadRequest, false, nil, "Can only clear in_progress updates", "Current status: "+updateLog.Status) + handlers.SendResponse(w, http.StatusBadRequest, false, nil, "Can only clear in_progress updates", "Current status: "+string(updateLog.Status)) return } - + currentLogs := "" + if updateLog.Logs != nil { + currentLogs = *updateLog.Logs + } // Mark as failed with note that it was manually cleared errMsg := "Update was manually cleared by administrator" - clearLog := updateLog.Logs + "\n⚠️ " + errMsg + "\n" + clearLog := currentLogs + "\n⚠️ " + errMsg + "\n" err = models.UpdateUpdateLogStatus(id, "failed", clearLog, &errMsg) if err != nil { log.Error().Err(err).Int64("update_log_id", id).Msg("Failed to clear stuck update") diff --git a/server/docker/deployer.go b/server/docker/deployer.go index 83ceaeb..2f34af5 100644 --- a/server/docker/deployer.go +++ b/server/docker/deployer.go @@ -1,7 +1,6 @@ package docker import ( - "database/sql" "encoding/json" "fmt" "os" @@ -12,9 +11,10 @@ import ( "github.com/corecollectives/mist/constants" "github.com/corecollectives/mist/models" "github.com/corecollectives/mist/utils" + "gorm.io/gorm" ) -func DeployApp(dep *models.Deployment, app *models.App, appContextPath, imageTag, containerName string, db *sql.DB, logfile *os.File, logger *utils.DeploymentLogger) error { +func DeployApp(dep *models.Deployment, app *models.App, appContextPath, imageTag, containerName string, db *gorm.DB, logfile *os.File, logger *utils.DeploymentLogger) error { logger.Info("Starting deployment process") @@ -203,45 +203,43 @@ func DeployApp(dep *models.Deployment, app *models.App, appContextPath, imageTag return nil } -func UpdateDeployment(dep *models.Deployment, db *sql.DB) error { - stmt, err := db.Prepare("UPDATE deployments SET status=?, stage=?, progress=?, logs=?, error_message=?, finished_at=? WHERE id=?") - if err != nil { - return err - } - defer stmt.Close() - - _, err = stmt.Exec(dep.Status, dep.Stage, dep.Progress, dep.Logs, dep.ErrorMessage, dep.FinishedAt, dep.ID) - return err +func UpdateDeployment(dep *models.Deployment, db *gorm.DB) error { + return db.Model(dep).Updates(map[string]interface{}{ + "status": dep.Status, + "stage": dep.Stage, + "progress": dep.Progress, + "logs": dep.Logs, + "error_message": dep.ErrorMessage, + "finished_at": dep.FinishedAt, + }).Error } func GetLogsPath(commitHash string, depId int64) string { return filepath.Join(constants.Constants["LogPath"].(string), commitHash+strconv.FormatInt(depId, 10)+"_build_logs") } -func UpdateAppStatus(appID int64, status string, db *sql.DB) error { - query := `UPDATE apps SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?` - _, err := db.Exec(query, status, appID) - return err +func UpdateAppStatus(appID int64, status string, db *gorm.DB) error { + return db.Model(&models.App{ID: appID}).Update("status", status).Error } -func GetDeploymentConfig(deploymentID int64, app *models.App, db *sql.DB) (int, []string, map[string]string, error) { +func GetDeploymentConfig(deploymentID int64, app *models.App, db *gorm.DB) (int, []string, map[string]string, error) { appID, err := models.GetAppIDByDeploymentID(deploymentID) if err != nil { return 0, nil, nil, fmt.Errorf("get app ID failed: %w", err) } - var port *int - err = db.QueryRow("SELECT port FROM apps WHERE id = ?", appID).Scan(&port) + var port int + err = db.Model(&models.App{}).Select("port").Where("id = ?", appID).Scan(&port).Error if err != nil { return 0, nil, nil, fmt.Errorf("get port failed: %w", err) } - if port == nil { - defaultPort := 3000 - port = &defaultPort + + if port == 0 { + port = 3000 } domains, err := models.GetDomainsByAppID(appID) - if err != nil && err != sql.ErrNoRows { + if err != nil { return 0, nil, nil, fmt.Errorf("get domains failed: %w", err) } @@ -251,7 +249,7 @@ func GetDeploymentConfig(deploymentID int64, app *models.App, db *sql.DB) (int, } envs, err := models.GetEnvVariablesByAppID(appID) - if err != nil && err != sql.ErrNoRows { + if err != nil { return 0, nil, nil, fmt.Errorf("get env variables failed: %w", err) } @@ -273,5 +271,284 @@ func GetDeploymentConfig(deploymentID int64, app *models.App, db *sql.DB) (int, envMap[env.Key] = env.Value } - return *port, domainStrings, envMap, nil + return port, domainStrings, envMap, nil } + +// package docker + +// import ( +// "database/sql" +// "encoding/json" +// "fmt" +// "os" +// "path/filepath" +// "strconv" +// "time" + +// "github.com/corecollectives/mist/constants" +// "github.com/corecollectives/mist/models" +// "github.com/corecollectives/mist/utils" +// "gorm.io/gorm" +// ) + +// func DeployApp(dep *models.Deployment, app *models.App, appContextPath, imageTag, containerName string, db *gorm.DB, logfile *os.File, logger *utils.DeploymentLogger) error { + +// logger.Info("Starting deployment process") + +// logger.Info("Getting port, domains, and environment variables") +// port, domains, envVars, err := GetDeploymentConfig(dep.ID, app, db) +// if err != nil { +// logger.Error(err, "Failed to get deployment configuration") +// dep.Status = "failed" +// dep.Stage = "failed" +// dep.Progress = 0 +// errMsg := fmt.Sprintf("Failed to get deployment config: %v", err) +// dep.ErrorMessage = &errMsg +// UpdateDeployment(dep, db) +// models.UpdateDeploymentStatus(dep.ID, "failed", "failed", 0, &errMsg) +// return fmt.Errorf("get deployment config failed: %w", err) +// } + +// logger.InfoWithFields("Configuration loaded", map[string]interface{}{ +// "domains": domains, +// "port": port, +// "envVars": len(envVars), +// "appType": app.AppType, +// }) + +// if app.AppType == models.AppTypeDatabase { +// logger.Info("Database app detected - pulling Docker image instead of building") + +// if app.TemplateName == nil || *app.TemplateName == "" { +// logger.Error(nil, "Database app missing template name") +// dep.Status = "failed" +// dep.Stage = "failed" +// dep.Progress = 0 +// errMsg := "Database app requires a template name" +// dep.ErrorMessage = &errMsg +// UpdateDeployment(dep, db) +// models.UpdateDeploymentStatus(dep.ID, "failed", "failed", 0, &errMsg) +// return fmt.Errorf("database app missing template") +// } + +// template, err := models.GetServiceTemplateByName(*app.TemplateName) +// if err != nil { +// logger.Error(err, "Failed to get service template") +// dep.Status = "failed" +// dep.Stage = "failed" +// dep.Progress = 0 +// errMsg := fmt.Sprintf("Failed to get template: %v", err) +// dep.ErrorMessage = &errMsg +// UpdateDeployment(dep, db) +// models.UpdateDeploymentStatus(dep.ID, "failed", "failed", 0, &errMsg) +// return fmt.Errorf("get template failed: %w", err) +// } + +// if template == nil { +// logger.Error(nil, "Template not found") +// dep.Status = "failed" +// dep.Stage = "failed" +// dep.Progress = 0 +// errMsg := fmt.Sprintf("Template not found: %s", *app.TemplateName) +// dep.ErrorMessage = &errMsg +// UpdateDeployment(dep, db) +// models.UpdateDeploymentStatus(dep.ID, "failed", "failed", 0, &errMsg) +// return fmt.Errorf("template not found") +// } + +// dep.Status = "building" +// dep.Stage = "pulling" +// dep.Progress = 50 +// UpdateDeployment(dep, db) +// models.UpdateDeploymentStatus(dep.ID, "building", "pulling", 50, nil) + +// imageName := template.DockerImage +// if template.DockerImageVersion != nil && *template.DockerImageVersion != "" { +// imageName = imageName + ":" + *template.DockerImageVersion +// } + +// logger.InfoWithFields("Pulling Docker image", map[string]interface{}{ +// "image": imageName, +// }) + +// if err := PullDockerImage(imageName, logfile); err != nil { +// logger.Error(err, "Docker image pull failed") +// dep.Status = "failed" +// dep.Stage = "failed" +// dep.Progress = 0 +// errMsg := fmt.Sprintf("Pull failed: %v", err) +// dep.ErrorMessage = &errMsg +// UpdateDeployment(dep, db) +// models.UpdateDeploymentStatus(dep.ID, "failed", "failed", 0, &errMsg) +// UpdateAppStatus(app.ID, "error", db) +// return fmt.Errorf("pull image failed: %w", err) +// } + +// logger.Info("Docker image pulled successfully") +// imageTag = imageName + +// } else { +// dep.Status = "building" +// dep.Stage = "building" +// dep.Progress = 50 +// UpdateDeployment(dep, db) +// models.UpdateDeploymentStatus(dep.ID, "building", "building", 50, nil) + +// logger.Info("Building Docker image with environment variables") +// if err := BuildImage(imageTag, appContextPath, envVars, logfile); err != nil { +// logger.Error(err, "Docker image build failed") +// dep.Status = "failed" +// dep.Stage = "failed" +// dep.Progress = 0 +// errMsg := fmt.Sprintf("Build failed: %v", err) +// dep.ErrorMessage = &errMsg +// UpdateDeployment(dep, db) +// models.UpdateDeploymentStatus(dep.ID, "failed", "failed", 0, &errMsg) +// UpdateAppStatus(app.ID, "error", db) +// return fmt.Errorf("build image failed: %w", err) +// } + +// logger.Info("Docker image built successfully") +// } + +// dep.Status = "deploying" +// dep.Stage = "deploying" +// dep.Progress = 80 +// UpdateDeployment(dep, db) +// models.UpdateDeploymentStatus(dep.ID, "deploying", "deploying", 80, nil) + +// logger.Info("Stopping existing container if exists") +// err = StopRemoveContainer(containerName, logfile) +// if err != nil { +// logger.Error(err, "Failed to stop/remove existing container") +// dep.Status = "failed" +// dep.Stage = "failed" +// dep.Progress = 0 +// errMsg := fmt.Sprintf("Failed to stop/remove container: %v", err) +// dep.ErrorMessage = &errMsg +// UpdateDeployment(dep, db) +// models.UpdateDeploymentStatus(dep.ID, "failed", "failed", 0, &errMsg) +// UpdateAppStatus(app.ID, "error", db) +// return fmt.Errorf("stop/remove container failed: %w", err) +// } + +// logger.InfoWithFields("Running container", map[string]interface{}{ +// "domains": domains, +// "port": port, +// "envVars": len(envVars), +// "appType": app.AppType, +// }) + +// if err := RunContainer(app, imageTag, containerName, domains, port, envVars, logfile); err != nil { +// logger.Error(err, "Failed to run container") +// dep.Status = "failed" +// dep.Stage = "failed" +// dep.Progress = 0 +// errMsg := fmt.Sprintf("Failed to run container: %v", err) +// dep.ErrorMessage = &errMsg +// UpdateDeployment(dep, db) +// models.UpdateDeploymentStatus(dep.ID, "failed", "failed", 0, &errMsg) +// UpdateAppStatus(app.ID, "error", db) +// return fmt.Errorf("run container failed: %w", err) +// } + +// dep.Status = "success" +// dep.Stage = "success" +// dep.Progress = 100 +// now := time.Now() +// dep.FinishedAt = &now +// UpdateDeployment(dep, db) +// models.UpdateDeploymentStatus(dep.ID, "success", "success", 100, nil) + +// logger.Info("Updating app status to running") +// err = UpdateAppStatus(app.ID, "running", db) +// if err != nil { +// logger.Error(err, "Failed to update app status (non-fatal)") +// } + +// logger.Info("Cleaning up old Docker images") +// if err := CleanupOldImages(app.ID, 5); err != nil { +// logger.Error(err, "Failed to cleanup old images (non-fatal)") +// } + +// logger.InfoWithFields("Deployment succeeded", map[string]interface{}{ +// "deployment_id": dep.ID, +// "container": containerName, +// "app_status": "running", +// }) + +// return nil +// } + +// func UpdateDeployment(dep *models.Deployment, db *gorm.DB) error { +// stmt, err := db.Prepare("UPDATE deployments SET status=?, stage=?, progress=?, logs=?, error_message=?, finished_at=? WHERE id=?") +// if err != nil { +// return err +// } +// defer stmt.Close() + +// _, err = stmt.Exec(dep.Status, dep.Stage, dep.Progress, dep.Logs, dep.ErrorMessage, dep.FinishedAt, dep.ID) +// return err +// } + +// func GetLogsPath(commitHash string, depId int64) string { +// return filepath.Join(constants.Constants["LogPath"].(string), commitHash+strconv.FormatInt(depId, 10)+"_build_logs") +// } + +// func UpdateAppStatus(appID int64, status string, db *sql.DB) error { +// query := `UPDATE apps SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?` +// _, err := db.Exec(query, status, appID) +// return err +// } + +// func GetDeploymentConfig(deploymentID int64, app *models.App, db *sql.DB) (int, []string, map[string]string, error) { +// appID, err := models.GetAppIDByDeploymentID(deploymentID) +// if err != nil { +// return 0, nil, nil, fmt.Errorf("get app ID failed: %w", err) +// } + +// var port *int +// err = db.QueryRow("SELECT port FROM apps WHERE id = ?", appID).Scan(&port) +// if err != nil { +// return 0, nil, nil, fmt.Errorf("get port failed: %w", err) +// } +// if port == nil { +// defaultPort := 3000 +// port = &defaultPort +// } + +// domains, err := models.GetDomainsByAppID(appID) +// if err != nil && err != sql.ErrNoRows { +// return 0, nil, nil, fmt.Errorf("get domains failed: %w", err) +// } + +// var domainStrings []string +// for _, d := range domains { +// domainStrings = append(domainStrings, d.Domain) +// } + +// envs, err := models.GetEnvVariablesByAppID(appID) +// if err != nil && err != sql.ErrNoRows { +// return 0, nil, nil, fmt.Errorf("get env variables failed: %w", err) +// } + +// envMap := make(map[string]string) + +// if app.AppType == models.AppTypeDatabase && app.TemplateName != nil { +// template, err := models.GetServiceTemplateByName(*app.TemplateName) +// if err == nil && template != nil && template.DefaultEnvVars != nil { +// var defaultEnvs map[string]string +// if err := json.Unmarshal([]byte(*template.DefaultEnvVars), &defaultEnvs); err == nil { +// for k, v := range defaultEnvs { +// envMap[k] = v +// } +// } +// } +// } + +// for _, env := range envs { +// envMap[env.Key] = env.Value +// } + +// return *port, domainStrings, envMap, nil +// } diff --git a/server/docker/deployerMain.go b/server/docker/deployerMain.go index a28a8fe..1300316 100644 --- a/server/docker/deployerMain.go +++ b/server/docker/deployerMain.go @@ -1,7 +1,6 @@ package docker import ( - "database/sql" "fmt" "os" "path/filepath" @@ -9,9 +8,10 @@ import ( "github.com/corecollectives/mist/constants" "github.com/corecollectives/mist/models" "github.com/corecollectives/mist/utils" + "gorm.io/gorm" ) -func DeployerMain(Id int64, db *sql.DB, logFile *os.File, logger *utils.DeploymentLogger) (string, error) { +func DeployerMain(Id int64, db *gorm.DB, logFile *os.File, logger *utils.DeploymentLogger) (string, error) { dep, err := LoadDeployment(Id, db) if err != nil { logger.Error(err, "Failed to load deployment") @@ -19,7 +19,9 @@ func DeployerMain(Id int64, db *sql.DB, logFile *os.File, logger *utils.Deployme } var appId int64 - err = db.QueryRow("SELECT app_id FROM deployments WHERE id = ?", Id).Scan(&appId) + // err = db.QueryRow("SELECT app_id FROM deployments WHERE id = ?", Id).Scan(&appId) + err = db.Table("deployments").Select("app_id").Where("id = ?", Id).Take(&appId).Error + if err != nil { logger.Error(err, "Failed to get app_id") return "", fmt.Errorf("failed to get app_id: %w", err) diff --git a/server/docker/loadDeployment.go b/server/docker/loadDeployment.go index 196db66..77b0281 100644 --- a/server/docker/loadDeployment.go +++ b/server/docker/loadDeployment.go @@ -1,27 +1,18 @@ package docker import ( - "database/sql" - "github.com/corecollectives/mist/models" + "gorm.io/gorm" ) -func LoadDeployment(depId int64, db *sql.DB) (*models.Deployment, error) { - row := db.QueryRow("SELECT id, app_id, commit_hash, commit_message, triggered_by, logs, status, created_at, finished_at FROM deployments WHERE id = ?", depId) +func LoadDeployment(depId int64, db *gorm.DB) (*models.Deployment, error) { + // row := db.QueryRow("SELECT id, app_id, commit_hash, commit_message, triggered_by, logs, status, created_at, finished_at FROM deployments WHERE id = ?", depId) dep := &models.Deployment{} - var triggeredBy sql.NullInt64 - var finishedAt sql.NullTime - var logs sql.NullString - err := row.Scan(&dep.ID, &dep.AppID, &dep.CommitHash, &dep.CommitMessage, &triggeredBy, &logs, &dep.Status, &dep.CreatedAt, &finishedAt) + err := db.Table("deployments").Where("id=?", depId).First(&dep).Error + if err != nil { return nil, err } - if triggeredBy.Valid { - dep.TriggeredBy = &triggeredBy.Int64 - } - if finishedAt.Valid { - dep.FinishedAt = &finishedAt.Time - } return dep, nil } diff --git a/server/main.go b/server/main.go index d943101..2ec74ab 100644 --- a/server/main.go +++ b/server/main.go @@ -24,14 +24,14 @@ func main() { &models.EnvVariable{}, &models.GithubApp{}, &models.Project{}, - &models.ProjectMembers{}, + &models.ProjectMember{}, &models.GitProvider{}, &models.GithubInstallation{}, &models.AppRepositories{}, &models.Domain{}, &models.Volume{}, &models.Cron{}, - &models.Registries{}, + &models.Registry{}, &models.SystemSettings{}, &models.Logs{}, &models.AuditLog{}, @@ -46,7 +46,12 @@ func main() { log.Fatal().Err(err).Msg("Error initializing database") return } - defer dbInstance.Close() + sqldb, err := dbInstance.DB() + if err != nil { + log.Fatal().Err(err).Msg("Error getting sql.DB from gorm DB") + return + } + defer sqldb.Close() log.Info().Msg("Database initialized successfully") models.SetDB(dbInstance) diff --git a/server/models/app.go b/server/models/app.go index 4788877..1b55a21 100644 --- a/server/models/app.go +++ b/server/models/app.go @@ -195,7 +195,7 @@ func GetAppRepoInfo(appId int64) (string, string, int64, string, error) { return repo, app.GitBranch, app.ProjectID, app.Name, nil } -func getAppRepoAndBranch(appId int64) (string, string, error) { +func GetAppRepoAndBranch(appId int64) (string, string, error) { var app App err := db.Select("git_repository, git_branch").First(&app, appId).Error if err != nil { diff --git a/server/models/temp.go b/server/models/temp.go index e69de29..778a4c0 100644 --- a/server/models/temp.go +++ b/server/models/temp.go @@ -0,0 +1 @@ +package models \ No newline at end of file diff --git a/server/queue/deployQueue.go b/server/queue/deployQueue.go index 5418e39..15d1c8d 100644 --- a/server/queue/deployQueue.go +++ b/server/queue/deployQueue.go @@ -2,11 +2,11 @@ package queue import ( "context" - "database/sql" "fmt" "sync" "github.com/rs/zerolog/log" + "gorm.io/gorm" ) type Queue struct { @@ -18,7 +18,7 @@ type Queue struct { var queue *Queue -func NewQueue(buffer int, db *sql.DB) *Queue { +func NewQueue(buffer int, db *gorm.DB) *Queue { ctx, cancel := context.WithCancel(context.Background()) q := &Queue{ jobs: make(chan int64, buffer), @@ -36,7 +36,7 @@ func GetQueue() *Queue { return queue } -func (q *Queue) StartWorker(db *sql.DB) { +func (q *Queue) StartWorker(db *gorm.DB) { q.wg.Add(1) go func() { defer q.wg.Done() diff --git a/server/queue/handleWork.go b/server/queue/handleWork.go index d6dfed6..3b09c8c 100644 --- a/server/queue/handleWork.go +++ b/server/queue/handleWork.go @@ -1,7 +1,6 @@ package queue import ( - "database/sql" "fmt" "sync" @@ -10,6 +9,7 @@ import ( "github.com/corecollectives/mist/github" "github.com/corecollectives/mist/models" "github.com/corecollectives/mist/utils" + "gorm.io/gorm" ) // to prevent concurrent deployments of same app @@ -19,7 +19,7 @@ import ( // then this will be helpful var deploymentLocks sync.Map -func (q *Queue) HandleWork(id int64, db *sql.DB) { +func (q *Queue) HandleWork(id int64, db *gorm.DB) { defer func() { if r := recover(); r != nil { errMsg := fmt.Sprintf("panic during deployment: %v", r) diff --git a/server/queue/main.go b/server/queue/main.go index bff4daf..0bb1675 100644 --- a/server/queue/main.go +++ b/server/queue/main.go @@ -1,8 +1,10 @@ package queue -import "database/sql" +import ( + "gorm.io/gorm" +) -func InitQueue(db *sql.DB) *Queue { +func InitQueue(db *gorm.DB) *Queue { q := NewQueue(5, db) return q } From 75f5b504fb9c378bc1b0ed342c65b1275d0c2b56 Mon Sep 17 00:00:00 2001 From: Tanish Date: Thu, 15 Jan 2026 14:52:28 +0530 Subject: [PATCH 13/16] gormified everythnig,old code successfully archived --- server/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/main.go b/server/main.go index 2ec74ab..ed76312 100644 --- a/server/main.go +++ b/server/main.go @@ -40,6 +40,7 @@ func main() { &models.Session{}, &models.Notification{}, &models.UpdateLog{}, + &models.SystemSettingsDbVer{}, //this needs checking ) _ = queue.InitQueue(dbInstance) if err != nil { From 6a25e3f78498e9d8dee01f022460423b8cc2f03e Mon Sep 17 00:00:00 2001 From: Tanish Date: Thu, 15 Jan 2026 18:48:23 +0530 Subject: [PATCH 14/16] fixed system setting table --- server/main.go | 4 ++-- server/models/systemSettings.go | 8 ++++---- server/models/systemSettingsDbVer.go | 7 ------- 3 files changed, 6 insertions(+), 13 deletions(-) delete mode 100644 server/models/systemSettingsDbVer.go diff --git a/server/main.go b/server/main.go index ed76312..2388a89 100644 --- a/server/main.go +++ b/server/main.go @@ -32,7 +32,7 @@ func main() { &models.Volume{}, &models.Cron{}, &models.Registry{}, - &models.SystemSettings{}, + &models.SystemSettingEntry{}, &models.Logs{}, &models.AuditLog{}, &models.ServiceTemplate{}, @@ -40,7 +40,7 @@ func main() { &models.Session{}, &models.Notification{}, &models.UpdateLog{}, - &models.SystemSettingsDbVer{}, //this needs checking + // &models.SystemSettingsDbVer{}, //this needs checking ) _ = queue.InitQueue(dbInstance) if err != nil { diff --git a/server/models/systemSettings.go b/server/models/systemSettings.go index 262a9bf..8af0dcf 100644 --- a/server/models/systemSettings.go +++ b/server/models/systemSettings.go @@ -23,13 +23,13 @@ type SystemSettings struct { AutoCleanupImages bool `json:"autoCleanupImages"` } -type systemSettingEntry struct { +type SystemSettingEntry struct { Key string `gorm:"primaryKey" json:"key"` Value string `json:"value"` UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` } -func (systemSettingEntry) TableName() string { +func (SystemSettingEntry) TableName() string { return "system_settings" } @@ -42,7 +42,7 @@ func generateRandomSecret(length int) (string, error) { } func GetSystemSetting(key string) (string, error) { - var entry systemSettingEntry + var entry SystemSettingEntry err := db.Where("key = ?", key).First(&entry).Error if err != nil { @@ -55,7 +55,7 @@ func GetSystemSetting(key string) (string, error) { } func SetSystemSetting(key, value string) error { - entry := systemSettingEntry{ + entry := SystemSettingEntry{ Key: key, Value: value, } diff --git a/server/models/systemSettingsDbVer.go b/server/models/systemSettingsDbVer.go deleted file mode 100644 index b285fdc..0000000 --- a/server/models/systemSettingsDbVer.go +++ /dev/null @@ -1,7 +0,0 @@ -package models - -type SystemSettingsDbVer struct { - key string `gorm:"primaryKey"` - value string `gorm:"not null"` - updatedAt int64 `gorm:"autoUpdateTime"` -} From abba0d9fba7185a2ca7b2c1b6326382677340ea0 Mon Sep 17 00:00:00 2001 From: 07calc Date: Thu, 15 Jan 2026 20:41:12 +0530 Subject: [PATCH 15/16] fix: gorm existing table issues --- cli/cmd/db.go | 15 +- cli/cmd/user.go | 1 - cli/go.mod | 9 +- cli/go.sum | 20 ++- server/api/handlers/applications/preview.go | 5 +- server/api/handlers/auth/login.go | 5 +- server/api/handlers/auth/me.go | 4 +- server/api/handlers/github/repositories.go | 5 +- server/api/handlers/projects/deleteProject.go | 5 +- .../api/handlers/projects/getProjectFromId.go | 5 +- server/api/handlers/projects/updateMembers.go | 5 +- server/api/handlers/projects/updateProject.go | 5 +- server/api/handlers/users/deleteUser.go | 5 +- server/api/handlers/users/getUserById.go | 5 +- server/db/main.go | 7 +- server/db/migrations.go | 151 ++++++++++++++++++ server/github/types.go | 6 +- server/github/webHook.go | 4 +- server/main.go | 28 ---- 19 files changed, 218 insertions(+), 72 deletions(-) create mode 100644 server/db/migrations.go diff --git a/cli/cmd/db.go b/cli/cmd/db.go index d667d82..557a78a 100644 --- a/cli/cmd/db.go +++ b/cli/cmd/db.go @@ -1,36 +1,27 @@ package cmd import ( - "database/sql" "fmt" "os" "github.com/corecollectives/mist/models" _ "github.com/mattn/go-sqlite3" + "gorm.io/driver/sqlite" + "gorm.io/gorm" ) const dbPath = "/var/lib/mist/mist.db" -// initDB initializes the database connection for CLI -// It expects the database file to already exist func initDB() error { - // Check if database file exists if _, err := os.Stat(dbPath); os.IsNotExist(err) { return fmt.Errorf("database file not found at %s. Please ensure Mist is installed and running", dbPath) } - // Open database connection - db, err := sql.Open("sqlite3", dbPath) + db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) if err != nil { return fmt.Errorf("failed to open database: %v", err) } - // Test connection - if err := db.Ping(); err != nil { - return fmt.Errorf("failed to connect to database: %v", err) - } - - // Set the database instance for models models.SetDB(db) return nil } diff --git a/cli/cmd/user.go b/cli/cmd/user.go index d836fe3..1c546bf 100644 --- a/cli/cmd/user.go +++ b/cli/cmd/user.go @@ -128,7 +128,6 @@ func changePassword(args []string) { } func listUsers(args []string) { - // Initialize database if err := initDB(); err != nil { fmt.Printf("Error: %v\n", err) os.Exit(1) diff --git a/cli/go.mod b/cli/go.mod index 64b2941..a61e38e 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -4,16 +4,23 @@ go 1.25.1 require ( github.com/corecollectives/mist v0.0.0 + github.com/mattn/go-sqlite3 v1.14.33 golang.org/x/crypto v0.43.0 golang.org/x/term v0.36.0 + gorm.io/driver/sqlite v1.6.0 + gorm.io/gorm v1.31.1 ) require ( + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/rs/zerolog v1.34.0 // indirect golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/corecollectives/mist => ../server diff --git a/cli/go.sum b/cli/go.sum index 5582f98..f1199d7 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -1,12 +1,18 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= -github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= +github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= @@ -20,3 +26,13 @@ golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= +gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/server/api/handlers/applications/preview.go b/server/api/handlers/applications/preview.go index 4640afd..7d3770f 100644 --- a/server/api/handlers/applications/preview.go +++ b/server/api/handlers/applications/preview.go @@ -1,8 +1,8 @@ package applications import ( - "database/sql" "encoding/json" + "errors" "fmt" "net" "net/http" @@ -10,6 +10,7 @@ import ( "github.com/corecollectives/mist/api/handlers" "github.com/corecollectives/mist/api/middleware" "github.com/corecollectives/mist/models" + "gorm.io/gorm" ) func getOutboundIP() string { @@ -56,7 +57,7 @@ func GetPreviewURL(w http.ResponseWriter, r *http.Request) { domain, err := models.GetPrimaryDomainByAppID(req.AppID) if err != nil { - if err == sql.ErrNoRows || err.Error() == "sql: no rows in result set" { + if errors.Is(err, gorm.ErrRecordNotFound) { app, appErr := models.GetApplicationByID(req.AppID) if appErr != nil { handlers.SendResponse(w, http.StatusInternalServerError, false, nil, "Failed to get application", appErr.Error()) diff --git a/server/api/handlers/auth/login.go b/server/api/handlers/auth/login.go index 4b6729d..b0b91dd 100644 --- a/server/api/handlers/auth/login.go +++ b/server/api/handlers/auth/login.go @@ -1,8 +1,8 @@ package auth import ( - "database/sql" "encoding/json" + "errors" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/corecollectives/mist/api/middleware" "github.com/corecollectives/mist/models" "github.com/rs/zerolog/log" + "gorm.io/gorm" ) func LoginHandler(w http.ResponseWriter, r *http.Request) { @@ -28,7 +29,7 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { return } user, err := models.GetUserByEmail(cred.Email) - if err == sql.ErrNoRows { + if errors.Is(err, gorm.ErrRecordNotFound) { handlers.SendResponse(w, http.StatusUnauthorized, false, nil, "Invalid email or password", "Unauthorized") return } else if err != nil { diff --git a/server/api/handlers/auth/me.go b/server/api/handlers/auth/me.go index 68bd7fb..270f5ac 100644 --- a/server/api/handlers/auth/me.go +++ b/server/api/handlers/auth/me.go @@ -1,7 +1,6 @@ package auth import ( - "database/sql" "errors" "net/http" @@ -9,6 +8,7 @@ import ( "github.com/corecollectives/mist/api/middleware" "github.com/corecollectives/mist/models" "github.com/corecollectives/mist/store" + "gorm.io/gorm" ) func MeHandler(w http.ResponseWriter, r *http.Request) { @@ -29,7 +29,7 @@ func MeHandler(w http.ResponseWriter, r *http.Request) { userId := claims.UserID user, err := models.GetUserByID(userId) - if errors.Is(err, sql.ErrNoRows) { + if errors.Is(err, gorm.ErrRecordNotFound) { handlers.SendResponse(w, http.StatusOK, true, map[string]interface{}{"setupRequired": setupRequired, "user": nil}, "User not found", "") return } else if err != nil { diff --git a/server/api/handlers/github/repositories.go b/server/api/handlers/github/repositories.go index fc8f2ed..698cc17 100644 --- a/server/api/handlers/github/repositories.go +++ b/server/api/handlers/github/repositories.go @@ -1,8 +1,8 @@ package github import ( - "database/sql" "encoding/json" + "errors" "fmt" "net/http" "time" @@ -11,6 +11,7 @@ import ( "github.com/corecollectives/mist/api/middleware" "github.com/corecollectives/mist/github" "github.com/corecollectives/mist/models" + "gorm.io/gorm" ) type RepoListResponse struct { @@ -26,7 +27,7 @@ func GetRepositories(w http.ResponseWriter, r *http.Request) { } installationID, err := models.GetInstallationID(int(userData.ID)) - if err == sql.ErrNoRows { + if errors.Is(err, gorm.ErrRecordNotFound) { handlers.SendResponse(w, http.StatusNotFound, false, nil, "no installation found for user", "No installation found") return } diff --git a/server/api/handlers/projects/deleteProject.go b/server/api/handlers/projects/deleteProject.go index e496859..1864a8f 100644 --- a/server/api/handlers/projects/deleteProject.go +++ b/server/api/handlers/projects/deleteProject.go @@ -1,13 +1,14 @@ package projects import ( - "database/sql" + "errors" "net/http" "strconv" "github.com/corecollectives/mist/api/handlers" "github.com/corecollectives/mist/api/middleware" "github.com/corecollectives/mist/models" + "gorm.io/gorm" ) func DeleteProject(w http.ResponseWriter, r *http.Request) { @@ -29,7 +30,7 @@ func DeleteProject(w http.ResponseWriter, r *http.Request) { } project, err := models.GetProjectByID(projectId) - if err == sql.ErrNoRows { + if errors.Is(err, gorm.ErrRecordNotFound) { handlers.SendResponse(w, http.StatusNotFound, false, nil, "Project not found", "no project with that ID") return } else if err != nil { diff --git a/server/api/handlers/projects/getProjectFromId.go b/server/api/handlers/projects/getProjectFromId.go index e60ba68..6c9b024 100644 --- a/server/api/handlers/projects/getProjectFromId.go +++ b/server/api/handlers/projects/getProjectFromId.go @@ -1,13 +1,14 @@ package projects import ( - "database/sql" + "errors" "net/http" "strconv" "github.com/corecollectives/mist/api/handlers" "github.com/corecollectives/mist/api/middleware" "github.com/corecollectives/mist/models" + "gorm.io/gorm" ) func GetProjectFromId(w http.ResponseWriter, r *http.Request) { @@ -31,7 +32,7 @@ func GetProjectFromId(w http.ResponseWriter, r *http.Request) { } project, err := models.GetProjectByID(projectId) - if err == sql.ErrNoRows { + if errors.Is(err, gorm.ErrRecordNotFound) { handlers.SendResponse(w, http.StatusNotFound, false, nil, "Project not found", "no project with that ID") return } else if err != nil { diff --git a/server/api/handlers/projects/updateMembers.go b/server/api/handlers/projects/updateMembers.go index 149c4d6..df865cd 100644 --- a/server/api/handlers/projects/updateMembers.go +++ b/server/api/handlers/projects/updateMembers.go @@ -1,14 +1,15 @@ package projects import ( - "database/sql" "encoding/json" + "errors" "net/http" "strconv" "github.com/corecollectives/mist/api/handlers" "github.com/corecollectives/mist/api/middleware" "github.com/corecollectives/mist/models" + "gorm.io/gorm" ) func UpdateMembers(w http.ResponseWriter, r *http.Request) { @@ -38,7 +39,7 @@ func UpdateMembers(w http.ResponseWriter, r *http.Request) { } project, err := models.GetProjectByID(projectId) - if err == sql.ErrNoRows { + if errors.Is(err, gorm.ErrRecordNotFound) { handlers.SendResponse(w, http.StatusNotFound, false, nil, "Project not found", "no such project") return } else if err != nil { diff --git a/server/api/handlers/projects/updateProject.go b/server/api/handlers/projects/updateProject.go index 7eb8a9b..71974e3 100644 --- a/server/api/handlers/projects/updateProject.go +++ b/server/api/handlers/projects/updateProject.go @@ -1,14 +1,15 @@ package projects import ( - "database/sql" "encoding/json" + "errors" "net/http" "strconv" "github.com/corecollectives/mist/api/handlers" "github.com/corecollectives/mist/api/middleware" "github.com/corecollectives/mist/models" + "gorm.io/gorm" ) func UpdateProject(w http.ResponseWriter, r *http.Request) { @@ -44,7 +45,7 @@ func UpdateProject(w http.ResponseWriter, r *http.Request) { } existingProject, err := models.GetProjectByID(projectId) - if err == sql.ErrNoRows { + if errors.Is(err, gorm.ErrRecordNotFound) { handlers.SendResponse(w, http.StatusNotFound, false, nil, "Project not found", "no such project") return } else if err != nil { diff --git a/server/api/handlers/users/deleteUser.go b/server/api/handlers/users/deleteUser.go index 4684de0..a3e6e32 100644 --- a/server/api/handlers/users/deleteUser.go +++ b/server/api/handlers/users/deleteUser.go @@ -1,7 +1,7 @@ package users import ( - "database/sql" + "errors" "net/http" "strconv" @@ -9,6 +9,7 @@ import ( "github.com/corecollectives/mist/api/middleware" "github.com/corecollectives/mist/models" "github.com/corecollectives/mist/store" + "gorm.io/gorm" ) func DeleteUser(w http.ResponseWriter, r *http.Request) { @@ -43,7 +44,7 @@ func DeleteUser(w http.ResponseWriter, r *http.Request) { return } userToDeleteRole, err := models.GetUserRole(int64(id)) - if err == sql.ErrNoRows { + if errors.Is(err, gorm.ErrRecordNotFound) { handlers.SendResponse(w, http.StatusNotFound, false, nil, "User not found", "No such user") return } else if err != nil { diff --git a/server/api/handlers/users/getUserById.go b/server/api/handlers/users/getUserById.go index d8ce507..37173d7 100644 --- a/server/api/handlers/users/getUserById.go +++ b/server/api/handlers/users/getUserById.go @@ -1,13 +1,14 @@ package users import ( - "database/sql" + "errors" "net/http" "strconv" "github.com/corecollectives/mist/api/handlers" "github.com/corecollectives/mist/api/middleware" "github.com/corecollectives/mist/models" + "gorm.io/gorm" ) func GetUserById(w http.ResponseWriter, r *http.Request) { @@ -30,7 +31,7 @@ func GetUserById(w http.ResponseWriter, r *http.Request) { } user, err := models.GetUserByID(userID) if err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, gorm.ErrRecordNotFound) { handlers.SendResponse(w, http.StatusNotFound, false, nil, "User not found", "No user exists with the given ID") return } diff --git a/server/db/main.go b/server/db/main.go index 584bd48..9bffea3 100644 --- a/server/db/main.go +++ b/server/db/main.go @@ -9,7 +9,6 @@ import ( _ "github.com/mattn/go-sqlite3" "gorm.io/driver/sqlite" "gorm.io/gorm" - "gorm.io/gorm/logger" ) func InitDB() (*gorm.DB, error) { @@ -25,11 +24,15 @@ func InitDB() (*gorm.DB, error) { return nil, fmt.Errorf("failed to create database directory: %v", err) } db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Info), + // Logger: logger.Default.LogMode(logger.Info), }) if err != nil { return nil, fmt.Errorf("failed to open database: %v", err) } + err = MigrateDb(db) + if err != nil { + return nil, err + } // if err := runMigrations(db); err != nil { // return nil, fmt.Errorf("failed to run migrations: %v", err) // } diff --git a/server/db/migrations.go b/server/db/migrations.go new file mode 100644 index 0000000..b07b282 --- /dev/null +++ b/server/db/migrations.go @@ -0,0 +1,151 @@ +package db + +import ( + "fmt" + "reflect" + "strings" + + "github.com/corecollectives/mist/models" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" +) + +func migrateExistingTable(db *gorm.DB, model interface{}) error { + migrator := db.Migrator() + + stmt := &gorm.Statement{DB: db} + if err := stmt.Parse(model); err != nil { + return fmt.Errorf("failed to parse model: %w", err) + } + + tableName := stmt.Schema.Table + + for _, field := range stmt.Schema.Fields { + if field.DBName == "" { + continue + } + + if !migrator.HasColumn(model, field.DBName) { + columnType := getSQLiteColumnType(field) + if columnType == "" { + continue + } + + sql := fmt.Sprintf("ALTER TABLE `%s` ADD COLUMN `%s` %s", tableName, field.DBName, columnType) + + if field.HasDefaultValue && field.DefaultValue != "" { + sql += fmt.Sprintf(" DEFAULT %s", field.DefaultValue) + } + + if err := db.Exec(sql).Error; err != nil { + fmt.Printf("migration.go: warning adding column %s.%s: %v\n", tableName, field.DBName, err) + } + } + } + + return nil +} + +func getSQLiteColumnType(field *schema.Field) string { + switch field.DataType { + case schema.Bool: + return "BOOLEAN" + case schema.Int, schema.Uint: + return "INTEGER" + case schema.Float: + return "REAL" + case schema.String: + return "TEXT" + case schema.Time: + return "DATETIME" + case schema.Bytes: + return "BLOB" + default: + kind := field.FieldType.Kind() + if kind == reflect.Ptr { + kind = field.FieldType.Elem().Kind() + } + switch kind { + case reflect.Bool: + return "BOOLEAN" + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return "INTEGER" + case reflect.Float32, reflect.Float64: + return "REAL" + case reflect.String: + return "TEXT" + case reflect.Struct: + typeName := field.FieldType.String() + if strings.Contains(typeName, "Time") || strings.Contains(typeName, "DeletedAt") { + return "DATETIME" + } + return "TEXT" + default: + return "TEXT" + } + } +} + +func MigrateDb(dbInstance *gorm.DB) error { + migrator := dbInstance.Migrator() + + allModels := []interface{}{ + &models.User{}, + &models.ApiToken{}, + &models.App{}, + &models.AuditLog{}, + &models.Backup{}, + &models.Deployment{}, + &models.EnvVariable{}, + &models.GithubApp{}, + &models.Project{}, + &models.ProjectMember{}, + &models.GitProvider{}, + &models.GithubInstallation{}, + &models.AppRepositories{}, + &models.Domain{}, + &models.Volume{}, + &models.Cron{}, + &models.Registry{}, + &models.SystemSettingEntry{}, + &models.Logs{}, + &models.ServiceTemplate{}, + &models.Session{}, + &models.Notification{}, + &models.UpdateLog{}, + } + + for _, model := range allModels { + if migrator.HasTable(model) { + if err := migrateExistingTable(dbInstance, model); err != nil { + fmt.Printf("migration.go: warning migrating existing table: %v\n", err) + } + } else { + if err := dbInstance.AutoMigrate(model); err != nil { + fmt.Printf("migration.go: error creating table: %v\n", err) + return err + } + } + } + + var wildCardDomain = models.SystemSettingEntry{ + Key: "wildcard_domain", + Value: " ", + } + var MistAppName = models.SystemSettingEntry{ + Key: "mist_app_name", + Value: "mist", + } + var Version = models.SystemSettingEntry{ + Key: "version", + Value: "1.0.4", + } + + dbInstance.Clauses(clause.Insert{Modifier: "OR IGNORE"}).Create(&wildCardDomain) + dbInstance.Clauses(clause.Insert{Modifier: "OR IGNORE"}).Create(&MistAppName) + dbInstance.Clauses(clause.Insert{Modifier: "OR REPLACE"}).Create(&Version) + + return nil +} diff --git a/server/github/types.go b/server/github/types.go index 1a517eb..27f4da5 100644 --- a/server/github/types.go +++ b/server/github/types.go @@ -1,7 +1,5 @@ package github -import "database/sql" - type PushEvent struct { Ref string `json:"ref"` Before string `json:"before"` @@ -186,8 +184,8 @@ type GithubApp struct { type GithubInstallation struct { InstallationID int - AccessToken sql.NullString - TokenExpiresAt sql.NullTime + AccessToken *string + TokenExpiresAt *string } type LatestCommit struct { diff --git a/server/github/webHook.go b/server/github/webHook.go index 249c779..3b7a24b 100644 --- a/server/github/webHook.go +++ b/server/github/webHook.go @@ -1,12 +1,12 @@ package github import ( - "database/sql" "errors" "strings" "github.com/corecollectives/mist/models" "github.com/rs/zerolog/log" + "gorm.io/gorm" ) func CreateDeploymentFromGithubPushEvent(evt PushEvent) (int64, error) { @@ -58,7 +58,7 @@ func CreateDeploymentFromGithubPushEvent(evt PushEvent) (int64, error) { } dep, err := models.GetDeploymentByAppIDAndCommitHash(appID, commit) - if err != nil && err != sql.ErrNoRows { + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { log.Error().Err(err). Int64("app_id", appID). Str("commit", commit). diff --git a/server/main.go b/server/main.go index 2388a89..051ffb3 100644 --- a/server/main.go +++ b/server/main.go @@ -14,34 +14,6 @@ func main() { utils.InitLogger() log.Info().Msg("Starting Mist server") dbInstance, err := db.InitDB() - dbInstance.AutoMigrate( - &models.User{}, - &models.ApiToken{}, - &models.App{}, - &models.AuditLog{}, - &models.Backup{}, - &models.Deployment{}, - &models.EnvVariable{}, - &models.GithubApp{}, - &models.Project{}, - &models.ProjectMember{}, - &models.GitProvider{}, - &models.GithubInstallation{}, - &models.AppRepositories{}, - &models.Domain{}, - &models.Volume{}, - &models.Cron{}, - &models.Registry{}, - &models.SystemSettingEntry{}, - &models.Logs{}, - &models.AuditLog{}, - &models.ServiceTemplate{}, - &models.ApiToken{}, - &models.Session{}, - &models.Notification{}, - &models.UpdateLog{}, - // &models.SystemSettingsDbVer{}, //this needs checking - ) _ = queue.InitQueue(dbInstance) if err != nil { log.Fatal().Err(err).Msg("Error initializing database") From 5ae7bcb2d8a7c3ea75f4ecad5bf5a77e47d47da9 Mon Sep 17 00:00:00 2001 From: 07calc Date: Thu, 15 Jan 2026 22:42:12 +0530 Subject: [PATCH 16/16] fix: gorm logging --- server/db/main.go | 3 +- server/db/migrations.go | 2 +- server/go.mod | 22 ++++++------ server/go.sum | 52 +++++++++++++++++++-------- server/lib/cleanup.go | 4 +-- server/models/deployment.go | 72 +++++++++++++++++++++---------------- server/models/updateLog.go | 3 -- 7 files changed, 95 insertions(+), 63 deletions(-) diff --git a/server/db/main.go b/server/db/main.go index 9bffea3..a7fb9f7 100644 --- a/server/db/main.go +++ b/server/db/main.go @@ -9,6 +9,7 @@ import ( _ "github.com/mattn/go-sqlite3" "gorm.io/driver/sqlite" "gorm.io/gorm" + "gorm.io/gorm/logger" ) func InitDB() (*gorm.DB, error) { @@ -24,7 +25,7 @@ func InitDB() (*gorm.DB, error) { return nil, fmt.Errorf("failed to create database directory: %v", err) } db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{ - // Logger: logger.Default.LogMode(logger.Info), + Logger: logger.Discard, }) if err != nil { return nil, fmt.Errorf("failed to open database: %v", err) diff --git a/server/db/migrations.go b/server/db/migrations.go index b07b282..e5efc68 100644 --- a/server/db/migrations.go +++ b/server/db/migrations.go @@ -140,7 +140,7 @@ func MigrateDb(dbInstance *gorm.DB) error { } var Version = models.SystemSettingEntry{ Key: "version", - Value: "1.0.4", + Value: "1.0.3", } dbInstance.Clauses(clause.Insert{Modifier: "OR IGNORE"}).Create(&wildCardDomain) diff --git a/server/go.mod b/server/go.mod index 4b3ffc5..77e377a 100644 --- a/server/go.mod +++ b/server/go.mod @@ -3,13 +3,19 @@ module github.com/corecollectives/mist go 1.25.1 require ( + github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/gorilla/websocket v1.5.3 github.com/mattn/go-sqlite3 v1.14.33 + github.com/moby/go-archive v0.2.0 + github.com/moby/moby/api v1.52.0 + github.com/moby/moby/client v0.2.1 github.com/rs/zerolog v1.34.0 github.com/shirou/gopsutil v3.21.11+incompatible golang.org/x/crypto v0.46.0 gopkg.in/yaml.v3 v3.0.1 + gorm.io/driver/sqlite v1.6.0 + gorm.io/gorm v1.31.1 ) require ( @@ -27,32 +33,27 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-git/gcfg/v2 v2.0.2 // indirect github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd // indirect - github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/klauspost/compress v1.18.2 // indirect - github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/compress v1.18.2 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/go-archive v0.2.0 // indirect - github.com/moby/moby/api v1.52.0 // indirect - github.com/moby/moby/client v0.2.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect github.com/pjbgf/sha1cd v0.5.0 // indirect github.com/sergi/go-diff v1.4.0 // indirect - github.com/stretchr/testify v1.11.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect @@ -61,12 +62,9 @@ require ( go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect - golang.org/x/sys v0.37.0 // indirect - golang.org/x/text v0.33.0 // indirect - gorm.io/driver/sqlite v1.6.0 // indirect - gorm.io/gorm v1.31.1 // indirect golang.org/x/net v0.48.0 // indirect golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect ) replace github.com/docker/docker/api => github.com/moby/moby/api v1.52.0-beta.2 diff --git a/server/go.sum b/server/go.sum index 17a841a..8c19508 100644 --- a/server/go.sum +++ b/server/go.sum @@ -1,7 +1,13 @@ +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -26,10 +32,14 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo= github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs= github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd h1:Gd/f9cGi/3h1JOPaa6er+CkKUGyGX2DBJdFbDKVO+R0= github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd/go.mod h1:d3XQcsHu1idnquxt48kAv+h+1MUiYKLH/e7LAzjP+pI= +github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251229094738-4b14af179146 h1:xYfxAopYyL44ot6dMBIb1Z1njFM0ZBQ99HdIB99KxLs= +github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251229094738-4b14af179146/go.mod h1:QE/75B8tBSLNGyUUbA9tw3EGHoFtYOtypa2h8YJxsWI= github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19 h1:0lz2eJScP8v5YZQsrEw+ggWC5jNySjg4bIZo5BIh6iI= github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19/go.mod h1:L+Evfcs7EdTqxwv854354cb6+++7TFL3hJn3Wy4g+3w= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -44,28 +54,34 @@ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keL github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= -github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= -github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -93,6 +109,8 @@ github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= @@ -103,9 +121,8 @@ github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMT github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= @@ -122,10 +139,12 @@ go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= @@ -135,21 +154,26 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/server/lib/cleanup.go b/server/lib/cleanup.go index c0214e1..6d03887 100644 --- a/server/lib/cleanup.go +++ b/server/lib/cleanup.go @@ -104,7 +104,7 @@ func cleanupUpdates() error { Str("version", currentVersion). Msg("Completing successful update that was interrupted by service restart") - completionLog := latestLog.Logs + "\n✅ Update completed successfully (verified on restart)\n" + completionLog := *latestLog.Logs + "\n✅ Update completed successfully (verified on restart)\n" err = models.UpdateUpdateLogStatus(latestLog.ID, "success", completionLog, nil) if err != nil { log.Error().Err(err).Int64("update_log_id", latestLog.ID).Msg("Failed to complete pending update") @@ -127,7 +127,7 @@ func cleanupUpdates() error { Msg("Update appears to have failed (version mismatch detected on startup)") errMsg := "Update process was interrupted and version does not match target" - failureLog := latestLog.Logs + "\n❌ " + errMsg + "\n" + failureLog := *latestLog.Logs + "\n❌ " + errMsg + "\n" err = models.UpdateUpdateLogStatus(latestLog.ID, "failed", failureLog, &errMsg) if err != nil { log.Error().Err(err).Int64("update_log_id", latestLog.ID).Msg("Failed to mark failed update") diff --git a/server/models/deployment.go b/server/models/deployment.go index 02f8e5c..d02fadb 100644 --- a/server/models/deployment.go +++ b/server/models/deployment.go @@ -500,40 +500,52 @@ func UpdateContainerInfo(depID int64, containerID, containerName, imageTag strin // return err // } func GetIncompleteDeployments() ([]Deployment, error) { - query := ` - SELECT id, app_id, commit_hash, commit_message, commit_author, triggered_by, - deployment_number, container_id, container_name, image_tag, - logs, build_logs_path, status, stage, progress, error_message, - created_at, started_at, finished_at, duration, is_active, rolled_back_from - FROM deployments - WHERE status = 'building' OR status = 'deploying' - ORDER BY created_at DESC - ` - - rows, err := db.Query(query) - if err != nil { - return nil, err - } - defer rows.Close() - var deployments []Deployment - for rows.Next() { - var d Deployment - if err := rows.Scan( - &d.ID, &d.AppID, &d.CommitHash, &d.CommitMessage, &d.CommitAuthor, - &d.TriggeredBy, &d.DeploymentNumber, &d.ContainerID, &d.ContainerName, - &d.ImageTag, &d.Logs, &d.BuildLogsPath, &d.Status, &d.Stage, &d.Progress, - &d.ErrorMessage, &d.CreatedAt, &d.StartedAt, &d.FinishedAt, &d.Duration, - &d.IsActive, &d.RolledBackFrom, - ); err != nil { - return nil, err - } - deployments = append(deployments, d) - } - if err := rows.Err(); err != nil { + err := db. + Where("status IN ?", []string{"building", "deploying"}). + Order("created_at DESC"). + Find(&deployments).Error + + if err != nil { return nil, err } return deployments, nil + // query := ` + // SELECT id, app_id, commit_hash, commit_message, commit_author, triggered_by, + // deployment_number, container_id, container_name, image_tag, + // logs, build_logs_path, status, stage, progress, error_message, + // created_at, started_at, finished_at, duration, is_active, rolled_back_from + // FROM deployments + // WHERE status = 'building' OR status = 'deploying' + // ORDER BY created_at DESC + // ` + // + // rows, err := db.Query(query) + // if err != nil { + // return nil, err + // } + // defer rows.Close() + // + // var deployments []Deployment + // for rows.Next() { + // var d Deployment + // if err := rows.Scan( + // &d.ID, &d.AppID, &d.CommitHash, &d.CommitMessage, &d.CommitAuthor, + // &d.TriggeredBy, &d.DeploymentNumber, &d.ContainerID, &d.ContainerName, + // &d.ImageTag, &d.Logs, &d.BuildLogsPath, &d.Status, &d.Stage, &d.Progress, + // &d.ErrorMessage, &d.CreatedAt, &d.StartedAt, &d.FinishedAt, &d.Duration, + // &d.IsActive, &d.RolledBackFrom, + // ); err != nil { + // return nil, err + // } + // deployments = append(deployments, d) + // } + // + // if err := rows.Err(); err != nil { + // return nil, err + // } + // + // return deployments, nil } diff --git a/server/models/updateLog.go b/server/models/updateLog.go index 7cb7920..1d9da56 100644 --- a/server/models/updateLog.go +++ b/server/models/updateLog.go @@ -174,7 +174,6 @@ func GetUpdateLogsAsString() (string, error) { return builder.String(), nil } -<<<<<<< HEAD func CheckAndCompletePendingUpdates() error { logs, err := GetUpdateLogs(1) @@ -585,5 +584,3 @@ func CheckAndCompletePendingUpdates() error { // return nil // } -======= ->>>>>>> main