package store import ( "database/sql" "errors" "fmt" "github.com/google/uuid" ) // volumeColumns is the canonical column list for volume queries. const volumeColumns = `id, project_id, source, target, mode, scope, name, created_at, updated_at` // CreateVolume inserts a new volume configuration for a project. func (s *Store) CreateVolume(vol Volume) (Volume, error) { vol.ID = uuid.New().String() vol.CreatedAt = Now() vol.UpdatedAt = vol.CreatedAt // Default scope for backward compatibility. if vol.Scope == "" { switch vol.Mode { case "isolated": vol.Scope = "instance" default: vol.Scope = "project" } } _, err := s.db.Exec( `INSERT INTO volumes (`+volumeColumns+`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, vol.ID, vol.ProjectID, vol.Source, vol.Target, vol.Mode, vol.Scope, vol.Name, vol.CreatedAt, vol.UpdatedAt, ) if err != nil { return Volume{}, fmt.Errorf("insert volume: %w", err) } return vol, nil } // GetVolumesByProjectID returns all volume configurations for a project. func (s *Store) GetVolumesByProjectID(projectID string) ([]Volume, error) { rows, err := s.db.Query( `SELECT `+volumeColumns+` FROM volumes WHERE project_id = ? ORDER BY target`, projectID, ) if err != nil { return nil, fmt.Errorf("query volumes: %w", err) } defer rows.Close() vols := []Volume{} for rows.Next() { vol, err := scanVolume(rows) if err != nil { return nil, err } vols = append(vols, vol) } return vols, rows.Err() } // GetVolumeByID returns a single volume by its ID. func (s *Store) GetVolumeByID(id string) (Volume, error) { var vol Volume err := s.db.QueryRow( `SELECT `+volumeColumns+` FROM volumes WHERE id = ?`, id, ).Scan(&vol.ID, &vol.ProjectID, &vol.Source, &vol.Target, &vol.Mode, &vol.Scope, &vol.Name, &vol.CreatedAt, &vol.UpdatedAt) if errors.Is(err, sql.ErrNoRows) { return Volume{}, fmt.Errorf("volume %s: %w", id, ErrNotFound) } if err != nil { return Volume{}, fmt.Errorf("query volume: %w", err) } return vol, nil } // UpdateVolume updates an existing volume configuration. func (s *Store) UpdateVolume(vol Volume) error { vol.UpdatedAt = Now() result, err := s.db.Exec( `UPDATE volumes SET source=?, target=?, mode=?, scope=?, name=?, updated_at=? WHERE id=?`, vol.Source, vol.Target, vol.Mode, vol.Scope, vol.Name, vol.UpdatedAt, vol.ID, ) if err != nil { return fmt.Errorf("update volume: %w", err) } n, _ := result.RowsAffected() if n == 0 { return fmt.Errorf("volume %s: %w", vol.ID, ErrNotFound) } return nil } // DeleteVolume removes a volume configuration by ID. func (s *Store) DeleteVolume(id string) error { result, err := s.db.Exec(`DELETE FROM volumes WHERE id = ?`, id) if err != nil { return fmt.Errorf("delete volume: %w", err) } n, _ := result.RowsAffected() if n == 0 { return fmt.Errorf("volume %s: %w", id, ErrNotFound) } return nil } // scanVolume scans a volume row from a *sql.Rows cursor. func scanVolume(rows *sql.Rows) (Volume, error) { var vol Volume err := rows.Scan(&vol.ID, &vol.ProjectID, &vol.Source, &vol.Target, &vol.Mode, &vol.Scope, &vol.Name, &vol.CreatedAt, &vol.UpdatedAt) if err != nil { return Volume{}, fmt.Errorf("scan volume: %w", err) } return vol, nil }