use std::{path::Path, sync::Arc}; use async_trait::async_trait; use serde::{Deserialize, Serialize}; use tokio::{fs, io}; use crate::{ application::ports::share_ports::ShareStoragePort, common::{config::AppConfig, errors::DomainError}, domain::{ entities::share::{Share, ShareItemType}, }, }; // Estructura para almacenar en el sistema de archivos #[derive(Debug, Clone, Serialize, Deserialize)] struct ShareRecord { id: String, item_id: String, item_type: String, token: String, password_hash: Option, expires_at: Option, permissions_read: bool, permissions_write: bool, permissions_reshare: bool, created_at: u64, created_by: String, access_count: u64, } pub struct ShareFsRepository { config: Arc, } impl ShareFsRepository { pub fn new(config: Arc) -> Self { Self { config } } /// Obtiene la ruta del archivo JSON donde se almacenan los enlaces compartidos fn get_shares_path(&self) -> String { format!("{}/shares.json", self.config.storage_path.display()) } /// Lee todos los enlaces compartidos del archivo JSON async fn read_shares(&self) -> Result, io::Error> { let path = self.get_shares_path(); let path = Path::new(&path); if !path.exists() { return Ok(Vec::new()); } let content = fs::read_to_string(path).await?; let shares: Vec = serde_json::from_str(&content).unwrap_or_default(); Ok(shares) } /// Guarda todos los enlaces compartidos en el archivo JSON async fn write_shares(&self, shares: &[ShareRecord]) -> Result<(), io::Error> { let path = self.get_shares_path(); let json = serde_json::to_string_pretty(shares)?; // Asegúrate de que el directorio existe let dir = Path::new(&path).parent().unwrap(); if !dir.exists() { fs::create_dir_all(dir).await? } fs::write(path, json).await } /// Convierte un registro del sistema de archivos a una entidad de dominio fn to_entity(&self, record: &ShareRecord) -> Share { let item_type = ShareItemType::try_from(record.item_type.as_str()) .unwrap_or(ShareItemType::File); let permissions = crate::domain::entities::share::SharePermissions::new( record.permissions_read, record.permissions_write, record.permissions_reshare, ); Share { id: record.id.clone(), item_id: record.item_id.clone(), item_type, token: record.token.clone(), password_hash: record.password_hash.clone(), expires_at: record.expires_at, permissions, created_at: record.created_at, created_by: record.created_by.clone(), access_count: record.access_count, } } /// Convierte una entidad de dominio a un registro para el sistema de archivos fn to_record(&self, share: &Share) -> ShareRecord { ShareRecord { id: share.id.clone(), item_id: share.item_id.clone(), item_type: share.item_type.to_string(), token: share.token.clone(), password_hash: share.password_hash.clone(), expires_at: share.expires_at, permissions_read: share.permissions.read, permissions_write: share.permissions.write, permissions_reshare: share.permissions.reshare, created_at: share.created_at, created_by: share.created_by.clone(), access_count: share.access_count, } } } #[async_trait] impl ShareStoragePort for ShareFsRepository { async fn save_share(&self, share: &Share) -> Result { let mut shares = self.read_shares().await .map_err(|e| DomainError::internal_error("Share", e.to_string()))?; // Verifica si el enlace ya existe let existing_index = shares.iter().position(|s| s.id == share.id); let record = self.to_record(share); if let Some(index) = existing_index { // Actualización shares[index] = record; } else { // Inserción shares.push(record); } self.write_shares(&shares).await .map_err(|e| DomainError::internal_error("Share", e.to_string()))?; Ok(share.clone()) } async fn find_share_by_id(&self, id: &str) -> Result { let shares = self.read_shares().await .map_err(|e| DomainError::internal_error("Share", e.to_string()))?; let share = shares.iter() .find(|s| s.id == id) .ok_or_else(|| { DomainError::not_found("Share", format!("Share with ID {} not found", id)) }); match share { Ok(record) => Ok(self.to_entity(record)), Err(e) => Err(e), } } async fn find_share_by_token(&self, token: &str) -> Result { let shares = self.read_shares().await .map_err(|e| DomainError::internal_error("Share", e.to_string()))?; let share = shares.iter() .find(|s| s.token == token) .ok_or_else(|| { DomainError::not_found("Share", format!("Share with token {} not found", token)) }); match share { Ok(record) => Ok(self.to_entity(record)), Err(e) => Err(e), } } async fn find_shares_by_item(&self, item_id: &str, item_type: &ShareItemType) -> Result, DomainError> { let shares = self.read_shares().await .map_err(|e| DomainError::internal_error("Share", e.to_string()))?; let type_str = item_type.to_string(); let result: Vec = shares.iter() .filter(|s| s.item_id == item_id && s.item_type == type_str) .map(|record| self.to_entity(record)) .collect(); Ok(result) } async fn update_share(&self, share: &Share) -> Result { let mut shares = self.read_shares().await .map_err(|e| DomainError::internal_error("Share", e.to_string()))?; // Busca el índice del enlace a actualizar let index = shares.iter().position(|s| s.id == share.id) .ok_or_else(|| { DomainError::not_found("Share", format!("Share with ID {} not found for update", share.id)) })?; // Actualiza el registro shares[index] = self.to_record(share); // Guarda los cambios self.write_shares(&shares).await .map_err(|e| DomainError::internal_error("Share", e.to_string()))?; Ok(share.clone()) } async fn delete_share(&self, id: &str) -> Result<(), DomainError> { let mut shares = self.read_shares().await .map_err(|e| DomainError::internal_error("Share", e.to_string()))?; // Encuentra el índice del enlace a eliminar let initial_len = shares.len(); shares.retain(|s| s.id != id); // Si no se eliminó ningún enlace, significa que no existía if shares.len() == initial_len { return Err(DomainError::not_found("Share", format!("Share with ID {} not found for deletion", id))); } // Guarda los cambios self.write_shares(&shares).await .map_err(|e| DomainError::internal_error("Share", e.to_string()))?; Ok(()) } async fn find_shares_by_user(&self, user_id: &str, offset: usize, limit: usize) -> Result<(Vec, usize), DomainError> { let shares = self.read_shares().await .map_err(|e| DomainError::internal_error("Share", e.to_string()))?; // Filtra los enlaces del usuario let user_shares: Vec = shares.into_iter() .filter(|s| s.created_by == user_id) .collect(); // Calcula el total let total = user_shares.len(); // Aplica la paginación let paginated: Vec = user_shares.iter() .skip(offset) .take(limit) .map(|record| self.to_entity(record)) .collect(); Ok((paginated, total)) } }