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}