store.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. package dbstore
  2. import (
  3. "context"
  4. "database/sql"
  5. "fmt"
  6. "os"
  7. "git.linuxforward.com/byop/byop-engine/models"
  8. _ "github.com/mattn/go-sqlite3" // Import SQLite driver for compatibility
  9. "gorm.io/driver/sqlite"
  10. "gorm.io/gorm"
  11. "gorm.io/gorm/logger"
  12. )
  13. // Store defines the interface for all database operations.
  14. // This will include methods for all models (User, Client, App, Component, Deployment, Ticket, etc.)
  15. type Store interface {
  16. // User methods
  17. CreateUser(ctx context.Context, user *models.User) error
  18. GetUserByEmail(ctx context.Context, email string) (*models.User, error)
  19. GetUserByID(ctx context.Context, id uint) (*models.User, error)
  20. GetUsers(ctx context.Context) ([]*models.User, error)
  21. UpdateUser(ctx context.Context, user *models.User) error
  22. DeleteUser(ctx context.Context, id uint) error
  23. CreateDefaultAdmin(ctx context.Context) error
  24. GetByUsername(ctx context.Context, username string) (*models.User, error)
  25. // Client methods
  26. CreateClient(ctx context.Context, client *models.Client) error
  27. GetAllClients(ctx context.Context) ([]*models.Client, error)
  28. GetClientByID(ctx context.Context, id uint) (*models.Client, error)
  29. UpdateClient(ctx context.Context, client *models.Client) error
  30. DeleteClient(ctx context.Context, id uint) error
  31. // App methods
  32. CreateApp(ctx context.Context, app *models.App) error
  33. GetAppByID(ctx context.Context, id uint) (*models.App, error)
  34. GetAppsByUserID(ctx context.Context, userID uint) ([]*models.App, error)
  35. UpdateApp(ctx context.Context, app *models.App) error
  36. DeleteApp(ctx context.Context, id uint) error
  37. UpdateAppStatus(ctx context.Context, appID uint, status string, message string) error
  38. UpdateAppPreview(ctx context.Context, appID uint, previewID uint, previewURL string) error
  39. GetAllApps(ctx context.Context) ([]*models.App, error)
  40. UpdateAppCurrentImage(ctx context.Context, appID uint, imageTag string, imageURI string) error
  41. // Component methods
  42. CreateComponent(ctx context.Context, component *models.Component) error
  43. GetComponentByID(ctx context.Context, id uint) (*models.Component, error)
  44. GetComponentsByUserID(ctx context.Context, userID uint) ([]*models.Component, error)
  45. GetAllComponents(ctx context.Context) ([]*models.Component, error)
  46. UpdateComponent(ctx context.Context, component *models.Component) error
  47. UpdateComponentStatus(ctx context.Context, componentID uint, status string, errorMsg string) error
  48. DeleteComponent(ctx context.Context, id uint) error
  49. // Deployment methods
  50. CreateDeployment(ctx context.Context, deployment *models.Deployment) error
  51. GetDeploymentByID(ctx context.Context, id uint) (*models.Deployment, error)
  52. GetDeploymentsByClientID(ctx context.Context, clientID uint) ([]*models.Deployment, error)
  53. GetAllDeployments(ctx context.Context) ([]*models.Deployment, error)
  54. UpdateDeployment(ctx context.Context, deployment *models.Deployment) error
  55. DeleteDeployment(ctx context.Context, id uint) error
  56. // Preview methods
  57. CreatePreview(ctx context.Context, preview *models.Preview) error
  58. GetPreviewByID(ctx context.Context, id uint) (*models.Preview, error)
  59. GetPreviewByAppID(ctx context.Context, appID uint) (*models.Preview, error)
  60. UpdatePreview(ctx context.Context, preview *models.Preview) error
  61. DeletePreview(ctx context.Context, id uint) error
  62. UpdatePreviewVPS(ctx context.Context, previewID uint, vpsID string, ipAddress string, previewURL string) error
  63. UpdatePreviewStatus(ctx context.Context, previewID uint, status string, errorMsg string) error
  64. UpdatePreviewBuildLogs(ctx context.Context, previewID uint, logs string) error
  65. UpdatePreviewDeployLogs(ctx context.Context, previewID uint, logs string) error
  66. GetAllPreviews(ctx context.Context) ([]*models.Preview, error)
  67. GetPreviewsByStatus(ctx context.Context, status string) ([]*models.Preview, error)
  68. GetPreviewsByAppID(ctx context.Context, appID uint) ([]*models.Preview, error)
  69. // Ticket methods
  70. CreateTicket(ctx context.Context, ticket *models.Ticket) error
  71. GetTicketByID(ctx context.Context, id uint) (*models.Ticket, error)
  72. GetTickets(ctx context.Context) ([]*models.Ticket, error)
  73. UpdateTicket(ctx context.Context, ticket *models.Ticket) error
  74. // TicketComment methods
  75. CreateTicketComment(ctx context.Context, comment *models.TicketComment) error
  76. GetTicketComments(ctx context.Context, ticketID uint) ([]*models.TicketComment, error)
  77. // BuildJob methods
  78. CreateBuildJob(ctx context.Context, job *models.BuildJob) error
  79. GetBuildJobByID(ctx context.Context, id uint) (*models.BuildJob, error)
  80. UpdateBuildJob(ctx context.Context, job *models.BuildJob) error
  81. UpdateBuildJobStatus(ctx context.Context, id uint, status models.BuildStatus, errorMessage string) error
  82. AppendBuildJobLog(ctx context.Context, id uint, logMessage string) error
  83. GetQueuedBuildJobs(ctx context.Context, limit int) ([]*models.BuildJob, error)
  84. GetBuildJobsByAppID(ctx context.Context, appID uint, page, pageSize int) ([]*models.BuildJob, int64, error)
  85. // General DB methods
  86. GetDB() *sql.DB
  87. GetGormDB() *gorm.DB
  88. Close() error
  89. }
  90. // SQLiteStore implements the Store interface for SQLite using GORM
  91. type SQLiteStore struct {
  92. db *gorm.DB
  93. rawDB *sql.DB // Keep for backward compatibility and specific raw SQL operations
  94. dsn string
  95. }
  96. // NewSQLiteStore initializes a new SQLiteStore with GORM
  97. func NewSQLiteStore(dataSourceName string) (*SQLiteStore, error) {
  98. // First check if the database file exists
  99. isNewDb := !fileExists(dataSourceName)
  100. if isNewDb {
  101. // Create the database file if it doesn't exist
  102. file, err := os.Create(dataSourceName)
  103. if err != nil {
  104. return nil, fmt.Errorf("failed to create SQLite database file: %w", err)
  105. }
  106. defer file.Close()
  107. }
  108. // Open GORM database
  109. gormDB, err := gorm.Open(sqlite.Open(dataSourceName), &gorm.Config{
  110. Logger: logger.Default.LogMode(logger.Silent), // Change to logger.Info for debugging
  111. })
  112. if err != nil {
  113. return nil, fmt.Errorf("failed to open database with GORM: %w", err)
  114. }
  115. // Get underlying SQL DB for backward compatibility
  116. rawDB, err := gormDB.DB()
  117. if err != nil {
  118. return nil, fmt.Errorf("failed to get underlying SQL DB: %w", err)
  119. }
  120. if err := rawDB.Ping(); err != nil {
  121. return nil, fmt.Errorf("failed to ping database: %w", err)
  122. }
  123. // Enable foreign keys in SQLite
  124. err = gormDB.Exec("PRAGMA foreign_keys = ON").Error
  125. if err != nil {
  126. return nil, fmt.Errorf("failed to enable foreign keys: %w", err)
  127. }
  128. // Auto-migrate all models
  129. err = gormDB.AutoMigrate(
  130. &models.User{},
  131. &models.Client{},
  132. &models.App{},
  133. &models.Component{},
  134. &models.Deployment{},
  135. &models.Preview{},
  136. &models.Ticket{},
  137. &models.TicketComment{},
  138. &models.Provider{},
  139. &models.BuildJob{},
  140. )
  141. if err != nil {
  142. return nil, fmt.Errorf("failed to auto-migrate models: %w", err)
  143. }
  144. store := &SQLiteStore{
  145. db: gormDB,
  146. rawDB: rawDB,
  147. dsn: dataSourceName,
  148. }
  149. return store, nil
  150. }
  151. // fileExists checks if a file exists
  152. func fileExists(filename string) bool {
  153. _, err := os.Stat(filename)
  154. return !os.IsNotExist(err)
  155. }
  156. // GetDB returns the raw SQL database instance for backward compatibility
  157. func (m *SQLiteStore) GetDB() *sql.DB {
  158. return m.rawDB
  159. }
  160. // GetGormDB returns the GORM database instance
  161. func (m *SQLiteStore) GetGormDB() *gorm.DB {
  162. return m.db
  163. }
  164. // Connect establishes a connection to the SQLite database
  165. func (m *SQLiteStore) Connect() error {
  166. // Connection is already established in NewSQLiteStore
  167. return nil
  168. }
  169. // Disconnect closes the connection to the SQLite database
  170. func (m *SQLiteStore) Disconnect() error {
  171. sqlDB, err := m.db.DB()
  172. if err != nil {
  173. return fmt.Errorf("failed to get underlying SQL DB: %w", err)
  174. }
  175. return sqlDB.Close()
  176. }
  177. // Close provides a more standard name for closing the database connection.
  178. // It simply calls Disconnect.
  179. func (m *SQLiteStore) Close() error {
  180. return m.Disconnect()
  181. }