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}