// src/rbac.rs use uuid::Uuid; #[derive (Clone)] pub struct RbacService { pool: sqlx::PgPool, } impl RbacService { pub fn new(pool: sqlx::PgPool) -> Self { Self { pool } } pub async fn has_permission(&self, user_id: Uuid, resource: &str) -> bool { let result: Result, _> = sqlx::query_scalar( r#" SELECT rp.item FROM roles r INNER JOIN role_permissions rp ON r.id = rp.role_id INNER JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = $1 "#, ) .bind(user_id) .fetch_all(&self.pool) .await; match result { Ok(patterns) => patterns.iter() .any(|pattern| permission_matches(pattern, resource)), Err(_) => false, } } } /// Wildcard permission matching (e.g., "article:edit:*" matches "article:edit:123") fn permission_matches(pattern: &str, resource: &str) -> bool { let pattern_segments: Vec<&str> = pattern.split(':').collect(); let resource_segments: Vec<&str> = resource.split(':').collect(); if pattern_segments.len() != resource_segments.len() { return false; } pattern_segments.iter() .zip(resource_segments.iter()) .all(|(p, r)| p == r || *p == "*") }