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
25impl BaseModel for UserServerGroup {
26    const NAME: &'static str = "user_server_group";
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_server_groups.uuid",
35                compact_str::format_compact!("{prefix}uuid"),
36            ),
37            (
38                "user_server_groups.name",
39                compact_str::format_compact!("{prefix}name"),
40            ),
41            (
42                "user_server_groups.order_",
43                compact_str::format_compact!("{prefix}order"),
44            ),
45            (
46                "user_server_groups.server_order",
47                compact_str::format_compact!("{prefix}server_order"),
48            ),
49            (
50                "user_server_groups.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            order: row.try_get(compact_str::format_compact!("{prefix}order").as_str())?,
64            server_order: row
65                .try_get(compact_str::format_compact!("{prefix}server_order").as_str())?,
66            created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
67        })
68    }
69}
70
71impl UserServerGroup {
72    pub async fn by_user_uuid_uuid(
73        database: &crate::database::Database,
74        user_uuid: uuid::Uuid,
75        uuid: uuid::Uuid,
76    ) -> Result<Option<Self>, crate::database::DatabaseError> {
77        let row = sqlx::query(&format!(
78            r#"
79            SELECT {}
80            FROM user_server_groups
81            WHERE user_server_groups.user_uuid = $1 AND user_server_groups.uuid = $2
82            "#,
83            Self::columns_sql(None)
84        ))
85        .bind(user_uuid)
86        .bind(uuid)
87        .fetch_optional(database.read())
88        .await?;
89
90        row.try_map(|row| Self::map(None, &row))
91    }
92
93    pub async fn all_by_user_uuid(
94        database: &crate::database::Database,
95        user_uuid: uuid::Uuid,
96    ) -> Result<Vec<Self>, crate::database::DatabaseError> {
97        let rows = sqlx::query(&format!(
98            r#"
99            SELECT {}
100            FROM user_server_groups
101            WHERE user_server_groups.user_uuid = $1
102            ORDER BY user_server_groups.order_, user_server_groups.created
103            "#,
104            Self::columns_sql(None)
105        ))
106        .bind(user_uuid)
107        .fetch_all(database.read())
108        .await?;
109
110        rows.into_iter()
111            .map(|row| Self::map(None, &row))
112            .try_collect_vec()
113    }
114
115    pub async fn count_by_user_uuid(
116        database: &crate::database::Database,
117        user_uuid: uuid::Uuid,
118    ) -> i64 {
119        sqlx::query_scalar(
120            r#"
121            SELECT COUNT(*)
122            FROM user_server_groups
123            WHERE user_server_groups.user_uuid = $1
124            "#,
125        )
126        .bind(user_uuid)
127        .fetch_one(database.read())
128        .await
129        .unwrap_or(0)
130    }
131
132    #[inline]
133    pub fn into_api_object(self) -> ApiUserServerGroup {
134        ApiUserServerGroup {
135            uuid: self.uuid,
136            name: self.name,
137            order: self.order,
138            server_order: self.server_order,
139            created: self.created.and_utc(),
140        }
141    }
142}
143
144#[derive(ToSchema, Deserialize, Validate)]
145pub struct CreateUserServerGroupOptions {
146    #[garde(skip)]
147    pub user_uuid: uuid::Uuid,
148
149    #[garde(length(chars, min = 2, max = 31))]
150    #[schema(min_length = 2, max_length = 31)]
151    pub name: compact_str::CompactString,
152
153    #[garde(length(max = 100))]
154    #[schema(max_length = 100)]
155    pub server_order: Vec<uuid::Uuid>,
156}
157
158#[async_trait::async_trait]
159impl CreatableModel for UserServerGroup {
160    type CreateOptions<'a> = CreateUserServerGroupOptions;
161    type CreateResult = Self;
162
163    fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
164        static CREATE_LISTENERS: LazyLock<CreateListenerList<UserServerGroup>> =
165            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
166
167        &CREATE_LISTENERS
168    }
169
170    async fn create(
171        state: &crate::State,
172        mut options: Self::CreateOptions<'_>,
173    ) -> Result<Self, crate::database::DatabaseError> {
174        options.validate()?;
175
176        let mut transaction = state.database.write().begin().await?;
177
178        let mut query_builder = InsertQueryBuilder::new("user_server_groups");
179
180        Self::run_create_handlers(&mut options, &mut query_builder, state, &mut transaction)
181            .await?;
182
183        query_builder
184            .set("user_uuid", options.user_uuid)
185            .set("name", &options.name)
186            .set("server_order", &options.server_order);
187
188        let row = query_builder
189            .returning(&Self::columns_sql(None))
190            .fetch_one(&mut *transaction)
191            .await?;
192        let user_server_group = Self::map(None, &row)?;
193
194        transaction.commit().await?;
195
196        Ok(user_server_group)
197    }
198}
199
200#[derive(ToSchema, Serialize, Deserialize, Validate, Default)]
201pub struct UpdateUserServerGroupOptions {
202    #[garde(length(chars, min = 2, max = 31))]
203    #[schema(min_length = 2, max_length = 31, value_type = String)]
204    pub name: Option<compact_str::CompactString>,
205
206    #[garde(length(max = 100))]
207    #[schema(max_length = 100)]
208    pub server_order: Option<Vec<uuid::Uuid>>,
209}
210
211#[async_trait::async_trait]
212impl UpdatableModel for UserServerGroup {
213    type UpdateOptions = UpdateUserServerGroupOptions;
214
215    fn get_update_handlers() -> &'static LazyLock<UpdateListenerList<Self>> {
216        static UPDATE_LISTENERS: LazyLock<UpdateListenerList<UserServerGroup>> =
217            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
218
219        &UPDATE_LISTENERS
220    }
221
222    async fn update(
223        &mut self,
224        state: &crate::State,
225        mut options: Self::UpdateOptions,
226    ) -> Result<(), crate::database::DatabaseError> {
227        options.validate()?;
228
229        let mut transaction = state.database.write().begin().await?;
230
231        let mut query_builder = UpdateQueryBuilder::new("user_server_groups");
232
233        Self::run_update_handlers(
234            self,
235            &mut options,
236            &mut query_builder,
237            state,
238            &mut transaction,
239        )
240        .await?;
241
242        query_builder
243            .set("name", options.name.as_ref())
244            .set("server_order", options.server_order.as_ref())
245            .where_eq("uuid", self.uuid);
246
247        query_builder.execute(&mut *transaction).await?;
248
249        if let Some(name) = options.name {
250            self.name = name;
251        }
252        if let Some(server_order) = options.server_order {
253            self.server_order = server_order;
254        }
255
256        transaction.commit().await?;
257
258        Ok(())
259    }
260}
261
262#[async_trait::async_trait]
263impl DeletableModel for UserServerGroup {
264    type DeleteOptions = ();
265
266    fn get_delete_handlers() -> &'static LazyLock<DeleteListenerList<Self>> {
267        static DELETE_LISTENERS: LazyLock<DeleteListenerList<UserServerGroup>> =
268            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
269
270        &DELETE_LISTENERS
271    }
272
273    async fn delete(
274        &self,
275        state: &crate::State,
276        options: Self::DeleteOptions,
277    ) -> Result<(), anyhow::Error> {
278        let mut transaction = state.database.write().begin().await?;
279
280        self.run_delete_handlers(&options, state, &mut transaction)
281            .await?;
282
283        sqlx::query(
284            r#"
285            DELETE FROM user_server_groups
286            WHERE user_server_groups.uuid = $1
287            "#,
288        )
289        .bind(self.uuid)
290        .execute(&mut *transaction)
291        .await?;
292
293        transaction.commit().await?;
294
295        Ok(())
296    }
297}
298
299#[derive(ToSchema, Serialize)]
300#[schema(title = "UserServerGroup")]
301pub struct ApiUserServerGroup {
302    pub uuid: uuid::Uuid,
303
304    pub name: compact_str::CompactString,
305    pub order: i16,
306
307    pub server_order: Vec<uuid::Uuid>,
308
309    pub created: chrono::DateTime<chrono::Utc>,
310}