1use crate::{
2 models::{InsertQueryBuilder, UpdateQueryBuilder},
3 prelude::*,
4};
5use garde::Validate;
6use serde::{Deserialize, Serialize};
7use sqlx::{Row, postgres::PgRow};
8use std::{
9 collections::BTreeMap,
10 sync::{Arc, LazyLock},
11};
12use utoipa::ToSchema;
13
14#[derive(Serialize, Deserialize, Clone)]
15pub struct Nest {
16 pub uuid: uuid::Uuid,
17
18 pub name: compact_str::CompactString,
19 pub description: Option<compact_str::CompactString>,
20 pub author: compact_str::CompactString,
21
22 pub created: chrono::NaiveDateTime,
23
24 extension_data: super::ModelExtensionData,
25}
26
27impl BaseModel for Nest {
28 const NAME: &'static str = "nest";
29
30 fn get_extension_list() -> &'static super::ModelExtensionList {
31 static EXTENSIONS: LazyLock<super::ModelExtensionList> =
32 LazyLock::new(|| std::sync::RwLock::new(Vec::new()));
33
34 &EXTENSIONS
35 }
36
37 fn get_extension_data(&self) -> &super::ModelExtensionData {
38 &self.extension_data
39 }
40
41 #[inline]
42 fn base_columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
43 let prefix = prefix.unwrap_or_default();
44
45 BTreeMap::from([
46 ("nests.uuid", compact_str::format_compact!("{prefix}uuid")),
47 ("nests.name", compact_str::format_compact!("{prefix}name")),
48 (
49 "nests.description",
50 compact_str::format_compact!("{prefix}description"),
51 ),
52 (
53 "nests.author",
54 compact_str::format_compact!("{prefix}author"),
55 ),
56 (
57 "nests.created",
58 compact_str::format_compact!("{prefix}created"),
59 ),
60 ])
61 }
62
63 #[inline]
64 fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
65 let prefix = prefix.unwrap_or_default();
66
67 Ok(Self {
68 uuid: row.try_get(compact_str::format_compact!("{prefix}uuid").as_str())?,
69 name: row.try_get(compact_str::format_compact!("{prefix}name").as_str())?,
70 description: row
71 .try_get(compact_str::format_compact!("{prefix}description").as_str())?,
72 author: row.try_get(compact_str::format_compact!("{prefix}author").as_str())?,
73 created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
74 extension_data: Self::map_extensions(prefix, row)?,
75 })
76 }
77}
78
79impl Nest {
80 pub async fn all_with_pagination(
81 database: &crate::database::Database,
82 page: i64,
83 per_page: i64,
84 search: Option<&str>,
85 ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
86 let offset = (page - 1) * per_page;
87
88 let rows = sqlx::query(&format!(
89 r#"
90 SELECT {}, COUNT(*) OVER() AS total_count
91 FROM nests
92 WHERE ($1 IS NULL OR nests.name ILIKE '%' || $1 || '%')
93 ORDER BY nests.created
94 LIMIT $2 OFFSET $3
95 "#,
96 Self::columns_sql(None)
97 ))
98 .bind(search)
99 .bind(per_page)
100 .bind(offset)
101 .fetch_all(database.read())
102 .await?;
103
104 Ok(super::Pagination {
105 total: rows
106 .first()
107 .map_or(Ok(0), |row| row.try_get("total_count"))?,
108 per_page,
109 page,
110 data: rows
111 .into_iter()
112 .map(|row| Self::map(None, &row))
113 .try_collect_vec()?,
114 })
115 }
116
117 pub async fn by_name(
118 database: &crate::database::Database,
119 name: &str,
120 ) -> Result<Option<Self>, crate::database::DatabaseError> {
121 let row = sqlx::query(&format!(
122 r#"
123 SELECT {}
124 FROM nests
125 WHERE nests.name = $1
126 "#,
127 Self::columns_sql(None)
128 ))
129 .bind(name)
130 .fetch_optional(database.read())
131 .await?;
132
133 row.try_map(|row| Self::map(None, &row))
134 }
135}
136
137#[async_trait::async_trait]
138impl IntoAdminApiObject for Nest {
139 type AdminApiObject = AdminApiNest;
140 type ExtraArgs<'a> = ();
141
142 async fn into_admin_api_object<'a>(
143 self,
144 state: &crate::State,
145 _args: Self::ExtraArgs<'a>,
146 ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
147 let api_object = AdminApiNest::init_hooks(&self, state).await?;
148
149 let api_object = finish_extendible!(
150 AdminApiNest {
151 uuid: self.uuid,
152 name: self.name,
153 description: self.description,
154 author: self.author,
155 created: self.created.and_utc(),
156 },
157 api_object,
158 state
159 )?;
160
161 Ok(api_object)
162 }
163}
164
165#[async_trait::async_trait]
166impl ByUuid for Nest {
167 async fn by_uuid(
168 database: &crate::database::Database,
169 uuid: uuid::Uuid,
170 ) -> Result<Self, crate::database::DatabaseError> {
171 let row = sqlx::query(&format!(
172 r#"
173 SELECT {}
174 FROM nests
175 WHERE nests.uuid = $1
176 "#,
177 Self::columns_sql(None)
178 ))
179 .bind(uuid)
180 .fetch_one(database.read())
181 .await?;
182
183 Self::map(None, &row)
184 }
185
186 async fn by_uuid_with_transaction(
187 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
188 uuid: uuid::Uuid,
189 ) -> Result<Self, crate::database::DatabaseError> {
190 let row = sqlx::query(&format!(
191 r#"
192 SELECT {}
193 FROM nests
194 WHERE nests.uuid = $1
195 "#,
196 Self::columns_sql(None)
197 ))
198 .bind(uuid)
199 .fetch_one(&mut **transaction)
200 .await?;
201
202 Self::map(None, &row)
203 }
204}
205
206#[derive(ToSchema, Deserialize, Validate)]
207pub struct CreateNestOptions {
208 #[garde(length(chars, min = 2, max = 255))]
209 #[schema(min_length = 2, max_length = 255)]
210 pub author: compact_str::CompactString,
211 #[garde(length(chars, min = 1, max = 255))]
212 #[schema(min_length = 1, max_length = 255)]
213 pub name: compact_str::CompactString,
214 #[garde(length(chars, min = 1, max = 1024))]
215 #[schema(min_length = 1, max_length = 1024)]
216 pub description: Option<compact_str::CompactString>,
217}
218
219#[async_trait::async_trait]
220impl CreatableModel for Nest {
221 type CreateOptions<'a> = CreateNestOptions;
222 type CreateResult = Self;
223
224 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
225 static CREATE_LISTENERS: LazyLock<CreateListenerList<Nest>> =
226 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
227
228 &CREATE_LISTENERS
229 }
230
231 async fn create_with_transaction(
232 state: &crate::State,
233 mut options: Self::CreateOptions<'_>,
234 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
235 ) -> Result<Self, crate::database::DatabaseError> {
236 options.validate()?;
237
238 let mut query_builder = InsertQueryBuilder::new("nests");
239
240 Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
241
242 query_builder
243 .set("author", &options.author)
244 .set("name", &options.name)
245 .set("description", &options.description);
246
247 let row = query_builder
248 .returning(&Self::columns_sql(None))
249 .fetch_one(&mut **transaction)
250 .await?;
251 let mut nest = Self::map(None, &row)?;
252
253 Self::run_after_create_handlers(&mut nest, &options, state, transaction).await?;
254
255 Ok(nest)
256 }
257}
258
259#[derive(ToSchema, Serialize, Deserialize, Validate, Clone, Default)]
260pub struct UpdateNestOptions {
261 #[garde(length(chars, min = 2, max = 255))]
262 #[schema(min_length = 2, max_length = 255)]
263 pub author: Option<compact_str::CompactString>,
264 #[garde(length(chars, min = 1, max = 255))]
265 #[schema(min_length = 1, max_length = 255)]
266 pub name: Option<compact_str::CompactString>,
267 #[garde(length(chars, min = 1, max = 1024))]
268 #[schema(min_length = 1, max_length = 1024)]
269 #[serde(
270 default,
271 skip_serializing_if = "Option::is_none",
272 with = "::serde_with::rust::double_option"
273 )]
274 pub description: Option<Option<compact_str::CompactString>>,
275}
276
277#[async_trait::async_trait]
278impl UpdatableModel for Nest {
279 type UpdateOptions = UpdateNestOptions;
280
281 fn get_update_handlers() -> &'static LazyLock<UpdateHandlerList<Self>> {
282 static UPDATE_LISTENERS: LazyLock<UpdateHandlerList<Nest>> =
283 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
284
285 &UPDATE_LISTENERS
286 }
287
288 async fn update_with_transaction(
289 &mut self,
290 state: &crate::State,
291 mut options: Self::UpdateOptions,
292 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
293 ) -> Result<(), crate::database::DatabaseError> {
294 options.validate()?;
295
296 let mut query_builder = UpdateQueryBuilder::new("nests");
297
298 self.run_update_handlers(&mut options, &mut query_builder, state, transaction)
299 .await?;
300
301 query_builder
302 .set("author", options.author.as_ref())
303 .set("name", options.name.as_ref())
304 .set(
305 "description",
306 options.description.as_ref().map(|d| d.as_ref()),
307 )
308 .where_eq("uuid", self.uuid);
309
310 query_builder.execute(&mut **transaction).await?;
311
312 if let Some(author) = options.author {
313 self.author = author;
314 }
315 if let Some(name) = options.name {
316 self.name = name;
317 }
318 if let Some(description) = options.description {
319 self.description = description;
320 }
321
322 self.run_after_update_handlers(state, transaction).await?;
323
324 Ok(())
325 }
326}
327
328#[async_trait::async_trait]
329impl DeletableModel for Nest {
330 type DeleteOptions = ();
331
332 fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
333 static DELETE_LISTENERS: LazyLock<DeleteHandlerList<Nest>> =
334 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
335
336 &DELETE_LISTENERS
337 }
338
339 async fn delete_with_transaction(
340 &self,
341 state: &crate::State,
342 options: Self::DeleteOptions,
343 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
344 ) -> Result<(), anyhow::Error> {
345 self.run_delete_handlers(&options, state, transaction)
346 .await?;
347
348 sqlx::query(
349 r#"
350 DELETE FROM nests
351 WHERE nests.uuid = $1
352 "#,
353 )
354 .bind(self.uuid)
355 .execute(&mut **transaction)
356 .await?;
357
358 self.run_after_delete_handlers(&options, state, transaction)
359 .await?;
360
361 Ok(())
362 }
363}
364
365#[schema_extension_derive::extendible]
366#[init_args(Nest, crate::State)]
367#[hook_args(crate::State)]
368#[derive(ToSchema, Serialize)]
369#[schema(title = "Nest")]
370pub struct AdminApiNest {
371 pub uuid: uuid::Uuid,
372
373 pub name: compact_str::CompactString,
374 pub description: Option<compact_str::CompactString>,
375 pub author: compact_str::CompactString,
376
377 pub created: chrono::DateTime<chrono::Utc>,
378}