airlab_lib/model/
group.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 GroupBmc {
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  name character varying NOT NULL,
19  institution character varying,
20  description character varying,
21  location character varying,
22  tags character varying(64)[],
23  url character varying,
24  is_open boolean DEFAULT false NOT NULL,
25  meta jsonb,
26  created_at timestamp with time zone DEFAULT now() NOT NULL
27);
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)]
39pub struct AirLabGroup {
40    pub id: i32,
41
42    pub name: String,
43    pub institution: String,
44    pub url: Option<String>,
45    pub meta: Option<serde_json::Value>,
46    #[serde(rename = "createdAt")]
47    pub created_at: chrono::DateTime<chrono::Utc>,
48    #[serde(rename = "isOpen")]
49    pub is_open: bool,
50}
51
52#[derive(Debug, Clone, Fields, FromRow, Serialize, Deserialize)]
53pub struct Group {
54    pub id: i32,
55
56    pub name: String,
57    pub institution: String,
58    pub description: Option<String>,
59    pub location: Option<String>,
60    pub url: Option<String>,
61    pub meta: Option<serde_json::Value>,
62    #[serde(rename = "createdAt")]
63    pub created_at: chrono::DateTime<chrono::Utc>,
64    #[serde(rename = "isOpen")]
65    pub is_open: bool,
66    pub tags: Option<Vec<String>>,
67}
68
69#[derive(Fields, Deserialize, Clone, Debug)]
70pub struct GroupForCreate {
71    pub name: String,
72    pub institution: String,
73    pub url: Option<String>,
74    #[serde(rename = "isOpen")]
75    pub is_open: bool,
76    pub tags: Option<Vec<String>>,
77}
78
79#[derive(Fields, Default, Deserialize, Debug)]
80pub struct GroupForUpdate {
81    pub name: String,
82    pub institution: String,
83    pub url: String,
84    //pub meta: Option<String>,
85    #[serde(rename = "isOpen")]
86    pub is_open: bool,
87}
88
89#[derive(FilterNodes, Deserialize, Default, Debug)]
90pub struct GroupFilter {
91    id: Option<OpValsInt64>,
92
93    name: Option<OpValsString>,
94}
95
96pub struct GroupBmc;
97
98impl DbBmc for GroupBmc {
99    const TABLE: &'static str = "group";
100}
101
102impl GroupBmc {
103    pub async fn create(ctx: &Ctx, mm: &ModelManager, group_c: GroupForCreate) -> Result<i32> {
104        base::create::<Self, _>(ctx, mm, group_c).await
105    }
106    pub async fn create_full(ctx: &Ctx, mm: &ModelManager, group_c: Group) -> Result<i32> {
107        base::create::<Self, _>(ctx, mm, group_c).await
108    }
109
110    pub async fn get(ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<Group> {
111        base::get::<Self, _>(ctx, mm, id).await
112    }
113
114    pub async fn list(
115        ctx: &Ctx,
116        mm: &ModelManager,
117        filters: Option<Vec<GroupFilter>>,
118        list_options: Option<ListOptions>,
119    ) -> Result<Vec<Group>> {
120        base::list::<Self, _, _>(ctx, mm, filters, list_options).await
121    }
122
123    pub async fn airlab_list(
124        ctx: &Ctx,
125        mm: &ModelManager,
126        filters: Option<Vec<GroupFilter>>,
127        list_options: Option<ListOptions>,
128    ) -> Result<Vec<AirLabGroup>> {
129        base::list::<Self, _, _>(ctx, mm, filters, list_options).await
130    }
131
132    pub async fn update(
133        ctx: &Ctx,
134        mm: &ModelManager,
135        id: i32,
136        group_u: GroupForUpdate,
137    ) -> Result<()> {
138        base::update::<Self, _>(ctx, mm, id, group_u).await
139    }
140
141    pub async fn delete(ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<()> {
142        base::delete::<Self>(ctx, mm, id).await
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use crate::_dev_utils;
150    use crate::model::Error;
151    use anyhow::Result;
152    use serde_json::json;
153
154    #[tokio::test]
155    async fn test_group_create_ok() -> Result<()> {
156        let mm = ModelManager::new().await?;
157
158        let ctx = Ctx::root_ctx();
159        let fx_name = "test_create_ok name";
160
161        let group_c = GroupForCreate {
162            name: fx_name.to_string(),
163            institution: "inst 01".to_string(),
164            url: Some("url 01".to_string()),
165            is_open: false,
166            tags: None,
167        };
168        let id = GroupBmc::create(&ctx, &mm, group_c).await?;
169
170        let group = GroupBmc::get(&ctx, &mm, id).await?;
171        assert_eq!(group.name, fx_name);
172
173        GroupBmc::delete(&ctx, &mm, id).await?;
174
175        Ok(())
176    }
177
178    #[tokio::test]
179    async fn test_group_get_err_not_found() -> Result<()> {
180        let mm = ModelManager::new().await?;
181        let ctx = Ctx::root_ctx();
182        let fx_id = 100;
183
184        let res = GroupBmc::get(&ctx, &mm, fx_id).await;
185
186        assert!(
187            matches!(
188                res,
189                Err(Error::EntityNotFound {
190                    entity: "group",
191                    id: 100
192                })
193            ),
194            "EntityNotFound not matching"
195        );
196
197        Ok(())
198    }
199
200    #[tokio::test]
201    async fn test_group_list_all_ok() -> Result<()> {
202        let mm = ModelManager::new().await?;
203        let ctx = Ctx::root_ctx();
204        let tname = "test_group_list_all_ok";
205        let gseeds = _dev_utils::get_group_seed(tname);
206        let _groups = _dev_utils::seed_groups(&ctx, &mm, &gseeds).await?;
207
208        let groups = GroupBmc::list(&ctx, &mm, None, None).await?;
209
210        let groups: Vec<Group> = groups
211            .into_iter()
212            .filter(|t| t.name.starts_with(tname))
213            .collect();
214        assert_eq!(groups.len(), gseeds.len(), "number of seeded groups.");
215
216        for group in groups.iter() {
217            GroupBmc::delete(&ctx, &mm, group.id).await?;
218        }
219
220        Ok(())
221    }
222
223    #[tokio::test]
224    async fn test_group_list_by_filter_ok() -> Result<()> {
225        let mm = ModelManager::new().await?;
226        let ctx = Ctx::root_ctx();
227        let tname = "test_group_list_by_filter_ok";
228        let fx_names = _dev_utils::get_group_seed(tname);
229        _dev_utils::seed_groups(&ctx, &mm, &fx_names).await?;
230
231        let filters: Vec<GroupFilter> = serde_json::from_value(json!([
232            {
233                "name": {
234                    "$contains": tname,
235                    "$containsAny": ["01"]
236                }
237            }
238        ]))?;
239        let list_options = serde_json::from_value(json!({
240            "order_bys": "!id"
241        }))?;
242        let groups = GroupBmc::list(&ctx, &mm, Some(filters), Some(list_options)).await?;
243
244        assert_eq!(groups.len(), 1);
245        assert!(groups[0].name.ends_with("01"));
246
247        let groups = GroupBmc::list(
248            &ctx,
249            &mm,
250            Some(serde_json::from_value(json!([{
251                "name": {"$startsWith": tname}
252            }]))?),
253            None,
254        )
255        .await?;
256        for group in groups.iter() {
257            GroupBmc::delete(&ctx, &mm, group.id).await?;
258        }
259
260        Ok(())
261    }
262
263    #[tokio::test]
264    async fn test_group_update_ok() -> Result<()> {
265        let mm = ModelManager::new().await?;
266        let ctx = Ctx::root_ctx();
267        let tname = "test_group_update_ok";
268        let fx_names = _dev_utils::get_group_seed(tname);
269        let fx_name_new = "test_update_ok - group 01 - new";
270        let fx_group = _dev_utils::seed_groups(&ctx, &mm, &fx_names)
271            .await?
272            .remove(0);
273
274        GroupBmc::update(
275            &ctx,
276            &mm,
277            fx_group.id,
278            GroupForUpdate {
279                name: fx_name_new.to_string(),
280                ..Default::default()
281            },
282        )
283        .await?;
284
285        let group = GroupBmc::get(&ctx, &mm, fx_group.id).await?;
286        assert_eq!(group.name, fx_name_new);
287
288        Ok(())
289    }
290
291    #[tokio::test]
292    async fn test_group_delete_err_not_found() -> Result<()> {
293        let mm = ModelManager::new().await?;
294        let ctx = Ctx::root_ctx();
295        let fx_id = 100;
296
297        let res = GroupBmc::delete(&ctx, &mm, fx_id).await;
298
299        assert!(
300            matches!(
301                res,
302                Err(Error::EntityNotFound {
303                    entity: "group",
304                    id: 100
305                })
306            ),
307            "EntityNotFound not matching"
308        );
309
310        Ok(())
311    }
312}