apps.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. package dbstore
  2. import (
  3. "context"
  4. "database/sql"
  5. "encoding/json"
  6. "fmt"
  7. "time"
  8. "git.linuxforward.com/byop/byop-engine/models"
  9. "github.com/pkg/errors"
  10. )
  11. // App operations
  12. func (s *SQLiteStore) CreateApp(ctx context.Context, app *models.App) (int, error) {
  13. // Convert components slice to JSON
  14. componentsJSON, err := json.Marshal(app.Components)
  15. if err != nil {
  16. return 0, models.NewErrInternalServer("failed to marshal app components", err)
  17. }
  18. // Handle preview_id: if 0, pass NULL to database
  19. var previewID interface{}
  20. if app.PreviewID == 0 {
  21. previewID = nil
  22. } else {
  23. previewID = app.PreviewID
  24. }
  25. query := `INSERT INTO apps (user_id, name, description, status, components, preview_id, preview_url, current_image_tag, current_image_uri, error_msg, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
  26. now := time.Now().Format(time.RFC3339)
  27. result, err := s.db.ExecContext(ctx, query, app.UserID, app.Name, app.Description, app.Status, string(componentsJSON), previewID, app.PreviewURL, app.CurrentImageTag, app.CurrentImageURI, app.ErrorMsg, now, now)
  28. if err != nil {
  29. return 0, models.NewErrInternalServer("failed to create app", err)
  30. }
  31. id, err := result.LastInsertId()
  32. if err != nil {
  33. return 0, models.NewErrInternalServer("failed to get last insert ID for app", err)
  34. }
  35. return int(id), nil
  36. }
  37. func (s *SQLiteStore) GetAllApps(ctx context.Context) ([]*models.App, error) {
  38. query := `SELECT id, user_id, name, description, status, components, preview_id, preview_url, current_image_tag, current_image_uri, error_msg, created_at, updated_at FROM apps`
  39. rows, err := s.db.QueryContext(ctx, query)
  40. if err != nil {
  41. return nil, models.NewErrInternalServer("failed to query apps", err)
  42. }
  43. defer rows.Close()
  44. var apps []*models.App
  45. for rows.Next() {
  46. var app models.App
  47. var componentsJSON string
  48. var previewID sql.NullInt64
  49. err := rows.Scan(&app.ID, &app.UserID, &app.Name, &app.Description, &app.Status, &componentsJSON, &previewID, &app.PreviewURL, &app.CurrentImageTag, &app.CurrentImageURI, &app.ErrorMsg, &app.CreatedAt, &app.UpdatedAt)
  50. if err != nil {
  51. return nil, models.NewErrInternalServer("failed to scan app row", err)
  52. }
  53. // Handle nullable preview_id
  54. if previewID.Valid {
  55. app.PreviewID = int(previewID.Int64)
  56. } else {
  57. app.PreviewID = 0
  58. }
  59. // Parse components JSON
  60. if componentsJSON != "" {
  61. err := json.Unmarshal([]byte(componentsJSON), &app.Components)
  62. if err != nil {
  63. return nil, models.NewErrInternalServer("failed to unmarshal app components", err)
  64. }
  65. } else {
  66. app.Components = []int{} // Initialize as empty slice if null
  67. }
  68. apps = append(apps, &app)
  69. }
  70. if err = rows.Err(); err != nil {
  71. return nil, models.NewErrInternalServer("error iterating app rows", err)
  72. }
  73. return apps, nil
  74. }
  75. // GetAppByID retrieves a single app by ID
  76. func (s *SQLiteStore) GetAppByID(ctx context.Context, id int) (*models.App, error) {
  77. query := `SELECT id, user_id, name, description, status, components, preview_id, preview_url, current_image_tag, current_image_uri, error_msg, created_at, updated_at FROM apps WHERE id = ?`
  78. var app models.App
  79. var componentsJSON string
  80. var previewID sql.NullInt64
  81. err := s.db.QueryRowContext(ctx, query, id).Scan(&app.ID, &app.UserID, &app.Name, &app.Description, &app.Status, &componentsJSON, &previewID, &app.PreviewURL, &app.CurrentImageTag, &app.CurrentImageURI, &app.ErrorMsg, &app.CreatedAt, &app.UpdatedAt)
  82. if err != nil {
  83. if errors.Is(err, sql.ErrNoRows) {
  84. return nil, models.NewErrNotFound(fmt.Sprintf("app with ID %d not found", id), err)
  85. }
  86. return nil, models.NewErrInternalServer(fmt.Sprintf("failed to get app with ID %d", id), err)
  87. }
  88. // Handle nullable preview_id
  89. if previewID.Valid {
  90. app.PreviewID = int(previewID.Int64)
  91. } else {
  92. app.PreviewID = 0
  93. }
  94. // Parse components JSON
  95. if componentsJSON != "" {
  96. err := json.Unmarshal([]byte(componentsJSON), &app.Components)
  97. if err != nil {
  98. return nil, models.NewErrInternalServer("failed to unmarshal app components for app ID "+fmt.Sprint(id), err)
  99. }
  100. } else {
  101. app.Components = []int{} // Initialize as empty slice if null
  102. }
  103. return &app, nil
  104. }
  105. // UpdateApp updates an existing app
  106. func (s *SQLiteStore) UpdateApp(ctx context.Context, app *models.App) error {
  107. // Convert components slice to JSON
  108. componentsJSON, err := json.Marshal(app.Components)
  109. if err != nil {
  110. return models.NewErrInternalServer("failed to marshal app components for update", err)
  111. }
  112. // Handle preview_id: if 0, pass NULL to database
  113. var previewID interface{}
  114. if app.PreviewID == 0 {
  115. previewID = nil
  116. } else {
  117. previewID = app.PreviewID
  118. }
  119. query := `UPDATE apps SET user_id = ?, name = ?, description = ?, status = ?, components = ?, preview_id = ?, preview_url = ?, current_image_tag = ?, current_image_uri = ?, error_msg = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`
  120. result, err := s.db.ExecContext(ctx, query, app.UserID, app.Name, app.Description, app.Status, string(componentsJSON), previewID, app.PreviewURL, app.CurrentImageTag, app.CurrentImageURI, app.ErrorMsg, app.ID)
  121. if err != nil {
  122. return models.NewErrInternalServer(fmt.Sprintf("failed to update app with ID %d", app.ID), err)
  123. }
  124. rowsAffected, err := result.RowsAffected()
  125. if err != nil {
  126. return models.NewErrInternalServer(fmt.Sprintf("failed to get rows affected for app update ID %d", app.ID), err)
  127. }
  128. if rowsAffected == 0 {
  129. return models.NewErrNotFound(fmt.Sprintf("app with ID %d not found for update", app.ID), nil)
  130. }
  131. return nil
  132. }
  133. // DeleteApp deletes an app by ID with verification checks
  134. func (s *SQLiteStore) DeleteApp(ctx context.Context, id int) error {
  135. // First check if the app exists
  136. _, err := s.GetAppByID(ctx, id)
  137. if err != nil {
  138. return err
  139. }
  140. // Check if the app is used in any deployments
  141. deployments, err := s.GetDeploymentsByAppID(ctx, id)
  142. if err != nil {
  143. var nfErr *models.ErrNotFound
  144. if errors.As(err, &nfErr) {
  145. } else {
  146. return models.NewErrInternalServer(fmt.Sprintf("failed to check app deployments for app ID %d", id), err)
  147. }
  148. }
  149. if len(deployments) > 0 {
  150. return models.NewErrConflict(fmt.Sprintf("cannot delete app: it is used in %d deployment(s). Please delete the deployments first", len(deployments)), nil)
  151. }
  152. // If no deployments use this app, proceed with deletion
  153. query := `DELETE FROM apps WHERE id = ?`
  154. result, err := s.db.ExecContext(ctx, query, id)
  155. if err != nil {
  156. return models.NewErrInternalServer(fmt.Sprintf("failed to delete app with ID %d", id), err)
  157. }
  158. rowsAffected, err := result.RowsAffected()
  159. if err != nil {
  160. return models.NewErrInternalServer(fmt.Sprintf("failed to get rows affected for app deletion ID %d", id), err)
  161. }
  162. if rowsAffected == 0 {
  163. return models.NewErrNotFound(fmt.Sprintf("app with ID %d not found for deletion", id), nil)
  164. }
  165. return nil
  166. }
  167. func (s *SQLiteStore) UpdateAppStatus(ctx context.Context, appID int, status, errorMsg string) error {
  168. query := `UPDATE apps SET status = ?, error_msg = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`
  169. result, err := s.db.ExecContext(ctx, query, status, errorMsg, appID)
  170. if err != nil {
  171. return models.NewErrInternalServer(fmt.Sprintf("failed to update app status for ID %d", appID), err)
  172. }
  173. rowsAffected, err := result.RowsAffected()
  174. if err != nil {
  175. return models.NewErrInternalServer(fmt.Sprintf("failed to get rows affected for app status update ID %d", appID), err)
  176. }
  177. if rowsAffected == 0 {
  178. return models.NewErrNotFound(fmt.Sprintf("app with ID %d not found for status update", appID), nil)
  179. }
  180. return nil
  181. }
  182. // UpdateAppCurrentImage updates the current image tag and URI for an app.
  183. func (s *SQLiteStore) UpdateAppCurrentImage(ctx context.Context, appID int, imageTag string, imageURI string) error {
  184. query := `UPDATE apps SET current_image_tag = ?, current_image_uri = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`
  185. _, err := s.db.ExecContext(ctx, query, imageTag, imageURI, appID)
  186. if err != nil {
  187. return models.NewErrInternalServer(fmt.Sprintf("failed to update app current image for ID %d", appID), err)
  188. }
  189. return nil
  190. }