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(|| 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 (
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(&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 let offset = (page - 1) * per_page;
114
115 let rows = sqlx::query(&format!(
116 r#"
117 SELECT {}, COUNT(*) OVER() AS total_count
118 FROM user_command_snippets
119 WHERE user_command_snippets.user_uuid = $1 AND ($2 IS NULL OR user_command_snippets.name ILIKE '%' || $2 || '%')
120 ORDER BY user_command_snippets.created
121 LIMIT $3 OFFSET $4
122 "#,
123 Self::columns_sql(None)
124 ))
125 .bind(user_uuid)
126 .bind(search)
127 .bind(per_page)
128 .bind(offset)
129 .fetch_all(database.read())
130 .await?;
131
132 Ok(super::Pagination {
133 total: rows
134 .first()
135 .map_or(Ok(0), |row| row.try_get("total_count"))?,
136 per_page,
137 page,
138 data: rows
139 .into_iter()
140 .map(|row| Self::map(None, &row))
141 .try_collect_vec()?,
142 })
143 }
144
145 pub async fn all_by_user_uuid_nest_egg_uuid(
146 database: &crate::database::Database,
147 user_uuid: uuid::Uuid,
148 nest_egg_uuid: uuid::Uuid,
149 ) -> Result<Vec<Self>, crate::database::DatabaseError> {
150 let rows = sqlx::query(&format!(
151 r#"
152 SELECT {}
153 FROM user_command_snippets
154 WHERE user_command_snippets.user_uuid = $1 AND $2 = ANY(user_command_snippets.eggs)
155 ORDER BY user_command_snippets.created
156 "#,
157 Self::columns_sql(None)
158 ))
159 .bind(user_uuid)
160 .bind(nest_egg_uuid)
161 .fetch_all(database.read())
162 .await?;
163
164 rows.into_iter()
165 .map(|row| Self::map(None, &row))
166 .try_collect_vec()
167 }
168
169 pub async fn count_by_user_uuid(
170 database: &crate::database::Database,
171 user_uuid: uuid::Uuid,
172 ) -> Result<i64, sqlx::Error> {
173 sqlx::query_scalar(
174 r#"
175 SELECT COUNT(*)
176 FROM user_command_snippets
177 WHERE user_command_snippets.user_uuid = $1
178 "#,
179 )
180 .bind(user_uuid)
181 .fetch_one(database.read())
182 .await
183 }
184}
185
186#[async_trait::async_trait]
187impl IntoApiObject for UserCommandSnippet {
188 type ApiObject = ApiUserCommandSnippet;
189 type ExtraArgs<'a> = ();
190
191 async fn into_api_object<'a>(
192 self,
193 state: &crate::State,
194 _args: Self::ExtraArgs<'a>,
195 ) -> Result<Self::ApiObject, crate::database::DatabaseError> {
196 let api_object = ApiUserCommandSnippet::init_hooks(&self, state).await?;
197
198 let api_object = finish_extendible!(
199 ApiUserCommandSnippet {
200 uuid: self.uuid,
201 name: self.name,
202 eggs: self.eggs,
203 command: self.command,
204 created: self.created.and_utc(),
205 },
206 api_object,
207 state
208 )?;
209
210 Ok(api_object)
211 }
212}
213
214#[derive(ToSchema, Deserialize, Validate)]
215pub struct CreateUserCommandSnippetOptions {
216 #[garde(skip)]
217 pub user_uuid: uuid::Uuid,
218
219 #[garde(length(chars, min = 1, max = 31))]
220 #[schema(min_length = 1, max_length = 31)]
221 pub name: compact_str::CompactString,
222
223 #[garde(length(max = 100))]
224 #[schema(max_length = 100)]
225 pub eggs: Vec<uuid::Uuid>,
226 #[garde(length(chars, min = 1, max = 1024))]
227 #[schema(min_length = 1, max_length = 1024)]
228 pub command: compact_str::CompactString,
229}
230
231#[async_trait::async_trait]
232impl CreatableModel for UserCommandSnippet {
233 type CreateOptions<'a> = CreateUserCommandSnippetOptions;
234 type CreateResult = Self;
235
236 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
237 static CREATE_LISTENERS: LazyLock<CreateListenerList<UserCommandSnippet>> =
238 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
239
240 &CREATE_LISTENERS
241 }
242
243 async fn create_with_transaction(
244 state: &crate::State,
245 mut options: Self::CreateOptions<'_>,
246 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
247 ) -> Result<Self, crate::database::DatabaseError> {
248 options.validate()?;
249
250 let mut query_builder = InsertQueryBuilder::new("user_command_snippets");
251
252 Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
253
254 query_builder
255 .set("user_uuid", options.user_uuid)
256 .set("name", &options.name)
257 .set("eggs", &options.eggs)
258 .set("command", &options.command);
259
260 let row = query_builder
261 .returning(&Self::columns_sql(None))
262 .fetch_one(&mut **transaction)
263 .await?;
264 let mut user_command_snippet = Self::map(None, &row)?;
265
266 Self::run_after_create_handlers(&mut user_command_snippet, &options, state, transaction)
267 .await?;
268
269 Ok(user_command_snippet)
270 }
271}
272
273#[derive(ToSchema, Serialize, Deserialize, Validate, Default)]
274pub struct UpdateUserCommandSnippetOptions {
275 #[garde(length(chars, min = 1, max = 31))]
276 #[schema(min_length = 1, max_length = 31, value_type = String)]
277 pub name: Option<compact_str::CompactString>,
278
279 #[garde(length(max = 100))]
280 #[schema(max_length = 100)]
281 pub eggs: Option<Vec<uuid::Uuid>>,
282 #[garde(length(chars, min = 1, max = 1024))]
283 #[schema(min_length = 1, max_length = 1024)]
284 pub command: Option<compact_str::CompactString>,
285}
286
287#[async_trait::async_trait]
288impl UpdatableModel for UserCommandSnippet {
289 type UpdateOptions = UpdateUserCommandSnippetOptions;
290
291 fn get_update_handlers() -> &'static LazyLock<UpdateHandlerList<Self>> {
292 static UPDATE_LISTENERS: LazyLock<UpdateHandlerList<UserCommandSnippet>> =
293 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
294
295 &UPDATE_LISTENERS
296 }
297
298 async fn update_with_transaction(
299 &mut self,
300 state: &crate::State,
301 mut options: Self::UpdateOptions,
302 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
303 ) -> Result<(), crate::database::DatabaseError> {
304 options.validate()?;
305
306 let mut query_builder = UpdateQueryBuilder::new("user_command_snippets");
307
308 self.run_update_handlers(&mut options, &mut query_builder, state, transaction)
309 .await?;
310
311 query_builder
312 .set("name", options.name.as_ref())
313 .set("eggs", options.eggs.as_ref())
314 .set("command", options.command.as_ref())
315 .where_eq("uuid", self.uuid);
316
317 query_builder.execute(&mut **transaction).await?;
318
319 if let Some(name) = options.name {
320 self.name = name;
321 }
322 if let Some(eggs) = options.eggs {
323 self.eggs = eggs;
324 }
325 if let Some(command) = options.command {
326 self.command = command;
327 }
328
329 self.run_after_update_handlers(state, transaction).await?;
330
331 Ok(())
332 }
333}
334
335#[async_trait::async_trait]
336impl DeletableModel for UserCommandSnippet {
337 type DeleteOptions = ();
338
339 fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
340 static DELETE_LISTENERS: LazyLock<DeleteHandlerList<UserCommandSnippet>> =
341 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
342
343 &DELETE_LISTENERS
344 }
345
346 async fn delete_with_transaction(
347 &self,
348 state: &crate::State,
349 options: Self::DeleteOptions,
350 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
351 ) -> Result<(), anyhow::Error> {
352 self.run_delete_handlers(&options, state, transaction)
353 .await?;
354
355 sqlx::query(
356 r#"
357 DELETE FROM user_command_snippets
358 WHERE user_command_snippets.uuid = $1
359 "#,
360 )
361 .bind(self.uuid)
362 .execute(&mut **transaction)
363 .await?;
364
365 self.run_after_delete_handlers(&options, state, transaction)
366 .await?;
367
368 Ok(())
369 }
370}
371
372#[schema_extension_derive::extendible]
373#[init_args(UserCommandSnippet, crate::State)]
374#[hook_args(crate::State)]
375#[derive(ToSchema, Serialize)]
376#[schema(title = "UserCommandSnippet")]
377pub struct ApiUserCommandSnippet {
378 pub uuid: uuid::Uuid,
379
380 pub name: compact_str::CompactString,
381
382 pub eggs: Vec<uuid::Uuid>,
383 pub command: compact_str::CompactString,
384
385 pub created: chrono::DateTime<chrono::Utc>,
386}