1use crate::ctx::Ctx;
2use crate::model::ModelManager;
3use crate::model::Result;
4use crate::model::base::{self, DbBmc};
5use 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 }
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 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 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}