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 #[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}