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}