airlab_lib/model/
panel.rs

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