1use async_trait::async_trait;
2use serde::Deserialize;
3use serde_json::json;
4use std::error::Error;
5use std::fmt;
6
7#[async_trait]
9pub trait HttpClient {
10 async fn get(&self, url: &str) -> Result<String, ApiError>;
11 async fn post(&self, url: &str, json_body: &str) -> Result<String, ApiError>;
12 async fn post_form(&self, url: &str, form_data: &[(&str, &str)]) -> Result<String, ApiError>;
13}
14
15#[derive(Debug)]
17pub enum ApiError {
18 AuthenticationError,
19 RequestError(String),
20 ParsingError(String),
21}
22
23impl fmt::Display for ApiError {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 match self {
26 ApiError::AuthenticationError => write!(f, "Authentication error"),
27 ApiError::RequestError(msg) => write!(f, "Request error: {}", msg),
28 ApiError::ParsingError(msg) => write!(f, "Parsing error: {}", msg),
29 }
30 }
31}
32
33impl Error for ApiError {}
34
35pub struct IamRespondingAPI<C> {
37 client: C,
38 token_for_api: Option<String>, member_id: Option<i64>, }
41
42impl<C> IamRespondingAPI<C>
43where
44 C: HttpClient + Send + Sync,
45{
46 pub fn new(client: C) -> Self {
47 Self {
48 client,
49 token_for_api: None,
50 member_id: None,
51 }
52 }
53
54 pub async fn login(
56 &mut self,
57 agency: &str,
58 user: &str,
59 pass: &str,
60 ) -> Result<(), ApiError> {
61 let login_params = json!({
62 "memberLogin": true,
63 "agencyName": agency,
64 "memberfname": user,
65 "memberpwd": pass,
66 "rememberPwd": false,
67 "urlTo": "",
68 "overrideSession": true,
69 });
70
71 let serialized_params = serde_json::to_string(&login_params)
72 .map_err(|e| ApiError::ParsingError(e.to_string()))?;
73
74 let response = self
75 .client
76 .post(
77 "https://iamresponding.com/v3/Pages/memberlogin.aspx/ValidateLoginInfo",
78 &serialized_params,
79 )
80 .await?;
81
82 if response.contains("The log-in information that you have entered is incorrect.") {
83 return Err(ApiError::AuthenticationError);
84 }
85
86 let login_data: LoginResponse = serde_json::from_str(&response)
90 .map_err(|e| ApiError::ParsingError(e.to_string()))?;
91
92 self.token_for_api = Some(login_data.token_for_api);
93 self.member_id = Some(login_data.member_id);
94
95 Ok(())
96 }
97
98 pub async fn get_now_responding(&self) -> Result<Vec<NowResponding>, ApiError> {
100 let response = self
101 .client
102 .post_form("https://iamresponding.com/v3/AgencyServices.asmx/GetNowRespondingWithSort", &[])
103 .await?;
104
105 let data: NowRespondingResponse = serde_json::from_str(&response)
106 .map_err(|e| ApiError::ParsingError(e.to_string()))?;
107
108 Ok(data.now_responding)
109 }
110
111 pub async fn get_on_schedule(&self) -> Result<Vec<OnSchedule>, ApiError> {
113 let response = self
114 .client
115 .post_form("https://iamresponding.com/v3/AgencyServices.asmx/GetOnScheduleWithSort", &[])
116 .await?;
117
118 let data: OnScheduleResponse = serde_json::from_str(&response)
119 .map_err(|e| ApiError::ParsingError(e.to_string()))?;
120
121 Ok(data.on_schedule)
122 }
123
124 pub async fn list_dispatch_messages(&self) -> Result<Vec<DispatchMessage>, ApiError> {
126 let response = self
127 .client
128 .post_form("https://iamresponding.com/v3/DispatchMessages.asmx/ListWithParser", &[])
129 .await?;
130
131 let data: DispatchMessageResponse = serde_json::from_str(&response)
132 .map_err(|e| ApiError::ParsingError(e.to_string()))?;
133
134 Ok(data.dispatch_messages)
135 }
136
137 pub async fn get_incident_info(&self, incident_id: i64) -> Result<IncidentInfoData, ApiError> {
139 let params = json!({
140 "messageID": incident_id,
141 "token": self.token_for_api,
142 });
143
144 let serialized_params = serde_json::to_string(¶ms)
145 .map_err(|e| ApiError::ParsingError(e.to_string()))?;
146
147 let response = self
148 .client
149 .post("https://iamresponding.com/v3/agency/IncidentsDashboard.aspx/GetIncidentInfo", &serialized_params)
150 .await?;
151
152 let data: IncidentInfoResponse = serde_json::from_str(&response)
153 .map_err(|e| ApiError::ParsingError(e.to_string()))?;
154
155 data.data.into_iter().next().ok_or(ApiError::ParsingError("No data for incident".to_string()))
156 }
157
158 pub async fn get_latest_incidents(&self) -> Result<Vec<IncidentInfoData>, ApiError> {
160 let token = self.token_for_api.as_deref().ok_or(ApiError::AuthenticationError)?;
161 let member_id = self.member_id.ok_or(ApiError::AuthenticationError)?;
162
163 let params = json!({
164 "memberID": member_id,
165 "token": token,
166 });
167
168 let serialized_params = serde_json::to_string(¶ms)
169 .map_err(|e| ApiError::ParsingError(e.to_string()))?;
170
171 let response = self
172 .client
173 .post("https://iamresponding.com/v3/agency/IncidentsDashboard.aspx/GetLatestIncidents", &serialized_params)
174 .await?;
175
176 let data: IncidentInfoResponse = serde_json::from_str(&response)
177 .map_err(|e| ApiError::ParsingError(e.to_string()))?;
178
179 Ok(data.data)
180 }
181
182 pub async fn get_reminders_by_member(&self) -> Result<Vec<EventReminder>, ApiError> {
184 let token = self.token_for_api.as_deref().ok_or(ApiError::AuthenticationError)?;
185 let member_id = self.member_id.ok_or(ApiError::AuthenticationError)?;
186
187 let member_id_str = member_id.to_string();
188 let days_str = "7".to_string();
189
190 let form_data = [
191 ("subsString", &member_id_str as &str),
192 ("days", &days_str as &str), ];
194
195 let response = self
196 .client
197 .post_form("https://iamresponding.com/v3/AgencyServices.asmx/GetRemindersByMember", &form_data)
198 .await?;
199
200 let data: RemindersResponse = serde_json::from_str(&response)
201 .map_err(|e| ApiError::ParsingError(e.to_string()))?;
202
203 Ok(data.reminders)
204 }
205}
206
207#[derive(Debug, Deserialize)]
209struct LoginResponse {
210 token_for_api: String,
211 member_id: i64,
212}
213
214#[derive(Debug, Deserialize)]
216struct NowRespondingResponse {
217 now_responding: Vec<NowResponding>,
218}
219
220#[derive(Debug, Deserialize)]
221pub struct NowResponding {
222 pub member_name: String,
223}
224
225#[derive(Debug, Deserialize)]
226struct OnScheduleResponse {
227 on_schedule: Vec<OnSchedule>,
228}
229
230#[derive(Debug, Deserialize)]
231pub struct OnSchedule {
232 pub member_name: String,
233}
234
235#[derive(Debug, Deserialize)]
236struct DispatchMessageResponse {
237 dispatch_messages: Vec<DispatchMessage>,
238}
239
240#[derive(Debug, Deserialize)]
241pub struct DispatchMessage {
242 pub message_body: String,
243 pub address: Option<String>,
244}
245
246#[derive(Debug, Deserialize)]
247struct IncidentInfoResponse {
248 data: Vec<IncidentInfoData>,
249}
250
251#[derive(Debug, Deserialize)]
252pub struct IncidentInfoData {
253 pub id: i64,
254 pub incident_type: String,
255}
256
257#[derive(Debug, Deserialize)]
259struct RemindersResponse {
260 reminders: Vec<EventReminder>,
261}
262
263#[derive(Debug, Deserialize)]
264pub struct EventReminder {
265 pub event_name: String,
266 pub date: String,
267 pub time: String,
268}