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