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