package store import ( "database/sql" "errors" "fmt" "github.com/google/uuid" ) const appColumns = `id, name, description, created_at, updated_at` func scanApp(scanner interface{ Scan(...any) error }) (App, error) { var a App err := scanner.Scan(&a.ID, &a.Name, &a.Description, &a.CreatedAt, &a.UpdatedAt) return a, err } // CreateApp inserts a new app row. Names must be unique. func (s *Store) CreateApp(a App) (App, error) { if a.ID == "" { a.ID = uuid.New().String() } a.CreatedAt = Now() a.UpdatedAt = a.CreatedAt _, err := s.db.Exec( `INSERT INTO apps (`+appColumns+`) VALUES (?, ?, ?, ?, ?)`, a.ID, a.Name, a.Description, a.CreatedAt, a.UpdatedAt, ) if err != nil { return App{}, fmt.Errorf("insert app: %w", err) } return a, nil } // GetAppByID returns an app by ID. func (s *Store) GetAppByID(id string) (App, error) { a, err := scanApp(s.db.QueryRow( `SELECT `+appColumns+` FROM apps WHERE id = ?`, id, )) if errors.Is(err, sql.ErrNoRows) { return App{}, fmt.Errorf("app %s: %w", id, ErrNotFound) } if err != nil { return App{}, fmt.Errorf("query app: %w", err) } return a, nil } // ListApps returns all apps, ordered by name. func (s *Store) ListApps() ([]App, error) { rows, err := s.db.Query( `SELECT ` + appColumns + ` FROM apps ORDER BY name`, ) if err != nil { return nil, fmt.Errorf("query apps: %w", err) } defer rows.Close() out := []App{} for rows.Next() { a, err := scanApp(rows) if err != nil { return nil, fmt.Errorf("scan app: %w", err) } out = append(out, a) } return out, rows.Err() } // UpdateApp updates the mutable fields (name, description) of an app row. func (s *Store) UpdateApp(a App) error { a.UpdatedAt = Now() result, err := s.db.Exec( `UPDATE apps SET name=?, description=?, updated_at=? WHERE id=?`, a.Name, a.Description, a.UpdatedAt, a.ID, ) if err != nil { return fmt.Errorf("update app: %w", err) } n, _ := result.RowsAffected() if n == 0 { return fmt.Errorf("app %s: %w", a.ID, ErrNotFound) } return nil } // DeleteApp removes an app and clears its app_id from any workloads. // Workloads survive — they just become unassigned. func (s *Store) DeleteApp(id string) error { tx, err := s.db.Begin() if err != nil { return fmt.Errorf("begin delete app: %w", err) } defer tx.Rollback() if _, err := tx.Exec(`UPDATE workloads SET app_id = '' WHERE app_id = ?`, id); err != nil { return fmt.Errorf("clear app_id on workloads: %w", err) } result, err := tx.Exec(`DELETE FROM apps WHERE id = ?`, id) if err != nil { return fmt.Errorf("delete app: %w", err) } n, _ := result.RowsAffected() if n == 0 { return fmt.Errorf("app %s: %w", id, ErrNotFound) } return tx.Commit() }