Part of per-user profile isolation: profile rows are no longer a global singleton, so every read/write requires the caller's userID. This also requires scoping ListActivities, ListWorkoutKinds, UpsertActivity, CreateWorkoutKind, GetSyncState, UpdateSyncState, and ResetAllSyncedData to userID, plus updating all related tests in the store package.
135 lines
6.3 KiB
Go
135 lines
6.3 KiB
Go
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 {
|
|
// Name labels this profile so a future multi-profile setup can show
|
|
// which one is active. Only one profile row exists today (id=1).
|
|
Name string
|
|
GarminEmail string
|
|
GarminPassword string
|
|
RollingWindowDays int
|
|
// BackfillHorizonDays bounds how far back "Sync now" reaches when
|
|
// walking backward from today; it's read fresh on every sync (not fixed
|
|
// at server startup), so changing it here takes effect on the next
|
|
// click. Unrelated to RollingWindowDays, which is for the (not yet
|
|
// implemented) relative classification window.
|
|
BackfillHorizonDays int
|
|
MaxHeartRate *float64
|
|
RestingHeartRate *float64
|
|
|
|
HRZone1MinPct, HRZone1MaxPct float64
|
|
HRZone2MinPct, HRZone2MaxPct float64
|
|
HRZone3MinPct, HRZone3MaxPct float64
|
|
HRZone4MinPct, HRZone4MaxPct float64
|
|
HRZone5MinPct, HRZone5MaxPct float64
|
|
|
|
// WarmupMinutes/CooldownMinutes apply uniformly to every fixed-duration
|
|
// workout type's phase detection (Easy, Long, Tempo, Threshold 30'/60',
|
|
// MAS Test). Interval workouts detect phases from lap data directly and
|
|
// don't use these.
|
|
WarmupMinutes, CooldownMinutes float64
|
|
|
|
// MinRepresentativePaceSecPerKm/MinRepresentativeTimeSeconds drive the
|
|
// Review Queue pace chart's artifact filter: a stretch of samples slower
|
|
// than MinRepresentativePaceSecPerKm is dropped from the chart (and its
|
|
// Y-axis scale) unless it persists for at least
|
|
// MinRepresentativeTimeSeconds, in which case it's treated as a real
|
|
// stop or walk break rather than noise (e.g. GPS/motion still settling
|
|
// right as recording starts, before the run itself begins).
|
|
MinRepresentativePaceSecPerKm, MinRepresentativeTimeSeconds float64
|
|
|
|
// Chart colors: PaceColor/HeartRateColor are the "main line" color for
|
|
// each metric's chart; Warmup/Effort/Recovery/CooldownColor are the
|
|
// "effort kind" colors. The frontend derives the under-the-line fill
|
|
// (effort color tinted by the main line color) and the phase background
|
|
// fill (effort color, darkened) from these six -- see
|
|
// frontend's ExpectedVsActualChart.
|
|
PaceColor, HeartRateColor string
|
|
WarmupColor, EffortColor, RecoveryColor, CooldownColor string
|
|
// MainLineTintPct (0-100) is how strongly the main line color mixes into
|
|
// the effort-kind fill under the line -- see frontend's mixColor(). Never
|
|
// affects the phase background above the line.
|
|
MainLineTintPct float64
|
|
// BackgroundDarkenPct (0-100) is how strongly the effort-kind color is
|
|
// darkened for the phase background above the line -- see frontend's
|
|
// darken(). Never mixed with the main line color.
|
|
BackgroundDarkenPct float64
|
|
// TargetBrightenPct (0-100) is how strongly a chart's main line color
|
|
// (and everything tinted from it) is brightened when that chart has a
|
|
// structured-workout target range to show -- a color cue for "this has a
|
|
// target" instead of a text label -- see frontend's brighten().
|
|
TargetBrightenPct float64
|
|
|
|
CreatedAt, UpdatedAt string
|
|
}
|
|
|
|
const profileColumns = `
|
|
name, garmin_email, garmin_password, rolling_window_days, backfill_horizon_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,
|
|
warmup_minutes, cooldown_minutes,
|
|
min_representative_pace_sec_per_km, min_representative_time_seconds,
|
|
pace_color, heart_rate_color, warmup_color, effort_color, recovery_color, cooldown_color,
|
|
main_line_tint_pct, background_darken_pct, target_brighten_pct,
|
|
created_at, updated_at
|
|
`
|
|
|
|
// GetProfile returns the profile row for userID.
|
|
func (db *DB) GetProfile(ctx context.Context, userID int64) (Profile, error) {
|
|
var p Profile
|
|
err := db.QueryRowContext(ctx, `SELECT `+profileColumns+` FROM profile WHERE user_id = ?`, userID).Scan(
|
|
&p.Name, &p.GarminEmail, &p.GarminPassword, &p.RollingWindowDays, &p.BackfillHorizonDays, &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.WarmupMinutes, &p.CooldownMinutes,
|
|
&p.MinRepresentativePaceSecPerKm, &p.MinRepresentativeTimeSeconds,
|
|
&p.PaceColor, &p.HeartRateColor, &p.WarmupColor, &p.EffortColor, &p.RecoveryColor, &p.CooldownColor,
|
|
&p.MainLineTintPct, &p.BackgroundDarkenPct, &p.TargetBrightenPct,
|
|
&p.CreatedAt, &p.UpdatedAt,
|
|
)
|
|
if err != nil {
|
|
return Profile{}, fmt.Errorf("get profile for user %d: %w", userID, err)
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// UpdateProfile overwrites userID's 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, userID int64, p Profile) error {
|
|
_, err := db.ExecContext(ctx, `
|
|
UPDATE profile SET
|
|
name=?, garmin_email=?, garmin_password=?, rolling_window_days=?, backfill_horizon_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=?,
|
|
warmup_minutes=?, cooldown_minutes=?,
|
|
min_representative_pace_sec_per_km=?, min_representative_time_seconds=?,
|
|
pace_color=?, heart_rate_color=?, warmup_color=?, effort_color=?, recovery_color=?, cooldown_color=?,
|
|
main_line_tint_pct=?, background_darken_pct=?, target_brighten_pct=?,
|
|
updated_at=datetime('now')
|
|
WHERE user_id = ?`,
|
|
p.Name, p.GarminEmail, p.GarminPassword, p.RollingWindowDays, p.BackfillHorizonDays, 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.WarmupMinutes, p.CooldownMinutes,
|
|
p.MinRepresentativePaceSecPerKm, p.MinRepresentativeTimeSeconds,
|
|
p.PaceColor, p.HeartRateColor, p.WarmupColor, p.EffortColor, p.RecoveryColor, p.CooldownColor,
|
|
p.MainLineTintPct, p.BackgroundDarkenPct, p.TargetBrightenPct,
|
|
userID,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("update profile for user %d: %w", userID, err)
|
|
}
|
|
return nil
|
|
}
|