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