Skip to main content

shared/models/
nest_egg_mount.rs

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