components.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. package dbstore
  2. import (
  3. "context"
  4. "database/sql"
  5. "fmt"
  6. "git.linuxforward.com/byop/byop-engine/models"
  7. "github.com/pkg/errors"
  8. )
  9. // Component operations
  10. func (s *SQLiteStore) CreateComponent(ctx context.Context, component *models.Component) (int, error) {
  11. query := `INSERT INTO components (user_id, name, description, type, status, config, repository, branch) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
  12. result, err := s.db.ExecContext(ctx, query, component.UserID, component.Name, component.Description, component.Type, component.Status, component.Config, component.Repository, component.Branch)
  13. if err != nil {
  14. return 0, models.NewErrInternalServer("failed to create component", err)
  15. }
  16. id, err := result.LastInsertId()
  17. if err != nil {
  18. return 0, models.NewErrInternalServer("failed to get last insert ID for component", err)
  19. }
  20. return int(id), nil
  21. }
  22. func (s *SQLiteStore) GetComponentsByUserID(ctx context.Context, userID int) ([]models.Component, error) {
  23. query := `SELECT id, user_id, name, description, type, status, config, repository, branch, error_msg, current_image_tag, current_image_uri, created_at, updated_at FROM components WHERE user_id = ?`
  24. rows, err := s.db.QueryContext(ctx, query, userID)
  25. if err != nil {
  26. return nil, models.NewErrInternalServer(fmt.Sprintf("failed to query components for user ID %d", userID), err)
  27. }
  28. defer rows.Close()
  29. var components []models.Component
  30. for rows.Next() {
  31. var component models.Component
  32. err := rows.Scan(&component.ID, &component.UserID, &component.Name, &component.Description, &component.Type, &component.Status, &component.Config, &component.Repository, &component.Branch, &component.ErrorMsg, &component.CurrentImageTag, &component.CurrentImageURI, &component.CreatedAt, &component.UpdatedAt)
  33. if err != nil {
  34. return nil, models.NewErrInternalServer("failed to scan component row", err)
  35. }
  36. components = append(components, component)
  37. }
  38. if err = rows.Err(); err != nil {
  39. return nil, models.NewErrInternalServer(fmt.Sprintf("error iterating component rows for user ID %d", userID), err)
  40. }
  41. return components, nil
  42. }
  43. // GetAllComponents retrieves all components
  44. func (s *SQLiteStore) GetAllComponents(ctx context.Context) ([]models.Component, error) {
  45. query := `SELECT id, user_id, name, description, type, status, config, repository, branch, error_msg, current_image_tag, current_image_uri, created_at, updated_at FROM components`
  46. rows, err := s.db.QueryContext(ctx, query)
  47. if err != nil {
  48. return nil, models.NewErrInternalServer("failed to query all components", err)
  49. }
  50. defer rows.Close()
  51. var components []models.Component
  52. for rows.Next() {
  53. var component models.Component
  54. err := rows.Scan(&component.ID, &component.UserID, &component.Name, &component.Description, &component.Type, &component.Status, &component.Config, &component.Repository, &component.Branch, &component.ErrorMsg, &component.CurrentImageTag, &component.CurrentImageURI, &component.CreatedAt, &component.UpdatedAt)
  55. if err != nil {
  56. return nil, models.NewErrInternalServer("failed to scan component row", err)
  57. }
  58. components = append(components, component)
  59. }
  60. if err = rows.Err(); err != nil {
  61. return nil, models.NewErrInternalServer("error iterating all component rows", err)
  62. }
  63. return components, nil
  64. }
  65. // GetComponentByID retrieves a component by ID
  66. func (s *SQLiteStore) GetComponentByID(ctx context.Context, id int) (*models.Component, error) {
  67. component := &models.Component{}
  68. query := `SELECT id, user_id, name, description, type, status, config, repository, branch, error_msg, current_image_tag, current_image_uri, created_at, updated_at FROM components WHERE id = ?`
  69. err := s.db.QueryRowContext(ctx, query, id).Scan(&component.ID, &component.UserID, &component.Name, &component.Description, &component.Type, &component.Status, &component.Config, &component.Repository, &component.Branch, &component.ErrorMsg, &component.CurrentImageTag, &component.CurrentImageURI, &component.CreatedAt, &component.UpdatedAt)
  70. if err != nil {
  71. if errors.Is(err, sql.ErrNoRows) {
  72. return nil, models.NewErrNotFound(fmt.Sprintf("component with ID %d not found", id), err)
  73. }
  74. return nil, models.NewErrInternalServer(fmt.Sprintf("failed to get component with ID %d", id), err)
  75. }
  76. return component, nil
  77. }
  78. // UpdateComponent updates an existing component
  79. func (s *SQLiteStore) UpdateComponent(ctx context.Context, component models.Component) error {
  80. query := `UPDATE components SET name = ?, description = ?, type = ?, status = ?, config = ?, repository = ?, branch = ?, current_image_tag = ?, current_image_uri = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`
  81. result, err := s.db.ExecContext(ctx, query, component.Name, component.Description, component.Type, component.Status, component.Config, component.Repository, component.Branch, component.CurrentImageTag, component.CurrentImageURI, component.ID)
  82. if err != nil {
  83. return models.NewErrInternalServer(fmt.Sprintf("failed to update component with ID %d", component.ID), err)
  84. }
  85. rowsAffected, err := result.RowsAffected()
  86. if err != nil {
  87. return models.NewErrInternalServer(fmt.Sprintf("failed to get rows affected for component update ID %d", component.ID), err)
  88. }
  89. if rowsAffected == 0 {
  90. return models.NewErrNotFound(fmt.Sprintf("component with ID %d not found for update", component.ID), nil)
  91. }
  92. return nil
  93. }
  94. // UpdateComponentStatus updates the validation status of a component
  95. func (s *SQLiteStore) UpdateComponentStatus(ctx context.Context, id int, status, errorMsg string) error {
  96. query := `
  97. UPDATE components
  98. SET status = ?, error_msg = ?, updated_at = CURRENT_TIMESTAMP
  99. WHERE id = ?
  100. `
  101. result, err := s.db.ExecContext(ctx, query, status, errorMsg, id)
  102. if err != nil {
  103. return models.NewErrInternalServer(fmt.Sprintf("failed to update component status for ID %d", id), err)
  104. }
  105. rowsAffected, err := result.RowsAffected()
  106. if err != nil {
  107. return models.NewErrInternalServer(fmt.Sprintf("failed to get rows affected for component status update ID %d", id), err)
  108. }
  109. if rowsAffected == 0 {
  110. return models.NewErrNotFound(fmt.Sprintf("component with ID %d not found for status update", id), nil)
  111. }
  112. return nil
  113. }
  114. // UpdateComponentImageInfo updates the image information for a component after a successful build
  115. func (s *SQLiteStore) UpdateComponentImageInfo(ctx context.Context, componentID int, imageTag, imageURI string) error {
  116. query := `
  117. UPDATE components
  118. SET current_image_tag = ?, current_image_uri = ?, updated_at = CURRENT_TIMESTAMP
  119. WHERE id = ?
  120. `
  121. result, err := s.db.ExecContext(ctx, query, imageTag, imageURI, componentID)
  122. if err != nil {
  123. return models.NewErrInternalServer(fmt.Sprintf("failed to update component image info for ID %d", componentID), err)
  124. }
  125. rowsAffected, err := result.RowsAffected()
  126. if err != nil {
  127. return models.NewErrInternalServer(fmt.Sprintf("failed to get rows affected for component image info update ID %d", componentID), err)
  128. }
  129. if rowsAffected == 0 {
  130. return models.NewErrNotFound(fmt.Sprintf("component with ID %d not found for image info update", componentID), nil)
  131. }
  132. return nil
  133. }
  134. // DeleteComponent deletes a component by ID with verification checks
  135. func (s *SQLiteStore) DeleteComponent(ctx context.Context, id int) error {
  136. // First check if the component exists
  137. _, err := s.GetComponentByID(ctx, id)
  138. if err != nil {
  139. return err // GetComponentByID already returns custom errors
  140. }
  141. // Check if the component is used in any apps
  142. apps, err := s.GetAllApps(ctx) // Use context-aware GetAllApps
  143. if err != nil {
  144. // GetAllApps should return a custom error if it fails
  145. return models.NewErrInternalServer(fmt.Sprintf("failed to check component usage in apps for component ID %d", id), err)
  146. }
  147. var appsUsingComponent []string
  148. for _, app := range apps {
  149. for _, componentID := range app.Components {
  150. if componentID == id {
  151. appsUsingComponent = append(appsUsingComponent, app.Name)
  152. break
  153. }
  154. }
  155. }
  156. if len(appsUsingComponent) > 0 {
  157. return models.NewErrConflict(fmt.Sprintf("cannot delete component: it is used in the following app(s): %v. Please remove it from these apps first", appsUsingComponent), nil)
  158. }
  159. // If no apps use this component, proceed with deletion
  160. query := `DELETE FROM components WHERE id = ?`
  161. result, err := s.db.ExecContext(ctx, query, id)
  162. if err != nil {
  163. return models.NewErrInternalServer(fmt.Sprintf("failed to delete component with ID %d", id), err)
  164. }
  165. rowsAffected, err := result.RowsAffected()
  166. if err != nil {
  167. return models.NewErrInternalServer(fmt.Sprintf("failed to get rows affected for component deletion ID %d", id), err)
  168. }
  169. if rowsAffected == 0 {
  170. // This case should ideally be caught by GetComponentByID earlier, but as a safeguard:
  171. return models.NewErrNotFound(fmt.Sprintf("component with ID %d not found for deletion", id), nil)
  172. }
  173. return nil
  174. }