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}