Skip to main content

shared/models/
nest.rs

1use crate::{
2    models::{InsertQueryBuilder, UpdateQueryBuilder},
3    prelude::*,
4};
5use garde::Validate;
6use serde::{Deserialize, Serialize};
7use sqlx::{Row, postgres::PgRow};
8use std::{
9    collections::BTreeMap,
10    sync::{Arc, LazyLock},
11};
12use utoipa::ToSchema;
13
14#[derive(Serialize, Deserialize, Clone)]
15pub struct Nest {
16    pub uuid: uuid::Uuid,
17
18    pub name: compact_str::CompactString,
19    pub description: Option<compact_str::CompactString>,
20    pub author: compact_str::CompactString,
21
22    pub created: chrono::NaiveDateTime,
23
24    extension_data: super::ModelExtensionData,
25}
26
27impl BaseModel for Nest {
28    const NAME: &'static str = "nest";
29
30    fn get_extension_list() -> &'static super::ModelExtensionList {
31        static EXTENSIONS: LazyLock<super::ModelExtensionList> =
32            LazyLock::new(|| std::sync::RwLock::new(Vec::new()));
33
34        &EXTENSIONS
35    }
36
37    fn get_extension_data(&self) -> &super::ModelExtensionData {
38        &self.extension_data
39    }
40
41    #[inline]
42    fn base_columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
43        let prefix = prefix.unwrap_or_default();
44
45        BTreeMap::from([
46            ("nests.uuid", compact_str::format_compact!("{prefix}uuid")),
47            ("nests.name", compact_str::format_compact!("{prefix}name")),
48            (
49                "nests.description",
50                compact_str::format_compact!("{prefix}description"),
51            ),
52            (
53                "nests.author",
54                compact_str::format_compact!("{prefix}author"),
55            ),
56            (
57                "nests.created",
58                compact_str::format_compact!("{prefix}created"),
59            ),
60        ])
61    }
62
63    #[inline]
64    fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
65        let prefix = prefix.unwrap_or_default();
66
67        Ok(Self {
68            uuid: row.try_get(compact_str::format_compact!("{prefix}uuid").as_str())?,
69            name: row.try_get(compact_str::format_compact!("{prefix}name").as_str())?,
70            description: row
71                .try_get(compact_str::format_compact!("{prefix}description").as_str())?,
72            author: row.try_get(compact_str::format_compact!("{prefix}author").as_str())?,
73            created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
74            extension_data: Self::map_extensions(prefix, row)?,
75        })
76    }
77}
78
79impl Nest {
80    pub async fn all_with_pagination(
81        database: &crate::database::Database,
82        page: i64,
83        per_page: i64,
84        search: Option<&str>,
85    ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
86        let offset = (page - 1) * per_page;
87
88        let rows = sqlx::query(&format!(
89            r#"
90            SELECT {}, COUNT(*) OVER() AS total_count
91            FROM nests
92            WHERE ($1 IS NULL OR nests.name ILIKE '%' || $1 || '%')
93            ORDER BY nests.created
94            LIMIT $2 OFFSET $3
95            "#,
96            Self::columns_sql(None)
97        ))
98        .bind(search)
99        .bind(per_page)
100        .bind(offset)
101        .fetch_all(database.read())
102        .await?;
103
104        Ok(super::Pagination {
105            total: rows
106                .first()
107                .map_or(Ok(0), |row| row.try_get("total_count"))?,
108            per_page,
109            page,
110            data: rows
111                .into_iter()
112                .map(|row| Self::map(None, &row))
113                .try_collect_vec()?,
114        })
115    }
116
117    pub async fn by_name(
118        database: &crate::database::Database,
119        name: &str,
120    ) -> Result<Option<Self>, crate::database::DatabaseError> {
121        let row = sqlx::query(&format!(
122            r#"
123            SELECT {}
124            FROM nests
125            WHERE nests.name = $1
126            "#,
127            Self::columns_sql(None)
128        ))
129        .bind(name)
130        .fetch_optional(database.read())
131        .await?;
132
133        row.try_map(|row| Self::map(None, &row))
134    }
135}
136
137#[async_trait::async_trait]
138impl IntoAdminApiObject for Nest {
139    type AdminApiObject = AdminApiNest;
140    type ExtraArgs<'a> = ();
141
142    async fn into_admin_api_object<'a>(
143        self,
144        state: &crate::State,
145        _args: Self::ExtraArgs<'a>,
146    ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
147        let api_object = AdminApiNest::init_hooks(&self, state).await?;
148
149        let api_object = finish_extendible!(
150            AdminApiNest {
151                uuid: self.uuid,
152                name: self.name,
153                description: self.description,
154                author: self.author,
155                created: self.created.and_utc(),
156            },
157            api_object,
158            state
159        )?;
160
161        Ok(api_object)
162    }
163}
164
165#[async_trait::async_trait]
166impl ByUuid for Nest {
167    async fn by_uuid(
168        database: &crate::database::Database,
169        uuid: uuid::Uuid,
170    ) -> Result<Self, crate::database::DatabaseError> {
171        let row = sqlx::query(&format!(
172            r#"
173            SELECT {}
174            FROM nests
175            WHERE nests.uuid = $1
176            "#,
177            Self::columns_sql(None)
178        ))
179        .bind(uuid)
180        .fetch_one(database.read())
181        .await?;
182
183        Self::map(None, &row)
184    }
185
186    async fn by_uuid_with_transaction(
187        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
188        uuid: uuid::Uuid,
189    ) -> Result<Self, crate::database::DatabaseError> {
190        let row = sqlx::query(&format!(
191            r#"
192            SELECT {}
193            FROM nests
194            WHERE nests.uuid = $1
195            "#,
196            Self::columns_sql(None)
197        ))
198        .bind(uuid)
199        .fetch_one(&mut **transaction)
200        .await?;
201
202        Self::map(None, &row)
203    }
204}
205
206#[derive(ToSchema, Deserialize, Validate)]
207pub struct CreateNestOptions {
208    #[garde(length(chars, min = 2, max = 255))]
209    #[schema(min_length = 2, max_length = 255)]
210    pub author: compact_str::CompactString,
211    #[garde(length(chars, min = 1, max = 255))]
212    #[schema(min_length = 1, max_length = 255)]
213    pub name: compact_str::CompactString,
214    #[garde(length(chars, min = 1, max = 1024))]
215    #[schema(min_length = 1, max_length = 1024)]
216    pub description: Option<compact_str::CompactString>,
217}
218
219#[async_trait::async_trait]
220impl CreatableModel for Nest {
221    type CreateOptions<'a> = CreateNestOptions;
222    type CreateResult = Self;
223
224    fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
225        static CREATE_LISTENERS: LazyLock<CreateListenerList<Nest>> =
226            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
227
228        &CREATE_LISTENERS
229    }
230
231    async fn create_with_transaction(
232        state: &crate::State,
233        mut options: Self::CreateOptions<'_>,
234        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
235    ) -> Result<Self, crate::database::DatabaseError> {
236        options.validate()?;
237
238        let mut query_builder = InsertQueryBuilder::new("nests");
239
240        Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
241
242        query_builder
243            .set("author", &options.author)
244            .set("name", &options.name)
245            .set("description", &options.description);
246
247        let row = query_builder
248            .returning(&Self::columns_sql(None))
249            .fetch_one(&mut **transaction)
250            .await?;
251        let mut nest = Self::map(None, &row)?;
252
253        Self::run_after_create_handlers(&mut nest, &options, state, transaction).await?;
254
255        Ok(nest)
256    }
257}
258
259#[derive(ToSchema, Serialize, Deserialize, Validate, Clone, Default)]
260pub struct UpdateNestOptions {
261    #[garde(length(chars, min = 2, max = 255))]
262    #[schema(min_length = 2, max_length = 255)]
263    pub author: Option<compact_str::CompactString>,
264    #[garde(length(chars, min = 1, max = 255))]
265    #[schema(min_length = 1, max_length = 255)]
266    pub name: Option<compact_str::CompactString>,
267    #[garde(length(chars, min = 1, max = 1024))]
268    #[schema(min_length = 1, max_length = 1024)]
269    #[serde(
270        default,
271        skip_serializing_if = "Option::is_none",
272        with = "::serde_with::rust::double_option"
273    )]
274    pub description: Option<Option<compact_str::CompactString>>,
275}
276
277#[async_trait::async_trait]
278impl UpdatableModel for Nest {
279    type UpdateOptions = UpdateNestOptions;
280
281    fn get_update_handlers() -> &'static LazyLock<UpdateHandlerList<Self>> {
282        static UPDATE_LISTENERS: LazyLock<UpdateHandlerList<Nest>> =
283            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
284
285        &UPDATE_LISTENERS
286    }
287
288    async fn update_with_transaction(
289        &mut self,
290        state: &crate::State,
291        mut options: Self::UpdateOptions,
292        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
293    ) -> Result<(), crate::database::DatabaseError> {
294        options.validate()?;
295
296        let mut query_builder = UpdateQueryBuilder::new("nests");
297
298        self.run_update_handlers(&mut options, &mut query_builder, state, transaction)
299            .await?;
300
301        query_builder
302            .set("author", options.author.as_ref())
303            .set("name", options.name.as_ref())
304            .set(
305                "description",
306                options.description.as_ref().map(|d| d.as_ref()),
307            )
308            .where_eq("uuid", self.uuid);
309
310        query_builder.execute(&mut **transaction).await?;
311
312        if let Some(author) = options.author {
313            self.author = author;
314        }
315        if let Some(name) = options.name {
316            self.name = name;
317        }
318        if let Some(description) = options.description {
319            self.description = description;
320        }
321
322        self.run_after_update_handlers(state, transaction).await?;
323
324        Ok(())
325    }
326}
327
328#[async_trait::async_trait]
329impl DeletableModel for Nest {
330    type DeleteOptions = ();
331
332    fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
333        static DELETE_LISTENERS: LazyLock<DeleteHandlerList<Nest>> =
334            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
335
336        &DELETE_LISTENERS
337    }
338
339    async fn delete_with_transaction(
340        &self,
341        state: &crate::State,
342        options: Self::DeleteOptions,
343        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
344    ) -> Result<(), anyhow::Error> {
345        self.run_delete_handlers(&options, state, transaction)
346            .await?;
347
348        sqlx::query(
349            r#"
350            DELETE FROM nests
351            WHERE nests.uuid = $1
352            "#,
353        )
354        .bind(self.uuid)
355        .execute(&mut **transaction)
356        .await?;
357
358        self.run_after_delete_handlers(&options, state, transaction)
359            .await?;
360
361        Ok(())
362    }
363}
364
365#[schema_extension_derive::extendible]
366#[init_args(Nest, crate::State)]
367#[hook_args(crate::State)]
368#[derive(ToSchema, Serialize)]
369#[schema(title = "Nest")]
370pub struct AdminApiNest {
371    pub uuid: uuid::Uuid,
372
373    pub name: compact_str::CompactString,
374    pub description: Option<compact_str::CompactString>,
375    pub author: compact_str::CompactString,
376
377    pub created: chrono::DateTime<chrono::Utc>,
378}