use std::{path::PathBuf, sync::Arc}; use cache::AppCache; use dashmap::DashMap; use db::database::AppDatabase; use model::repos::RepoModel; use sqlx::query_as; use uuid::Uuid; use crate::bare::GitBare; const STORAGE_PATH_CACHE_KEY_PREFIX: &str = "git:rpc:repo:storage_path"; #[derive(Clone)] pub struct RepoRegistry { repos: DashMap, db: AppDatabase, cache: AppCache, } impl RepoRegistry { pub fn new(db: AppDatabase, cache: AppCache) -> Self { Self { repos: DashMap::new(), db, cache, } } pub fn register(&self, repo_id: Uuid, bare_dir: PathBuf) { let bare = GitBare { bare_dir }; self.repos.insert(repo_id, bare); } pub fn unregister(&self, repo_id: &Uuid) { self.repos.remove(repo_id); } pub async fn get( &self, repo_id_str: &str, ) -> Result { let repo_id = repo_id_str.parse::().map_err(|e| { tonic::Status::invalid_argument(format!( "invalid repo_id UUID: {e}" )) })?; if let Some(bare) = self.repos.get(&repo_id) { return Ok(bare.value().clone()); } let storage_path = self.lookup_storage_path(repo_id).await?; let bare = GitBare { bare_dir: PathBuf::from(storage_path), }; self.repos.insert(repo_id, bare.clone()); Ok(bare) } async fn lookup_storage_path( &self, repo_id: Uuid, ) -> Result { let cache_key = format!("{STORAGE_PATH_CACHE_KEY_PREFIX}:{repo_id}"); if let Ok(Some(path)) = self.cache.get::(&cache_key).await { return Ok(path); } let model: RepoModel = query_as( "SELECT id, wk, name, description, default_branch, visibility, size_bytes, is_archived, is_template, is_mirror, created_by, storage_path, created_at, updated_at, deleted_at FROM repo WHERE id = $1 AND deleted_at IS NULL", ) .bind(repo_id) .fetch_one(self.db.reader()) .await .map_err(|e| tonic::Status::internal(format!("database error: {e}")))?; let path = model.storage_path; let _ = self.cache.set(&cache_key, &path).await; Ok(path) } pub fn list(&self) -> Vec { self.repos.iter().map(|r| *r.key()).collect() } } pub fn shared_registry(db: AppDatabase, cache: AppCache) -> Arc { Arc::new(RepoRegistry::new(db, cache)) }