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