Skip to main content

qrz_logbook_api/
models.rs

1use chrono::{NaiveDate, NaiveTime};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5/// QSO record for the logbook
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct QsoRecord {
8    /// Called station's callsign
9    pub call: String,
10    /// Station callsign (your callsign)
11    pub station_callsign: String,
12    /// QSO date
13    pub qso_date: NaiveDate,
14    /// Time on (start time)
15    pub time_on: NaiveTime,
16    /// Time off (end time, optional)
17    pub time_off: Option<NaiveTime>,
18    /// Band (e.g., "20m", "40m")
19    pub band: String,
20    /// Mode (e.g., "SSB", "CW", "FT8")
21    pub mode: String,
22    /// Frequency in MHz (optional)
23    pub freq: Option<f64>,
24    /// RST sent (optional)
25    pub rst_sent: Option<String>,
26    /// RST received (optional)
27    pub rst_rcvd: Option<String>,
28    /// QTH (location, optional)
29    pub qth: Option<String>,
30    /// Name (optional)
31    pub name: Option<String>,
32    /// Comments (optional)
33    pub comment: Option<String>,
34    /// Additional ADIF fields
35    pub additional_fields: HashMap<String, String>,
36}
37
38impl QsoRecord {
39    /// Create a new QSO record builder
40    pub fn builder() -> QsoRecordBuilder {
41        QsoRecordBuilder::new()
42    }
43}
44
45/// Builder for QSO records
46#[derive(Debug, Default)]
47pub struct QsoRecordBuilder {
48    call: Option<String>,
49    station_callsign: Option<String>,
50    qso_date: Option<NaiveDate>,
51    time_on: Option<NaiveTime>,
52    time_off: Option<NaiveTime>,
53    band: Option<String>,
54    mode: Option<String>,
55    freq: Option<f64>,
56    rst_sent: Option<String>,
57    rst_rcvd: Option<String>,
58    qth: Option<String>,
59    name: Option<String>,
60    comment: Option<String>,
61    additional_fields: HashMap<String, String>,
62}
63
64impl QsoRecordBuilder {
65    pub fn new() -> Self {
66        Self::default()
67    }
68
69    pub fn call(mut self, call: impl Into<String>) -> Self {
70        self.call = Some(call.into());
71        self
72    }
73
74    pub fn station_callsign(mut self, callsign: impl Into<String>) -> Self {
75        self.station_callsign = Some(callsign.into());
76        self
77    }
78
79    pub fn date(mut self, date: NaiveDate) -> Self {
80        self.qso_date = Some(date);
81        self
82    }
83
84    pub fn time_on(mut self, time: NaiveTime) -> Self {
85        self.time_on = Some(time);
86        self
87    }
88
89    pub fn time_off(mut self, time: NaiveTime) -> Self {
90        self.time_off = Some(time);
91        self
92    }
93
94    pub fn band(mut self, band: impl Into<String>) -> Self {
95        self.band = Some(band.into());
96        self
97    }
98
99    pub fn mode(mut self, mode: impl Into<String>) -> Self {
100        self.mode = Some(mode.into());
101        self
102    }
103
104    pub fn freq(mut self, freq: f64) -> Self {
105        self.freq = Some(freq);
106        self
107    }
108
109    pub fn rst_sent(mut self, rst: impl Into<String>) -> Self {
110        self.rst_sent = Some(rst.into());
111        self
112    }
113
114    pub fn rst_rcvd(mut self, rst: impl Into<String>) -> Self {
115        self.rst_rcvd = Some(rst.into());
116        self
117    }
118
119    pub fn qth(mut self, qth: impl Into<String>) -> Self {
120        self.qth = Some(qth.into());
121        self
122    }
123
124    pub fn name(mut self, name: impl Into<String>) -> Self {
125        self.name = Some(name.into());
126        self
127    }
128
129    pub fn comment(mut self, comment: impl Into<String>) -> Self {
130        self.comment = Some(comment.into());
131        self
132    }
133
134    pub fn additional_field(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
135        self.additional_fields.insert(key.into(), value.into());
136        self
137    }
138
139    pub fn build(self) -> QsoRecord {
140        QsoRecord {
141            call: self.call.unwrap_or_default(),
142            station_callsign: self.station_callsign.unwrap_or_default(),
143            qso_date: self
144                .qso_date
145                .unwrap_or_else(|| NaiveDate::from_ymd_opt(1900, 1, 1).unwrap()),
146            time_on: self
147                .time_on
148                .unwrap_or_else(|| NaiveTime::from_hms_opt(0, 0, 0).unwrap()),
149            time_off: self.time_off,
150            band: self.band.unwrap_or_default(),
151            mode: self.mode.unwrap_or_default(),
152            freq: self.freq,
153            rst_sent: self.rst_sent,
154            rst_rcvd: self.rst_rcvd,
155            qth: self.qth,
156            name: self.name,
157            comment: self.comment,
158            additional_fields: self.additional_fields,
159        }
160    }
161}
162
163/// Response from INSERT action
164#[derive(Debug, Clone)]
165pub struct InsertResponse {
166    pub logid: u64,
167    pub count: u32,
168}
169
170/// Response from DELETE action
171#[derive(Debug, Clone)]
172pub struct DeleteResponse {
173    pub deleted_count: u32,
174    pub not_found_logids: Vec<u64>,
175}
176
177/// Response from STATUS action
178#[derive(Debug, Clone)]
179pub struct StatusResponse {
180    pub data: HashMap<String, String>,
181}
182
183/// Response from FETCH action
184#[derive(Debug, Clone)]
185pub struct FetchResponse {
186    pub count: u32,
187    pub logids: Vec<u64>,
188    pub qsos: Vec<QsoRecord>,
189}
190
191/// Fetch options for filtering QSOs
192#[derive(Debug, Clone, Default)]
193pub struct FetchOptions {
194    /// Fetch all records
195    pub all: bool,
196    /// Filter by band
197    pub band: Option<String>,
198    /// Filter by mode
199    pub mode: Option<String>,
200    /// Filter by callsign
201    pub call: Option<String>,
202    /// Maximum number of records to return
203    pub max: Option<u32>,
204    /// Start after this logid for paging
205    pub after_logid: Option<u64>,
206    /// Filter by date range (start)
207    pub date_from: Option<NaiveDate>,
208    /// Filter by date range (end)
209    pub date_to: Option<NaiveDate>,
210}
211
212impl FetchOptions {
213    pub fn new() -> Self {
214        Self::default()
215    }
216
217    pub fn all() -> Self {
218        Self {
219            all: true,
220            ..Default::default()
221        }
222    }
223
224    pub fn band(mut self, band: impl Into<String>) -> Self {
225        self.band = Some(band.into());
226        self
227    }
228
229    pub fn mode(mut self, mode: impl Into<String>) -> Self {
230        self.mode = Some(mode.into());
231        self
232    }
233
234    pub fn call(mut self, call: impl Into<String>) -> Self {
235        self.call = Some(call.into());
236        self
237    }
238
239    pub fn max(mut self, max: u32) -> Self {
240        self.max = Some(max);
241        self
242    }
243
244    pub fn after_logid(mut self, logid: u64) -> Self {
245        self.after_logid = Some(logid);
246        self
247    }
248
249    pub fn date_range(mut self, from: NaiveDate, to: NaiveDate) -> Self {
250        self.date_from = Some(from);
251        self.date_to = Some(to);
252        self
253    }
254
255    /// Convert to API option string
256    pub fn to_option_string(&self) -> String {
257        let mut options = Vec::new();
258
259        if self.all {
260            options.push("ALL".to_string());
261        }
262
263        if let Some(ref band) = self.band {
264            options.push(format!("BAND:{}", band));
265        }
266
267        if let Some(ref mode) = self.mode {
268            options.push(format!("MODE:{}", mode));
269        }
270
271        if let Some(ref call) = self.call {
272            options.push(format!("CALL:{}", call));
273        }
274
275        if let Some(max) = self.max {
276            options.push(format!("MAX:{}", max));
277        }
278
279        if let Some(logid) = self.after_logid {
280            options.push(format!("AFTERLOGID:{}", logid));
281        }
282
283        if let Some(date) = self.date_from {
284            options.push(format!("DATEFROM:{}", date.format("%Y%m%d")));
285        }
286
287        if let Some(date) = self.date_to {
288            options.push(format!("DATETO:{}", date.format("%Y%m%d")));
289        }
290
291        options.join(",")
292    }
293}