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
98impl Role {
99 pub async fn all_with_pagination(
100 database: &crate::database::Database,
101 page: i64,
102 per_page: i64,
103 search: Option<&str>,
104 ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
105 let offset = (page - 1) * per_page;
106
107 let rows = sqlx::query(sqlx::AssertSqlSafe(format!(
108 r#"
109 SELECT {}, COUNT(*) OVER() AS total_count
110 FROM roles
111 WHERE ($1 IS NULL OR roles.name ILIKE '%' || $1 || '%')
112 ORDER BY roles.created
113 LIMIT $2 OFFSET $3
114 "#,
115 Self::columns_sql(None)
116 )))
117 .bind(search)
118 .bind(per_page)
119 .bind(offset)
120 .fetch_all(database.read())
121 .await?;
122
123 Ok(super::Pagination {
124 total: rows
125 .first()
126 .map_or(Ok(0), |row| row.try_get("total_count"))?,
127 per_page,
128 page,
129 data: rows
130 .into_iter()
131 .map(|row| Self::map(None, &row))
132 .try_collect_vec()?,
133 })
134 }
135}
136
137#[async_trait::async_trait]
138impl IntoAdminApiObject for Role {
139 type AdminApiObject = AdminApiRole;
140 type ExtraArgs<'a> = ();
141
142 async fn into_admin_api_object<'a>(
143 self,
144 state: &crate::State,
145 _args: Self::ExtraArgs<'a>,
146 ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
147 let api_object = AdminApiRole::init_hooks(&self, state).await?;
148
149 let api_object = finish_extendible!(
150 AdminApiRole {
151 uuid: self.uuid,
152 name: self.name,
153 description: self.description,
154 require_two_factor: self.require_two_factor,
155 admin_permissions: self.admin_permissions,
156 server_permissions: self.server_permissions,
157 created: self.created.and_utc(),
158 },
159 api_object,
160 state
161 )?;
162
163 Ok(api_object)
164 }
165}
166
167#[async_trait::async_trait]
168impl ByUuid for Role {
169 async fn by_uuid(
170 database: &crate::database::Database,
171 uuid: uuid::Uuid,
172 ) -> Result<Self, crate::database::DatabaseError> {
173 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
174 r#"
175 SELECT {}
176 FROM roles
177 WHERE roles.uuid = $1
178 "#,
179 Self::columns_sql(None)
180 )))
181 .bind(uuid)
182 .fetch_one(database.read())
183 .await?;
184
185 Self::map(None, &row)
186 }
187
188 async fn by_uuid_with_transaction(
189 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
190 uuid: uuid::Uuid,
191 ) -> Result<Self, crate::database::DatabaseError> {
192 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
193 r#"
194 SELECT {}
195 FROM roles
196 WHERE roles.uuid = $1
197 "#,
198 Self::columns_sql(None)
199 )))
200 .bind(uuid)
201 .fetch_one(&mut **transaction)
202 .await?;
203
204 Self::map(None, &row)
205 }
206}
207
208#[derive(ToSchema, Deserialize, Validate)]
209pub struct CreateRoleOptions {
210 #[garde(length(chars, min = 1, max = 255))]
211 #[schema(min_length = 1, max_length = 255)]
212 pub name: compact_str::CompactString,
213 #[garde(length(chars, min = 1, max = 1024))]
214 #[schema(min_length = 1, max_length = 1024)]
215 pub description: Option<compact_str::CompactString>,
216 #[garde(skip)]
217 pub require_two_factor: bool,
218 #[garde(custom(crate::permissions::validate_admin_permissions))]
219 pub admin_permissions: Vec<compact_str::CompactString>,
220 #[garde(custom(crate::permissions::validate_server_permissions))]
221 pub server_permissions: Vec<compact_str::CompactString>,
222}
223
224#[async_trait::async_trait]
225impl CreatableModel for Role {
226 type CreateOptions<'a> = CreateRoleOptions;
227 type CreateResult = Self;
228
229 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
230 static CREATE_LISTENERS: LazyLock<CreateListenerList<Role>> =
231 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
232
233 &CREATE_LISTENERS
234 }
235
236 async fn create_with_transaction(
237 state: &crate::State,
238 mut options: Self::CreateOptions<'_>,
239 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
240 ) -> Result<Self, crate::database::DatabaseError> {
241 options.validate()?;
242
243 let mut query_builder = InsertQueryBuilder::new("roles");
244
245 Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
246
247 query_builder
248 .set("name", &options.name)
249 .set("description", &options.description)
250 .set("require_two_factor", options.require_two_factor)
251 .set("admin_permissions", &options.admin_permissions)
252 .set("server_permissions", &options.server_permissions);
253
254 let row = query_builder
255 .returning(&Self::columns_sql(None))
256 .fetch_one(&mut **transaction)
257 .await?;
258 let mut role = Self::map(None, &row)?;
259
260 Self::run_after_create_handlers(&mut role, &options, state, transaction).await?;
261
262 Ok(role)
263 }
264}
265
266#[derive(ToSchema, Serialize, Deserialize, Validate, Clone, Default)]
267pub struct UpdateRoleOptions {
268 #[garde(length(chars, min = 1, max = 255))]
269 #[schema(min_length = 1, max_length = 255)]
270 pub name: Option<compact_str::CompactString>,
271 #[garde(length(chars, min = 1, max = 1024))]
272 #[schema(min_length = 1, max_length = 1024)]
273 #[serde(
274 default,
275 skip_serializing_if = "Option::is_none",
276 with = "::serde_with::rust::double_option"
277 )]
278 pub description: Option<Option<compact_str::CompactString>>,
279 #[garde(skip)]
280 pub require_two_factor: Option<bool>,
281 #[garde(inner(custom(crate::permissions::validate_admin_permissions)))]
282 pub admin_permissions: Option<Vec<compact_str::CompactString>>,
283 #[garde(inner(custom(crate::permissions::validate_server_permissions)))]
284 pub server_permissions: Option<Vec<compact_str::CompactString>>,
285}
286
287#[async_trait::async_trait]
288impl UpdatableModel for Role {
289 type UpdateOptions = UpdateRoleOptions;
290
291 fn get_update_handlers() -> &'static LazyLock<UpdateHandlerList<Self>> {
292 static UPDATE_LISTENERS: LazyLock<UpdateHandlerList<Role>> =
293 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
294
295 &UPDATE_LISTENERS
296 }
297
298 async fn update_with_transaction(
299 &mut self,
300 state: &crate::State,
301 mut options: Self::UpdateOptions,
302 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
303 ) -> Result<(), crate::database::DatabaseError> {
304 options.validate()?;
305
306 let mut query_builder = UpdateQueryBuilder::new("roles");
307
308 self.run_update_handlers(&mut options, &mut query_builder, state, transaction)
309 .await?;
310
311 query_builder
312 .set("name", options.name.as_ref())
313 .set(
314 "description",
315 options.description.as_ref().map(|d| d.as_ref()),
316 )
317 .set("require_two_factor", options.require_two_factor)
318 .set("admin_permissions", options.admin_permissions.as_ref())
319 .set("server_permissions", options.server_permissions.as_ref())
320 .where_eq("uuid", self.uuid);
321
322 query_builder.execute(&mut **transaction).await?;
323
324 if let Some(name) = options.name {
325 self.name = name;
326 }
327 if let Some(description) = options.description {
328 self.description = description;
329 }
330 if let Some(require_two_factor) = options.require_two_factor {
331 self.require_two_factor = require_two_factor;
332 }
333 if let Some(admin_permissions) = options.admin_permissions {
334 self.admin_permissions = Arc::new(admin_permissions);
335 }
336 if let Some(server_permissions) = options.server_permissions {
337 self.server_permissions = Arc::new(server_permissions);
338 }
339
340 self.run_after_update_handlers(state, transaction).await?;
341
342 Ok(())
343 }
344}
345
346#[async_trait::async_trait]
347impl DeletableModel for Role {
348 type DeleteOptions = ();
349
350 fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
351 static DELETE_LISTENERS: LazyLock<DeleteHandlerList<Role>> =
352 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
353
354 &DELETE_LISTENERS
355 }
356
357 async fn delete_with_transaction(
358 &self,
359 state: &crate::State,
360 options: Self::DeleteOptions,
361 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
362 ) -> Result<(), anyhow::Error> {
363 self.run_delete_handlers(&options, state, transaction)
364 .await?;
365
366 sqlx::query(
367 r#"
368 DELETE FROM roles
369 WHERE roles.uuid = $1
370 "#,
371 )
372 .bind(self.uuid)
373 .execute(&mut **transaction)
374 .await?;
375
376 self.run_after_delete_handlers(&options, state, transaction)
377 .await?;
378
379 Ok(())
380 }
381}
382
383#[derive(Validate)]
384pub struct DuplicateRoleOptions {
385 #[garde(length(chars, min = 1, max = 255))]
386 pub name: compact_str::CompactString,
387}
388
389#[async_trait::async_trait]
390impl DuplicableModel for Role {
391 type DuplicateOptions<'a> = DuplicateRoleOptions;
392
393 fn get_duplicate_handlers() -> &'static LazyLock<DuplicateHandlerList<Self>> {
394 static DUPLICATE_LISTENERS: LazyLock<DuplicateHandlerList<Role>> =
395 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
396
397 &DUPLICATE_LISTENERS
398 }
399
400 async fn duplicate_with_transaction(
401 &self,
402 state: &crate::State,
403 options: Self::DuplicateOptions<'_>,
404 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
405 ) -> Result<Self, crate::database::DatabaseError> {
406 options.validate()?;
407
408 self.run_duplicate_handlers(&options, state, transaction)
409 .await?;
410
411 let mut query_builder = InsertQueryBuilder::new("roles");
412
413 query_builder
414 .set("name", &options.name)
415 .set("description", &self.description)
416 .set("require_two_factor", self.require_two_factor)
417 .set("admin_permissions", self.admin_permissions.as_ref())
418 .set("server_permissions", self.server_permissions.as_ref());
419
420 let row = query_builder
421 .returning(&Self::columns_sql(None))
422 .fetch_one(&mut **transaction)
423 .await?;
424 let mut role = Self::map(None, &row)?;
425
426 self.run_after_duplicate_handlers(&mut role, &options, state, transaction)
427 .await?;
428
429 Ok(role)
430 }
431}
432
433#[schema_extension_derive::extendible]
434#[init_args(Role, crate::State)]
435#[hook_args(crate::State)]
436#[derive(ToSchema, Serialize)]
437#[schema(title = "Role")]
438pub struct AdminApiRole {
439 pub uuid: uuid::Uuid,
440
441 pub name: compact_str::CompactString,
442 pub description: Option<compact_str::CompactString>,
443
444 pub require_two_factor: bool,
445
446 pub admin_permissions: Arc<Vec<compact_str::CompactString>>,
447 pub server_permissions: Arc<Vec<compact_str::CompactString>>,
448
449 pub created: chrono::DateTime<chrono::Utc>,
450}