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(|| parking_lot::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    fn cache_invalidation_keys(&self) -> Vec<compact_str::CompactString> {
79        vec![compact_str::format_compact!(
80            "{}::{}",
81            Self::NAME,
82            self.uuid
83        )]
84    }
85}
86
87impl Nest {
88    pub async fn all_with_pagination(
89        database: &crate::database::Database,
90        page: i64,
91        per_page: i64,
92        search: Option<&str>,
93    ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
94        super::PaginatedQueryBuilder::new(
95            Self::columns_sql(None),
96            format!(
97                r#"
98                FROM nests
99                WHERE {search}
100                "#,
101                search = super::search_sql(1, &["nests.name"], &["nests.uuid"]),
102            ),
103            "nests.created, nests.uuid",
104        )
105        .bind(search)
106        .fetch(database, page, per_page)
107        .await
108    }
109
110    pub async fn by_name(
111        database: &crate::database::Database,
112        name: &str,
113    ) -> Result<Option<Self>, crate::database::DatabaseError> {
114        let row = sqlx::query(sqlx::AssertSqlSafe(format!(
115            r#"
116            SELECT {}
117            FROM nests
118            WHERE nests.name = $1
119            "#,
120            Self::columns_sql(None)
121        )))
122        .bind(name)
123        .fetch_optional(database.read())
124        .await?;
125
126        row.try_map(|row| Self::map(None, &row))
127    }
128}
129
130#[async_trait::async_trait]
131impl IntoAdminApiObject for Nest {
132    type AdminApiObject = AdminApiNest;
133    type ExtraArgs<'a> = ();
134
135    async fn into_admin_api_object<'a>(
136        self,
137        state: &crate::State,
138        _args: Self::ExtraArgs<'a>,
139    ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
140        let api_object = AdminApiNest::init_hooks(&self, state).await?;
141
142        let api_object = finish_extendible!(
143            AdminApiNest {
144                uuid: self.uuid,
145                name: self.name,
146                description: self.description,
147                author: self.author,
148                created: self.created.and_utc(),
149            },
150            api_object,
151            state
152        )?;
153
154        Ok(api_object)
155    }
156}
157
158#[async_trait::async_trait]
159impl ByUuid for Nest {
160    async fn by_uuid(
161        database: &crate::database::Database,
162        uuid: uuid::Uuid,
163    ) -> Result<Self, crate::database::DatabaseError> {
164        let row = sqlx::query(sqlx::AssertSqlSafe(format!(
165            r#"
166            SELECT {}
167            FROM nests
168            WHERE nests.uuid = $1
169            "#,
170            Self::columns_sql(None)
171        )))
172        .bind(uuid)
173        .fetch_one(database.read())
174        .await?;
175
176        Self::map(None, &row)
177    }
178
179    async fn by_uuid_with_transaction(
180        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
181        uuid: uuid::Uuid,
182    ) -> Result<Self, crate::database::DatabaseError> {
183        let row = sqlx::query(sqlx::AssertSqlSafe(format!(
184            r#"
185            SELECT {}
186            FROM nests
187            WHERE nests.uuid = $1
188            "#,
189            Self::columns_sql(None)
190        )))
191        .bind(uuid)
192        .fetch_one(&mut **transaction)
193        .await?;
194
195        Self::map(None, &row)
196    }
197}
198
199#[derive(ToSchema, Deserialize, Validate)]
200pub struct CreateNestOptions {
201    #[garde(length(chars, min = 2, max = 255))]
202    #[schema(min_length = 2, max_length = 255)]
203    pub author: compact_str::CompactString,
204    #[garde(length(chars, min = 1, max = 255))]
205    #[schema(min_length = 1, max_length = 255)]
206    pub name: compact_str::CompactString,
207    #[garde(length(chars, min = 1, max = 1024))]
208    #[schema(min_length = 1, max_length = 1024)]
209    pub description: Option<compact_str::CompactString>,
210}
211
212#[async_trait::async_trait]
213impl CreatableModel for Nest {
214    type CreateOptions<'a> = CreateNestOptions;
215    type CreateResult = Self;
216
217    fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
218        static CREATE_LISTENERS: LazyLock<CreateListenerList<Nest>> =
219            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
220
221        &CREATE_LISTENERS
222    }
223
224    async fn create_with_transaction(
225        state: &crate::State,
226        mut options: Self::CreateOptions<'_>,
227        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
228    ) -> Result<Self, crate::database::DatabaseError> {
229        options.validate()?;
230
231        let mut query_builder = InsertQueryBuilder::new("nests");
232
233        Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
234
235        query_builder
236            .set("author", &options.author)
237            .set("name", &options.name)
238            .set("description", &options.description);
239
240        let row = query_builder
241            .returning(&Self::columns_sql(None))
242            .fetch_one(&mut **transaction)
243            .await?;
244        let mut nest = Self::map(None, &row)?;
245
246        Self::run_after_create_handlers(&mut nest, &options, state, transaction).await?;
247
248        Ok(nest)
249    }
250}
251
252#[derive(ToSchema, Serialize, Deserialize, Validate, Clone, Default)]
253pub struct UpdateNestOptions {
254    #[garde(length(chars, min = 2, max = 255))]
255    #[schema(min_length = 2, max_length = 255)]
256    pub author: Option<compact_str::CompactString>,
257    #[garde(length(chars, min = 1, max = 255))]
258    #[schema(min_length = 1, max_length = 255)]
259    pub name: Option<compact_str::CompactString>,
260    #[garde(length(chars, min = 1, max = 1024))]
261    #[schema(min_length = 1, max_length = 1024)]
262    #[serde(
263        default,
264        skip_serializing_if = "Option::is_none",
265        with = "::serde_with::rust::double_option"
266    )]
267    pub description: Option<Option<compact_str::CompactString>>,
268}
269
270#[async_trait::async_trait]
271impl UpdatableModel for Nest {
272    type UpdateOptions = UpdateNestOptions;
273
274    fn get_update_handlers() -> &'static LazyLock<UpdateHandlerList<Self>> {
275        static UPDATE_LISTENERS: LazyLock<UpdateHandlerList<Nest>> =
276            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
277
278        &UPDATE_LISTENERS
279    }
280
281    async fn update_with_transaction(
282        &mut self,
283        state: &crate::State,
284        mut options: Self::UpdateOptions,
285        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
286    ) -> Result<(), crate::database::DatabaseError> {
287        options.validate()?;
288
289        let mut query_builder = UpdateQueryBuilder::new("nests");
290
291        self.run_update_handlers(&mut options, &mut query_builder, state, transaction)
292            .await?;
293
294        query_builder
295            .set("author", options.author.as_ref())
296            .set("name", options.name.as_ref())
297            .set(
298                "description",
299                options.description.as_ref().map(|d| d.as_ref()),
300            )
301            .where_eq("uuid", self.uuid);
302
303        query_builder.execute(&mut **transaction).await?;
304
305        if let Some(author) = options.author {
306            self.author = author;
307        }
308        if let Some(name) = options.name {
309            self.name = name;
310        }
311        if let Some(description) = options.description {
312            self.description = description;
313        }
314
315        self.run_after_update_handlers(state, transaction).await?;
316
317        Ok(())
318    }
319}
320
321#[async_trait::async_trait]
322impl DeletableModel for Nest {
323    type DeleteOptions = ();
324
325    fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
326        static DELETE_LISTENERS: LazyLock<DeleteHandlerList<Nest>> =
327            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
328
329        &DELETE_LISTENERS
330    }
331
332    async fn delete_with_transaction(
333        &self,
334        state: &crate::State,
335        options: Self::DeleteOptions,
336        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
337    ) -> Result<(), anyhow::Error> {
338        self.run_delete_handlers(&options, state, transaction)
339            .await?;
340
341        sqlx::query(
342            r#"
343            DELETE FROM nests
344            WHERE nests.uuid = $1
345            "#,
346        )
347        .bind(self.uuid)
348        .execute(&mut **transaction)
349        .await?;
350
351        self.run_after_delete_handlers(&options, state, transaction)
352            .await?;
353
354        Ok(())
355    }
356}
357
358#[schema_extension_derive::extendible]
359#[init_args(Nest, crate::State)]
360#[hook_args(crate::State)]
361#[derive(ToSchema, Serialize)]
362#[schema(title = "Nest")]
363pub struct AdminApiNest {
364    pub uuid: uuid::Uuid,
365
366    pub name: compact_str::CompactString,
367    pub description: Option<compact_str::CompactString>,
368    pub author: compact_str::CompactString,
369
370    pub created: chrono::DateTime<chrono::Utc>,
371}