pb_sdk/
lib.rs

1
2use std::option::Option;
3use std::error::Error;
4use std::fmt::{Debug, Display, Formatter};
5use lazy_static::lazy_static;
6use reqwest::Client;
7use std::sync::Arc;
8use serde::{Deserialize, Serialize};
9use serde::de::DeserializeOwned;
10use serde_json::Value;
11
12#[derive(Serialize, Deserialize, Debug)]
13struct AdminResponse {
14    token: String,
15    admin: Admin,
16}
17
18#[derive(Serialize, Deserialize, Debug)]
19struct Admin {
20    id: String,
21    created: String,
22    updated: String,
23    email: String,
24    avatar: u8,
25}
26
27#[derive(Deserialize)]
28struct PbAuthError {
29    code: u16,
30    message: String,
31    data: ErrorData,
32}
33
34#[derive(Serialize, Deserialize, Debug)]
35struct ErrorData {
36    password: Option<PasswordError>,
37}
38
39#[derive(Serialize, Deserialize, Debug)]
40struct PasswordError {
41    code: String,
42    message: String,
43}
44
45#[derive(Debug, Clone)]
46pub struct PbAdminClient {
47    url: String,
48    username: Option<String>,
49    password: Option<String>,
50    token: Option<String>,
51}
52
53#[derive(Debug)]
54struct PbError {
55    detail: String
56}
57
58impl PbError {
59    fn new(msg: &str) -> PbError {
60        PbError { detail: msg.to_string() }
61    }
62}
63impl Display for PbError {
64    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
65        write!(f, "{}", self.detail)
66    }
67}
68
69lazy_static! {
70    static ref REQ_CLIENT: Arc<Client> = Arc::new(Client::new());
71}
72impl Error for PbError {}
73
74
75#[derive(Serialize, Debug)]
76struct PasswordAuth {
77    identity: String,
78    password: String
79}
80
81
82impl PbAdminClient {
83    pub fn new(url: &str) -> Self {
84        PbAdminClient {
85            url: url.trim_end_matches("/").to_string(),
86            password: None,
87            username: None,
88            token: None,
89        }
90    }
91    pub async fn auth_with_password(mut self, username: &str, password: &str) -> Result<Self, Box<dyn Error>> {
92        self.password = Option::from(username.to_string());
93        self.username = Option::from(password.to_string());
94        let auth_input = PasswordAuth {
95            identity: username.to_string(),
96            password: password.to_string()
97        };
98
99        let req = REQ_CLIENT.post(format!("{}/api/admins/auth-with-password", self.url))
100            .json(&auth_input).send().await?;
101        let code = req.status();
102        if code == 400 {
103            let err: PbAuthError = req.json().await?;
104            return Err(Box::new(PbError::new(&err.message)))
105        }
106        let req: AdminResponse = req.json().await?;
107        self.token = Option::from(req.token);
108
109
110        return Ok(self);
111    }
112    pub fn collection(&self, collection: &str) -> Result<PbCollection, Box<dyn Error>> {
113
114        if self.token.is_none() {
115            return Err(Box::new(PbError::new("unauthenticated")));
116        }
117        let pbcol = PbCollection {
118            url: self.url.clone(),
119            token: self.token.clone().unwrap(),
120            collection: collection.to_string()
121        };
122
123        Ok(pbcol)
124    }
125}
126
127#[derive(Debug)]
128pub struct PbCollection {
129    url: String,
130    token: String,
131    collection: String
132}
133
134impl PbCollection {
135    pub fn get_list(self, page: u16, per_page: u16) -> PbGetList {
136        PbGetList {
137            url: self.url,
138            token: self.token,
139            collection: self.collection,
140            page,
141            per_page,
142            sort: None,
143            filter: None,
144            expand: None,
145            fields: None,
146            skip_total: None,
147        }
148    }
149    pub fn get_one(self, record_id: &str) -> PbViewRecord {
150        PbViewRecord {
151            url: self.url,
152            token: self.token,
153            collection: self.collection,
154            expand: None,
155            fields: None,
156            record_id: record_id.to_string(),
157        }
158    }
159
160    pub fn create<T: Serialize>( self, record: T) -> PbCreateRecord<T> {
161        PbCreateRecord {
162            url: self.url,
163            token: self.token,
164            collection: self.collection,
165            record,
166            expand: None,
167            fields: None
168        }
169    }
170    pub fn update<T: Serialize>( self, record_id: &str, record: T) -> PbUpdateRecord<T> {
171        PbUpdateRecord {
172            url: self.url,
173            token: self.token,
174            collection: self.collection,
175            record,
176            expand: None,
177            fields: None,
178            record_id: record_id.to_string(),
179        }
180    }
181    pub fn delete( self, record_id: &str) -> PbDeleteRecord {
182        PbDeleteRecord {
183            url: self.url,
184            token: self.token,
185            collection: self.collection,
186            record_id: record_id.to_string()
187        }
188    }
189}
190
191pub struct PbDeleteRecord {
192    url: String,
193    token: String,
194    collection: String,
195    record_id: String,
196}
197
198impl PbDeleteRecord {
199    pub async fn call (self) -> Result<(), Box<dyn Error>> {
200
201        let req = REQ_CLIENT.delete(format!("{}/api/collections/{}/records/{}", self.url, self.collection, self.record_id))
202            .header("authorization", format!("Bearer {}", self.token))
203            .send().await?;
204
205        if req.status() == 204 {
206            Ok(())
207        }else {
208            let obj: GetListFail = req.json().await?;
209            Err(obj.into())
210        }
211    }
212}
213
214
215pub struct PbUpdateRecord<T> {
216    url: String,
217    token: String,
218    collection: String,
219    record: T,
220    expand: Option<String>,
221    fields: Option<String>,
222    record_id: String
223}
224
225impl<T: Serialize> PbUpdateRecord<T> {
226    pub fn expand(mut self, expand: &str) -> Self {
227        self.expand = Option::from(expand.to_string());
228        self
229    }
230    pub fn fields(mut self, fields: &str) -> Self {
231        self.fields = Option::from(fields.to_string());
232        self
233    }
234    pub async fn call<Y: DeserializeOwned> (self) -> Result<Y, Box<dyn Error>> {
235        let mut query: String = "?".to_string();
236        if self.expand.is_some() {
237            if query.eq("?"){
238                query += &format!("expand={}", self.expand.unwrap());
239            }else {
240                query += &format!("&expand={}", self.expand.unwrap());
241            }
242        }
243        if self.fields.is_some() {
244            if query.eq("?"){
245                query += &format!("fields={}", self.fields.unwrap());
246            }else {
247                query += &format!("&fields={}", self.fields.unwrap());
248            }
249        }
250
251        let req = REQ_CLIENT.patch(format!("{}/api/collections/{}/records/{}{}", self.url, self.collection, self.record_id, query))
252            .header("authorization", format!("Bearer {}", self.token))
253            .json(&self.record)
254            .send().await?;
255
256        if req.status() == 200 {
257            Ok(req.json().await?)
258        }else {
259            let obj: GetListFail = req.json().await?;
260            Err(obj.into())
261        }
262    }
263}
264
265pub struct PbCreateRecord<T> {
266    url: String,
267    token: String,
268    collection: String,
269    record: T,
270    expand: Option<String>,
271    fields: Option<String>,
272}
273
274impl<T: Serialize> PbCreateRecord<T> {
275    pub fn expand(mut self, expand: &str) -> Self {
276        self.expand = Option::from(expand.to_string());
277        self
278    }
279    pub fn fields(mut self, fields: &str) -> Self {
280        self.fields = Option::from(fields.to_string());
281        self
282    }
283    pub async fn call<Y: DeserializeOwned> (self) -> Result<Y, Box<dyn Error>> {
284        let mut query: String = "?".to_string();
285        if self.expand.is_some() {
286            if query.eq("?"){
287                query += &format!("expand={}", self.expand.unwrap());
288            }else {
289                query += &format!("&expand={}", self.expand.unwrap());
290            }
291        }
292        if self.fields.is_some() {
293            if query.eq("?"){
294                query += &format!("fields={}", self.fields.unwrap());
295            }else {
296                query += &format!("&fields={}", self.fields.unwrap());
297            }
298        }
299
300        let req = REQ_CLIENT.post(format!("{}/api/collections/{}/records{}", self.url, self.collection, query))
301            .header("authorization", format!("Bearer {}", self.token))
302            .json(&self.record)
303            .send().await?;
304
305        if req.status() == 200 {
306            Ok(req.json().await?)
307        }else {
308            let obj: GetListFail = req.json().await?;
309            Err(obj.into())
310        }
311    }
312}
313pub struct PbViewRecord {
314    url: String,
315    token: String,
316    collection: String,
317    expand: Option<String>,
318    fields: Option<String>,
319    record_id: String,
320}
321
322impl PbViewRecord {
323    pub fn expand(mut self, expand: &str) -> Self {
324        self.expand = Option::from(expand.to_string());
325        self
326    }
327    pub fn fields(mut self, fields: &str) -> Self {
328        self.fields = Option::from(fields.to_string());
329        self
330    }
331    pub async fn call<T: DeserializeOwned> (self) -> Result<T, Box<dyn Error>> {
332        let mut query: String = "?".to_string();
333
334        if self.expand.is_some() {
335            if query.eq("?"){
336                query += &format!("expand={}", self.expand.unwrap());
337            }else {
338                query += &format!("&expand={}", self.expand.unwrap());
339            }
340        }
341        if self.fields.is_some() {
342            if query.eq("?"){
343                query += &format!("fields={}", self.fields.unwrap());
344            }else {
345                query += &format!("&fields={}", self.fields.unwrap());
346            }
347        }
348
349        let req = REQ_CLIENT.get(format!("{}/api/collections/{}/records/{}{}", self.url, self.collection, self.record_id, query))
350            .header("authorization", format!("Bearer {}", self.token))
351            .send().await?;
352
353        if req.status() == 200 {
354            Ok(req.json().await?)
355        }else {
356            let obj: GetListFail = req.json().await?;
357            Err(obj.into())
358        }
359    }
360}
361
362
363pub struct PbGetList {
364    url: String,
365    token: String,
366    collection: String,
367    page: u16,
368    per_page: u16,
369    sort: Option<String>,
370    filter: Option<String>,
371    expand: Option<String>,
372    fields: Option<String>,
373    skip_total: Option<bool>
374}
375impl PbGetList {
376    pub fn page(mut self, page: u16) -> Self {
377        self.page = page;
378        self
379    }
380    pub fn per_page(mut self, per_page: u16) -> Self {
381        self.per_page = per_page;
382        self
383    }
384    pub fn sort(mut self, sort: &str) -> Self {
385        self.sort = Option::from(sort.to_string());
386        self
387    }
388    pub fn filter(mut self, filter: &str) -> Self {
389        self.filter = Option::from(filter.to_string());
390        self
391    }
392    pub fn expand(mut self, expand: &str) -> Self {
393        self.expand = Option::from(expand.to_string());
394        self
395    }
396    pub fn fields(mut self, fields: &str) -> Self {
397        self.fields = Option::from(fields.to_string());
398        self
399    }
400    pub fn skip_total(mut self, skip_total: bool) -> Self {
401        self.skip_total = Option::from(skip_total);
402        self
403    }
404    pub async fn call<T: DeserializeOwned> (self) -> Result<GetListSuccess<T>, Box<dyn Error>> {
405        let mut query: String = "?".to_string();
406
407        query += &format!("page={}", self.page);
408        query += &format!("&perPage={}", self.per_page);
409        if self.sort.is_some() {
410            query += &format!("&sort={}", self.sort.unwrap());
411        }
412        if self.filter.is_some() {
413            query += &format!("&filter={}", self.filter.unwrap());
414        }
415        if self.expand.is_some() {
416            query += &format!("&expand={}", self.expand.unwrap());
417        }
418        if self.fields.is_some() {
419            query += &format!("&fields=({})", self.fields.unwrap());
420        }
421        if self.skip_total.is_some() {
422            query += &format!("&skipTotal={}", self.skip_total.unwrap());
423        }
424
425        let req = REQ_CLIENT.get(format!("{}/api/collections/{}/records{}", self.url, self.collection, query))
426            .header("authorization", format!("Bearer {}", self.token))
427            .send().await?;
428
429        if req.status() == 200 {
430            Ok(req.json().await?)
431        }else {
432            let obj: GetListFail = req.json().await?;
433            Err(obj.into())
434        }
435
436
437
438
439
440
441
442    }
443}
444
445#[derive(Deserialize, Debug, Serialize)]
446pub struct GetListSuccess<T> {
447    pub page: u16,
448    #[serde(rename = "perPage")]
449    pub per_page: u16,
450    #[serde(rename = "totalItems")]
451    pub total_items: u16,
452    #[serde(rename = "totalPages")]
453    pub total_pages: u16,
454    pub items: Vec<T>
455}
456#[derive(Deserialize, Debug, Serialize)]
457pub struct GetListFail {
458    pub code: u16,
459    pub message: String,
460    pub data: Value
461}
462
463impl Display for GetListFail {
464    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
465        write!(f, "{}", self.message)
466    }
467}
468
469impl Error for GetListFail {}
470
471
472
473
474
475
476#[cfg(test)]
477mod tests {}