airlab_lib/model/
clone.rs

1use crate::ctx::Ctx;
2use crate::model::ModelManager;
3use crate::model::Result;
4use crate::model::base::{self, DbBmc};
5use modql::field::Fields;
6use modql::filter::{FilterNodes, ListOptions, OpValsInt64, OpValsString};
7use serde::{Deserialize, Serialize};
8use sqlx::FromRow;
9
10impl CloneBmc {
11    #[must_use]
12    pub fn get_create_sql(drop_table: bool) -> String {
13        let table = Self::TABLE;
14        format!(
15            r##"{}
16create table if not exists "{table}" (
17  id serial primary key,
18  group_id integer NOT NULL,
19  created_by integer NOT NULL,
20  protein_id integer NOT NULL,
21  species_id integer,
22  name character varying NOT NULL,
23  isotype character varying,
24  epitope character varying,
25  is_phospho boolean DEFAULT false NOT NULL,
26  is_polyclonal boolean DEFAULT false NOT NULL,
27  reactivity integer[],
28  application jsonb,
29  is_archived boolean DEFAULT false NOT NULL,
30  meta jsonb,
31  created_at timestamp with time zone DEFAULT now() NOT NULL,
32  updated_at timestamp with time zone DEFAULT now() NOT NULL
33);
34CREATE INDEX "IDX_clone_created_by" ON clone USING btree (created_by);
35CREATE INDEX "IDX_clone_group_id" ON clone USING btree (group_id);
36CREATE INDEX "IDX_clone_name" ON clone USING btree (name);
37CREATE INDEX "IDX_clone_protein_id" ON clone USING btree (protein_id);
38        "##,
39            if drop_table {
40                format!("drop table if exists {table};")
41            } else {
42                String::new()
43            }
44        )
45    }
46}
47
48#[derive(Debug, Clone, Fields, FromRow, Serialize, Deserialize, Default)]
49pub struct Clone {
50    pub id: i32,
51
52    #[serde(rename = "groupId")]
53    pub group_id: i32,
54    #[serde(rename = "createdBy")]
55    pub created_by: i32,
56    #[serde(rename = "proteinId")]
57    pub protein_id: i32,
58    #[serde(rename = "speciesId")]
59    pub species_id: Option<i32>,
60    pub name: String,
61    pub isotype: Option<String>,
62    pub epitope: Option<String>,
63    #[serde(rename = "isPhospho")]
64    pub is_phospho: bool,
65    #[serde(rename = "isPolyclonal")]
66    pub is_polyclonal: bool,
67    pub reactivity: Option<Vec<i32>>,
68    pub application: Option<serde_json::Value>,
69    #[serde(rename = "isArchived")]
70    pub is_archived: bool,
71    pub meta: Option<serde_json::Value>,
72    #[serde(rename = "createdAt")]
73    pub created_at: chrono::DateTime<chrono::Utc>,
74    #[serde(rename = "updatedAt")]
75    pub updated_at: chrono::DateTime<chrono::Utc>,
76}
77
78#[derive(Fields, Deserialize, Clone, Debug)]
79pub struct CloneForCreate {
80    #[serde(rename = "groupId")]
81    pub group_id: i32,
82    #[serde(rename = "createdBy")]
83    pub created_by: Option<i32>,
84    #[serde(rename = "proteinId")]
85    pub protein_id: i32,
86    #[serde(rename = "speciesId")]
87    pub species_id: Option<i32>,
88    pub name: String,
89    pub isotype: String,
90    pub epitope: String,
91    #[serde(rename = "isPhospho")]
92    pub is_phospho: bool,
93    #[serde(rename = "isPolyclonal")]
94    pub is_polyclonal: bool,
95    #[serde(rename = "isArchived")]
96    pub is_archived: Option<bool>,
97    pub reactivity: Option<Vec<i32>>,
98    pub application: Option<serde_json::Value>,
99}
100
101#[derive(Fields, Default, Deserialize, Debug)]
102pub struct CloneForUpdate {
103    pub name: Option<String>,
104    pub isotype: Option<String>,
105    pub epitope: Option<String>,
106    #[serde(rename = "isPhospho")]
107    pub is_phospho: bool,
108    #[serde(rename = "isPolyclonal")]
109    pub is_polyclonal: bool,
110    #[serde(rename = "isArchived")]
111    pub is_archived: Option<bool>,
112    pub reactivity: Option<Vec<i32>>,
113    pub application: Option<serde_json::Value>,
114}
115
116#[derive(FilterNodes, Deserialize, Default, Debug)]
117pub struct CloneFilter {
118    id: Option<OpValsInt64>,
119    species_id: Option<OpValsInt64>,
120    protein_id: Option<OpValsInt64>,
121    group_id: Option<OpValsInt64>,
122    name: Option<OpValsString>,
123}
124
125pub struct CloneBmc;
126
127impl DbBmc for CloneBmc {
128    const TABLE: &'static str = "clone";
129}
130
131impl CloneBmc {
132    pub async fn create(ctx: &Ctx, mm: &ModelManager, clone_c: CloneForCreate) -> Result<i32> {
133        base::create::<Self, _>(ctx, mm, clone_c).await
134    }
135    pub async fn create_full(ctx: &Ctx, mm: &ModelManager, clone_c: Clone) -> Result<i32> {
136        base::create::<Self, _>(ctx, mm, clone_c).await
137    }
138
139    pub async fn get(ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<Clone> {
140        base::get::<Self, _>(ctx, mm, id).await
141    }
142
143    pub async fn list(
144        ctx: &Ctx,
145        mm: &ModelManager,
146        filters: Option<Vec<CloneFilter>>,
147        list_options: Option<ListOptions>,
148    ) -> Result<Vec<Clone>> {
149        base::list::<Self, _, _>(ctx, mm, filters, list_options).await
150    }
151
152    pub async fn update(
153        ctx: &Ctx,
154        mm: &ModelManager,
155        id: i32,
156        clone_u: CloneForUpdate,
157    ) -> Result<()> {
158        base::update::<Self, _>(ctx, mm, id, clone_u).await
159    }
160
161    pub async fn delete(ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<()> {
162        base::delete::<Self>(ctx, mm, id).await
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use crate::_dev_utils;
170    use crate::model::Error;
171    use anyhow::Result;
172    use serde_json::json;
173
174    #[ignore]
175    #[tokio::test]
176    async fn test_clone_create_ok() -> Result<()> {
177        let mm = ModelManager::new().await?;
178        let ctx = Ctx::root_ctx();
179        let fx_name = "test_create_ok name";
180
181        let clone_c = CloneForCreate {
182            name: fx_name.to_string(),
183            group_id: 1000,
184            created_by: Some(1303),
185            epitope: String::new(),
186            is_archived: None,
187            is_phospho: false,
188            reactivity: None,
189            application: None,
190            is_polyclonal: false,
191            isotype: String::new(),
192            protein_id: 1002,
193            species_id: Some(1004),
194        };
195        let id = CloneBmc::create(&ctx, &mm, clone_c).await?;
196
197        let clone = CloneBmc::get(&ctx, &mm, id).await?;
198        assert_eq!(clone.name, fx_name);
199
200        CloneBmc::delete(&ctx, &mm, id).await?;
201
202        Ok(())
203    }
204
205    #[ignore]
206    #[tokio::test]
207    async fn test_clone_get_err_not_found() -> Result<()> {
208        let mm = ModelManager::new().await?;
209        let ctx = Ctx::root_ctx();
210        let fx_id = 100;
211
212        let res = CloneBmc::get(&ctx, &mm, fx_id).await;
213
214        assert!(
215            matches!(
216                res,
217                Err(Error::EntityNotFound {
218                    entity: "clone",
219                    id: 100
220                })
221            ),
222            "EntityNotFound not matching"
223        );
224
225        Ok(())
226    }
227
228    #[ignore]
229    #[tokio::test]
230    async fn test_clone_list_all_ok() -> Result<()> {
231        let mm = ModelManager::new().await?;
232        let ctx = Ctx::root_ctx();
233        let tname = "test_clone_list_all_ok";
234        let seeds = _dev_utils::get_clone_seed(tname);
235        _dev_utils::seed_clones(&ctx, &mm, &seeds).await?;
236
237        let clones = CloneBmc::list(&ctx, &mm, None, None).await?;
238        println!("n clones: {}", clones.len());
239
240        let clones: Vec<Clone> = clones
241            .into_iter()
242            .filter(|t| t.name.starts_with("test_list_all_ok-clone"))
243            .collect();
244        println!("n clones: {}", clones.len());
245
246        for clone in clones.iter() {
247            if false {
248                CloneBmc::delete(&ctx, &mm, clone.id).await?;
249            }
250        }
251
252        Ok(())
253    }
254
255    #[ignore]
256    #[tokio::test]
257    async fn test_clone_list_by_filter_ok() -> Result<()> {
258        let mm = ModelManager::new().await?;
259        let ctx = Ctx::root_ctx();
260        let tname = "test_clone_list_by_filter_ok";
261        let seeds = _dev_utils::get_clone_seed(tname);
262        _dev_utils::seed_clones(&ctx, &mm, &seeds).await?;
263
264        let filters: Vec<CloneFilter> = serde_json::from_value(json!([
265            {
266                "name": {
267                    "$endsWith": ".a",
268                    "$containsAny": ["01", "02"]
269                }
270            },
271            {
272                "name": {"$contains": "03"}
273            }
274        ]))?;
275        let list_options = serde_json::from_value(json!({
276            "order_bys": "!id"
277        }))?;
278        let clones = CloneBmc::list(&ctx, &mm, Some(filters), Some(list_options)).await?;
279        println!("n clones: {}", clones.len());
280
281        assert_eq!(clones.len(), 3);
282        assert!(clones[0].name.ends_with("03"));
283        assert!(clones[1].name.ends_with("02.a"));
284        assert!(clones[2].name.ends_with("01.a"));
285
286        if false {
287            let clones = CloneBmc::list(
288                &ctx,
289                &mm,
290                Some(serde_json::from_value(json!([{
291                    "name": {"$startsWith": "test_list_by_filter_ok"}
292                }]))?),
293                None,
294            )
295            .await?;
296            assert_eq!(clones.len(), 5);
297            for clone in clones.iter() {
298                CloneBmc::delete(&ctx, &mm, clone.id).await?;
299            }
300        }
301
302        Ok(())
303    }
304
305    #[ignore]
306    #[tokio::test]
307    async fn test_clone_update_ok() -> Result<()> {
308        let mm = ModelManager::new().await?;
309        let ctx = Ctx::root_ctx();
310        let tname = "test_clone_update_ok";
311        let seeds = _dev_utils::get_clone_seed(tname);
312        _dev_utils::seed_clones(&ctx, &mm, &seeds).await?;
313        let fx_clone = _dev_utils::seed_clones(&ctx, &mm, &seeds).await?.remove(0);
314
315        CloneBmc::update(
316            &ctx,
317            &mm,
318            fx_clone.id,
319            CloneForUpdate {
320                name: Some(tname.to_string()),
321                ..Default::default()
322            },
323        )
324        .await?;
325
326        let clone = CloneBmc::get(&ctx, &mm, fx_clone.id).await?;
327        assert_eq!(clone.name, tname);
328
329        Ok(())
330    }
331
332    #[ignore]
333    #[tokio::test]
334    async fn test_clone_delete_err_not_found() -> Result<()> {
335        let mm = ModelManager::new().await?;
336        let ctx = Ctx::root_ctx();
337        let fx_id = 100;
338
339        let res = CloneBmc::delete(&ctx, &mm, fx_id).await;
340
341        assert!(
342            matches!(
343                res,
344                Err(Error::EntityNotFound {
345                    entity: "clone",
346                    id: 100
347                })
348            ),
349            "EntityNotFound not matching"
350        );
351
352        Ok(())
353    }
354}