package store import ( "context" "fmt" ) // Profile is the single active user's Garmin credentials plus every // tunable analysis-engine parameter. Always exactly one row (id=1). type Profile struct { GarminEmail string GarminPassword string RollingWindowDays int MaxHeartRate *float64 RestingHeartRate *float64 HRZone1MinPct, HRZone1MaxPct float64 HRZone2MinPct, HRZone2MaxPct float64 HRZone3MinPct, HRZone3MaxPct float64 HRZone4MinPct, HRZone4MaxPct float64 HRZone5MinPct, HRZone5MaxPct float64 EasyWarmupMinutes, EasyCooldownMinutes float64 LongWarmupMinutes, LongCooldownMinutes float64 TempoWarmupMinutes, TempoCooldownMinutes float64 Threshold30WarmupMinutes, Threshold30CooldownMinutes float64 Threshold60WarmupMinutes, Threshold60CooldownMinutes float64 MASTestWarmupMinutes, MASTestCooldownMinutes float64 // IntervalWarmupMinutes/IntervalCooldownMinutes are persisted to dedicated // columns (DEFAULT 0) but not yet used by phase-detection logic; reserved // for future Interval fixed-duration strategy if needed. IntervalWarmupMinutes, IntervalCooldownMinutes float64 CreatedAt, UpdatedAt string } const profileColumns = ` garmin_email, garmin_password, rolling_window_days, max_heart_rate, resting_heart_rate, hr_zone1_min_pct, hr_zone1_max_pct, hr_zone2_min_pct, hr_zone2_max_pct, hr_zone3_min_pct, hr_zone3_max_pct, hr_zone4_min_pct, hr_zone4_max_pct, hr_zone5_min_pct, hr_zone5_max_pct, easy_warmup_minutes, easy_cooldown_minutes, long_warmup_minutes, long_cooldown_minutes, tempo_warmup_minutes, tempo_cooldown_minutes, threshold30_warmup_minutes, threshold30_cooldown_minutes, threshold60_warmup_minutes, threshold60_cooldown_minutes, mas_test_warmup_minutes, mas_test_cooldown_minutes, interval_warmup_minutes, interval_cooldown_minutes, created_at, updated_at ` // GetProfile returns the single profile row. func (db *DB) GetProfile(ctx context.Context) (Profile, error) { var p Profile err := db.QueryRowContext(ctx, `SELECT `+profileColumns+` FROM profile WHERE id = 1`).Scan( &p.GarminEmail, &p.GarminPassword, &p.RollingWindowDays, &p.MaxHeartRate, &p.RestingHeartRate, &p.HRZone1MinPct, &p.HRZone1MaxPct, &p.HRZone2MinPct, &p.HRZone2MaxPct, &p.HRZone3MinPct, &p.HRZone3MaxPct, &p.HRZone4MinPct, &p.HRZone4MaxPct, &p.HRZone5MinPct, &p.HRZone5MaxPct, &p.EasyWarmupMinutes, &p.EasyCooldownMinutes, &p.LongWarmupMinutes, &p.LongCooldownMinutes, &p.TempoWarmupMinutes, &p.TempoCooldownMinutes, &p.Threshold30WarmupMinutes, &p.Threshold30CooldownMinutes, &p.Threshold60WarmupMinutes, &p.Threshold60CooldownMinutes, &p.MASTestWarmupMinutes, &p.MASTestCooldownMinutes, &p.IntervalWarmupMinutes, &p.IntervalCooldownMinutes, &p.CreatedAt, &p.UpdatedAt, ) if err != nil { return Profile{}, fmt.Errorf("get profile: %w", err) } return p, nil } // UpdateProfile overwrites the single profile row. Callers should read via // GetProfile first and modify the fields they intend to change, since this // replaces every column. func (db *DB) UpdateProfile(ctx context.Context, p Profile) error { _, err := db.ExecContext(ctx, ` UPDATE profile SET garmin_email=?, garmin_password=?, rolling_window_days=?, max_heart_rate=?, resting_heart_rate=?, hr_zone1_min_pct=?, hr_zone1_max_pct=?, hr_zone2_min_pct=?, hr_zone2_max_pct=?, hr_zone3_min_pct=?, hr_zone3_max_pct=?, hr_zone4_min_pct=?, hr_zone4_max_pct=?, hr_zone5_min_pct=?, hr_zone5_max_pct=?, easy_warmup_minutes=?, easy_cooldown_minutes=?, long_warmup_minutes=?, long_cooldown_minutes=?, tempo_warmup_minutes=?, tempo_cooldown_minutes=?, threshold30_warmup_minutes=?, threshold30_cooldown_minutes=?, threshold60_warmup_minutes=?, threshold60_cooldown_minutes=?, mas_test_warmup_minutes=?, mas_test_cooldown_minutes=?, interval_warmup_minutes=?, interval_cooldown_minutes=?, updated_at=datetime('now') WHERE id = 1`, p.GarminEmail, p.GarminPassword, p.RollingWindowDays, p.MaxHeartRate, p.RestingHeartRate, p.HRZone1MinPct, p.HRZone1MaxPct, p.HRZone2MinPct, p.HRZone2MaxPct, p.HRZone3MinPct, p.HRZone3MaxPct, p.HRZone4MinPct, p.HRZone4MaxPct, p.HRZone5MinPct, p.HRZone5MaxPct, p.EasyWarmupMinutes, p.EasyCooldownMinutes, p.LongWarmupMinutes, p.LongCooldownMinutes, p.TempoWarmupMinutes, p.TempoCooldownMinutes, p.Threshold30WarmupMinutes, p.Threshold30CooldownMinutes, p.Threshold60WarmupMinutes, p.Threshold60CooldownMinutes, p.MASTestWarmupMinutes, p.MASTestCooldownMinutes, p.IntervalWarmupMinutes, p.IntervalCooldownMinutes, ) if err != nil { return fmt.Errorf("update profile: %w", err) } return nil }