//! Filesystem-backed [`SystemPermissionStore`] for project system permissions. use std::sync::Arc; use async_trait::async_trait; use domain::ports::{FileSystem, FsError, RemotePath, StoreError, SystemPermissionStore}; use domain::{Project, ProjectSystemPermissions}; const SYSTEM_PERMISSIONS_FILE: &str = "system-permissions.json"; /// JSON-file implementation for `/.ideai/system-permissions.json`. #[derive(Clone)] pub struct FsSystemPermissionStore { fs: Arc, } impl FsSystemPermissionStore { /// Builds the store from an injected filesystem port. #[must_use] pub fn new(fs: Arc) -> Self { Self { fs } } fn path(project: &Project) -> RemotePath { let root = project.root.as_str().trim_end_matches(['/', '\\']); RemotePath::new(format!("{root}/.ideai/{SYSTEM_PERMISSIONS_FILE}")) } async fn ensure_ideai(&self, project: &Project) -> Result<(), StoreError> { let root = project.root.as_str().trim_end_matches(['/', '\\']); self.fs .create_dir_all(&RemotePath::new(format!("{root}/.ideai"))) .await .map_err(|e| StoreError::Io(e.to_string())) } } #[async_trait] impl SystemPermissionStore for FsSystemPermissionStore { async fn load_system_permissions( &self, project: &Project, ) -> Result { match self.fs.read(&Self::path(project)).await { Ok(bytes) => { serde_json::from_slice(&bytes).map_err(|e| StoreError::Serialization(e.to_string())) } Err(FsError::NotFound(_)) => Ok(ProjectSystemPermissions::default()), Err(e) => Err(StoreError::Io(e.to_string())), } } async fn save_system_permissions( &self, project: &Project, permissions: &ProjectSystemPermissions, ) -> Result<(), StoreError> { self.ensure_ideai(project).await?; let mut bytes = serde_json::to_vec_pretty(permissions) .map_err(|e| StoreError::Serialization(e.to_string()))?; bytes.push(b'\n'); self.fs .write(&Self::path(project), &bytes) .await .map_err(|e| StoreError::Io(e.to_string())) } }