Skip to main content

shared/models/
egg_repository_egg.rs

1use crate::prelude::*;
2use serde::{Deserialize, Serialize};
3use sqlx::{Row, postgres::PgRow};
4use std::{
5    collections::BTreeMap,
6    sync::{Arc, LazyLock},
7};
8use utoipa::ToSchema;
9
10#[derive(Serialize, Deserialize, Clone)]
11pub struct EggRepositoryEgg {
12    pub uuid: uuid::Uuid,
13    pub path: String,
14    pub egg_repository: Fetchable<super::egg_repository::EggRepository>,
15
16    pub name: compact_str::CompactString,
17    pub description: Option<compact_str::CompactString>,
18    pub author: compact_str::CompactString,
19
20    pub exported_egg: super::nest_egg::ExportedNestEgg,
21
22    extension_data: super::ModelExtensionData,
23}
24
25impl BaseModel for EggRepositoryEgg {
26    const NAME: &'static str = "egg_repository_egg";
27
28    fn get_extension_list() -> &'static super::ModelExtensionList {
29        static EXTENSIONS: LazyLock<super::ModelExtensionList> =
30            LazyLock::new(|| std::sync::RwLock::new(Vec::new()));
31
32        &EXTENSIONS
33    }
34
35    fn get_extension_data(&self) -> &super::ModelExtensionData {
36        &self.extension_data
37    }
38
39    #[inline]
40    fn base_columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
41        let prefix = prefix.unwrap_or_default();
42
43        BTreeMap::from([
44            (
45                "egg_repository_eggs.uuid",
46                compact_str::format_compact!("{prefix}uuid"),
47            ),
48            (
49                "egg_repository_eggs.path",
50                compact_str::format_compact!("{prefix}path"),
51            ),
52            (
53                "egg_repository_eggs.egg_repository_uuid",
54                compact_str::format_compact!("{prefix}egg_repository_uuid"),
55            ),
56            (
57                "egg_repository_eggs.name",
58                compact_str::format_compact!("{prefix}name"),
59            ),
60            (
61                "egg_repository_eggs.description",
62                compact_str::format_compact!("{prefix}description"),
63            ),
64            (
65                "egg_repository_eggs.author",
66                compact_str::format_compact!("{prefix}author"),
67            ),
68            (
69                "egg_repository_eggs.exported_egg",
70                compact_str::format_compact!("{prefix}exported_egg"),
71            ),
72        ])
73    }
74
75    #[inline]
76    fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
77        let prefix = prefix.unwrap_or_default();
78
79        Ok(Self {
80            uuid: row.try_get(compact_str::format_compact!("{prefix}uuid").as_str())?,
81            path: row.try_get(compact_str::format_compact!("{prefix}path").as_str())?,
82            egg_repository: super::egg_repository::EggRepository::get_fetchable(
83                row.try_get(compact_str::format_compact!("{prefix}egg_repository_uuid").as_str())?,
84            ),
85            name: row.try_get(compact_str::format_compact!("{prefix}name").as_str())?,
86            description: row
87                .try_get(compact_str::format_compact!("{prefix}description").as_str())?,
88            author: row.try_get(compact_str::format_compact!("{prefix}author").as_str())?,
89            exported_egg: serde_json::from_value(
90                row.try_get(compact_str::format_compact!("{prefix}exported_egg").as_str())?,
91            )?,
92            extension_data: Self::map_extensions(prefix, row)?,
93        })
94    }
95}
96
97impl EggRepositoryEgg {
98    pub async fn create(
99        database: &crate::database::Database,
100        egg_repository_uuid: uuid::Uuid,
101        path: impl AsRef<str>,
102        name: &str,
103        description: Option<&str>,
104        author: &str,
105        exported_egg: &super::nest_egg::ExportedNestEgg,
106    ) -> Result<Self, crate::database::DatabaseError> {
107        let row = sqlx::query(&format!(
108            r#"
109            INSERT INTO egg_repository_eggs (egg_repository_uuid, path, author, name, description, exported_egg)
110            VALUES ($1, $2, $3, $4, $5, $6)
111            ON CONFLICT (egg_repository_uuid, path) DO UPDATE SET
112                name = EXCLUDED.name,
113                description = EXCLUDED.description,
114                author = EXCLUDED.author,
115                exported_egg = EXCLUDED.exported_egg
116            RETURNING {}
117            "#,
118            Self::columns_sql(None)
119        ))
120        .bind(egg_repository_uuid)
121        .bind(path.as_ref())
122        .bind(author)
123        .bind(name)
124        .bind(description)
125        .bind(OrderedJson(exported_egg))
126        .fetch_one(database.write())
127        .await?;
128
129        Self::map(None, &row)
130    }
131
132    pub async fn by_egg_repository_uuid_uuid(
133        database: &crate::database::Database,
134        egg_repository_uuid: uuid::Uuid,
135        uuid: uuid::Uuid,
136    ) -> Result<Option<Self>, crate::database::DatabaseError> {
137        let row = sqlx::query(&format!(
138            r#"
139            SELECT {}
140            FROM egg_repository_eggs
141            WHERE egg_repository_eggs.egg_repository_uuid = $1 AND egg_repository_eggs.uuid = $2
142            "#,
143            Self::columns_sql(None)
144        ))
145        .bind(egg_repository_uuid)
146        .bind(uuid)
147        .fetch_optional(database.read())
148        .await?;
149
150        match row {
151            Some(row) => Ok(Some(Self::map(None, &row)?)),
152            None => Ok(None),
153        }
154    }
155
156    pub async fn by_egg_repository_uuid_with_pagination(
157        database: &crate::database::Database,
158        egg_repository_uuid: uuid::Uuid,
159        page: i64,
160        per_page: i64,
161        search: Option<&str>,
162    ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
163        let offset = (page - 1) * per_page;
164
165        let rows = sqlx::query(&format!(
166            r#"
167            SELECT {}, COUNT(*) OVER() AS total_count
168            FROM egg_repository_eggs
169            WHERE egg_repository_eggs.egg_repository_uuid = $1 AND ($2 IS NULL OR egg_repository_eggs.path ILIKE '%' || $2 || '%' OR egg_repository_eggs.name ILIKE '%' || $2 || '%')
170            ORDER BY egg_repository_eggs.name
171            LIMIT $3 OFFSET $4
172            "#,
173            Self::columns_sql(None)
174        ))
175        .bind(egg_repository_uuid)
176        .bind(search)
177        .bind(per_page)
178        .bind(offset)
179        .fetch_all(database.read())
180        .await?;
181
182        Ok(super::Pagination {
183            total: rows
184                .first()
185                .map_or(Ok(0), |row| row.try_get("total_count"))?,
186            per_page,
187            page,
188            data: rows
189                .into_iter()
190                .map(|row| Self::map(None, &row))
191                .try_collect_vec()?,
192        })
193    }
194
195    pub async fn delete_unused(
196        database: &crate::database::Database,
197        egg_repository_uuid: uuid::Uuid,
198        paths: &[compact_str::CompactString],
199    ) -> Result<(), crate::database::DatabaseError> {
200        sqlx::query(
201            r#"
202            DELETE FROM egg_repository_eggs
203            WHERE egg_repository_eggs.egg_repository_uuid = $1 AND egg_repository_eggs.path != ALL($2)
204            "#,
205        )
206        .bind(egg_repository_uuid)
207        .bind(paths)
208        .execute(database.write())
209        .await?;
210
211        Ok(())
212    }
213
214    pub async fn into_admin_egg_api_object(
215        self,
216        state: &crate::State,
217        _args: (),
218    ) -> Result<AdminApiEggEggRepositoryEgg, crate::database::DatabaseError> {
219        Ok(AdminApiEggEggRepositoryEgg {
220            uuid: self.uuid,
221            path: self.path,
222            egg_repository: self
223                .egg_repository
224                .fetch_cached(&state.database)
225                .await?
226                .into_admin_api_object(state, ())
227                .await?,
228            name: self.name,
229            description: self.description,
230            author: self.author,
231            exported_egg: self.exported_egg,
232        })
233    }
234}
235
236#[async_trait::async_trait]
237impl IntoAdminApiObject for EggRepositoryEgg {
238    type AdminApiObject = AdminApiEggRepositoryEgg;
239    type ExtraArgs<'a> = ();
240
241    async fn into_admin_api_object<'a>(
242        self,
243        state: &crate::State,
244        _args: Self::ExtraArgs<'a>,
245    ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
246        let api_object = AdminApiEggRepositoryEgg::init_hooks(&self, state).await?;
247
248        let api_object = finish_extendible!(
249            AdminApiEggRepositoryEgg {
250                uuid: self.uuid,
251                path: self.path,
252                name: self.name,
253                description: self.description,
254                author: self.author,
255                exported_egg: self.exported_egg,
256            },
257            api_object,
258            state
259        )?;
260
261        Ok(api_object)
262    }
263}
264
265#[async_trait::async_trait]
266impl ByUuid for EggRepositoryEgg {
267    async fn by_uuid(
268        database: &crate::database::Database,
269        uuid: uuid::Uuid,
270    ) -> Result<Self, crate::database::DatabaseError> {
271        let row = sqlx::query(&format!(
272            r#"
273            SELECT {}
274            FROM egg_repository_eggs
275            WHERE egg_repository_eggs.uuid = $1
276            "#,
277            Self::columns_sql(None)
278        ))
279        .bind(uuid)
280        .fetch_one(database.read())
281        .await?;
282
283        Self::map(None, &row)
284    }
285
286    async fn by_uuid_with_transaction(
287        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
288        uuid: uuid::Uuid,
289    ) -> Result<Self, crate::database::DatabaseError> {
290        let row = sqlx::query(&format!(
291            r#"
292            SELECT {}
293            FROM egg_repository_eggs
294            WHERE egg_repository_eggs.uuid = $1
295            "#,
296            Self::columns_sql(None)
297        ))
298        .bind(uuid)
299        .fetch_one(&mut **transaction)
300        .await?;
301
302        Self::map(None, &row)
303    }
304}
305
306#[async_trait::async_trait]
307impl DeletableModel for EggRepositoryEgg {
308    type DeleteOptions = ();
309
310    fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
311        static DELETE_LISTENERS: LazyLock<DeleteHandlerList<EggRepositoryEgg>> =
312            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
313
314        &DELETE_LISTENERS
315    }
316
317    async fn delete_with_transaction(
318        &self,
319        state: &crate::State,
320        options: Self::DeleteOptions,
321        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
322    ) -> Result<(), anyhow::Error> {
323        self.run_delete_handlers(&options, state, transaction)
324            .await?;
325
326        sqlx::query(
327            r#"
328            DELETE FROM egg_repository_eggs
329            WHERE egg_repository_eggs.path = $1
330            "#,
331        )
332        .bind(&self.path)
333        .execute(&mut **transaction)
334        .await?;
335
336        self.run_after_delete_handlers(&options, state, transaction)
337            .await?;
338
339        Ok(())
340    }
341}
342
343#[schema_extension_derive::extendible]
344#[init_args(EggRepositoryEgg, crate::State)]
345#[hook_args(crate::State)]
346#[derive(ToSchema, Serialize)]
347#[schema(title = "EggRepositoryEgg")]
348pub struct AdminApiEggRepositoryEgg {
349    pub uuid: uuid::Uuid,
350    pub path: String,
351
352    pub name: compact_str::CompactString,
353    pub description: Option<compact_str::CompactString>,
354    pub author: compact_str::CompactString,
355
356    pub exported_egg: super::nest_egg::ExportedNestEgg,
357}
358
359#[derive(ToSchema, Serialize)]
360#[schema(title = "EggEggRepositoryEgg")]
361pub struct AdminApiEggEggRepositoryEgg {
362    pub uuid: uuid::Uuid,
363    pub path: String,
364    pub egg_repository: super::egg_repository::AdminApiEggRepository,
365
366    pub name: compact_str::CompactString,
367    pub description: Option<compact_str::CompactString>,
368    pub author: compact_str::CompactString,
369
370    pub exported_egg: super::nest_egg::ExportedNestEgg,
371}