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 ProteinBmc {
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 name character varying NOT NULL,
21 description character varying,
22 meta jsonb,
23 created_at timestamp with time zone DEFAULT now() NOT NULL
24);
25CREATE INDEX "IDX_protein_created_by" ON protein USING btree (created_by);
26CREATE INDEX "IDX_protein_group_id" ON protein USING btree (group_id);
27CREATE INDEX "IDX_protein_name" ON protein USING btree (name);
28 "##,
29 if drop_table {
30 format!("drop table if exists {table};")
31 } else {
32 String::new()
33 }
34 )
35 }
36}
37
38#[derive(Debug, Clone, Fields, FromRow, Serialize, Deserialize, Default)]
39pub struct Protein {
40 pub id: i32,
41 #[serde(rename = "groupId")]
42 pub group_id: i32,
43
44 #[serde(rename = "createdBy")]
45 pub created_by: i32,
46 pub name: String,
47 pub description: Option<String>,
48 pub meta: Option<serde_json::Value>,
49 #[serde(rename = "createdAt")]
50 pub created_at: chrono::DateTime<chrono::Utc>, }
52
53#[derive(Fields, Deserialize, Clone)]
54pub struct ProteinForCreate {
55 pub name: String,
56 pub description: Option<String>,
57 pub group_id: i32,
58 pub created_by: i32,
59}
60
61#[derive(Fields, Default, Deserialize)]
62pub struct ProteinForUpdate {
63 pub name: Option<String>,
64 pub description: Option<String>,
65}
66
67#[derive(FilterNodes, Deserialize, Default, Debug)]
68pub struct ProteinFilter {
69 id: Option<OpValsInt64>,
70 group_id: Option<OpValsInt64>,
71 name: Option<OpValsString>,
72 description: Option<OpValsString>,
73}
74
75pub struct ProteinBmc;
76
77impl DbBmc for ProteinBmc {
78 const TABLE: &'static str = "protein";
79}
80
81impl ProteinBmc {
82 pub async fn create(ctx: &Ctx, mm: &ModelManager, protein_c: ProteinForCreate) -> Result<i32> {
83 base::create::<Self, _>(ctx, mm, protein_c).await
84 }
85 pub async fn create_full(ctx: &Ctx, mm: &ModelManager, protein_c: Protein) -> Result<i32> {
86 base::create::<Self, _>(ctx, mm, protein_c).await
87 }
88
89 pub async fn get(ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<Protein> {
90 base::get::<Self, _>(ctx, mm, id).await
91 }
92
93 pub async fn list(
94 ctx: &Ctx,
95 mm: &ModelManager,
96 filters: Option<Vec<ProteinFilter>>,
97 list_options: Option<ListOptions>,
98 ) -> Result<Vec<Protein>> {
99 base::list::<Self, _, _>(ctx, mm, filters, list_options).await
100 }
101
102 pub async fn update(
103 ctx: &Ctx,
104 mm: &ModelManager,
105 id: i32,
106 protein_u: ProteinForUpdate,
107 ) -> Result<()> {
108 base::update::<Self, _>(ctx, mm, id, protein_u).await
109 }
110
111 pub async fn delete(ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<()> {
112 base::delete::<Self>(ctx, mm, id).await
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::_dev_utils;
120 use crate::model::Error;
121 use anyhow::Result;
122 use serde_json::json;
123
124 #[ignore]
125 #[tokio::test]
126 async fn test_protein_create_ok() -> Result<()> {
127 let ctx = Ctx::root_ctx();
128 let mm = ModelManager::new().await?;
129 let fx_name = "test_create_ok name";
130
131 let protein_c = ProteinForCreate {
132 name: fx_name.to_string(),
133 description: Some(fx_name.to_string()),
134 group_id: 1,
135 created_by: 261,
136 };
137 let id = ProteinBmc::create(&ctx, &mm, protein_c).await?;
138
139 let protein = ProteinBmc::get(&ctx, &mm, id).await?;
140 assert_eq!(protein.name, fx_name);
141
142 ProteinBmc::delete(&ctx, &mm, id).await?;
143
144 Ok(())
145 }
146
147 #[ignore]
148 #[tokio::test]
149 async fn test_protein_get_err_not_found() -> Result<()> {
150 let mm = ModelManager::new().await?;
151 let ctx = Ctx::root_ctx();
152 let fx_id = 100;
153
154 let res = ProteinBmc::get(&ctx, &mm, fx_id).await;
155
156 assert!(
157 matches!(
158 res,
159 Err(Error::EntityNotFound {
160 entity: "protein",
161 id: 100
162 })
163 ),
164 "EntityNotFound not matching"
165 );
166
167 Ok(())
168 }
169
170 #[ignore]
171 #[tokio::test]
172 async fn test_protein_list_all_ok() -> Result<()> {
173 let mm = ModelManager::new().await?;
174 let ctx = Ctx::root_ctx();
175 let tname = "test_protein_list_all_ok";
176 let seeds = _dev_utils::get_protein_seed(tname);
177 _dev_utils::seed_proteins(&ctx, &mm, &seeds).await?;
178
179 let proteins = ProteinBmc::list(&ctx, &mm, None, None).await?;
180
181 let proteins: Vec<Protein> = proteins
182 .into_iter()
183 .filter(|t| t.name.starts_with("test_list_all_ok-protein"))
184 .collect();
185 assert_eq!(proteins.len(), 4, "number of seeded proteins.");
186
187 if false {
188 for protein in proteins.iter() {
189 ProteinBmc::delete(&ctx, &mm, protein.id).await?;
190 }
191 }
192
193 Ok(())
194 }
195
196 #[ignore]
197 #[tokio::test]
198 async fn test_protein_list_by_filter_ok() -> Result<()> {
199 let mm = ModelManager::new().await?;
200 let ctx = Ctx::root_ctx();
201 let tname = "test_protein_list_by_filter_ok";
202 let seeds = _dev_utils::get_protein_seed(tname);
203 _dev_utils::seed_proteins(&ctx, &mm, &seeds).await?;
204
205 let filters: Vec<ProteinFilter> = serde_json::from_value(json!([
206 {
207 "name": {
208 "$endsWith": ".a",
209 "$containsAny": ["01", "02"]
210 }
211 },
212 {
213 "name": {"$contains": "03"}
214 }
215 ]))?;
216 let list_options = serde_json::from_value(json!({
217 "order_bys": "!id"
218 }))?;
219 let proteins = ProteinBmc::list(&ctx, &mm, Some(filters), Some(list_options)).await?;
220
221 assert_eq!(proteins.len(), 3);
222 assert!(proteins[0].name.ends_with("03"));
223 assert!(proteins[1].name.ends_with("02.a"));
224 assert!(proteins[2].name.ends_with("01.a"));
225
226 if false {
227 let proteins = ProteinBmc::list(
228 &ctx,
229 &mm,
230 Some(serde_json::from_value(json!([{
231 "name": {"$startsWith": "test_list_by_filter_ok"}
232 }]))?),
233 None,
234 )
235 .await?;
236 assert_eq!(proteins.len(), 5);
237 for protein in proteins.iter() {
238 ProteinBmc::delete(&ctx, &mm, protein.id).await?;
239 }
240 }
241
242 Ok(())
243 }
244
245 #[ignore]
246 #[tokio::test]
247 async fn test_protein_update_ok() -> Result<()> {
248 let mm = ModelManager::new().await?;
249 let ctx = Ctx::root_ctx();
250 let tname = "test_protein_list_by_filter_ok";
251 let seeds = _dev_utils::get_protein_seed(tname);
252 let fx_protein = _dev_utils::seed_proteins(&ctx, &mm, &seeds)
253 .await?
254 .remove(0);
255
256 ProteinBmc::update(
257 &ctx,
258 &mm,
259 fx_protein.id,
260 ProteinForUpdate {
261 name: Some(tname.to_string()),
262 ..Default::default()
263 },
264 )
265 .await?;
266
267 let protein = ProteinBmc::get(&ctx, &mm, fx_protein.id).await?;
268 assert_eq!(protein.name, tname);
269
270 Ok(())
271 }
272
273 #[ignore]
274 #[tokio::test]
275 async fn test_protein_delete_err_not_found() -> Result<()> {
276 let mm = ModelManager::new().await?;
277 let ctx = Ctx::root_ctx();
278 let fx_id = 100;
279
280 let res = ProteinBmc::delete(&ctx, &mm, fx_id).await;
281
282 assert!(
283 matches!(
284 res,
285 Err(Error::EntityNotFound {
286 entity: "protein",
287 id: 100
288 })
289 ),
290 "EntityNotFound not matching"
291 );
292
293 Ok(())
294 }
295}