completely refactor the backend

This commit is contained in:
2025-07-15 06:14:57 +02:00
parent 85bc353a5a
commit 5631158b72
51 changed files with 3563 additions and 526 deletions

View File

@@ -0,0 +1,192 @@
use anyhow::{Context, anyhow};
use tokio::{fs, io::AsyncWriteExt as _};
use crate::domain::file_system::{
models::file::{
AbsoluteFilePath, CreateDirectoryError, CreateDirectoryRequest, CreateFileError,
CreateFileRequest, DeleteDirectoryError, DeleteDirectoryRequest, DeleteFileError,
DeleteFileRequest, File, FileMimeType, FileName, FilePath, FileType, ListFilesError,
ListFilesRequest,
},
ports::FileSystemRepository,
};
#[derive(Debug, Clone)]
pub struct FileSystemConfig {
base_directory: String,
}
impl FileSystemConfig {
pub fn new(base_directory: String) -> Self {
Self { base_directory }
}
}
#[derive(Debug, Clone)]
pub struct FileSystem {
base_directory: FilePath,
}
impl FileSystem {
pub fn new(config: FileSystemConfig) -> anyhow::Result<Self> {
let file_system = Self {
base_directory: FilePath::new(&config.base_directory)?,
};
Ok(file_system)
}
/// Combines `self.base_directory` with the specified path
///
/// * `path`: The absolute path (absolute in relation to the base directory)
fn get_target_path(&self, path: &AbsoluteFilePath) -> FilePath {
self.base_directory.join(&path.as_relative())
}
async fn get_all_files(&self, absolute_path: &AbsoluteFilePath) -> anyhow::Result<Vec<File>> {
let directory_path = self.get_target_path(absolute_path);
let mut dir = fs::read_dir(&directory_path).await?;
let mut files = Vec::new();
while let Ok(Some(entry)) = dir.next_entry().await {
let name = entry
.file_name()
.into_string()
.ok()
.context("Failed to get file name")?;
let file_type = {
let file_type = entry.file_type().await?;
if file_type.is_dir() {
FileType::Directory
} else if file_type.is_file() {
FileType::File
} else {
continue;
}
};
let mime_type = match file_type {
FileType::File => FileMimeType::from_name(&name),
_ => None,
};
files.push(File::new(FileName::new(&name)?, file_type, mime_type));
}
Ok(files)
}
/// Actually created a directory in the underlying file system
///
/// * `path`: The directory's absolute path (absolute not in relation to the root file system but `self.base_directory`)
async fn create_dir(&self, path: &AbsoluteFilePath) -> anyhow::Result<FilePath> {
let file_path = self.get_target_path(path);
fs::create_dir(&file_path).await?;
Ok(file_path)
}
/// Actually removes a directory from the underlying file system
///
/// * `path`: The directory's absolute path (absolute not in relation to the root file system but `self.base_directory`)
/// * `force`: Whether to delete directories that are not empty
async fn remove_dir(&self, path: &AbsoluteFilePath, force: bool) -> anyhow::Result<FilePath> {
let file_path = self.get_target_path(path);
if force {
fs::remove_dir_all(&file_path).await?;
} else {
fs::remove_dir(&file_path).await?;
}
Ok(file_path)
}
async fn write_file(&self, path: &AbsoluteFilePath, data: &[u8]) -> anyhow::Result<FilePath> {
let path = self.get_target_path(path);
let mut file = fs::OpenOptions::new()
.write(true)
.create(true)
.open(&path)
.await?;
file.write_all(data).await?;
Ok(path)
}
/// Actually removes a file from the underlying file system
///
/// * `path`: The file's absolute path (absolute not in relation to the root file system but `self.base_directory`)
async fn remove_file(&self, path: &AbsoluteFilePath) -> anyhow::Result<FilePath> {
let path = self.get_target_path(path);
fs::remove_file(&path).await?;
Ok(path)
}
}
impl FileSystemRepository for FileSystem {
async fn list_files(&self, request: ListFilesRequest) -> Result<Vec<File>, ListFilesError> {
let files = self.get_all_files(request.path()).await.map_err(|err| {
anyhow!(err).context(format!(
"Failed to get the files at path: {}",
request.path()
))
})?;
Ok(files)
}
async fn create_directory(
&self,
request: CreateDirectoryRequest,
) -> Result<FilePath, CreateDirectoryError> {
let created_path = self.create_dir(request.path()).await.context(format!(
"Failed to create directory at path {}",
request.path()
))?;
Ok(created_path)
}
async fn delete_directory(
&self,
request: DeleteDirectoryRequest,
) -> Result<FilePath, DeleteDirectoryError> {
let deleted_path = self
.remove_dir(request.path(), false)
.await
.context(format!("Failed to delete directory at {}", request.path()))?;
Ok(deleted_path)
}
async fn create_file(&self, request: CreateFileRequest) -> Result<FilePath, CreateFileError> {
let file_path = self
.write_file(request.path(), request.data())
.await
.context(format!(
"Failed to write {} byte(s) to path {}",
request.data().len(),
request.path()
))?;
Ok(file_path)
}
async fn delete_file(&self, request: DeleteFileRequest) -> Result<FilePath, DeleteFileError> {
let deleted_path = self
.remove_file(request.path())
.await
.context(format!("Failed to delete file at {}", request.path()))?;
Ok(deleted_path)
}
}