Skip to content
Clone

feat(project): add project cache layer

zhenyi committed
parent 7a4c01f commit 7b91a55 Browse files

1 file changed

+139 −0

Added lib/project/cache.rs View lib/project/cache.rs
@@ -0,0 +1,139 @@
1use std::sync::Arc;
2
3use cache::redis::RedisCache;
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7#[derive(Clone)]
8pub struct ProjectCache {
9 kv: Arc<RedisCache>,
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct CachedProjectDetail {
14 pub id: Uuid,
15 pub space_id: Uuid,
16 pub name: String,
17 pub state: i32,
18}
19
20impl ProjectCache {
21 pub fn new(kv: Arc<RedisCache>) -> Self {
22 Self { kv }
23 }
24
25 fn project_key(id: Uuid) -> String {
26 format!("project:{}", id)
27 }
28
29 fn member_role_key(project_id: Uuid, user_id: Uuid) -> String {
30 format!("pm_role:{}:{}", project_id, user_id)
31 }
32
33 fn favorites_key(user_id: Uuid) -> String {
34 format!("fav:{}", user_id)
35 }
36
37 pub async fn get_project(
38 &self,
39 project_id: Uuid,
40 ) -> Result<Option<CachedProjectDetail>, crate::error::ProjectError> {
41 let raw = self
42 .kv
43 .get_bytes(&Self::project_key(project_id))
44 .await
45 .map_err(|e| crate::error::ProjectError::Cache(e.to_string()))?;
46 match raw {
47 Some(b) => match serde_json::from_slice::<CachedProjectDetail>(&b) {
48 Ok(v) => Ok(Some(v)),
49 Err(_) => Ok(None),
50 },
51 None => Ok(None),
52 }
53 }
54
55 pub async fn put_project(
56 &self,
57 detail: &CachedProjectDetail,
58 ttl_secs: u64,
59 ) -> Result<(), crate::error::ProjectError> {
60 let bytes = serde_json::to_vec(detail)?;
61 self.kv
62 .set_bytes(&Self::project_key(detail.id), bytes, ttl_secs)
63 .await
64 .map_err(|e| crate::error::ProjectError::Cache(e.to_string()))?;
65 Ok(())
66 }
67
68 pub async fn invalidate_project(
69 &self,
70 project_id: Uuid,
71 ) -> Result<(), crate::error::ProjectError> {
72 self.kv
73 .delete(&Self::project_key(project_id))
74 .await
75 .map_err(|e| crate::error::ProjectError::Cache(e.to_string()))?;
76 Ok(())
77 }
78
79 pub async fn get_member_role(
80 &self,
81 project_id: Uuid,
82 user_id: Uuid,
83 ) -> Result<Option<i32>, crate::error::ProjectError> {
84 let raw = self
85 .kv
86 .get_bytes(&Self::member_role_key(project_id, user_id))
87 .await
88 .map_err(|e| crate::error::ProjectError::Cache(e.to_string()))?;
89 let Some(b) = raw else {
90 return Ok(None);
91 };
92 if b.len() != 4 {
93 return Ok(None);
94 }
95 let arr: [u8; 4] = [b[0], b[1], b[2], b[3]];
96 Ok(Some(i32::from_le_bytes(arr)))
97 }
98
99 pub async fn put_member_role(
100 &self,
101 project_id: Uuid,
102 user_id: Uuid,
103 role: i32,
104 ttl_secs: u64,
105 ) -> Result<(), crate::error::ProjectError> {
106 self.kv
107 .set_bytes(
108 &Self::member_role_key(project_id, user_id),
109 role.to_le_bytes().to_vec(),
110 ttl_secs,
111 )
112 .await
113 .map_err(|e| crate::error::ProjectError::Cache(e.to_string()))?;
114 Ok(())
115 }
116
117 pub async fn invalidate_member_role(
118 &self,
119 project_id: Uuid,
120 user_id: Uuid,
121 ) -> Result<(), crate::error::ProjectError> {
122 self.kv
123 .delete(&Self::member_role_key(project_id, user_id))
124 .await
125 .map_err(|e| crate::error::ProjectError::Cache(e.to_string()))?;
126 Ok(())
127 }
128
129 pub async fn invalidate_favorites(
130 &self,
131 user_id: Uuid,
132 ) -> Result<(), crate::error::ProjectError> {
133 self.kv
134 .delete(&Self::favorites_key(user_id))
135 .await
136 .map_err(|e| crate::error::ProjectError::Cache(e.to_string()))?;
137 Ok(())
138 }
139}