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}