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}