Skip to main content

shared/models/
user_command_snippet.rs

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}