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
29impl BaseModel for Role {
30 const NAME: &'static str = "role";
31
32 #[inline]
33 fn columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
34 let prefix = prefix.unwrap_or_default();
35
36 BTreeMap::from([
37 ("roles.uuid", compact_str::format_compact!("{prefix}uuid")),
38 ("roles.name", compact_str::format_compact!("{prefix}name")),
39 (
40 "roles.description",
41 compact_str::format_compact!("{prefix}description"),
42 ),
43 (
44 "roles.require_two_factor",
45 compact_str::format_compact!("{prefix}require_two_factor"),
46 ),
47 (
48 "roles.admin_permissions",
49 compact_str::format_compact!("{prefix}admin_permissions"),
50 ),
51 (
52 "roles.server_permissions",
53 compact_str::format_compact!("{prefix}server_permissions"),
54 ),
55 (
56 "roles.created",
57 compact_str::format_compact!("{prefix}created"),
58 ),
59 ])
60 }
61
62 #[inline]
63 fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
64 let prefix = prefix.unwrap_or_default();
65
66 Ok(Self {
67 uuid: row.try_get(compact_str::format_compact!("{prefix}uuid").as_str())?,
68 name: row.try_get(compact_str::format_compact!("{prefix}name").as_str())?,
69 description: row
70 .try_get(compact_str::format_compact!("{prefix}description").as_str())?,
71 require_two_factor: row
72 .try_get(compact_str::format_compact!("{prefix}require_two_factor").as_str())?,
73 admin_permissions: Arc::new(
74 row.try_get(compact_str::format_compact!("{prefix}admin_permissions").as_str())?,
75 ),
76 server_permissions: Arc::new(
77 row.try_get(compact_str::format_compact!("{prefix}server_permissions").as_str())?,
78 ),
79 created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
80 })
81 }
82}
83
84impl Role {
85 pub async fn all_with_pagination(
86 database: &crate::database::Database,
87 page: i64,
88 per_page: i64,
89 search: Option<&str>,
90 ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
91 let offset = (page - 1) * per_page;
92
93 let rows = sqlx::query(&format!(
94 r#"
95 SELECT {}, COUNT(*) OVER() AS total_count
96 FROM roles
97 WHERE ($1 IS NULL OR roles.name ILIKE '%' || $1 || '%')
98 ORDER BY roles.created
99 LIMIT $2 OFFSET $3
100 "#,
101 Self::columns_sql(None)
102 ))
103 .bind(search)
104 .bind(per_page)
105 .bind(offset)
106 .fetch_all(database.read())
107 .await?;
108
109 Ok(super::Pagination {
110 total: rows
111 .first()
112 .map_or(Ok(0), |row| row.try_get("total_count"))?,
113 per_page,
114 page,
115 data: rows
116 .into_iter()
117 .map(|row| Self::map(None, &row))
118 .try_collect_vec()?,
119 })
120 }
121
122 #[inline]
123 pub fn into_admin_api_object(self) -> AdminApiRole {
124 AdminApiRole {
125 uuid: self.uuid,
126 name: self.name,
127 description: self.description,
128 admin_permissions: self.admin_permissions,
129 server_permissions: self.server_permissions,
130 created: self.created.and_utc(),
131 }
132 }
133}
134
135#[async_trait::async_trait]
136impl ByUuid for Role {
137 async fn by_uuid(
138 database: &crate::database::Database,
139 uuid: uuid::Uuid,
140 ) -> Result<Self, crate::database::DatabaseError> {
141 let row = sqlx::query(&format!(
142 r#"
143 SELECT {}
144 FROM roles
145 WHERE roles.uuid = $1
146 "#,
147 Self::columns_sql(None)
148 ))
149 .bind(uuid)
150 .fetch_one(database.read())
151 .await?;
152
153 Self::map(None, &row)
154 }
155}
156
157#[derive(ToSchema, Deserialize, Validate)]
158pub struct CreateRoleOptions {
159 #[garde(length(chars, min = 3, max = 255))]
160 #[schema(min_length = 3, max_length = 255)]
161 pub name: compact_str::CompactString,
162 #[garde(length(chars, min = 1, max = 1024))]
163 #[schema(min_length = 1, max_length = 1024)]
164 pub description: Option<compact_str::CompactString>,
165 #[garde(skip)]
166 pub require_two_factor: bool,
167 #[garde(custom(crate::permissions::validate_admin_permissions))]
168 pub admin_permissions: Vec<compact_str::CompactString>,
169 #[garde(custom(crate::permissions::validate_server_permissions))]
170 pub server_permissions: Vec<compact_str::CompactString>,
171}
172
173#[async_trait::async_trait]
174impl CreatableModel for Role {
175 type CreateOptions<'a> = CreateRoleOptions;
176 type CreateResult = Self;
177
178 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
179 static CREATE_LISTENERS: LazyLock<CreateListenerList<Role>> =
180 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
181
182 &CREATE_LISTENERS
183 }
184
185 async fn create(
186 state: &crate::State,
187 mut options: Self::CreateOptions<'_>,
188 ) -> Result<Self, crate::database::DatabaseError> {
189 options.validate()?;
190
191 let mut transaction = state.database.write().begin().await?;
192
193 let mut query_builder = InsertQueryBuilder::new("roles");
194
195 Self::run_create_handlers(&mut options, &mut query_builder, state, &mut transaction)
196 .await?;
197
198 query_builder
199 .set("name", &options.name)
200 .set("description", &options.description)
201 .set("require_two_factor", options.require_two_factor)
202 .set("admin_permissions", &options.admin_permissions)
203 .set("server_permissions", &options.server_permissions);
204
205 let row = query_builder
206 .returning(&Self::columns_sql(None))
207 .fetch_one(&mut *transaction)
208 .await?;
209 let role = Self::map(None, &row)?;
210
211 transaction.commit().await?;
212
213 Ok(role)
214 }
215}
216
217#[derive(ToSchema, Serialize, Deserialize, Validate, Clone, Default)]
218pub struct UpdateRoleOptions {
219 #[garde(length(chars, min = 3, max = 255))]
220 #[schema(min_length = 3, max_length = 255)]
221 pub name: Option<compact_str::CompactString>,
222 #[garde(length(chars, min = 1, max = 1024))]
223 #[schema(min_length = 1, max_length = 1024)]
224 #[serde(
225 default,
226 skip_serializing_if = "Option::is_none",
227 with = "::serde_with::rust::double_option"
228 )]
229 pub description: Option<Option<compact_str::CompactString>>,
230 #[garde(skip)]
231 pub require_two_factor: Option<bool>,
232 #[garde(inner(custom(crate::permissions::validate_admin_permissions)))]
233 pub admin_permissions: Option<Vec<compact_str::CompactString>>,
234 #[garde(inner(custom(crate::permissions::validate_server_permissions)))]
235 pub server_permissions: Option<Vec<compact_str::CompactString>>,
236}
237
238#[async_trait::async_trait]
239impl UpdatableModel for Role {
240 type UpdateOptions = UpdateRoleOptions;
241
242 fn get_update_handlers() -> &'static LazyLock<UpdateListenerList<Self>> {
243 static UPDATE_LISTENERS: LazyLock<UpdateListenerList<Role>> =
244 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
245
246 &UPDATE_LISTENERS
247 }
248
249 async fn update(
250 &mut self,
251 state: &crate::State,
252 mut options: Self::UpdateOptions,
253 ) -> Result<(), crate::database::DatabaseError> {
254 options.validate()?;
255
256 let mut transaction = state.database.write().begin().await?;
257
258 let mut query_builder = UpdateQueryBuilder::new("roles");
259
260 Self::run_update_handlers(
261 self,
262 &mut options,
263 &mut query_builder,
264 state,
265 &mut transaction,
266 )
267 .await?;
268
269 query_builder
270 .set("name", options.name.as_ref())
271 .set(
272 "description",
273 options.description.as_ref().map(|d| d.as_ref()),
274 )
275 .set("require_two_factor", options.require_two_factor)
276 .set("admin_permissions", options.admin_permissions.as_ref())
277 .set("server_permissions", options.server_permissions.as_ref())
278 .where_eq("uuid", self.uuid);
279
280 query_builder.execute(&mut *transaction).await?;
281
282 if let Some(name) = options.name {
283 self.name = name;
284 }
285 if let Some(description) = options.description {
286 self.description = description;
287 }
288 if let Some(require_two_factor) = options.require_two_factor {
289 self.require_two_factor = require_two_factor;
290 }
291 if let Some(admin_permissions) = options.admin_permissions {
292 self.admin_permissions = Arc::new(admin_permissions);
293 }
294 if let Some(server_permissions) = options.server_permissions {
295 self.server_permissions = Arc::new(server_permissions);
296 }
297
298 transaction.commit().await?;
299
300 Ok(())
301 }
302}
303
304#[async_trait::async_trait]
305impl DeletableModel for Role {
306 type DeleteOptions = ();
307
308 fn get_delete_handlers() -> &'static LazyLock<DeleteListenerList<Self>> {
309 static DELETE_LISTENERS: LazyLock<DeleteListenerList<Role>> =
310 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
311
312 &DELETE_LISTENERS
313 }
314
315 async fn delete(
316 &self,
317 state: &crate::State,
318 options: Self::DeleteOptions,
319 ) -> Result<(), anyhow::Error> {
320 let mut transaction = state.database.write().begin().await?;
321
322 self.run_delete_handlers(&options, state, &mut transaction)
323 .await?;
324
325 sqlx::query(
326 r#"
327 DELETE FROM roles
328 WHERE roles.uuid = $1
329 "#,
330 )
331 .bind(self.uuid)
332 .execute(&mut *transaction)
333 .await?;
334
335 transaction.commit().await?;
336
337 Ok(())
338 }
339}
340
341#[derive(ToSchema, Serialize)]
342#[schema(title = "Role")]
343pub struct AdminApiRole {
344 pub uuid: uuid::Uuid,
345
346 pub name: compact_str::CompactString,
347 pub description: Option<compact_str::CompactString>,
348
349 pub admin_permissions: Arc<Vec<compact_str::CompactString>>,
350 pub server_permissions: Arc<Vec<compact_str::CompactString>>,
351
352 pub created: chrono::DateTime<chrono::Utc>,
353}