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