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(|| parking_lot::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 fn cache_invalidation_keys(&self) -> Vec<compact_str::CompactString> {
79 vec![compact_str::format_compact!(
80 "{}::{}",
81 Self::NAME,
82 self.uuid
83 )]
84 }
85}
86
87impl Nest {
88 pub async fn all_with_pagination(
89 database: &crate::database::Database,
90 page: i64,
91 per_page: i64,
92 search: Option<&str>,
93 ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
94 super::PaginatedQueryBuilder::new(
95 Self::columns_sql(None),
96 format!(
97 r#"
98 FROM nests
99 WHERE {search}
100 "#,
101 search = super::search_sql(1, &["nests.name"], &["nests.uuid"]),
102 ),
103 "nests.created, nests.uuid",
104 )
105 .bind(search)
106 .fetch(database, page, per_page)
107 .await
108 }
109
110 pub async fn by_name(
111 database: &crate::database::Database,
112 name: &str,
113 ) -> Result<Option<Self>, crate::database::DatabaseError> {
114 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
115 r#"
116 SELECT {}
117 FROM nests
118 WHERE nests.name = $1
119 "#,
120 Self::columns_sql(None)
121 )))
122 .bind(name)
123 .fetch_optional(database.read())
124 .await?;
125
126 row.try_map(|row| Self::map(None, &row))
127 }
128}
129
130#[async_trait::async_trait]
131impl IntoAdminApiObject for Nest {
132 type AdminApiObject = AdminApiNest;
133 type ExtraArgs<'a> = ();
134
135 async fn into_admin_api_object<'a>(
136 self,
137 state: &crate::State,
138 _args: Self::ExtraArgs<'a>,
139 ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
140 let api_object = AdminApiNest::init_hooks(&self, state).await?;
141
142 let api_object = finish_extendible!(
143 AdminApiNest {
144 uuid: self.uuid,
145 name: self.name,
146 description: self.description,
147 author: self.author,
148 created: self.created.and_utc(),
149 },
150 api_object,
151 state
152 )?;
153
154 Ok(api_object)
155 }
156}
157
158#[async_trait::async_trait]
159impl ByUuid for Nest {
160 async fn by_uuid(
161 database: &crate::database::Database,
162 uuid: uuid::Uuid,
163 ) -> Result<Self, crate::database::DatabaseError> {
164 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
165 r#"
166 SELECT {}
167 FROM nests
168 WHERE nests.uuid = $1
169 "#,
170 Self::columns_sql(None)
171 )))
172 .bind(uuid)
173 .fetch_one(database.read())
174 .await?;
175
176 Self::map(None, &row)
177 }
178
179 async fn by_uuid_with_transaction(
180 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
181 uuid: uuid::Uuid,
182 ) -> Result<Self, crate::database::DatabaseError> {
183 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
184 r#"
185 SELECT {}
186 FROM nests
187 WHERE nests.uuid = $1
188 "#,
189 Self::columns_sql(None)
190 )))
191 .bind(uuid)
192 .fetch_one(&mut **transaction)
193 .await?;
194
195 Self::map(None, &row)
196 }
197}
198
199#[derive(ToSchema, Deserialize, Validate)]
200pub struct CreateNestOptions {
201 #[garde(length(chars, min = 2, max = 255))]
202 #[schema(min_length = 2, max_length = 255)]
203 pub author: compact_str::CompactString,
204 #[garde(length(chars, min = 1, max = 255))]
205 #[schema(min_length = 1, max_length = 255)]
206 pub name: compact_str::CompactString,
207 #[garde(length(chars, min = 1, max = 1024))]
208 #[schema(min_length = 1, max_length = 1024)]
209 pub description: Option<compact_str::CompactString>,
210}
211
212#[async_trait::async_trait]
213impl CreatableModel for Nest {
214 type CreateOptions<'a> = CreateNestOptions;
215 type CreateResult = Self;
216
217 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
218 static CREATE_LISTENERS: LazyLock<CreateListenerList<Nest>> =
219 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
220
221 &CREATE_LISTENERS
222 }
223
224 async fn create_with_transaction(
225 state: &crate::State,
226 mut options: Self::CreateOptions<'_>,
227 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
228 ) -> Result<Self, crate::database::DatabaseError> {
229 options.validate()?;
230
231 let mut query_builder = InsertQueryBuilder::new("nests");
232
233 Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
234
235 query_builder
236 .set("author", &options.author)
237 .set("name", &options.name)
238 .set("description", &options.description);
239
240 let row = query_builder
241 .returning(&Self::columns_sql(None))
242 .fetch_one(&mut **transaction)
243 .await?;
244 let mut nest = Self::map(None, &row)?;
245
246 Self::run_after_create_handlers(&mut nest, &options, state, transaction).await?;
247
248 Ok(nest)
249 }
250}
251
252#[derive(ToSchema, Serialize, Deserialize, Validate, Clone, Default)]
253pub struct UpdateNestOptions {
254 #[garde(length(chars, min = 2, max = 255))]
255 #[schema(min_length = 2, max_length = 255)]
256 pub author: Option<compact_str::CompactString>,
257 #[garde(length(chars, min = 1, max = 255))]
258 #[schema(min_length = 1, max_length = 255)]
259 pub name: Option<compact_str::CompactString>,
260 #[garde(length(chars, min = 1, max = 1024))]
261 #[schema(min_length = 1, max_length = 1024)]
262 #[serde(
263 default,
264 skip_serializing_if = "Option::is_none",
265 with = "::serde_with::rust::double_option"
266 )]
267 pub description: Option<Option<compact_str::CompactString>>,
268}
269
270#[async_trait::async_trait]
271impl UpdatableModel for Nest {
272 type UpdateOptions = UpdateNestOptions;
273
274 fn get_update_handlers() -> &'static LazyLock<UpdateHandlerList<Self>> {
275 static UPDATE_LISTENERS: LazyLock<UpdateHandlerList<Nest>> =
276 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
277
278 &UPDATE_LISTENERS
279 }
280
281 async fn update_with_transaction(
282 &mut self,
283 state: &crate::State,
284 mut options: Self::UpdateOptions,
285 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
286 ) -> Result<(), crate::database::DatabaseError> {
287 options.validate()?;
288
289 let mut query_builder = UpdateQueryBuilder::new("nests");
290
291 self.run_update_handlers(&mut options, &mut query_builder, state, transaction)
292 .await?;
293
294 query_builder
295 .set("author", options.author.as_ref())
296 .set("name", options.name.as_ref())
297 .set(
298 "description",
299 options.description.as_ref().map(|d| d.as_ref()),
300 )
301 .where_eq("uuid", self.uuid);
302
303 query_builder.execute(&mut **transaction).await?;
304
305 if let Some(author) = options.author {
306 self.author = author;
307 }
308 if let Some(name) = options.name {
309 self.name = name;
310 }
311 if let Some(description) = options.description {
312 self.description = description;
313 }
314
315 self.run_after_update_handlers(state, transaction).await?;
316
317 Ok(())
318 }
319}
320
321#[async_trait::async_trait]
322impl DeletableModel for Nest {
323 type DeleteOptions = ();
324
325 fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
326 static DELETE_LISTENERS: LazyLock<DeleteHandlerList<Nest>> =
327 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
328
329 &DELETE_LISTENERS
330 }
331
332 async fn delete_with_transaction(
333 &self,
334 state: &crate::State,
335 options: Self::DeleteOptions,
336 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
337 ) -> Result<(), anyhow::Error> {
338 self.run_delete_handlers(&options, state, transaction)
339 .await?;
340
341 sqlx::query(
342 r#"
343 DELETE FROM nests
344 WHERE nests.uuid = $1
345 "#,
346 )
347 .bind(self.uuid)
348 .execute(&mut **transaction)
349 .await?;
350
351 self.run_after_delete_handlers(&options, state, transaction)
352 .await?;
353
354 Ok(())
355 }
356}
357
358#[schema_extension_derive::extendible]
359#[init_args(Nest, crate::State)]
360#[hook_args(crate::State)]
361#[derive(ToSchema, Serialize)]
362#[schema(title = "Nest")]
363pub struct AdminApiNest {
364 pub uuid: uuid::Uuid,
365
366 pub name: compact_str::CompactString,
367 pub description: Option<compact_str::CompactString>,
368 pub author: compact_str::CompactString,
369
370 pub created: chrono::DateTime<chrono::Utc>,
371}