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}