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