package dbstore import ( "context" "database/sql" "fmt" "git.linuxforward.com/byop/byop-engine/models" "github.com/pkg/errors" ) // Component operations func (s *SQLiteStore) CreateComponent(ctx context.Context, component *models.Component) (int, error) { query := `INSERT INTO components (user_id, name, description, type, status, config, repository, branch) VALUES (?, ?, ?, ?, ?, ?, ?, ?)` result, err := s.db.ExecContext(ctx, query, component.UserID, component.Name, component.Description, component.Type, component.Status, component.Config, component.Repository, component.Branch) if err != nil { return 0, models.NewErrInternalServer("failed to create component", err) } id, err := result.LastInsertId() if err != nil { return 0, models.NewErrInternalServer("failed to get last insert ID for component", err) } return int(id), nil } func (s *SQLiteStore) GetComponentsByUserID(ctx context.Context, userID int) ([]models.Component, error) { 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 = ?` rows, err := s.db.QueryContext(ctx, query, userID) if err != nil { return nil, models.NewErrInternalServer(fmt.Sprintf("failed to query components for user ID %d", userID), err) } defer rows.Close() var components []models.Component for rows.Next() { var component models.Component 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) if err != nil { return nil, models.NewErrInternalServer("failed to scan component row", err) } components = append(components, component) } if err = rows.Err(); err != nil { return nil, models.NewErrInternalServer(fmt.Sprintf("error iterating component rows for user ID %d", userID), err) } return components, nil } // GetAllComponents retrieves all components func (s *SQLiteStore) GetAllComponents(ctx context.Context) ([]models.Component, error) { 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` rows, err := s.db.QueryContext(ctx, query) if err != nil { return nil, models.NewErrInternalServer("failed to query all components", err) } defer rows.Close() var components []models.Component for rows.Next() { var component models.Component 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) if err != nil { return nil, models.NewErrInternalServer("failed to scan component row", err) } components = append(components, component) } if err = rows.Err(); err != nil { return nil, models.NewErrInternalServer("error iterating all component rows", err) } return components, nil } // GetComponentByID retrieves a component by ID func (s *SQLiteStore) GetComponentByID(ctx context.Context, id int) (*models.Component, error) { component := &models.Component{} 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 = ?` 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) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, models.NewErrNotFound(fmt.Sprintf("component with ID %d not found", id), err) } return nil, models.NewErrInternalServer(fmt.Sprintf("failed to get component with ID %d", id), err) } return component, nil } // UpdateComponent updates an existing component func (s *SQLiteStore) UpdateComponent(ctx context.Context, component models.Component) error { query := `UPDATE components SET name = ?, description = ?, type = ?, status = ?, config = ?, repository = ?, branch = ?, current_image_tag = ?, current_image_uri = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?` 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) if err != nil { return models.NewErrInternalServer(fmt.Sprintf("failed to update component with ID %d", component.ID), err) } rowsAffected, err := result.RowsAffected() if err != nil { return models.NewErrInternalServer(fmt.Sprintf("failed to get rows affected for component update ID %d", component.ID), err) } if rowsAffected == 0 { return models.NewErrNotFound(fmt.Sprintf("component with ID %d not found for update", component.ID), nil) } return nil } // UpdateComponentStatus updates the validation status of a component func (s *SQLiteStore) UpdateComponentStatus(ctx context.Context, id int, status, errorMsg string) error { query := ` UPDATE components SET status = ?, error_msg = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? ` result, err := s.db.ExecContext(ctx, query, status, errorMsg, id) if err != nil { return models.NewErrInternalServer(fmt.Sprintf("failed to update component status for ID %d", id), err) } rowsAffected, err := result.RowsAffected() if err != nil { return models.NewErrInternalServer(fmt.Sprintf("failed to get rows affected for component status update ID %d", id), err) } if rowsAffected == 0 { return models.NewErrNotFound(fmt.Sprintf("component with ID %d not found for status update", id), nil) } return nil } // UpdateComponentImageInfo updates the image information for a component after a successful build func (s *SQLiteStore) UpdateComponentImageInfo(ctx context.Context, componentID int, imageTag, imageURI string) error { query := ` UPDATE components SET current_image_tag = ?, current_image_uri = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? ` result, err := s.db.ExecContext(ctx, query, imageTag, imageURI, componentID) if err != nil { return models.NewErrInternalServer(fmt.Sprintf("failed to update component image info for ID %d", componentID), err) } rowsAffected, err := result.RowsAffected() if err != nil { return models.NewErrInternalServer(fmt.Sprintf("failed to get rows affected for component image info update ID %d", componentID), err) } if rowsAffected == 0 { return models.NewErrNotFound(fmt.Sprintf("component with ID %d not found for image info update", componentID), nil) } return nil } // DeleteComponent deletes a component by ID with verification checks func (s *SQLiteStore) DeleteComponent(ctx context.Context, id int) error { // First check if the component exists _, err := s.GetComponentByID(ctx, id) if err != nil { return err // GetComponentByID already returns custom errors } // Check if the component is used in any apps apps, err := s.GetAllApps(ctx) // Use context-aware GetAllApps if err != nil { // GetAllApps should return a custom error if it fails return models.NewErrInternalServer(fmt.Sprintf("failed to check component usage in apps for component ID %d", id), err) } var appsUsingComponent []string for _, app := range apps { for _, componentID := range app.Components { if componentID == id { appsUsingComponent = append(appsUsingComponent, app.Name) break } } } if len(appsUsingComponent) > 0 { 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) } // If no apps use this component, proceed with deletion query := `DELETE FROM components WHERE id = ?` result, err := s.db.ExecContext(ctx, query, id) if err != nil { return models.NewErrInternalServer(fmt.Sprintf("failed to delete component with ID %d", id), err) } rowsAffected, err := result.RowsAffected() if err != nil { return models.NewErrInternalServer(fmt.Sprintf("failed to get rows affected for component deletion ID %d", id), err) } if rowsAffected == 0 { // This case should ideally be caught by GetComponentByID earlier, but as a safeguard: return models.NewErrNotFound(fmt.Sprintf("component with ID %d not found for deletion", id), nil) } return nil }