omni_orchestrator/schemas/v1/db/queries/permission.rs
1use crate::models::permission::Permission;
2use crate::models::role::Role;
3use sqlx::{MySql, Pool};
4use anyhow::Context;
5
6//=============================================================================
7// Role Operations
8//=============================================================================
9
10/// Retrieves all roles in the system, ordered by creation time.
11///
12/// This function fetches all role records from the database, with
13/// the most recently created roles first. It provides a complete view
14/// of all roles defined in the system.
15///
16/// # Arguments
17///
18/// * `pool` - Database connection pool for executing the query
19///
20/// # Returns
21///
22/// * `Ok(Vec<Role>)` - Successfully retrieved list of roles
23/// * `Err(anyhow::Error)` - Failed to fetch roles
24///
25/// # Use Cases
26///
27/// Common use cases include:
28/// - Administrative interfaces for role management
29/// - Role selection dropdowns in user management interfaces
30/// - System audit and compliance reporting
31pub async fn list_roles(pool: &Pool<MySql>) -> anyhow::Result<Vec<Role>> {
32 let roles = sqlx::query_as::<_, Role>("SELECT * FROM roles ORDER BY created_at DESC")
33 .fetch_all(pool)
34 .await
35 .context("Failed to fetch roles")?;
36
37 Ok(roles)
38}
39
40/// Retrieves a specific role by its unique identifier.
41///
42/// This function fetches detailed information about a single role record.
43/// It's typically used when specific role details are needed, such as
44/// for displaying role information or editing role properties.
45///
46/// # Arguments
47///
48/// * `pool` - Database connection pool for executing the query
49/// * `id` - Unique identifier of the role to retrieve
50///
51/// # Returns
52///
53/// * `Ok(Role)` - Successfully retrieved role information
54/// * `Err(anyhow::Error)` - Failed to fetch role (including if not found)
55///
56/// # Error Handling
57///
58/// Returns an error if no role with the given ID exists or if a database
59/// error occurs during the query execution.
60pub async fn get_role_by_id(pool: &Pool<MySql>, id: i64) -> anyhow::Result<Role> {
61 let role = sqlx::query_as::<_, Role>("SELECT * FROM roles WHERE id = ?")
62 .bind(id)
63 .fetch_one(pool)
64 .await
65 .context("Failed to fetch role")?;
66
67 Ok(role)
68}
69
70/// Creates a new role in the system.
71///
72/// This function inserts a new role record with the provided name and description.
73/// Roles are a fundamental component of the role-based access control (RBAC) system,
74/// used to group related permissions and assign them to users.
75///
76/// # Arguments
77///
78/// * `pool` - Database connection pool for executing the query
79/// * `name` - Name of the new role (should be unique and descriptive)
80/// * `description` - Optional description explaining the role's purpose
81///
82/// # Returns
83///
84/// * `Ok(Role)` - Successfully created role record
85/// * `Err(anyhow::Error)` - Failed to create role record
86///
87/// # Transaction Handling
88///
89/// This function uses a database transaction to ensure atomicity of the operation.
90/// If any part of the operation fails, the entire operation is rolled back.
91///
92/// # Note
93///
94/// Creating a role doesn't automatically assign any permissions to it.
95/// Use `assign_permission_to_role` to associate permissions with the newly created role.
96pub async fn create_role(
97 pool: &Pool<MySql>,
98 name: &str,
99 description: Option<&str>,
100) -> anyhow::Result<Role> {
101 let mut tx = pool.begin().await?;
102
103 let role = sqlx::query_as::<_, Role>("INSERT INTO roles (name, description) VALUES (?, ?)")
104 .bind(name)
105 .bind(description)
106 .fetch_one(&mut *tx)
107 .await
108 .context("Failed to create role")?;
109
110 tx.commit().await?;
111 Ok(role)
112}
113
114/// Updates an existing role's information.
115///
116/// This function modifies a role record with the provided name and/or description.
117/// It supports partial updates, allowing you to update only the fields that need changing.
118///
119/// # Arguments
120///
121/// * `pool` - Database connection pool for executing the query
122/// * `id` - Unique identifier of the role to update
123/// * `name` - Optional new name for the role
124/// * `description` - Optional new description for the role
125///
126/// # Returns
127///
128/// * `Ok(Role)` - Successfully updated role record
129/// * `Err(anyhow::Error)` - Failed to update role
130///
131/// # Dynamic Query Building
132///
133/// This function dynamically builds an SQL query based on which parameters are provided.
134/// Only the fields specified with Some values will be updated, while None values are ignored.
135///
136/// # Transaction Handling
137///
138/// This function uses a database transaction to ensure atomicity of the operation.
139/// If any part of the operation fails, the entire operation is rolled back.
140pub async fn update_role(
141 pool: &Pool<MySql>,
142 id: i64,
143 name: Option<&str>,
144 description: Option<&str>,
145) -> anyhow::Result<Role> {
146 let mut tx = pool.begin().await?;
147
148 // Start with base query
149 let mut query = String::from("UPDATE roles SET id = id");
150
151 // Add clauses for provided fields
152 if let Some(name) = name {
153 query.push_str(", name = ?");
154 }
155 if let Some(description) = description {
156 query.push_str(", description = ?");
157 }
158
159 // Add WHERE clause
160 query.push_str(" WHERE id = ?");
161
162 // Prepare the query
163 let mut db_query = sqlx::query_as::<_, Role>(&query);
164
165 // Bind parameters for provided fields
166 if let Some(name) = name {
167 db_query = db_query.bind(name);
168 }
169 if let Some(description) = description {
170 db_query = db_query.bind(description);
171 }
172
173 // Bind the ID parameter
174 db_query = db_query.bind(id);
175
176 // Execute the query
177 let role = db_query
178 .fetch_one(&mut *tx)
179 .await
180 .context("Failed to update role")?;
181
182 tx.commit().await?;
183 Ok(role)
184}
185
186/// Deletes a role from the system.
187///
188/// This function permanently removes a role record from the database.
189/// It should be used with caution, as it affects user permissions and
190/// may impact system access for users with this role.
191///
192/// # Arguments
193///
194/// * `pool` - Database connection pool for executing the query
195/// * `id` - Unique identifier of the role to delete
196///
197/// # Returns
198///
199/// * `Ok(())` - Successfully deleted the role
200/// * `Err(anyhow::Error)` - Failed to delete the role
201///
202/// # Warning
203///
204/// This operation is irreversible. Before deleting a role, consider:
205/// - Users with this role will lose the associated permissions
206/// - Relationships in role_user and permissions_role tables may need to be cleaned up
207/// - System functionality may be affected if critical roles are removed
208///
209/// # Important
210///
211/// Depending on the database schema, this operation may:
212/// - Fail if foreign key constraints are enforced and the role is in use
213/// - Leave orphaned records if cascading deletes are not configured
214/// - Remove user access to system features
215///
216/// # Transaction Handling
217///
218/// This function uses a database transaction to ensure atomicity of the operation.
219/// If any part of the operation fails, the entire operation is rolled back.
220pub async fn delete_role(pool: &Pool<MySql>, id: i64) -> anyhow::Result<()> {
221 let mut tx = pool.begin().await?;
222
223 sqlx::query("DELETE FROM roles WHERE id = ?")
224 .bind(id)
225 .execute(&mut *tx)
226 .await
227 .context("Failed to delete role")?;
228
229 tx.commit().await?;
230 Ok(())
231}
232
233//=============================================================================
234// Permission Operations
235//=============================================================================
236
237/// Retrieves all permissions in the system, ordered by ID.
238///
239/// This function fetches all permission records from the database, providing
240/// a complete view of all defined permissions in the system. The results are
241/// ordered by ID in ascending order, which typically reflects the order in
242/// which permissions were created.
243///
244/// # Arguments
245///
246/// * `pool` - Database connection pool for executing the query
247///
248/// # Returns
249///
250/// * `Ok(Vec<Permission>)` - Successfully retrieved list of permissions
251/// * `Err(anyhow::Error)` - Failed to fetch permissions
252///
253/// # Use Cases
254///
255/// Common use cases include:
256/// - Administrative interfaces for permission management
257/// - Permission assignment interfaces when configuring roles
258/// - System audit and compliance reporting
259pub async fn list_permissions(pool: &Pool<MySql>) -> anyhow::Result<Vec<Permission>> {
260 let permissions = sqlx::query_as::<_, Permission>("SELECT * FROM permissions ORDER BY id ASC")
261 .fetch_all(pool)
262 .await
263 .context("Failed to fetch permissions")?;
264
265 Ok(permissions)
266}
267
268/// Retrieves a specific permission by its unique identifier.
269///
270/// This function fetches detailed information about a single permission record.
271/// It's typically used when specific permission details are needed, such as
272/// for displaying permission information or checking permission properties.
273///
274/// # Arguments
275///
276/// * `pool` - Database connection pool for executing the query
277/// * `id` - Unique identifier of the permission to retrieve
278///
279/// # Returns
280///
281/// * `Ok(Permission)` - Successfully retrieved permission information
282/// * `Err(anyhow::Error)` - Failed to fetch permission (including if not found)
283///
284/// # Error Handling
285///
286/// Returns an error if no permission with the given ID exists or if a database
287/// error occurs during the query execution.
288pub async fn get_permission_by_id(pool: &Pool<MySql>, id: i64) -> anyhow::Result<Permission> {
289 let permission = sqlx::query_as::<_, Permission>("SELECT * FROM permissions WHERE id = ?")
290 .bind(id)
291 .fetch_one(pool)
292 .await
293 .context("Failed to fetch permission")?;
294
295 Ok(permission)
296}
297
298/// Creates a new permission in the system.
299///
300/// This function inserts a new permission record with the provided name,
301/// description, and resource type. Permissions are a fundamental component
302/// of the role-based access control (RBAC) system, representing specific
303/// actions that can be performed on system resources.
304///
305/// # Arguments
306///
307/// * `pool` - Database connection pool for executing the query
308/// * `name` - Name of the new permission (should be unique and descriptive)
309/// * `description` - Optional description explaining the permission's purpose
310/// * `resource_type` - Type of resource this permission applies to (e.g., "app", "user", "deployment")
311///
312/// # Returns
313///
314/// * `Ok(Permission)` - Successfully created permission record
315/// * `Err(anyhow::Error)` - Failed to create permission record
316///
317/// # Permission Naming
318///
319/// Permission names are typically formatted as verb-noun pairs describing an action
320/// on a resource type, such as "create-app", "read-user", or "deploy-application".
321///
322/// # Transaction Handling
323///
324/// This function uses a database transaction to ensure atomicity of the operation.
325/// If any part of the operation fails, the entire operation is rolled back.
326///
327/// # Note
328///
329/// Creating a permission doesn't automatically assign it to any roles.
330/// Use `assign_permission_to_role` to associate the permission with roles.
331pub async fn create_permission(
332 pool: &Pool<MySql>,
333 name: &str,
334 description: Option<String>,
335 resource_type: String,
336) -> anyhow::Result<Permission> {
337 let mut tx = pool.begin().await?;
338
339 let permission = sqlx::query_as::<_, Permission>(
340 "INSERT INTO permissions (name, description, resource_type) VALUES (?, ?, ?)",
341 )
342 .bind(name)
343 .bind(description)
344 .bind(resource_type)
345 .fetch_one(&mut *tx)
346 .await
347 .context("Failed to create permission")?;
348
349 tx.commit().await?;
350 Ok(permission)
351}
352
353/// Updates an existing permission's information.
354///
355/// This function modifies a permission record with the provided name,
356/// description, and/or resource type. It supports partial updates,
357/// allowing you to update only the fields that need changing.
358///
359/// # Arguments
360///
361/// * `pool` - Database connection pool for executing the query
362/// * `id` - Unique identifier of the permission to update
363/// * `name` - Optional new name for the permission
364/// * `description` - Optional new description for the permission
365/// * `resource_type` - Optional new resource type for the permission
366///
367/// # Returns
368///
369/// * `Ok(Permission)` - Successfully updated permission record
370/// * `Err(anyhow::Error)` - Failed to update permission
371///
372/// # Dynamic Query Building
373///
374/// This function dynamically builds an SQL query based on which parameters
375/// are provided. Only the fields specified with Some values will be updated,
376/// while None values are ignored.
377///
378/// # Transaction Handling
379///
380/// This function uses a database transaction to ensure atomicity of the operation.
381/// If any part of the operation fails, the entire operation is rolled back.
382///
383/// # Important
384///
385/// Changing a permission's properties may affect the behavior of the RBAC system.
386/// Consider the impact on existing roles and users before making changes,
387/// especially to the name or resource_type fields.
388pub async fn update_permission(
389 pool: &Pool<MySql>,
390 id: i64,
391 name: Option<&str>,
392 description: Option<&str>,
393 resource_type: Option<&str>,
394) -> anyhow::Result<Permission> {
395 // Define which fields are being updated
396 let update_fields = [
397 (name.is_some(), "name = ?"),
398 (description.is_some(), "description = ?"),
399 (resource_type.is_some(), "resource_type = ?"),
400 ];
401
402 // Build update query with only the fields that have values
403 let field_clauses = update_fields
404 .iter()
405 .filter(|(has_value, _)| *has_value)
406 .enumerate()
407 .map(|(i, (_, field))| {
408 if i == 0 {
409 format!(" SET {}", field)
410 } else {
411 format!(", {}", field)
412 }
413 })
414 .collect::<String>();
415
416 let query = format!("UPDATE permissions{} WHERE id = ?", field_clauses);
417
418 // Start binding parameters
419 let mut db_query = sqlx::query_as::<_, Permission>(&query);
420
421 // Bind parameters
422 if let Some(val) = name {
423 db_query = db_query.bind(val);
424 }
425 if let Some(val) = description {
426 db_query = db_query.bind(val);
427 }
428 if let Some(val) = resource_type {
429 db_query = db_query.bind(val);
430 }
431
432 // Bind the ID parameter
433 db_query = db_query.bind(id);
434
435 // Execute the query in a transaction
436 let mut tx = pool.begin().await?;
437 let permission = db_query
438 .fetch_one(&mut *tx)
439 .await
440 .context("Failed to update permission")?;
441
442 tx.commit().await?;
443 Ok(permission)
444}
445
446/// Deletes a permission from the system.
447///
448/// This function permanently removes a permission record from the database.
449/// It should be used with caution, as it affects role capabilities and
450/// may impact system access control.
451///
452/// # Arguments
453///
454/// * `pool` - Database connection pool for executing the query
455/// * `id` - Unique identifier of the permission to delete
456///
457/// # Returns
458///
459/// * `Ok(())` - Successfully deleted the permission
460/// * `Err(anyhow::Error)` - Failed to delete the permission
461///
462/// # Warning
463///
464/// This operation is irreversible. Before deleting a permission, consider:
465/// - Roles with this permission will lose the associated capability
466/// - Relationships in permissions_role table may need to be cleaned up
467/// - System functionality may be affected if critical permissions are removed
468///
469/// # Important
470///
471/// Depending on the database schema, this operation may:
472/// - Fail if foreign key constraints are enforced and the permission is in use
473/// - Leave orphaned records if cascading deletes are not configured
474/// - Affect user access to system features
475///
476/// # Transaction Handling
477///
478/// This function uses a database transaction to ensure atomicity of the operation.
479/// If any part of the operation fails, the entire operation is rolled back.
480pub async fn delete_permission(pool: &Pool<MySql>, id: i64) -> anyhow::Result<()> {
481 let mut tx = pool.begin().await?;
482
483 sqlx::query("DELETE FROM permissions WHERE id = ?")
484 .bind(id)
485 .execute(&mut *tx)
486 .await
487 .context("Failed to delete permission")?;
488
489 tx.commit().await?;
490 Ok(())
491}
492
493//=============================================================================
494// Role-Permission Operations
495//=============================================================================
496
497/// Assigns a permission to a role.
498///
499/// This function creates an association between a permission and a role,
500/// granting the capability represented by the permission to users who have
501/// the specified role. This is a core operation in the RBAC system for
502/// building role capabilities.
503///
504/// # Arguments
505///
506/// * `pool` - Database connection pool for executing the query
507/// * `permission_id` - Unique identifier of the permission to assign
508/// * `role_id` - Unique identifier of the role to receive the permission
509///
510/// # Returns
511///
512/// * `Ok(())` - Successfully assigned the permission to the role
513/// * `Err(anyhow::Error)` - Failed to assign the permission
514///
515/// # Uniqueness
516///
517/// This function assumes that the combination of `permission_id` and `role_id`
518/// must be unique in the permissions_role table. If this association already
519/// exists, the operation will fail with a unique constraint violation.
520///
521/// # Transaction Handling
522///
523/// This function uses a database transaction to ensure atomicity of the operation.
524/// If any part of the operation fails, the entire operation is rolled back.
525///
526/// # Note
527///
528/// After this operation, all users who have the specified role will effectively
529/// gain the assigned permission.
530pub async fn assign_permission_to_role(
531 pool: &Pool<MySql>,
532 permission_id: i64,
533 role_id: i64,
534) -> anyhow::Result<()> {
535 let mut tx = pool.begin().await?;
536
537 sqlx::query("INSERT INTO permissions_role (permissions_id, role_id) VALUES (?, ?)")
538 .bind(permission_id)
539 .bind(role_id)
540 .execute(&mut *tx)
541 .await
542 .context("Failed to assign permission to role")?;
543
544 tx.commit().await?;
545 Ok(())
546}
547
548/// Removes a permission from a role.
549///
550/// This function deletes the association between a permission and a role,
551/// revoking the capability represented by the permission from users who have
552/// the specified role. This is used to adjust role capabilities in the RBAC system.
553///
554/// # Arguments
555///
556/// * `pool` - Database connection pool for executing the query
557/// * `permission_id` - Unique identifier of the permission to remove
558/// * `role_id` - Unique identifier of the role from which to remove the permission
559///
560/// # Returns
561///
562/// * `Ok(())` - Successfully removed the permission from the role
563/// * `Err(anyhow::Error)` - Failed to remove the permission
564///
565/// # Transaction Handling
566///
567/// This function uses a database transaction to ensure atomicity of the operation.
568/// If any part of the operation fails, the entire operation is rolled back.
569///
570/// # Note
571///
572/// After this operation, users who have the specified role will no longer have the
573/// capability granted by this permission, unless they have another role that includes it.
574pub async fn remove_permission_from_role(
575 pool: &Pool<MySql>,
576 permission_id: i64,
577 role_id: i64,
578) -> anyhow::Result<()> {
579 let mut tx = pool.begin().await?;
580
581 sqlx::query("DELETE FROM permissions_role WHERE permissions_id = ? AND role_id = ?")
582 .bind(permission_id)
583 .bind(role_id)
584 .execute(&mut *tx)
585 .await
586 .context("Failed to remove permission from role")?;
587
588 tx.commit().await?;
589 Ok(())
590}
591
592/// Retrieves all permissions associated with a specific role.
593///
594/// This function fetches all permissions that have been assigned to a given role.
595/// It's useful for displaying role capabilities or checking the full set of
596/// permissions granted by a particular role.
597///
598/// # Arguments
599///
600/// * `pool` - Database connection pool for executing the query
601/// * `role_id` - Unique identifier of the role whose permissions to retrieve
602///
603/// # Returns
604///
605/// * `Ok(Vec<Permission>)` - Successfully retrieved list of permissions for the role
606/// * `Err(anyhow::Error)` - Failed to fetch role permissions
607///
608/// # Query Details
609///
610/// This function performs a JOIN operation between the permissions and permissions_role
611/// tables to find all permissions associated with the specified role.
612pub async fn get_role_permissions(
613 pool: &Pool<MySql>,
614 role_id: i64,
615) -> anyhow::Result<Vec<Permission>> {
616 let permissions = sqlx::query_as::<_, Permission>(
617 r#"SELECT p.* FROM permissions p
618 JOIN permissions_role pr ON p.id = pr.permissions_id
619 WHERE pr.role_id = ?
620 ORDER BY p.created_at DESC"#,
621 )
622 .bind(role_id)
623 .fetch_all(pool)
624 .await
625 .context("Failed to fetch role permissions")?;
626
627 Ok(permissions)
628}
629
630//=============================================================================
631// User-Role Operations
632//=============================================================================
633
634/// Assigns a role to a user.
635///
636/// This function creates an association between a user and a role,
637/// granting the user all permissions associated with that role.
638/// This is a core operation in the RBAC system for controlling user access.
639///
640/// # Arguments
641///
642/// * `pool` - Database connection pool for executing the query
643/// * `user_id` - Unique identifier of the user to receive the role
644/// * `role_id` - Unique identifier of the role to assign
645///
646/// # Returns
647///
648/// * `Ok(())` - Successfully assigned the role to the user
649/// * `Err(anyhow::Error)` - Failed to assign the role
650///
651/// # Uniqueness
652///
653/// This function assumes that the combination of `user_id` and `role_id`
654/// must be unique in the role_user table. If this association already exists,
655/// the operation will fail with a unique constraint violation.
656///
657/// # Transaction Handling
658///
659/// This function uses a database transaction to ensure atomicity of the operation.
660/// If any part of the operation fails, the entire operation is rolled back.
661///
662/// # Note
663///
664/// After this operation, the user will have all permissions associated with
665/// the assigned role, as determined by the permissions_role table.
666pub async fn assign_role_to_user(
667 pool: &Pool<MySql>,
668 user_id: i64,
669 role_id: i64,
670) -> anyhow::Result<()> {
671 let mut tx = pool.begin().await?;
672
673 sqlx::query("INSERT INTO role_user (user_id, role_id) VALUES (?, ?)")
674 .bind(user_id)
675 .bind(role_id)
676 .execute(&mut *tx)
677 .await
678 .context("Failed to assign role to user")?;
679
680 tx.commit().await?;
681 Ok(())
682}
683
684/// Removes a role from a user.
685///
686/// This function deletes the association between a user and a role,
687/// revoking all permissions granted by that role from the user.
688/// This is used to adjust user access in the RBAC system.
689///
690/// # Arguments
691///
692/// * `pool` - Database connection pool for executing the query
693/// * `user_id` - Unique identifier of the user from whom to remove the role
694/// * `role_id` - Unique identifier of the role to remove
695///
696/// # Returns
697///
698/// * `Ok(())` - Successfully removed the role from the user
699/// * `Err(anyhow::Error)` - Failed to remove the role
700///
701/// # Transaction Handling
702///
703/// This function uses a database transaction to ensure atomicity of the operation.
704/// If any part of the operation fails, the entire operation is rolled back.
705///
706/// # Note
707///
708/// After this operation, the user will no longer have the permissions granted
709/// by this role, unless they have other roles that include the same permissions.
710pub async fn remove_role_from_user(
711 pool: &Pool<MySql>,
712 user_id: i64,
713 role_id: i64,
714) -> anyhow::Result<()> {
715 let mut tx = pool.begin().await?;
716
717 sqlx::query("DELETE FROM role_user WHERE user_id = ? AND role_id = ?")
718 .bind(user_id)
719 .bind(role_id)
720 .execute(&mut *tx)
721 .await
722 .context("Failed to remove role from user")?;
723
724 tx.commit().await?;
725 Ok(())
726}
727
728/// Retrieves all roles assigned to a specific user.
729///
730/// This function fetches all roles that have been assigned to a given user.
731/// It's useful for displaying user roles or checking role-based access control.
732///
733/// # Arguments
734///
735/// * `pool` - Database connection pool for executing the query
736/// * `user_id` - Unique identifier of the user whose roles to retrieve
737///
738/// # Returns
739///
740/// * `Ok(Vec<Role>)` - Successfully retrieved list of roles for the user
741/// * `Err(anyhow::Error)` - Failed to fetch user roles
742///
743/// # Query Details
744///
745/// This function performs a JOIN operation between the roles and role_user
746/// tables to find all roles associated with the specified user.
747pub async fn get_user_roles(pool: &Pool<MySql>, user_id: i64) -> anyhow::Result<Vec<Role>> {
748 let roles = sqlx::query_as::<_, Role>(
749 r#"SELECT r.* FROM roles r
750 JOIN role_user ru ON r.id = ru.role_id
751 WHERE ru.user_id = ?
752 ORDER BY r.created_at DESC"#,
753 )
754 .bind(user_id)
755 .fetch_all(pool)
756 .await
757 .context("Failed to fetch user roles")?;
758
759 Ok(roles)
760}
761
762/// Retrieves all permissions effectively granted to a specific user.
763///
764/// This function computes the complete set of permissions a user has based on
765/// all their assigned roles. It eliminates duplicate permissions when a user
766/// has multiple roles that grant the same permission.
767///
768/// # Arguments
769///
770/// * `pool` - Database connection pool for executing the query
771/// * `user_id` - Unique identifier of the user whose permissions to retrieve
772///
773/// # Returns
774///
775/// * `Ok(Vec<Permission>)` - Successfully retrieved list of user permissions
776/// * `Err(anyhow::Error)` - Failed to fetch user permissions
777///
778/// # Query Details
779///
780/// This function performs multiple JOIN operations across the permissions,
781/// permissions_role, and role_user tables to calculate the effective permissions
782/// for a user based on all their assigned roles.
783///
784/// # Performance Considerations
785///
786/// This query can be relatively expensive in systems with many roles and permissions.
787/// Consider caching the results when appropriate, especially for frequently accessed users.
788pub async fn get_user_permissions(
789 pool: &Pool<MySql>,
790 user_id: i64,
791) -> anyhow::Result<Vec<Permission>> {
792 let permissions = sqlx::query_as::<_, Permission>(
793 r#"SELECT DISTINCT p.* FROM permissions p
794 JOIN permissions_role pr ON p.id = pr.permissions_id
795 JOIN role_user ru ON pr.role_id = ru.role_id
796 WHERE ru.user_id = ?
797 ORDER BY p.created_at DESC"#,
798 )
799 .bind(user_id)
800 .fetch_all(pool)
801 .await
802 .context("Failed to fetch user permissions")?;
803
804 Ok(permissions)
805}