Skip to main content

garmin_cli/db/
models.rs

1//! Database models matching schema tables
2
3use chrono::{DateTime, NaiveDate, Utc};
4use serde::{Deserialize, Serialize};
5
6/// Profile for multi-account support
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Profile {
9    pub profile_id: i32,
10    pub display_name: String,
11    pub user_id: Option<i64>,
12    pub created_at: Option<DateTime<Utc>>,
13    pub last_sync_at: Option<DateTime<Utc>>,
14}
15
16/// Activity summary from Garmin API
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct Activity {
19    pub activity_id: i64,
20    pub profile_id: i32,
21    pub activity_name: Option<String>,
22    pub activity_type: Option<String>,
23    pub start_time_local: Option<DateTime<Utc>>,
24    pub start_time_gmt: Option<DateTime<Utc>>,
25    pub duration_sec: Option<f64>,
26    pub distance_m: Option<f64>,
27    pub calories: Option<i32>,
28    pub avg_hr: Option<i32>,
29    pub max_hr: Option<i32>,
30    pub avg_speed: Option<f64>,
31    pub max_speed: Option<f64>,
32    pub elevation_gain: Option<f64>,
33    pub elevation_loss: Option<f64>,
34    pub avg_cadence: Option<f64>,
35    pub avg_power: Option<i32>,
36    pub normalized_power: Option<i32>,
37    pub training_effect: Option<f64>,
38    pub training_load: Option<f64>,
39    pub start_lat: Option<f64>,
40    pub start_lon: Option<f64>,
41    pub end_lat: Option<f64>,
42    pub end_lon: Option<f64>,
43    pub ground_contact_time: Option<f64>,
44    pub vertical_oscillation: Option<f64>,
45    pub stride_length: Option<f64>,
46    pub location_name: Option<String>,
47    pub raw_json: Option<serde_json::Value>,
48}
49
50/// GPS track point with sensor data
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct TrackPoint {
53    pub id: Option<i64>,
54    pub activity_id: i64,
55    pub timestamp: DateTime<Utc>,
56    pub lat: Option<f64>,
57    pub lon: Option<f64>,
58    pub elevation: Option<f64>,
59    pub heart_rate: Option<i32>,
60    pub cadence: Option<i32>,
61    pub power: Option<i32>,
62    pub speed: Option<f64>,
63}
64
65/// Daily health metrics
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct DailyHealth {
68    pub id: Option<i64>,
69    pub profile_id: i32,
70    pub date: NaiveDate,
71    pub steps: Option<i32>,
72    pub step_goal: Option<i32>,
73    pub total_calories: Option<i32>,
74    pub active_calories: Option<i32>,
75    pub bmr_calories: Option<i32>,
76    pub resting_hr: Option<i32>,
77    pub sleep_seconds: Option<i32>,
78    pub deep_sleep_seconds: Option<i32>,
79    pub light_sleep_seconds: Option<i32>,
80    pub rem_sleep_seconds: Option<i32>,
81    pub sleep_score: Option<i32>,
82    pub avg_stress: Option<i32>,
83    pub max_stress: Option<i32>,
84    pub body_battery_start: Option<i32>,
85    pub body_battery_end: Option<i32>,
86    pub hrv_weekly_avg: Option<i32>,
87    pub hrv_last_night: Option<i32>,
88    pub hrv_status: Option<String>,
89    pub avg_respiration: Option<f64>,
90    pub avg_spo2: Option<i32>,
91    pub lowest_spo2: Option<i32>,
92    pub hydration_ml: Option<i32>,
93    pub moderate_intensity_min: Option<i32>,
94    pub vigorous_intensity_min: Option<i32>,
95    pub raw_json: Option<serde_json::Value>,
96}
97
98/// Performance metrics
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct PerformanceMetrics {
101    pub id: Option<i64>,
102    pub profile_id: i32,
103    pub date: NaiveDate,
104    pub vo2max: Option<f64>,
105    pub fitness_age: Option<i32>,
106    pub training_readiness: Option<i32>,
107    pub training_status: Option<String>,
108    pub lactate_threshold_hr: Option<i32>,
109    pub lactate_threshold_pace: Option<f64>,
110    pub race_5k_sec: Option<i32>,
111    pub race_10k_sec: Option<i32>,
112    pub race_half_sec: Option<i32>,
113    pub race_marathon_sec: Option<i32>,
114    pub endurance_score: Option<i32>,
115    pub hill_score: Option<i32>,
116    pub raw_json: Option<serde_json::Value>,
117}
118
119/// Weight entry
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct WeightEntry {
122    pub id: Option<i64>,
123    pub profile_id: i32,
124    pub date: NaiveDate,
125    pub weight_kg: Option<f64>,
126    pub bmi: Option<f64>,
127    pub body_fat_pct: Option<f64>,
128    pub muscle_mass_kg: Option<f64>,
129}
130
131/// Sync state for incremental updates
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct SyncState {
134    pub profile_id: i32,
135    pub data_type: String,
136    pub last_sync_date: Option<NaiveDate>,
137    pub last_activity_id: Option<i64>,
138}
139
140/// Sync task status
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
142#[serde(rename_all = "lowercase")]
143pub enum TaskStatus {
144    Pending,
145    InProgress,
146    Completed,
147    Failed,
148}
149
150impl std::fmt::Display for TaskStatus {
151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152        match self {
153            TaskStatus::Pending => write!(f, "pending"),
154            TaskStatus::InProgress => write!(f, "in_progress"),
155            TaskStatus::Completed => write!(f, "completed"),
156            TaskStatus::Failed => write!(f, "failed"),
157        }
158    }
159}
160
161/// Sync pipeline for task routing
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
163#[serde(rename_all = "snake_case")]
164pub enum SyncPipeline {
165    #[default]
166    Frontier,
167    Backfill,
168}
169
170impl std::fmt::Display for SyncPipeline {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        match self {
173            SyncPipeline::Frontier => write!(f, "frontier"),
174            SyncPipeline::Backfill => write!(f, "backfill"),
175        }
176    }
177}
178
179/// Sync task types
180#[derive(Debug, Clone, Serialize, Deserialize)]
181#[serde(tag = "type", rename_all = "snake_case")]
182pub enum SyncTaskType {
183    Activities {
184        start: u32,
185        limit: u32,
186        #[serde(default)]
187        min_date: Option<NaiveDate>,
188        #[serde(default)]
189        max_date: Option<NaiveDate>,
190    },
191    ActivityDetail {
192        activity_id: i64,
193    },
194    DownloadGpx {
195        activity_id: i64,
196        #[serde(default)]
197        activity_name: Option<String>,
198        #[serde(default)]
199        activity_date: Option<String>,
200    },
201    DailyHealth {
202        date: NaiveDate,
203    },
204    Performance {
205        date: NaiveDate,
206    },
207    Weight {
208        from: NaiveDate,
209        to: NaiveDate,
210    },
211    GenerateEmbeddings {
212        activity_ids: Vec<i64>,
213    },
214}
215
216/// Sync task for queue
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct SyncTask {
219    pub id: Option<i64>,
220    pub profile_id: i32,
221    pub task_type: SyncTaskType,
222    #[serde(default)]
223    pub pipeline: SyncPipeline,
224    pub status: TaskStatus,
225    pub attempts: i32,
226    pub last_error: Option<String>,
227    pub created_at: Option<DateTime<Utc>>,
228    pub next_retry_at: Option<DateTime<Utc>>,
229    pub completed_at: Option<DateTime<Utc>>,
230}
231
232impl SyncTask {
233    pub fn new(profile_id: i32, pipeline: SyncPipeline, task_type: SyncTaskType) -> Self {
234        Self {
235            id: None,
236            profile_id,
237            task_type,
238            pipeline,
239            status: TaskStatus::Pending,
240            attempts: 0,
241            last_error: None,
242            created_at: None,
243            next_retry_at: None,
244            completed_at: None,
245        }
246    }
247}