Skip to main content

shared/models/
user_server_group.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 UserServerGroup {
16    pub uuid: uuid::Uuid,
17    pub name: compact_str::CompactString,
18    pub order: i16,
19
20    pub server_order: Vec<uuid::Uuid>,
21
22    pub created: chrono::NaiveDateTime,
23
24    extension_data: super::ModelExtensionData,
25}
26
27impl BaseModel for UserServerGroup {
28    const NAME: &'static str = "user_server_group";
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_server_groups.uuid",
48                compact_str::format_compact!("{prefix}uuid"),
49            ),
50            (
51                "user_server_groups.name",
52                compact_str::format_compact!("{prefix}name"),
53            ),
54            (
55                "user_server_groups.order_",
56                compact_str::format_compact!("{prefix}order"),
57            ),
58            (
59                "user_server_groups.server_order",
60                compact_str::format_compact!("{prefix}server_order"),
61            ),
62            (
63                "user_server_groups.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            order: row.try_get(compact_str::format_compact!("{prefix}order").as_str())?,
77            server_order: row
78                .try_get(compact_str::format_compact!("{prefix}server_order").as_str())?,
79            created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
80            extension_data: Self::map_extensions(prefix, row)?,
81        })
82    }
83}
84
85impl UserServerGroup {
86    pub async fn by_user_uuid_uuid(
87        database: &crate::database::Database,
88        user_uuid: uuid::Uuid,
89        uuid: uuid::Uuid,
90    ) -> Result<Option<Self>, crate::database::DatabaseError> {
91        let row = sqlx::query(sqlx::AssertSqlSafe(format!(
92            r#"
93            SELECT {}
94            FROM user_server_groups
95            WHERE user_server_groups.user_uuid = $1 AND user_server_groups.uuid = $2
96            "#,
97            Self::columns_sql(None)
98        )))
99        .bind(user_uuid)
100        .bind(uuid)
101        .fetch_optional(database.read())
102        .await?;
103
104        row.try_map(|row| Self::map(None, &row))
105    }
106
107    pub async fn all_by_user_uuid(
108        database: &crate::database::Database,
109        user_uuid: uuid::Uuid,
110    ) -> Result<Vec<Self>, crate::database::DatabaseError> {
111        let rows = sqlx::query(sqlx::AssertSqlSafe(format!(
112            r#"
113            SELECT {}
114            FROM user_server_groups
115            WHERE user_server_groups.user_uuid = $1
116            ORDER BY user_server_groups.order_, user_server_groups.created
117            "#,
118            Self::columns_sql(None)
119        )))
120        .bind(user_uuid)
121        .fetch_all(database.read())
122        .await?;
123
124        rows.into_iter()
125            .map(|row| Self::map(None, &row))
126            .try_collect_vec()
127    }
128
129    pub async fn count_by_user_uuid(
130        database: &crate::database::Database,
131        user_uuid: uuid::Uuid,
132    ) -> Result<i64, sqlx::Error> {
133        sqlx::query_scalar(
134            r#"
135            SELECT COUNT(*)
136            FROM user_server_groups
137            WHERE user_server_groups.user_uuid = $1
138            "#,
139        )
140        .bind(user_uuid)
141        .fetch_one(database.read())
142        .await
143    }
144
145    pub async fn cleanup_uuid_arrays(
146        database: &crate::database::Database,
147    ) -> Result<u64, crate::database::DatabaseError> {
148        let result = sqlx::query(
149            "UPDATE user_server_groups
150            SET server_order = COALESCE(
151                (SELECT array_agg(u) FROM unnest(server_order) AS u
152                WHERE EXISTS (SELECT 1 FROM servers WHERE uuid = u)),
153                '{}'::uuid[]
154            )
155            WHERE EXISTS (
156                SELECT 1 FROM unnest(server_order) AS u
157                WHERE NOT EXISTS (SELECT 1 FROM servers WHERE uuid = u)
158            )",
159        )
160        .execute(database.write())
161        .await?;
162
163        Ok(result.rows_affected())
164    }
165}
166
167#[async_trait::async_trait]
168impl IntoApiObject for UserServerGroup {
169    type ApiObject = ApiUserServerGroup;
170    type ExtraArgs<'a> = ();
171
172    async fn into_api_object<'a>(
173        self,
174        state: &crate::State,
175        _args: Self::ExtraArgs<'a>,
176    ) -> Result<Self::ApiObject, crate::database::DatabaseError> {
177        let api_object = ApiUserServerGroup::init_hooks(&self, state).await?;
178
179        let api_object = finish_extendible!(
180            ApiUserServerGroup {
181                uuid: self.uuid,
182                name: self.name,
183                order: self.order,
184                server_order: self.server_order,
185                created: self.created.and_utc(),
186            },
187            api_object,
188            state
189        )?;
190
191        Ok(api_object)
192    }
193}
194
195#[derive(ToSchema, Deserialize, Validate)]
196pub struct CreateUserServerGroupOptions {
197    #[garde(skip)]
198    pub user_uuid: uuid::Uuid,
199
200    #[garde(length(chars, min = 2, max = 31))]
201    #[schema(min_length = 2, max_length = 31)]
202    pub name: compact_str::CompactString,
203
204    #[garde(length(max = 100))]
205    #[schema(max_length = 100)]
206    pub server_order: Vec<uuid::Uuid>,
207}
208
209#[async_trait::async_trait]
210impl CreatableModel for UserServerGroup {
211    type CreateOptions<'a> = CreateUserServerGroupOptions;
212    type CreateResult = Self;
213
214    fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
215        static CREATE_LISTENERS: LazyLock<CreateListenerList<UserServerGroup>> =
216            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
217
218        &CREATE_LISTENERS
219    }
220
221    async fn create_with_transaction(
222        state: &crate::State,
223        mut options: Self::CreateOptions<'_>,
224        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
225    ) -> Result<Self, crate::database::DatabaseError> {
226        options.validate()?;
227
228        let mut query_builder = InsertQueryBuilder::new("user_server_groups");
229
230        Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
231
232        query_builder
233            .set("user_uuid", options.user_uuid)
234            .set("name", &options.name)
235            .set("server_order", &options.server_order);
236
237        let row = query_builder
238            .returning(&Self::columns_sql(None))
239            .fetch_one(&mut **transaction)
240            .await?;
241        let mut user_server_group = Self::map(None, &row)?;
242
243        Self::run_after_create_handlers(&mut user_server_group, &options, state, transaction)
244            .await?;
245
246        Ok(user_server_group)
247    }
248}
249
250#[derive(ToSchema, Serialize, Deserialize, Validate, Default)]
251pub struct UpdateUserServerGroupOptions {
252    #[garde(length(chars, min = 2, max = 31))]
253    #[schema(min_length = 2, max_length = 31, value_type = String)]
254    pub name: Option<compact_str::CompactString>,
255
256    #[garde(length(max = 100))]
257    #[schema(max_length = 100)]
258    pub server_order: Option<Vec<uuid::Uuid>>,
259}
260
261#[async_trait::async_trait]
262impl UpdatableModel for UserServerGroup {
263    type UpdateOptions = UpdateUserServerGroupOptions;
264
265    fn get_update_handlers() -> &'static LazyLock<UpdateHandlerList<Self>> {
266        static UPDATE_LISTENERS: LazyLock<UpdateHandlerList<UserServerGroup>> =
267            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
268
269        &UPDATE_LISTENERS
270    }
271
272    async fn update_with_transaction(
273        &mut self,
274        state: &crate::State,
275        mut options: Self::UpdateOptions,
276        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
277    ) -> Result<(), crate::database::DatabaseError> {
278        options.validate()?;
279
280        let mut query_builder = UpdateQueryBuilder::new("user_server_groups");
281
282        self.run_update_handlers(&mut options, &mut query_builder, state, transaction)
283            .await?;
284
285        query_builder
286            .set("name", options.name.as_ref())
287            .set("server_order", options.server_order.as_ref())
288            .where_eq("uuid", self.uuid);
289
290        query_builder.execute(&mut **transaction).await?;
291
292        if let Some(name) = options.name {
293            self.name = name;
294        }
295        if let Some(server_order) = options.server_order {
296            self.server_order = server_order;
297        }
298
299        self.run_after_update_handlers(state, transaction).await?;
300
301        Ok(())
302    }
303}
304
305#[async_trait::async_trait]
306impl DeletableModel for UserServerGroup {
307    type DeleteOptions = ();
308
309    fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
310        static DELETE_LISTENERS: LazyLock<DeleteHandlerList<UserServerGroup>> =
311            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
312
313        &DELETE_LISTENERS
314    }
315
316    async fn delete_with_transaction(
317        &self,
318        state: &crate::State,
319        options: Self::DeleteOptions,
320        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
321    ) -> Result<(), anyhow::Error> {
322        self.run_delete_handlers(&options, state, transaction)
323            .await?;
324
325        sqlx::query(
326            r#"
327            DELETE FROM user_server_groups
328            WHERE user_server_groups.uuid = $1
329            "#,
330        )
331        .bind(self.uuid)
332        .execute(&mut **transaction)
333        .await?;
334
335        self.run_after_delete_handlers(&options, state, transaction)
336            .await?;
337
338        Ok(())
339    }
340}
341
342#[schema_extension_derive::extendible]
343#[init_args(UserServerGroup, crate::State)]
344#[hook_args(crate::State)]
345#[derive(ToSchema, Serialize)]
346#[schema(title = "UserServerGroup")]
347pub struct ApiUserServerGroup {
348    pub uuid: uuid::Uuid,
349
350    pub name: compact_str::CompactString,
351    pub order: i16,
352
353    pub server_order: Vec<uuid::Uuid>,
354
355    pub created: chrono::DateTime<chrono::Utc>,
356}