1use crate::{Result, Sockudo, SockudoError};
2use reqwest::Response;
3use serde::{Deserialize, Serialize};
4use sonic_rs::{JsonValueTrait, Value, json};
5use std::collections::{BTreeMap, HashMap};
6
7#[derive(Clone, Debug, Default, Serialize, Deserialize)]
8pub struct PushCursorParams {
9 pub limit: Option<u32>,
10 pub cursor: Option<String>,
11}
12
13impl PushCursorParams {
14 pub fn to_map(&self) -> BTreeMap<String, String> {
15 let mut map = BTreeMap::new();
16 if let Some(limit) = self.limit {
17 map.insert("limit".to_string(), limit.to_string());
18 }
19 if let Some(cursor) = &self.cursor {
20 map.insert("cursor".to_string(), cursor.clone());
21 }
22 map
23 }
24}
25
26#[derive(Clone, Debug, Default, Serialize, Deserialize)]
27pub struct PushSubscriptionParams {
28 pub limit: Option<u32>,
29 pub cursor: Option<String>,
30 pub channel: Option<String>,
31 pub device_id: Option<String>,
32}
33
34impl PushSubscriptionParams {
35 pub fn to_map(&self) -> BTreeMap<String, String> {
36 let mut map = PushCursorParams {
37 limit: self.limit,
38 cursor: self.cursor.clone(),
39 }
40 .to_map();
41 if let Some(channel) = &self.channel {
42 map.insert("channel".to_string(), channel.clone());
43 }
44 if let Some(device_id) = &self.device_id {
45 map.insert("deviceId".to_string(), device_id.clone());
46 }
47 map
48 }
49}
50
51fn push_headers(capability: &str, device_identity_token: Option<&str>) -> HashMap<String, String> {
52 let mut headers = HashMap::from([(
53 "X-Sockudo-Push-Capability".to_string(),
54 capability.to_string(),
55 )]);
56 if let Some(token) = device_identity_token {
57 headers.insert(
58 "X-Sockudo-Device-Identity-Token".to_string(),
59 token.to_string(),
60 );
61 }
62 headers
63}
64
65fn push_path(path: &str) -> String {
66 format!("/push{path}")
67}
68
69impl Sockudo {
70 pub async fn activate_device(&self, device: &Value) -> Result<Response> {
71 self.post_with_headers(
72 &push_path("/deviceRegistrations"),
73 device,
74 &push_headers("push-admin", None),
75 )
76 .await
77 }
78
79 pub async fn create_device_activation(&self, device: &Value) -> Result<Response> {
80 self.activate_device(device).await
81 }
82
83 pub async fn update_device_registration(
84 &self,
85 device: &Value,
86 device_identity_token: &str,
87 ) -> Result<Response> {
88 self.post_with_headers(
89 &push_path("/deviceRegistrations"),
90 device,
91 &push_headers("push-subscribe", Some(device_identity_token)),
92 )
93 .await
94 }
95
96 pub async fn list_device_registrations(
97 &self,
98 params: Option<&PushCursorParams>,
99 ) -> Result<Response> {
100 let params = params.map(PushCursorParams::to_map);
101 self.get_with_headers(
102 &push_path("/deviceRegistrations"),
103 params.as_ref(),
104 &push_headers("push-admin", None),
105 )
106 .await
107 }
108
109 pub async fn get_device_registration(
110 &self,
111 device_id: &str,
112 device_identity_token: Option<&str>,
113 ) -> Result<Response> {
114 let capability = if device_identity_token.is_some() {
115 "push-subscribe"
116 } else {
117 "push-admin"
118 };
119 self.get_with_headers(
120 &push_path(&format!("/deviceRegistrations/{device_id}")),
121 None,
122 &push_headers(capability, device_identity_token),
123 )
124 .await
125 }
126
127 pub async fn delete_device_registration(
128 &self,
129 device_id: &str,
130 device_identity_token: Option<&str>,
131 ) -> Result<Response> {
132 let capability = if device_identity_token.is_some() {
133 "push-subscribe"
134 } else {
135 "push-admin"
136 };
137 self.delete_with_headers(
138 &push_path(&format!("/deviceRegistrations/{device_id}")),
139 None,
140 &push_headers(capability, device_identity_token),
141 )
142 .await
143 }
144
145 pub async fn remove_device_registrations_by_client(&self, client_id: &str) -> Result<Response> {
146 let params = BTreeMap::from([("clientId".to_string(), client_id.to_string())]);
147 self.delete_with_headers(
148 &push_path("/deviceRegistrations"),
149 Some(¶ms),
150 &push_headers("push-admin", None),
151 )
152 .await
153 }
154
155 pub async fn upsert_channel_push_subscription(
156 &self,
157 subscription: &Value,
158 device_identity_token: Option<&str>,
159 ) -> Result<Response> {
160 let capability = if device_identity_token.is_some() {
161 "push-subscribe"
162 } else {
163 "push-admin"
164 };
165 self.post_with_headers(
166 &push_path("/channelSubscriptions"),
167 subscription,
168 &push_headers(capability, device_identity_token),
169 )
170 .await
171 }
172
173 pub async fn list_channel_push_subscriptions(
174 &self,
175 params: Option<&PushSubscriptionParams>,
176 device_identity_token: Option<&str>,
177 ) -> Result<Response> {
178 let capability = if device_identity_token.is_some() {
179 "push-subscribe"
180 } else {
181 "push-admin"
182 };
183 let params = params.map(PushSubscriptionParams::to_map);
184 self.get_with_headers(
185 &push_path("/channelSubscriptions"),
186 params.as_ref(),
187 &push_headers(capability, device_identity_token),
188 )
189 .await
190 }
191
192 pub async fn delete_channel_push_subscriptions(
193 &self,
194 params: &PushSubscriptionParams,
195 device_identity_token: Option<&str>,
196 ) -> Result<Response> {
197 let capability = if device_identity_token.is_some() {
198 "push-subscribe"
199 } else {
200 "push-admin"
201 };
202 let params = params.to_map();
203 self.delete_with_headers(
204 &push_path("/channelSubscriptions"),
205 Some(¶ms),
206 &push_headers(capability, device_identity_token),
207 )
208 .await
209 }
210
211 pub async fn list_channel_push_subscription_channels(
212 &self,
213 params: Option<&PushCursorParams>,
214 ) -> Result<Response> {
215 let params = params.map(PushCursorParams::to_map);
216 self.get_with_headers(
217 &push_path("/channelSubscriptions/channels"),
218 params.as_ref(),
219 &push_headers("push-admin", None),
220 )
221 .await
222 }
223
224 pub async fn list_push_credentials(
225 &self,
226 params: Option<&PushCursorParams>,
227 ) -> Result<Response> {
228 let params = params.map(PushCursorParams::to_map);
229 self.get_with_headers(
230 &push_path("/credentials"),
231 params.as_ref(),
232 &push_headers("push-admin", None),
233 )
234 .await
235 }
236
237 pub async fn put_push_credential(
238 &self,
239 provider: &str,
240 credential: &Value,
241 ) -> Result<Response> {
242 self.post_with_headers(
243 &push_path(&format!("/credentials/{provider}")),
244 credential,
245 &push_headers("push-admin", None),
246 )
247 .await
248 }
249
250 pub async fn publish_push(&self, request: &Value) -> Result<Response> {
251 let mut request = request.clone();
252 request["sync"] = json!(false);
253 self.post_with_headers(
254 &push_path("/publish"),
255 &request,
256 &push_headers("push-admin", None),
257 )
258 .await
259 }
260
261 pub async fn publish_push_direct(&self, request: &Value) -> Result<Response> {
262 self.publish_push(request).await
263 }
264
265 pub async fn publish_push_batch(&self, requests: &[Value]) -> Result<Response> {
266 let requests: Vec<Value> = requests
267 .iter()
268 .map(|request| {
269 let mut item = request.clone();
270 item["sync"] = json!(false);
271 item
272 })
273 .collect();
274 let body = sonic_rs::to_value(&requests).map_err(SockudoError::Json)?;
275 self.post_with_headers(
276 &push_path("/batch/publish"),
277 &body,
278 &push_headers("push-admin", None),
279 )
280 .await
281 }
282
283 pub async fn schedule_push(&self, request: &Value) -> Result<Response> {
284 if request.get("notBeforeMs").is_none() {
285 return Err(SockudoError::Validation {
286 message: "scheduled push requires notBeforeMs".to_string(),
287 });
288 }
289 self.publish_push(request).await
290 }
291
292 pub async fn get_publish_status(&self, publish_id: &str) -> Result<Response> {
293 self.get_with_headers(
294 &push_path(&format!("/publish/{publish_id}/status")),
295 None,
296 &push_headers("push-admin", None),
297 )
298 .await
299 }
300
301 pub async fn cancel_scheduled_push(&self, publish_id: &str) -> Result<Response> {
302 self.delete_with_headers(
303 &push_path(&format!("/scheduled/{publish_id}")),
304 None,
305 &push_headers("push-admin", None),
306 )
307 .await
308 }
309
310 pub async fn post_push_delivery_status(&self, event: &Value) -> Result<Response> {
311 self.post_with_headers(
312 &push_path("/deliveryStatus"),
313 event,
314 &push_headers("push-admin", None),
315 )
316 .await
317 }
318}