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}