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 extension_data: super::ModelExtensionData,
25}
26
27impl BaseModel for UserCommandSnippet {
28 const NAME: &'static str = "user_command_snippet";
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 (
47 "user_command_snippets.uuid",
48 compact_str::format_compact!("{prefix}uuid"),
49 ),
50 (
51 "user_command_snippets.name",
52 compact_str::format_compact!("{prefix}name"),
53 ),
54 (
55 "user_command_snippets.eggs",
56 compact_str::format_compact!("{prefix}eggs"),
57 ),
58 (
59 "user_command_snippets.command",
60 compact_str::format_compact!("{prefix}command"),
61 ),
62 (
63 "user_command_snippets.created",
64 compact_str::format_compact!("{prefix}created"),
65 ),
66 ])
67 }
68
69 #[inline]
70 fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
71 let prefix = prefix.unwrap_or_default();
72
73 Ok(Self {
74 uuid: row.try_get(compact_str::format_compact!("{prefix}uuid").as_str())?,
75 name: row.try_get(compact_str::format_compact!("{prefix}name").as_str())?,
76 eggs: row.try_get(compact_str::format_compact!("{prefix}eggs").as_str())?,
77 command: row.try_get(compact_str::format_compact!("{prefix}command").as_str())?,
78 created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
79 extension_data: Self::map_extensions(prefix, row)?,
80 })
81 }
82}
83
84impl UserCommandSnippet {
85 pub async fn by_user_uuid_uuid(
86 database: &crate::database::Database,
87 user_uuid: uuid::Uuid,
88 uuid: uuid::Uuid,
89 ) -> Result<Option<Self>, crate::database::DatabaseError> {
90 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
91 r#"
92 SELECT {}
93 FROM user_command_snippets
94 WHERE user_command_snippets.user_uuid = $1 AND user_command_snippets.uuid = $2
95 "#,
96 Self::columns_sql(None)
97 )))
98 .bind(user_uuid)
99 .bind(uuid)
100 .fetch_optional(database.read())
101 .await?;
102
103 row.try_map(|row| Self::map(None, &row))
104 }
105
106 pub async fn by_user_uuid_with_pagination(
107 database: &crate::database::Database,
108 user_uuid: uuid::Uuid,
109 page: i64,
110 per_page: i64,
111 search: Option<&str>,
112 ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
113 super::PaginatedQueryBuilder::new(
114 Self::columns_sql(None),
115 format!(
116 r#"
117 FROM user_command_snippets
118 WHERE user_command_snippets.user_uuid = $1 AND {search}
119 "#,
120 search = super::search_sql(
121 2,
122 &["user_command_snippets.name"],
123 &["user_command_snippets.uuid"]
124 ),
125 ),
126 "user_command_snippets.created, user_command_snippets.uuid",
127 )
128 .bind(user_uuid)
129 .bind(search)
130 .fetch(database, page, per_page)
131 .await
132 }
133
134 pub async fn all_by_user_uuid_nest_egg_uuid(
135 database: &crate::database::Database,
136 user_uuid: uuid::Uuid,
137 nest_egg_uuid: uuid::Uuid,
138 ) -> Result<Vec<Self>, crate::database::DatabaseError> {
139 let rows = sqlx::query(sqlx::AssertSqlSafe(format!(
140 r#"
141 SELECT {}
142 FROM user_command_snippets
143 WHERE user_command_snippets.user_uuid = $1 AND ($2 = ANY(user_command_snippets.eggs) OR user_command_snippets.eggs = '{{}}'::uuid[])
144 ORDER BY user_command_snippets.created, user_command_snippets.uuid
145 "#,
146 Self::columns_sql(None)
147 )))
148 .bind(user_uuid)
149 .bind(nest_egg_uuid)
150 .fetch_all(database.read())
151 .await?;
152
153 rows.into_iter()
154 .map(|row| Self::map(None, &row))
155 .try_collect_vec()
156 }
157
158 pub async fn count_by_user_uuid(
159 database: &crate::database::Database,
160 user_uuid: uuid::Uuid,
161 ) -> Result<i64, sqlx::Error> {
162 sqlx::query_scalar(
163 r#"
164 SELECT COUNT(*)
165 FROM user_command_snippets
166 WHERE user_command_snippets.user_uuid = $1
167 "#,
168 )
169 .bind(user_uuid)
170 .fetch_one(database.read())
171 .await
172 }
173
174 pub async fn cleanup_uuid_arrays(
175 database: &crate::database::Database,
176 ) -> Result<u64, crate::database::DatabaseError> {
177 let result = sqlx::query(
178 "UPDATE user_command_snippets
179 SET eggs = COALESCE(
180 (SELECT array_agg(u) FROM unnest(eggs) AS u
181 WHERE EXISTS (SELECT 1 FROM nest_eggs WHERE uuid = u)),
182 '{}'::uuid[]
183 )
184 WHERE EXISTS (
185 SELECT 1 FROM unnest(eggs) AS u
186 WHERE NOT EXISTS (SELECT 1 FROM nest_eggs WHERE uuid = u)
187 )",
188 )
189 .execute(database.write())
190 .await?;
191
192 Ok(result.rows_affected())
193 }
194}
195
196#[async_trait::async_trait]
197impl IntoApiObject for UserCommandSnippet {
198 type ApiObject = ApiUserCommandSnippet;
199 type ExtraArgs<'a> = ();
200
201 async fn into_api_object<'a>(
202 self,
203 state: &crate::State,
204 _args: Self::ExtraArgs<'a>,
205 ) -> Result<Self::ApiObject, crate::database::DatabaseError> {
206 let api_object = ApiUserCommandSnippet::init_hooks(&self, state).await?;
207
208 let api_object = finish_extendible!(
209 ApiUserCommandSnippet {
210 uuid: self.uuid,
211 name: self.name,
212 eggs: self.eggs,
213 command: self.command,
214 created: self.created.and_utc(),
215 },
216 api_object,
217 state
218 )?;
219
220 Ok(api_object)
221 }
222}
223
224#[derive(ToSchema, Deserialize, Validate)]
225pub struct CreateUserCommandSnippetOptions {
226 #[garde(skip)]
227 pub user_uuid: uuid::Uuid,
228
229 #[garde(length(chars, min = 1, max = 31))]
230 #[schema(min_length = 1, max_length = 31)]
231 pub name: compact_str::CompactString,
232
233 #[garde(length(max = 100))]
234 #[schema(max_length = 100)]
235 pub eggs: Vec<uuid::Uuid>,
236 #[garde(length(chars, min = 1, max = 1024))]
237 #[schema(min_length = 1, max_length = 1024)]
238 pub command: compact_str::CompactString,
239}
240
241#[async_trait::async_trait]
242impl CreatableModel for UserCommandSnippet {
243 type CreateOptions<'a> = CreateUserCommandSnippetOptions;
244 type CreateResult = Self;
245
246 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
247 static CREATE_LISTENERS: LazyLock<CreateListenerList<UserCommandSnippet>> =
248 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
249
250 &CREATE_LISTENERS
251 }
252
253 async fn create_with_transaction(
254 state: &crate::State,
255 mut options: Self::CreateOptions<'_>,
256 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
257 ) -> Result<Self, crate::database::DatabaseError> {
258 options.validate()?;
259
260 let mut query_builder = InsertQueryBuilder::new("user_command_snippets");
261
262 Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
263
264 query_builder
265 .set("user_uuid", options.user_uuid)
266 .set("name", &options.name)
267 .set("eggs", &options.eggs)
268 .set("command", &options.command);
269
270 let row = query_builder
271 .returning(&Self::columns_sql(None))
272 .fetch_one(&mut **transaction)
273 .await?;
274 let mut user_command_snippet = Self::map(None, &row)?;
275
276 Self::run_after_create_handlers(&mut user_command_snippet, &options, state, transaction)
277 .await?;
278
279 Ok(user_command_snippet)
280 }
281}
282
283#[derive(ToSchema, Serialize, Deserialize, Validate, Default)]
284pub struct UpdateUserCommandSnippetOptions {
285 #[garde(length(chars, min = 1, max = 31))]
286 #[schema(min_length = 1, max_length = 31, value_type = String)]
287 pub name: Option<compact_str::CompactString>,
288
289 #[garde(length(max = 100))]
290 #[schema(max_length = 100)]
291 pub eggs: Option<Vec<uuid::Uuid>>,
292 #[garde(length(chars, min = 1, max = 1024))]
293 #[schema(min_length = 1, max_length = 1024)]
294 pub command: Option<compact_str::CompactString>,
295}
296
297#[async_trait::async_trait]
298impl UpdatableModel for UserCommandSnippet {
299 type UpdateOptions = UpdateUserCommandSnippetOptions;
300
301 fn get_update_handlers() -> &'static LazyLock<UpdateHandlerList<Self>> {
302 static UPDATE_LISTENERS: LazyLock<UpdateHandlerList<UserCommandSnippet>> =
303 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
304
305 &UPDATE_LISTENERS
306 }
307
308 async fn update_with_transaction(
309 &mut self,
310 state: &crate::State,
311 mut options: Self::UpdateOptions,
312 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
313 ) -> Result<(), crate::database::DatabaseError> {
314 options.validate()?;
315
316 let mut query_builder = UpdateQueryBuilder::new("user_command_snippets");
317
318 self.run_update_handlers(&mut options, &mut query_builder, state, transaction)
319 .await?;
320
321 query_builder
322 .set("name", options.name.as_ref())
323 .set("eggs", options.eggs.as_ref())
324 .set("command", options.command.as_ref())
325 .where_eq("uuid", self.uuid);
326
327 query_builder.execute(&mut **transaction).await?;
328
329 if let Some(name) = options.name {
330 self.name = name;
331 }
332 if let Some(eggs) = options.eggs {
333 self.eggs = eggs;
334 }
335 if let Some(command) = options.command {
336 self.command = command;
337 }
338
339 self.run_after_update_handlers(state, transaction).await?;
340
341 Ok(())
342 }
343}
344
345#[async_trait::async_trait]
346impl DeletableModel for UserCommandSnippet {
347 type DeleteOptions = ();
348
349 fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
350 static DELETE_LISTENERS: LazyLock<DeleteHandlerList<UserCommandSnippet>> =
351 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
352
353 &DELETE_LISTENERS
354 }
355
356 async fn delete_with_transaction(
357 &self,
358 state: &crate::State,
359 options: Self::DeleteOptions,
360 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
361 ) -> Result<(), anyhow::Error> {
362 self.run_delete_handlers(&options, state, transaction)
363 .await?;
364
365 sqlx::query(
366 r#"
367 DELETE FROM user_command_snippets
368 WHERE user_command_snippets.uuid = $1
369 "#,
370 )
371 .bind(self.uuid)
372 .execute(&mut **transaction)
373 .await?;
374
375 self.run_after_delete_handlers(&options, state, transaction)
376 .await?;
377
378 Ok(())
379 }
380}
381
382#[derive(Validate)]
383pub struct DuplicateUserCommandSnippetOptions {
384 #[garde(skip)]
385 pub user_uuid: uuid::Uuid,
386 #[garde(length(chars, min = 1, max = 31))]
387 pub name: compact_str::CompactString,
388}
389
390#[async_trait::async_trait]
391impl DuplicableModel for UserCommandSnippet {
392 type DuplicateOptions<'a> = DuplicateUserCommandSnippetOptions;
393 type DuplicateResult = Self;
394
395 fn get_duplicate_handlers() -> &'static LazyLock<DuplicateHandlerList<Self>> {
396 static DUPLICATE_LISTENERS: LazyLock<DuplicateHandlerList<UserCommandSnippet>> =
397 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
398
399 &DUPLICATE_LISTENERS
400 }
401
402 async fn duplicate_with_transaction(
403 &self,
404 state: &crate::State,
405 options: Self::DuplicateOptions<'_>,
406 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
407 ) -> Result<Self, crate::database::DatabaseError> {
408 options.validate()?;
409
410 self.run_duplicate_handlers(&options, state, transaction)
411 .await?;
412
413 let mut query_builder = InsertQueryBuilder::new("user_command_snippets");
414
415 query_builder
416 .set("user_uuid", options.user_uuid)
417 .set("name", &options.name)
418 .set("eggs", &self.eggs)
419 .set("command", &self.command);
420
421 let row = query_builder
422 .returning(&Self::columns_sql(None))
423 .fetch_one(&mut **transaction)
424 .await?;
425 let mut user_command_snippet = Self::map(None, &row)?;
426
427 self.run_after_duplicate_handlers(&mut user_command_snippet, &options, state, transaction)
428 .await?;
429
430 Ok(user_command_snippet)
431 }
432}
433
434#[schema_extension_derive::extendible]
435#[init_args(UserCommandSnippet, crate::State)]
436#[hook_args(crate::State)]
437#[derive(ToSchema, Serialize)]
438#[schema(title = "UserCommandSnippet")]
439pub struct ApiUserCommandSnippet {
440 pub uuid: uuid::Uuid,
441
442 pub name: compact_str::CompactString,
443
444 pub eggs: Vec<uuid::Uuid>,
445 pub command: compact_str::CompactString,
446
447 pub created: chrono::DateTime<chrono::Utc>,
448}