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}