Files
geniusrun/backend/internal/store/workoutkinds.go

116 lines
3.7 KiB
Go
Raw Normal View History

package store
import (
"context"
"database/sql"
"fmt"
)
// WorkoutKind is a user-editable classification rule ("Easy", "Tempo", ...).
// RuleJSON holds the condition tree evaluated by internal/classify.
type WorkoutKind struct {
ID int64
Name string
Description string
Color string
RuleJSON string
Priority int
IsActive bool
CreatedAt string
UpdatedAt string
}
func scanWorkoutKind(row interface{ Scan(...any) error }) (WorkoutKind, error) {
var k WorkoutKind
err := row.Scan(&k.ID, &k.Name, &k.Description, &k.Color, &k.RuleJSON, &k.Priority, &k.IsActive, &k.CreatedAt, &k.UpdatedAt)
return k, err
}
const workoutKindColumns = `id, name, description, color, rule_json, priority, is_active, created_at, updated_at`
// CreateWorkoutKind inserts a new workout kind and returns its id.
func (db *DB) CreateWorkoutKind(ctx context.Context, k WorkoutKind) (int64, error) {
res, err := db.ExecContext(ctx, `
INSERT INTO workout_kinds (name, description, color, rule_json, priority, is_active)
VALUES (?,?,?,?,?,?)`,
k.Name, k.Description, k.Color, k.RuleJSON, k.Priority, k.IsActive)
if err != nil {
return 0, fmt.Errorf("create workout kind %q: %w", k.Name, err)
}
return res.LastInsertId()
}
// UpdateWorkoutKind updates an existing workout kind's editable fields.
func (db *DB) UpdateWorkoutKind(ctx context.Context, k WorkoutKind) error {
_, err := db.ExecContext(ctx, `
UPDATE workout_kinds SET name=?, description=?, color=?, rule_json=?, priority=?, is_active=?, updated_at=datetime('now')
WHERE id=?`,
k.Name, k.Description, k.Color, k.RuleJSON, k.Priority, k.IsActive, k.ID)
if err != nil {
return fmt.Errorf("update workout kind %d: %w", k.ID, err)
}
return nil
}
// GetWorkoutKind fetches one workout kind by id.
func (db *DB) GetWorkoutKind(ctx context.Context, id int64) (WorkoutKind, bool, error) {
row := db.QueryRowContext(ctx, `SELECT `+workoutKindColumns+` FROM workout_kinds WHERE id = ?`, id)
k, err := scanWorkoutKind(row)
if err == sql.ErrNoRows {
return WorkoutKind{}, false, nil
}
if err != nil {
return WorkoutKind{}, false, fmt.Errorf("get workout kind %d: %w", id, err)
}
return k, true, nil
}
// GetWorkoutKindByName fetches one workout kind by its unique name.
func (db *DB) GetWorkoutKindByName(ctx context.Context, name string) (WorkoutKind, bool, error) {
row := db.QueryRowContext(ctx, `SELECT `+workoutKindColumns+` FROM workout_kinds WHERE name = ?`, name)
k, err := scanWorkoutKind(row)
if err == sql.ErrNoRows {
return WorkoutKind{}, false, nil
}
if err != nil {
return WorkoutKind{}, false, fmt.Errorf("get workout kind %q: %w", name, err)
}
return k, true, nil
}
// ListWorkoutKinds returns workout kinds. If activeOnly, soft-deleted
// (is_active=0) kinds are excluded.
func (db *DB) ListWorkoutKinds(ctx context.Context, activeOnly bool) ([]WorkoutKind, error) {
query := `SELECT ` + workoutKindColumns + ` FROM workout_kinds`
if activeOnly {
query += ` WHERE is_active = 1`
}
query += ` ORDER BY priority DESC, name`
rows, err := db.QueryContext(ctx, query)
if err != nil {
return nil, fmt.Errorf("list workout kinds: %w", err)
}
defer rows.Close()
kinds := []WorkoutKind{}
for rows.Next() {
k, err := scanWorkoutKind(rows)
if err != nil {
return nil, fmt.Errorf("scan workout kind row: %w", err)
}
kinds = append(kinds, k)
}
return kinds, rows.Err()
}
// SoftDeleteWorkoutKind sets is_active=0, keeping history (kind_assignments
// referencing it) intact.
func (db *DB) SoftDeleteWorkoutKind(ctx context.Context, id int64) error {
_, err := db.ExecContext(ctx, `UPDATE workout_kinds SET is_active=0, updated_at=datetime('now') WHERE id=?`, id)
if err != nil {
return fmt.Errorf("soft delete workout kind %d: %w", id, err)
}
return nil
}