use std::sync::Arc; use cache::redis::RedisCache; use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Clone)] pub struct ProjectCache { kv: Arc, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CachedProjectDetail { pub id: Uuid, pub space_id: Uuid, pub name: String, pub state: i32, } impl ProjectCache { pub fn new(kv: Arc) -> Self { Self { kv } } fn project_key(id: Uuid) -> String { format!("project:{}", id) } fn member_role_key(project_id: Uuid, user_id: Uuid) -> String { format!("pm_role:{}:{}", project_id, user_id) } fn favorites_key(user_id: Uuid) -> String { format!("fav:{}", user_id) } pub async fn get_project( &self, project_id: Uuid, ) -> Result, crate::error::ProjectError> { let raw = self .kv .get_bytes(&Self::project_key(project_id)) .await .map_err(|e| crate::error::ProjectError::Cache(e.to_string()))?; match raw { Some(b) => match serde_json::from_slice::(&b) { Ok(v) => Ok(Some(v)), Err(_) => Ok(None), }, None => Ok(None), } } pub async fn put_project( &self, detail: &CachedProjectDetail, ttl_secs: u64, ) -> Result<(), crate::error::ProjectError> { let bytes = serde_json::to_vec(detail)?; self.kv .set_bytes(&Self::project_key(detail.id), bytes, ttl_secs) .await .map_err(|e| crate::error::ProjectError::Cache(e.to_string()))?; Ok(()) } pub async fn invalidate_project( &self, project_id: Uuid, ) -> Result<(), crate::error::ProjectError> { self.kv .delete(&Self::project_key(project_id)) .await .map_err(|e| crate::error::ProjectError::Cache(e.to_string()))?; Ok(()) } pub async fn get_member_role( &self, project_id: Uuid, user_id: Uuid, ) -> Result, crate::error::ProjectError> { let raw = self .kv .get_bytes(&Self::member_role_key(project_id, user_id)) .await .map_err(|e| crate::error::ProjectError::Cache(e.to_string()))?; let Some(b) = raw else { return Ok(None); }; if b.len() != 4 { return Ok(None); } let arr: [u8; 4] = [b[0], b[1], b[2], b[3]]; Ok(Some(i32::from_le_bytes(arr))) } pub async fn put_member_role( &self, project_id: Uuid, user_id: Uuid, role: i32, ttl_secs: u64, ) -> Result<(), crate::error::ProjectError> { self.kv .set_bytes( &Self::member_role_key(project_id, user_id), role.to_le_bytes().to_vec(), ttl_secs, ) .await .map_err(|e| crate::error::ProjectError::Cache(e.to_string()))?; Ok(()) } pub async fn invalidate_member_role( &self, project_id: Uuid, user_id: Uuid, ) -> Result<(), crate::error::ProjectError> { self.kv .delete(&Self::member_role_key(project_id, user_id)) .await .map_err(|e| crate::error::ProjectError::Cache(e.to_string()))?; Ok(()) } pub async fn invalidate_favorites( &self, user_id: Uuid, ) -> Result<(), crate::error::ProjectError> { self.kv .delete(&Self::favorites_key(user_id)) .await .map_err(|e| crate::error::ProjectError::Cache(e.to_string()))?; Ok(()) } }