configuring backend topology

This commit is contained in:
DioCrafts
2025-03-28 12:38:48 +01:00
parent da520db0b3
commit 3a4cb75ac7
21 changed files with 2048 additions and 103 deletions
+3 -1
View File
@@ -10,6 +10,7 @@ pub mod file_fs_write_repository;
pub mod trash_fs_repository;
pub mod file_fs_repository_trash;
pub mod folder_fs_repository_trash;
pub mod share_fs_repository;
// Repositorios PostgreSQL
pub mod pg;
@@ -19,4 +20,5 @@ pub use file_metadata_manager::FileMetadataManager;
pub use file_path_resolver::FilePathResolver;
pub use file_fs_read_repository::FileFsReadRepository;
pub use file_fs_write_repository::FileFsWriteRepository;
pub use pg::{UserPgRepository, SessionPgRepository};
pub use pg::{UserPgRepository, SessionPgRepository};
pub use share_fs_repository::ShareFsRepository;
@@ -0,0 +1,250 @@
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<String>,
expires_at: Option<u64>,
permissions_read: bool,
permissions_write: bool,
permissions_reshare: bool,
created_at: u64,
created_by: String,
access_count: u64,
}
pub struct ShareFsRepository {
config: Arc<AppConfig>,
}
impl ShareFsRepository {
pub fn new(config: Arc<AppConfig>) -> 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<Vec<ShareRecord>, 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<ShareRecord> = 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<Share, DomainError> {
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<Share, DomainError> {
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<Share, DomainError> {
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<Vec<Share>, 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<Share> = 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<Share, DomainError> {
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<Share>, 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<ShareRecord> = 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<Share> = user_shares.iter()
.skip(offset)
.take(limit)
.map(|record| self.to_entity(record))
.collect();
Ok((paginated, total))
}
}