1use dashmap::DashMap;
9use downcast_rs::{DowncastSync, impl_downcast};
10use std::any::Any;
11use std::sync::Arc;
12use std::time::Duration;
13
14use crate::storage::{SessionStorage, UserStorage};
15
16pub trait Plugin: Any + Send + Sync + DowncastSync {
18 fn name(&self) -> String;
20}
21impl_downcast!(sync Plugin);
22
23pub struct PluginManager<U: UserStorage, S: SessionStorage> {
25 plugins: DashMap<String, Arc<dyn Plugin>>,
26 user_storage: Arc<U>,
27 session_storage: Arc<S>,
28}
29
30impl<U: UserStorage, S: SessionStorage> PluginManager<U, S> {
31 pub fn new(user_storage: Arc<U>, session_storage: Arc<S>) -> Self {
40 Self {
41 plugins: DashMap::new(),
42 user_storage,
43 session_storage,
44 }
45 }
46
47 pub fn get_plugin<T: Plugin + 'static>(&self, name: &str) -> Option<Arc<T>> {
62 let plugin = self.plugins.get(name)?;
63 plugin.value().clone().downcast_arc::<T>().ok()
64 }
65
66 pub fn register_plugin<T: Plugin + 'static>(&mut self, plugin: T) {
79 let name = plugin.name();
80 let plugin = Arc::new(plugin);
81 self.plugins.insert(name.clone(), plugin.clone());
82 tracing::info!(plugin.name = name, "Registered plugin");
83 }
84
85 pub fn user_storage(&self) -> &Arc<U> {
86 &self.user_storage
87 }
88
89 pub fn session_storage(&self) -> &Arc<S> {
90 &self.session_storage
91 }
92
93 pub async fn start_session_cleanup_task(&self, config: SessionCleanupConfig) {
104 let storage = self.session_storage.clone();
105 tokio::spawn(async move {
106 let mut interval = tokio::time::interval(config.interval);
107 loop {
108 interval.tick().await;
109 if let Err(e) = storage.cleanup_expired_sessions().await {
110 tracing::error!("Failed to cleanup sessions: {}", e);
111 }
112 }
113 });
114 }
115}
116
117#[derive(Clone)]
118pub struct SessionCleanupConfig {
119 pub interval: Duration,
121 pub max_session_age: Duration,
123}
124
125impl Default for SessionCleanupConfig {
126 fn default() -> Self {
127 Self {
128 interval: Duration::from_secs(3600), max_session_age: Duration::from_secs(2592000), }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use async_trait::async_trait;
137
138 use crate::{Error, NewUser, Session, User, UserId, session::SessionId};
139
140 use super::*;
141
142 #[derive(Debug, Clone)]
143 struct TestPlugin;
144
145 impl Plugin for TestPlugin {
146 fn name(&self) -> String {
147 "test".to_string()
148 }
149 }
150
151 impl TestPlugin {
152 fn new() -> Self {
153 Self
154 }
155 }
156
157 struct TestStorage {
158 users: DashMap<UserId, User>,
159 sessions: DashMap<SessionId, Session>,
160 }
161
162 impl TestStorage {
163 fn new() -> Self {
164 Self {
165 users: DashMap::new(),
166 sessions: DashMap::new(),
167 }
168 }
169 }
170
171 #[async_trait]
172 impl UserStorage for TestStorage {
173 type Error = Error;
174
175 async fn get_user(&self, id: &UserId) -> Result<Option<User>, Self::Error> {
176 Ok(self.users.get(id).map(|u| u.clone()))
177 }
178
179 async fn get_user_by_email(&self, email: &str) -> Result<Option<User>, Self::Error> {
180 Ok(self
181 .users
182 .iter()
183 .find(|u| u.email == email)
184 .map(|u| u.clone()))
185 }
186
187 async fn get_or_create_user_by_email(&self, email: &str) -> Result<User, Self::Error> {
188 match self.get_user_by_email(email).await {
189 Ok(Some(user)) => Ok(user),
190 Ok(None) => {
191 self.create_user(
192 &NewUser::builder()
193 .id(UserId::new_random())
194 .email(email.to_string())
195 .build()
196 .unwrap(),
197 )
198 .await
199 }
200 Err(e) => Err(e),
201 }
202 }
203
204 async fn create_user(&self, new_user: &NewUser) -> Result<User, Self::Error> {
205 let user = User::builder()
206 .email(new_user.email.clone())
207 .created_at(chrono::Utc::now())
208 .email_verified_at(None)
209 .name(None)
210 .updated_at(chrono::Utc::now())
211 .build()
212 .unwrap();
213 self.users.insert(user.id.clone(), user.clone());
214 Ok(user)
215 }
216
217 async fn update_user(&self, user: &User) -> Result<User, Self::Error> {
218 self.users.insert(user.id.clone(), user.clone());
219 Ok(user.clone())
220 }
221
222 async fn delete_user(&self, id: &UserId) -> Result<(), Self::Error> {
223 self.users.remove(id);
224 Ok(())
225 }
226
227 async fn set_user_email_verified(&self, user_id: &UserId) -> Result<(), Self::Error> {
228 let mut user = self.users.get_mut(user_id).unwrap();
229 user.email_verified_at = Some(chrono::Utc::now());
230 self.users.insert(user_id.clone(), user.clone());
231 Ok(())
232 }
233 }
234
235 #[async_trait]
236 impl SessionStorage for TestStorage {
237 type Error = Error;
238
239 async fn get_session(&self, id: &SessionId) -> Result<Session, Self::Error> {
240 Ok(self.sessions.get(id).unwrap().clone())
241 }
242
243 async fn create_session(&self, session: &Session) -> Result<Session, Self::Error> {
244 self.sessions.insert(session.id.clone(), session.clone());
245 Ok(session.clone())
246 }
247
248 async fn delete_session(&self, id: &SessionId) -> Result<(), Self::Error> {
249 self.sessions.remove(id);
250 Ok(())
251 }
252
253 async fn delete_sessions_for_user(&self, user_id: &UserId) -> Result<(), Self::Error> {
254 self.sessions.retain(|_, s| s.user_id != *user_id);
255 Ok(())
256 }
257
258 async fn cleanup_expired_sessions(&self) -> Result<(), Self::Error> {
259 let now = chrono::Utc::now();
260 self.sessions.retain(|_, s| s.expires_at > now);
261 Ok(())
262 }
263 }
264
265 fn setup_test_storage() -> (Arc<TestStorage>, Arc<TestStorage>) {
267 let user_storage = Arc::new(TestStorage::new());
268 let session_storage = Arc::new(TestStorage::new());
269 (user_storage, session_storage)
270 }
271
272 #[tokio::test]
273 async fn test_plugin_manager() {
274 let (user_storage, session_storage) = setup_test_storage();
275 let mut plugin_manager = PluginManager::new(user_storage, session_storage);
276 plugin_manager.register_plugin(TestPlugin::new());
277 let plugin = plugin_manager.get_plugin::<TestPlugin>("test").unwrap();
278 assert_eq!(plugin.name(), "test");
279 }
280
281 #[tokio::test]
282 async fn test_session_cleanup() {
283 let (user_storage, session_storage) = setup_test_storage();
284 let plugin_manager = PluginManager::new(user_storage.clone(), session_storage.clone());
285
286 let expired_session = Session {
288 id: SessionId::new("expired"),
289 user_id: UserId::new("test"),
290 user_agent: None,
291 ip_address: None,
292 created_at: chrono::Utc::now(),
293 updated_at: chrono::Utc::now(),
294 expires_at: chrono::Utc::now() - chrono::Duration::hours(1),
295 };
296 session_storage
297 .create_session(&expired_session)
298 .await
299 .expect("Failed to create expired session");
300
301 let valid_session = Session {
303 id: SessionId::new("valid"),
304 user_id: UserId::new("test"),
305 user_agent: None,
306 ip_address: None,
307 created_at: chrono::Utc::now(),
308 updated_at: chrono::Utc::now(),
309 expires_at: chrono::Utc::now() + chrono::Duration::hours(1),
310 };
311 session_storage
312 .create_session(&valid_session)
313 .await
314 .expect("Failed to create valid session");
315
316 let config = SessionCleanupConfig {
318 interval: Duration::from_millis(100),
319 max_session_age: Duration::from_secs(3600),
320 };
321
322 plugin_manager.start_session_cleanup_task(config).await;
324
325 tokio::time::sleep(Duration::from_millis(200)).await;
327 }
328}