Initial commit
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
use serde::Serialize;
|
||||
use crate::domain::entities::file::File;
|
||||
|
||||
/// DTO for file responses
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct FileDto {
|
||||
/// File ID
|
||||
pub id: String,
|
||||
|
||||
/// File name
|
||||
pub name: String,
|
||||
|
||||
/// Path to the file (relative)
|
||||
pub path: String,
|
||||
|
||||
/// Size in bytes
|
||||
pub size: u64,
|
||||
|
||||
/// MIME type
|
||||
pub mime_type: String,
|
||||
|
||||
/// Parent folder ID
|
||||
pub folder_id: Option<String>,
|
||||
|
||||
/// Creation timestamp
|
||||
pub created_at: u64,
|
||||
|
||||
/// Last modification timestamp
|
||||
pub modified_at: u64,
|
||||
}
|
||||
|
||||
impl From<File> for FileDto {
|
||||
fn from(file: File) -> Self {
|
||||
Self {
|
||||
id: file.id,
|
||||
name: file.name,
|
||||
path: file.path.to_string_lossy().to_string(),
|
||||
size: file.size,
|
||||
mime_type: file.mime_type,
|
||||
folder_id: file.folder_id,
|
||||
created_at: file.created_at,
|
||||
modified_at: file.modified_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::domain::entities::folder::Folder;
|
||||
|
||||
/// DTO for folder creation requests
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateFolderDto {
|
||||
/// Name of the folder to create
|
||||
pub name: String,
|
||||
|
||||
/// Parent folder ID (None for root level)
|
||||
pub parent_id: Option<String>,
|
||||
}
|
||||
|
||||
/// DTO for folder rename requests
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RenameFolderDto {
|
||||
/// New name for the folder
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// DTO for folder move requests
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct MoveFolderDto {
|
||||
/// New parent folder ID (None for root level)
|
||||
pub parent_id: Option<String>,
|
||||
}
|
||||
|
||||
/// DTO for folder responses
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct FolderDto {
|
||||
/// Folder ID
|
||||
pub id: String,
|
||||
|
||||
/// Folder name
|
||||
pub name: String,
|
||||
|
||||
/// Path to the folder (relative)
|
||||
pub path: String,
|
||||
|
||||
/// Parent folder ID
|
||||
pub parent_id: Option<String>,
|
||||
|
||||
/// Creation timestamp
|
||||
pub created_at: u64,
|
||||
|
||||
/// Last modification timestamp
|
||||
pub modified_at: u64,
|
||||
|
||||
/// Whether this is a root folder
|
||||
pub is_root: bool,
|
||||
}
|
||||
|
||||
impl From<Folder> for FolderDto {
|
||||
fn from(folder: Folder) -> Self {
|
||||
let is_root = folder.parent_id.is_none();
|
||||
Self {
|
||||
id: folder.id,
|
||||
name: folder.name,
|
||||
path: folder.path.to_string_lossy().to_string(),
|
||||
parent_id: folder.parent_id,
|
||||
created_at: folder.created_at,
|
||||
modified_at: folder.modified_at,
|
||||
is_root,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::domain::services::i18n_service::Locale;
|
||||
|
||||
/// DTO for locale information
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct LocaleDto {
|
||||
/// Locale code (e.g., "en", "es")
|
||||
pub code: String,
|
||||
|
||||
/// Locale name in its own language (e.g., "English", "Español")
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl From<Locale> for LocaleDto {
|
||||
fn from(locale: Locale) -> Self {
|
||||
let (code, name) = match locale {
|
||||
Locale::English => ("en", "English"),
|
||||
Locale::Spanish => ("es", "Español"),
|
||||
};
|
||||
|
||||
Self {
|
||||
code: code.to_string(),
|
||||
name: name.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// DTO for translation request
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TranslationRequestDto {
|
||||
/// The translation key
|
||||
pub key: String,
|
||||
|
||||
/// The locale code (optional, defaults to "en")
|
||||
pub locale: Option<String>,
|
||||
}
|
||||
|
||||
/// DTO for translation response
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct TranslationResponseDto {
|
||||
/// The translation key
|
||||
pub key: String,
|
||||
|
||||
/// The locale code used for translation
|
||||
pub locale: String,
|
||||
|
||||
/// The translated text
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
/// DTO for translation error
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct TranslationErrorDto {
|
||||
/// The translation key that was not found
|
||||
pub key: String,
|
||||
|
||||
/// The locale code used for translation
|
||||
pub locale: String,
|
||||
|
||||
/// The error message
|
||||
pub error: String,
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod file_dto;
|
||||
pub mod folder_dto;
|
||||
pub mod i18n_dto;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod dtos;
|
||||
pub mod services;
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::domain::repositories::file_repository::{FileRepository, FileRepositoryResult};
|
||||
use crate::application::dtos::file_dto::FileDto;
|
||||
|
||||
/// Service for file operations
|
||||
pub struct FileService {
|
||||
file_repository: Arc<dyn FileRepository>,
|
||||
}
|
||||
|
||||
impl FileService {
|
||||
/// Creates a new file service
|
||||
pub fn new(file_repository: Arc<dyn FileRepository>) -> Self {
|
||||
Self { file_repository }
|
||||
}
|
||||
|
||||
/// Uploads a new file from bytes
|
||||
pub async fn upload_file_from_bytes(
|
||||
&self,
|
||||
name: String,
|
||||
folder_id: Option<String>,
|
||||
content_type: String,
|
||||
content: Vec<u8>,
|
||||
) -> FileRepositoryResult<FileDto>
|
||||
{
|
||||
let file = self.file_repository.save_file_from_bytes(name, folder_id, content_type, content).await?;
|
||||
Ok(FileDto::from(file))
|
||||
}
|
||||
|
||||
/// Gets a file by ID
|
||||
pub async fn get_file(&self, id: &str) -> FileRepositoryResult<FileDto> {
|
||||
let file = self.file_repository.get_file_by_id(id).await?;
|
||||
Ok(FileDto::from(file))
|
||||
}
|
||||
|
||||
/// Lists files in a folder
|
||||
pub async fn list_files(&self, folder_id: Option<&str>) -> FileRepositoryResult<Vec<FileDto>> {
|
||||
let files = self.file_repository.list_files(folder_id).await?;
|
||||
Ok(files.into_iter().map(FileDto::from).collect())
|
||||
}
|
||||
|
||||
/// Deletes a file
|
||||
pub async fn delete_file(&self, id: &str) -> FileRepositoryResult<()> {
|
||||
self.file_repository.delete_file(id).await
|
||||
}
|
||||
|
||||
/// Gets file content
|
||||
pub async fn get_file_content(&self, id: &str) -> FileRepositoryResult<Vec<u8>> {
|
||||
self.file_repository.get_file_content(id).await
|
||||
}
|
||||
|
||||
/// Moves a file to a new folder implementing direct save with new location without deleting first
|
||||
pub async fn move_file(&self, file_id: &str, folder_id: Option<String>) -> FileRepositoryResult<FileDto> {
|
||||
// Get the current file complete info
|
||||
let source_file = match self.file_repository.get_file_by_id(file_id).await {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
tracing::error!("Error al obtener archivo (ID: {}): {}", file_id, e);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
tracing::info!("Moviendo archivo: {} (ID: {}) de carpeta: {:?} a carpeta: {:?}",
|
||||
source_file.name, file_id, source_file.folder_id, folder_id);
|
||||
|
||||
// Special handling for PDF files
|
||||
let is_pdf = source_file.name.to_lowercase().ends_with(".pdf");
|
||||
if is_pdf {
|
||||
tracing::info!("Moviendo un archivo PDF: {}", source_file.name);
|
||||
}
|
||||
|
||||
// No hacer nada si ya estamos en la carpeta de destino
|
||||
if source_file.folder_id == folder_id {
|
||||
tracing::info!("El archivo ya está en la carpeta de destino, no es necesario moverlo");
|
||||
return Ok(FileDto::from(source_file));
|
||||
}
|
||||
|
||||
// Step 1: Get file content
|
||||
tracing::info!("Leyendo contenido del archivo: {}", source_file.name);
|
||||
let content = match self.file_repository.get_file_content(file_id).await {
|
||||
Ok(content) => {
|
||||
tracing::info!("Contenido del archivo leído correctamente: {} bytes", content.len());
|
||||
content
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Error al leer el contenido del archivo {}: {}", file_id, e);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
// Step 2: Save the file to the new location with a new ID
|
||||
tracing::info!("Guardando archivo en nueva ubicación: {} en carpeta: {:?}", source_file.name, folder_id);
|
||||
let new_file = match self.file_repository.save_file_from_bytes(
|
||||
source_file.name.clone(),
|
||||
folder_id.clone(),
|
||||
source_file.mime_type.clone(),
|
||||
content
|
||||
).await {
|
||||
Ok(file) => {
|
||||
tracing::info!("Archivo guardado en nueva ubicación con ID: {}", file.id);
|
||||
file
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Error al guardar archivo en nueva ubicación: {}", e);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
// Step 3: Only after ensuring new file is saved, try to delete the old file
|
||||
// If this fails, it's not critical - we already have the file in the new location
|
||||
tracing::info!("Eliminando archivo original con ID: {}", file_id);
|
||||
match self.file_repository.delete_file(file_id).await {
|
||||
Ok(_) => tracing::info!("Archivo original eliminado correctamente"),
|
||||
Err(e) => {
|
||||
tracing::warn!("Error al eliminar archivo original (ID: {}): {} - archivo duplicado posible", file_id, e);
|
||||
// Continue even if delete fails - at worst we'll have duplicate files
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!("Archivo movido exitosamente: {} (ID: {}) a carpeta: {:?}",
|
||||
new_file.name, new_file.id, folder_id);
|
||||
|
||||
Ok(FileDto::from(new_file))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::domain::repositories::folder_repository::{FolderRepository, FolderRepositoryResult};
|
||||
use crate::application::dtos::folder_dto::{CreateFolderDto, RenameFolderDto, MoveFolderDto, FolderDto};
|
||||
|
||||
/// Service for folder operations
|
||||
pub struct FolderService {
|
||||
folder_repository: Arc<dyn FolderRepository>,
|
||||
}
|
||||
|
||||
impl FolderService {
|
||||
/// Creates a new folder service
|
||||
pub fn new(folder_repository: Arc<dyn FolderRepository>) -> Self {
|
||||
Self { folder_repository }
|
||||
}
|
||||
|
||||
/// Creates a new folder
|
||||
pub async fn create_folder(&self, dto: CreateFolderDto) -> FolderRepositoryResult<FolderDto> {
|
||||
let parent_path = match &dto.parent_id {
|
||||
Some(parent_id) => {
|
||||
let parent = self.folder_repository.get_folder_by_id(parent_id).await?;
|
||||
Some(parent.path)
|
||||
},
|
||||
None => None
|
||||
};
|
||||
|
||||
let folder = self.folder_repository.create_folder(dto.name, parent_path).await?;
|
||||
Ok(FolderDto::from(folder))
|
||||
}
|
||||
|
||||
/// Gets a folder by ID
|
||||
pub async fn get_folder(&self, id: &str) -> FolderRepositoryResult<FolderDto> {
|
||||
let folder = self.folder_repository.get_folder_by_id(id).await?;
|
||||
Ok(FolderDto::from(folder))
|
||||
}
|
||||
|
||||
/// Gets a folder by path
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_folder_by_path(&self, path: &str) -> FolderRepositoryResult<FolderDto> {
|
||||
let path_buf = PathBuf::from(path);
|
||||
let folder = self.folder_repository.get_folder_by_path(&path_buf).await?;
|
||||
Ok(FolderDto::from(folder))
|
||||
}
|
||||
|
||||
/// Lists folders in a parent folder
|
||||
pub async fn list_folders(&self, parent_id: Option<&str>) -> FolderRepositoryResult<Vec<FolderDto>> {
|
||||
let folders = self.folder_repository.list_folders(parent_id).await?;
|
||||
Ok(folders.into_iter().map(FolderDto::from).collect())
|
||||
}
|
||||
|
||||
/// Renames a folder
|
||||
pub async fn rename_folder(&self, id: &str, dto: RenameFolderDto) -> FolderRepositoryResult<FolderDto> {
|
||||
let folder = self.folder_repository.rename_folder(id, dto.name).await?;
|
||||
Ok(FolderDto::from(folder))
|
||||
}
|
||||
|
||||
/// Moves a folder to a new parent
|
||||
pub async fn move_folder(&self, id: &str, dto: MoveFolderDto) -> FolderRepositoryResult<FolderDto> {
|
||||
let folder = self.folder_repository.move_folder(id, dto.parent_id.as_deref()).await?;
|
||||
Ok(FolderDto::from(folder))
|
||||
}
|
||||
|
||||
/// Deletes a folder
|
||||
pub async fn delete_folder(&self, id: &str) -> FolderRepositoryResult<()> {
|
||||
self.folder_repository.delete_folder(id).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::domain::services::i18n_service::{I18nService, I18nResult, Locale};
|
||||
|
||||
/// Service for i18n operations
|
||||
pub struct I18nApplicationService {
|
||||
i18n_service: Arc<dyn I18nService>,
|
||||
}
|
||||
|
||||
impl I18nApplicationService {
|
||||
/// Creates a new i18n application service
|
||||
pub fn new(i18n_service: Arc<dyn I18nService>) -> Self {
|
||||
Self { i18n_service }
|
||||
}
|
||||
|
||||
/// Get a translation for a key and locale
|
||||
pub async fn translate(&self, key: &str, locale: Option<Locale>) -> I18nResult<String> {
|
||||
let locale = locale.unwrap_or(Locale::default());
|
||||
self.i18n_service.translate(key, locale).await
|
||||
}
|
||||
|
||||
/// Load translations for a locale
|
||||
pub async fn load_translations(&self, locale: Locale) -> I18nResult<()> {
|
||||
self.i18n_service.load_translations(locale).await
|
||||
}
|
||||
|
||||
/// Load translations for all available locales
|
||||
#[allow(dead_code)]
|
||||
pub async fn load_all_translations(&self) -> Vec<(Locale, I18nResult<()>)> {
|
||||
let locales = self.i18n_service.available_locales().await;
|
||||
let mut results = Vec::new();
|
||||
|
||||
for locale in locales {
|
||||
let result = self.i18n_service.load_translations(locale).await;
|
||||
results.push((locale, result));
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
/// Get available locales
|
||||
pub async fn available_locales(&self) -> Vec<Locale> {
|
||||
self.i18n_service.available_locales().await
|
||||
}
|
||||
|
||||
/// Check if a locale is supported
|
||||
#[allow(dead_code)]
|
||||
pub async fn is_supported(&self, locale: Locale) -> bool {
|
||||
self.i18n_service.is_supported(locale).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod file_service;
|
||||
pub mod folder_service;
|
||||
pub mod i18n_application_service;
|
||||
|
||||
Reference in New Issue
Block a user