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 Role {
16 pub uuid: uuid::Uuid,
17
18 pub name: compact_str::CompactString,
19 pub description: Option<compact_str::CompactString>,
20
21 pub require_two_factor: bool,
22
23 pub admin_permissions: Arc<Vec<compact_str::CompactString>>,
24 pub server_permissions: Arc<Vec<compact_str::CompactString>>,
25
26 pub created: chrono::NaiveDateTime,
27
28 extension_data: super::ModelExtensionData,
29}
30
31impl BaseModel for Role {
32 const NAME: &'static str = "role";
33
34 fn get_extension_list() -> &'static super::ModelExtensionList {
35 static EXTENSIONS: LazyLock<super::ModelExtensionList> =
36 LazyLock::new(|| parking_lot::RwLock::new(Vec::new()));
37
38 &EXTENSIONS
39 }
40
41 fn get_extension_data(&self) -> &super::ModelExtensionData {
42 &self.extension_data
43 }
44
45 #[inline]
46 fn base_columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
47 let prefix = prefix.unwrap_or_default();
48
49 BTreeMap::from([
50 ("roles.uuid", compact_str::format_compact!("{prefix}uuid")),
51 ("roles.name", compact_str::format_compact!("{prefix}name")),
52 (
53 "roles.description",
54 compact_str::format_compact!("{prefix}description"),
55 ),
56 (
57 "roles.require_two_factor",
58 compact_str::format_compact!("{prefix}require_two_factor"),
59 ),
60 (
61 "roles.admin_permissions",
62 compact_str::format_compact!("{prefix}admin_permissions"),
63 ),
64 (
65 "roles.server_permissions",
66 compact_str::format_compact!("{prefix}server_permissions"),
67 ),
68 (
69 "roles.created",
70 compact_str::format_compact!("{prefix}created"),
71 ),
72 ])
73 }
74
75 #[inline]
76 fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
77 let prefix = prefix.unwrap_or_default();
78
79 Ok(Self {
80 uuid: row.try_get(compact_str::format_compact!("{prefix}uuid").as_str())?,
81 name: row.try_get(compact_str::format_compact!("{prefix}name").as_str())?,
82 description: row
83 .try_get(compact_str::format_compact!("{prefix}description").as_str())?,
84 require_two_factor: row
85 .try_get(compact_str::format_compact!("{prefix}require_two_factor").as_str())?,
86 admin_permissions: Arc::new(
87 row.try_get(compact_str::format_compact!("{prefix}admin_permissions").as_str())?,
88 ),
89 server_permissions: Arc::new(
90 row.try_get(compact_str::format_compact!("{prefix}server_permissions").as_str())?,
91 ),
92 created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
93 extension_data: Self::map_extensions(prefix, row)?,
94 })
95 }
96
97 fn cache_invalidation_keys(&self) -> Vec<compact_str::CompactString> {
98 vec![compact_str::format_compact!(
99 "{}::{}",
100 Self::NAME,
101 self.uuid
102 )]
103 }
104}
105
106impl Role {
107 async fn user_uuids(
108 &self,
109 database: &crate::database::Database,
110 ) -> Result<Vec<uuid::Uuid>, sqlx::Error> {
111 sqlx::query_scalar(
112 r#"
113 SELECT users.uuid
114 FROM users
115 WHERE users.role_uuid = $1
116 "#,
117 )
118 .bind(self.uuid)
119 .fetch_all(database.read())
120 .await
121 }
122
123 async fn invalidate_cached_with_users(
124 &self,
125 database: &crate::database::Database,
126 ) -> Result<(), sqlx::Error> {
127 Self::invalidate_cached(database, self.uuid).await;
128 for user_uuid in self.user_uuids(database).await? {
129 super::user::User::invalidate_cached(database, user_uuid).await;
130 }
131
132 Ok(())
133 }
134
135 pub async fn all_with_pagination(
136 database: &crate::database::Database,
137 page: i64,
138 per_page: i64,
139 search: Option<&str>,
140 ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
141 let offset = (page - 1) * per_page;
142
143 let rows = sqlx::query(sqlx::AssertSqlSafe(format!(
144 r#"
145 SELECT {}, COUNT(*) OVER() AS total_count
146 FROM roles
147 WHERE ($1 IS NULL OR roles.name ILIKE '%' || $1 || '%')
148 ORDER BY roles.created
149 LIMIT $2 OFFSET $3
150 "#,
151 Self::columns_sql(None)
152 )))
153 .bind(search)
154 .bind(per_page)
155 .bind(offset)
156 .fetch_all(database.read())
157 .await?;
158
159 Ok(super::Pagination {
160 total: rows
161 .first()
162 .map_or(Ok(0), |row| row.try_get("total_count"))?,
163 per_page,
164 page,
165 data: rows
166 .into_iter()
167 .map(|row| Self::map(None, &row))
168 .try_collect_vec()?,
169 })
170 }
171}
172
173#[async_trait::async_trait]
174impl IntoAdminApiObject for Role {
175 type AdminApiObject = AdminApiRole;
176 type ExtraArgs<'a> = ();
177
178 async fn into_admin_api_object<'a>(
179 self,
180 state: &crate::State,
181 _args: Self::ExtraArgs<'a>,
182 ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
183 let api_object = AdminApiRole::init_hooks(&self, state).await?;
184
185 let api_object = finish_extendible!(
186 AdminApiRole {
187 uuid: self.uuid,
188 name: self.name,
189 description: self.description,
190 require_two_factor: self.require_two_factor,
191 admin_permissions: self.admin_permissions,
192 server_permissions: self.server_permissions,
193 created: self.created.and_utc(),
194 },
195 api_object,
196 state
197 )?;
198
199 Ok(api_object)
200 }
201}
202
203#[async_trait::async_trait]
204impl ByUuid for Role {
205 async fn by_uuid(
206 database: &crate::database::Database,
207 uuid: uuid::Uuid,
208 ) -> Result<Self, crate::database::DatabaseError> {
209 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
210 r#"
211 SELECT {}
212 FROM roles
213 WHERE roles.uuid = $1
214 "#,
215 Self::columns_sql(None)
216 )))
217 .bind(uuid)
218 .fetch_one(database.read())
219 .await?;
220
221 Self::map(None, &row)
222 }
223
224 async fn by_uuid_with_transaction(
225 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
226 uuid: uuid::Uuid,
227 ) -> Result<Self, crate::database::DatabaseError> {
228 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
229 r#"
230 SELECT {}
231 FROM roles
232 WHERE roles.uuid = $1
233 "#,
234 Self::columns_sql(None)
235 )))
236 .bind(uuid)
237 .fetch_one(&mut **transaction)
238 .await?;
239
240 Self::map(None, &row)
241 }
242}
243
244#[derive(ToSchema, Deserialize, Validate)]
245pub struct CreateRoleOptions {
246 #[garde(length(chars, min = 1, max = 255))]
247 #[schema(min_length = 1, max_length = 255)]
248 pub name: compact_str::CompactString,
249 #[garde(length(chars, min = 1, max = 1024))]
250 #[schema(min_length = 1, max_length = 1024)]
251 pub description: Option<compact_str::CompactString>,
252 #[garde(skip)]
253 pub require_two_factor: bool,
254 #[garde(custom(crate::permissions::validate_admin_permissions))]
255 pub admin_permissions: Vec<compact_str::CompactString>,
256 #[garde(custom(crate::permissions::validate_server_permissions))]
257 pub server_permissions: Vec<compact_str::CompactString>,
258}
259
260#[async_trait::async_trait]
261impl CreatableModel for Role {
262 type CreateOptions<'a> = CreateRoleOptions;
263 type CreateResult = Self;
264
265 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
266 static CREATE_LISTENERS: LazyLock<CreateListenerList<Role>> =
267 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
268
269 &CREATE_LISTENERS
270 }
271
272 async fn create_with_transaction(
273 state: &crate::State,
274 mut options: Self::CreateOptions<'_>,
275 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
276 ) -> Result<Self, crate::database::DatabaseError> {
277 options.validate()?;
278
279 let mut query_builder = InsertQueryBuilder::new("roles");
280
281 Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
282
283 query_builder
284 .set("name", &options.name)
285 .set("description", &options.description)
286 .set("require_two_factor", options.require_two_factor)
287 .set("admin_permissions", &options.admin_permissions)
288 .set("server_permissions", &options.server_permissions);
289
290 let row = query_builder
291 .returning(&Self::columns_sql(None))
292 .fetch_one(&mut **transaction)
293 .await?;
294 let mut role = Self::map(None, &row)?;
295
296 Self::run_after_create_handlers(&mut role, &options, state, transaction).await?;
297
298 Ok(role)
299 }
300}
301
302#[derive(ToSchema, Serialize, Deserialize, Validate, Clone, Default)]
303pub struct UpdateRoleOptions {
304 #[garde(length(chars, min = 1, max = 255))]
305 #[schema(min_length = 1, max_length = 255)]
306 pub name: Option<compact_str::CompactString>,
307 #[garde(length(chars, min = 1, max = 1024))]
308 #[schema(min_length = 1, max_length = 1024)]
309 #[serde(
310 default,
311 skip_serializing_if = "Option::is_none",
312 with = "::serde_with::rust::double_option"
313 )]
314 pub description: Option<Option<compact_str::CompactString>>,
315 #[garde(skip)]
316 pub require_two_factor: Option<bool>,
317 #[garde(inner(custom(crate::permissions::validate_admin_permissions)))]
318 pub admin_permissions: Option<Vec<compact_str::CompactString>>,
319 #[garde(inner(custom(crate::permissions::validate_server_permissions)))]
320 pub server_permissions: Option<Vec<compact_str::CompactString>>,
321}
322
323#[async_trait::async_trait]
324impl UpdatableModel for Role {
325 type UpdateOptions = UpdateRoleOptions;
326
327 fn get_update_handlers() -> &'static LazyLock<UpdateHandlerList<Self>> {
328 static UPDATE_LISTENERS: LazyLock<UpdateHandlerList<Role>> =
329 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
330
331 &UPDATE_LISTENERS
332 }
333
334 async fn update(
335 &mut self,
336 state: &crate::State,
337 options: Self::UpdateOptions,
338 ) -> Result<(), crate::database::DatabaseError> {
339 let mut transaction = state.database.write().begin().await?;
340
341 if let Err(err) = self
342 .update_with_transaction(state, options, &mut transaction)
343 .await
344 {
345 transaction.rollback().await?;
346 return Err(err);
347 }
348
349 transaction.commit().await?;
350
351 self.invalidate_cached_with_users(&state.database).await?;
352
353 Ok(())
354 }
355
356 async fn update_with_transaction(
357 &mut self,
358 state: &crate::State,
359 mut options: Self::UpdateOptions,
360 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
361 ) -> Result<(), crate::database::DatabaseError> {
362 options.validate()?;
363
364 let mut query_builder = UpdateQueryBuilder::new("roles");
365
366 self.run_update_handlers(&mut options, &mut query_builder, state, transaction)
367 .await?;
368
369 query_builder
370 .set("name", options.name.as_ref())
371 .set(
372 "description",
373 options.description.as_ref().map(|d| d.as_ref()),
374 )
375 .set("require_two_factor", options.require_two_factor)
376 .set("admin_permissions", options.admin_permissions.as_ref())
377 .set("server_permissions", options.server_permissions.as_ref())
378 .where_eq("uuid", self.uuid);
379
380 query_builder.execute(&mut **transaction).await?;
381
382 if let Some(name) = options.name {
383 self.name = name;
384 }
385 if let Some(description) = options.description {
386 self.description = description;
387 }
388 if let Some(require_two_factor) = options.require_two_factor {
389 self.require_two_factor = require_two_factor;
390 }
391 if let Some(admin_permissions) = options.admin_permissions {
392 self.admin_permissions = Arc::new(admin_permissions);
393 }
394 if let Some(server_permissions) = options.server_permissions {
395 self.server_permissions = Arc::new(server_permissions);
396 }
397
398 self.run_after_update_handlers(state, transaction).await?;
399
400 Ok(())
401 }
402}
403
404#[async_trait::async_trait]
405impl DeletableModel for Role {
406 type DeleteOptions = ();
407
408 fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
409 static DELETE_LISTENERS: LazyLock<DeleteHandlerList<Role>> =
410 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
411
412 &DELETE_LISTENERS
413 }
414
415 async fn delete(
416 &self,
417 state: &crate::State,
418 options: Self::DeleteOptions,
419 ) -> Result<(), anyhow::Error> {
420 let user_uuids = self.user_uuids(&state.database).await?;
421
422 let mut transaction = state.database.write().begin().await?;
423
424 if let Err(err) = self
425 .delete_with_transaction(state, options, &mut transaction)
426 .await
427 {
428 transaction.rollback().await?;
429 return Err(err);
430 }
431
432 transaction.commit().await?;
433
434 Self::invalidate_cached(&state.database, self.uuid).await;
435 for user_uuid in user_uuids {
436 super::user::User::invalidate_cached(&state.database, user_uuid).await;
437 }
438
439 Ok(())
440 }
441
442 async fn delete_with_transaction(
443 &self,
444 state: &crate::State,
445 options: Self::DeleteOptions,
446 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
447 ) -> Result<(), anyhow::Error> {
448 self.run_delete_handlers(&options, state, transaction)
449 .await?;
450
451 sqlx::query(
452 r#"
453 DELETE FROM roles
454 WHERE roles.uuid = $1
455 "#,
456 )
457 .bind(self.uuid)
458 .execute(&mut **transaction)
459 .await?;
460
461 self.run_after_delete_handlers(&options, state, transaction)
462 .await?;
463
464 Ok(())
465 }
466}
467
468#[derive(Validate)]
469pub struct DuplicateRoleOptions {
470 #[garde(length(chars, min = 1, max = 255))]
471 pub name: compact_str::CompactString,
472}
473
474#[async_trait::async_trait]
475impl DuplicableModel for Role {
476 type DuplicateOptions<'a> = DuplicateRoleOptions;
477
478 fn get_duplicate_handlers() -> &'static LazyLock<DuplicateHandlerList<Self>> {
479 static DUPLICATE_LISTENERS: LazyLock<DuplicateHandlerList<Role>> =
480 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
481
482 &DUPLICATE_LISTENERS
483 }
484
485 async fn duplicate_with_transaction(
486 &self,
487 state: &crate::State,
488 options: Self::DuplicateOptions<'_>,
489 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
490 ) -> Result<Self, crate::database::DatabaseError> {
491 options.validate()?;
492
493 self.run_duplicate_handlers(&options, state, transaction)
494 .await?;
495
496 let mut query_builder = InsertQueryBuilder::new("roles");
497
498 query_builder
499 .set("name", &options.name)
500 .set("description", &self.description)
501 .set("require_two_factor", self.require_two_factor)
502 .set("admin_permissions", self.admin_permissions.as_ref())
503 .set("server_permissions", self.server_permissions.as_ref());
504
505 let row = query_builder
506 .returning(&Self::columns_sql(None))
507 .fetch_one(&mut **transaction)
508 .await?;
509 let mut role = Self::map(None, &row)?;
510
511 self.run_after_duplicate_handlers(&mut role, &options, state, transaction)
512 .await?;
513
514 Ok(role)
515 }
516}
517
518#[schema_extension_derive::extendible]
519#[init_args(Role, crate::State)]
520#[hook_args(crate::State)]
521#[derive(ToSchema, Serialize)]
522#[schema(title = "Role")]
523pub struct AdminApiRole {
524 pub uuid: uuid::Uuid,
525
526 pub name: compact_str::CompactString,
527 pub description: Option<compact_str::CompactString>,
528
529 pub require_two_factor: bool,
530
531 pub admin_permissions: Arc<Vec<compact_str::CompactString>>,
532 pub server_permissions: Arc<Vec<compact_str::CompactString>>,
533
534 pub created: chrono::DateTime<chrono::Utc>,
535}