feat: logging toegevoegd
This commit is contained in:
@@ -4,6 +4,7 @@ from pathlib import Path
|
||||
|
||||
from backend.app.api.errors import AppError
|
||||
from backend.app.api.schemas import DeleteResponse, MkdirResponse, RenameResponse, SaveResponse, ViewResponse
|
||||
from backend.app.db.history_repository import HistoryRepository
|
||||
from backend.app.fs.filesystem_adapter import FilesystemAdapter
|
||||
from backend.app.security.path_guard import PathGuard
|
||||
|
||||
@@ -27,93 +28,117 @@ SPECIAL_TEXT_FILENAMES = {
|
||||
|
||||
|
||||
class FileOpsService:
|
||||
def __init__(self, path_guard: PathGuard, filesystem: FilesystemAdapter):
|
||||
def __init__(self, path_guard: PathGuard, filesystem: FilesystemAdapter, history_repository: HistoryRepository | None = None):
|
||||
self._path_guard = path_guard
|
||||
self._filesystem = filesystem
|
||||
self._history_repository = history_repository
|
||||
|
||||
def mkdir(self, parent_path: str, name: str) -> MkdirResponse:
|
||||
resolved_parent = self._path_guard.resolve_directory_path(parent_path)
|
||||
safe_name = self._path_guard.validate_name(name, field="name")
|
||||
target_relative = self._join_relative(resolved_parent.relative, safe_name)
|
||||
resolved_target = self._path_guard.resolve_path(target_relative)
|
||||
|
||||
if resolved_target.absolute.exists():
|
||||
raise AppError(
|
||||
code="already_exists",
|
||||
message="Target path already exists",
|
||||
status_code=409,
|
||||
details={"path": resolved_target.relative},
|
||||
)
|
||||
|
||||
try:
|
||||
resolved_parent = self._path_guard.resolve_directory_path(parent_path)
|
||||
safe_name = self._path_guard.validate_name(name, field="name")
|
||||
target_relative = self._join_relative(resolved_parent.relative, safe_name)
|
||||
resolved_target = self._path_guard.resolve_path(target_relative)
|
||||
|
||||
if resolved_target.absolute.exists():
|
||||
raise AppError(
|
||||
code="already_exists",
|
||||
message="Target path already exists",
|
||||
status_code=409,
|
||||
details={"path": resolved_target.relative},
|
||||
)
|
||||
|
||||
self._filesystem.make_directory(resolved_target.absolute)
|
||||
self._record_history(operation="mkdir", status="completed", path=resolved_target.relative, finished_at=self._now_iso())
|
||||
return MkdirResponse(path=resolved_target.relative)
|
||||
except FileExistsError:
|
||||
raise AppError(
|
||||
error = AppError(
|
||||
code="already_exists",
|
||||
message="Target path already exists",
|
||||
status_code=409,
|
||||
details={"path": resolved_target.relative},
|
||||
details={"path": self._join_relative(parent_path, name)},
|
||||
)
|
||||
self._record_history_error(operation="mkdir", path=self._join_relative(parent_path, name), error=error)
|
||||
raise error
|
||||
except AppError as exc:
|
||||
self._record_history_error(operation="mkdir", path=self._join_relative(parent_path, name), error=exc)
|
||||
raise
|
||||
except OSError as exc:
|
||||
raise AppError(
|
||||
error = AppError(
|
||||
code="io_error",
|
||||
message="Filesystem operation failed",
|
||||
status_code=500,
|
||||
details={"reason": str(exc)},
|
||||
)
|
||||
|
||||
return MkdirResponse(path=resolved_target.relative)
|
||||
self._record_history_error(operation="mkdir", path=self._join_relative(parent_path, name), error=error)
|
||||
raise error
|
||||
|
||||
def rename(self, path: str, new_name: str) -> RenameResponse:
|
||||
resolved_source = self._path_guard.resolve_existing_path(path)
|
||||
safe_name = self._path_guard.validate_name(new_name, field="new_name")
|
||||
|
||||
parent_relative = self._path_guard.entry_relative_path(
|
||||
resolved_source.alias,
|
||||
resolved_source.absolute.parent,
|
||||
display_style=resolved_source.display_style,
|
||||
)
|
||||
target_relative = self._join_relative(parent_relative, safe_name)
|
||||
resolved_target = self._path_guard.resolve_path(target_relative)
|
||||
|
||||
if resolved_target.absolute.exists():
|
||||
raise AppError(
|
||||
code="already_exists",
|
||||
message="Target path already exists",
|
||||
status_code=409,
|
||||
details={"path": resolved_target.relative},
|
||||
)
|
||||
|
||||
try:
|
||||
resolved_source = self._path_guard.resolve_existing_path(path)
|
||||
safe_name = self._path_guard.validate_name(new_name, field="new_name")
|
||||
|
||||
parent_relative = self._path_guard.entry_relative_path(
|
||||
resolved_source.alias,
|
||||
resolved_source.absolute.parent,
|
||||
display_style=resolved_source.display_style,
|
||||
)
|
||||
target_relative = self._join_relative(parent_relative, safe_name)
|
||||
resolved_target = self._path_guard.resolve_path(target_relative)
|
||||
|
||||
if resolved_target.absolute.exists():
|
||||
raise AppError(
|
||||
code="already_exists",
|
||||
message="Target path already exists",
|
||||
status_code=409,
|
||||
details={"path": resolved_target.relative},
|
||||
)
|
||||
|
||||
self._filesystem.rename_path(resolved_source.absolute, resolved_target.absolute)
|
||||
self._record_history(
|
||||
operation="rename",
|
||||
status="completed",
|
||||
source=path,
|
||||
destination=resolved_target.relative,
|
||||
path=resolved_target.relative,
|
||||
finished_at=self._now_iso(),
|
||||
)
|
||||
return RenameResponse(path=resolved_target.relative)
|
||||
except FileNotFoundError:
|
||||
raise AppError(
|
||||
error = AppError(
|
||||
code="path_not_found",
|
||||
message="Requested path was not found",
|
||||
status_code=404,
|
||||
details={"path": path},
|
||||
)
|
||||
self._record_history_error(operation="rename", source=path, destination=new_name, path=path, error=error)
|
||||
raise error
|
||||
except FileExistsError:
|
||||
raise AppError(
|
||||
error = AppError(
|
||||
code="already_exists",
|
||||
message="Target path already exists",
|
||||
status_code=409,
|
||||
details={"path": resolved_target.relative},
|
||||
details={"path": new_name},
|
||||
)
|
||||
self._record_history_error(operation="rename", source=path, destination=new_name, path=path, error=error)
|
||||
raise error
|
||||
except AppError as exc:
|
||||
self._record_history_error(operation="rename", source=path, destination=new_name, path=path, error=exc)
|
||||
raise
|
||||
except OSError as exc:
|
||||
raise AppError(
|
||||
error = AppError(
|
||||
code="io_error",
|
||||
message="Filesystem operation failed",
|
||||
status_code=500,
|
||||
details={"reason": str(exc)},
|
||||
)
|
||||
|
||||
return RenameResponse(path=resolved_target.relative)
|
||||
self._record_history_error(operation="rename", source=path, destination=new_name, path=path, error=error)
|
||||
raise error
|
||||
|
||||
def delete(self, path: str) -> DeleteResponse:
|
||||
resolved_target = self._path_guard.resolve_existing_path(path)
|
||||
|
||||
try:
|
||||
resolved_target = self._path_guard.resolve_existing_path(path)
|
||||
|
||||
if resolved_target.absolute.is_file():
|
||||
self._filesystem.delete_file(resolved_target.absolute)
|
||||
elif resolved_target.absolute.is_dir():
|
||||
@@ -132,24 +157,29 @@ class FileOpsService:
|
||||
status_code=409,
|
||||
details={"path": resolved_target.relative},
|
||||
)
|
||||
except AppError:
|
||||
self._record_history(operation="delete", status="completed", path=resolved_target.relative, finished_at=self._now_iso())
|
||||
return DeleteResponse(path=resolved_target.relative)
|
||||
except AppError as exc:
|
||||
self._record_history_error(operation="delete", path=path, error=exc)
|
||||
raise
|
||||
except FileNotFoundError:
|
||||
raise AppError(
|
||||
error = AppError(
|
||||
code="path_not_found",
|
||||
message="Requested path was not found",
|
||||
status_code=404,
|
||||
details={"path": path},
|
||||
)
|
||||
self._record_history_error(operation="delete", path=path, error=error)
|
||||
raise error
|
||||
except OSError as exc:
|
||||
raise AppError(
|
||||
error = AppError(
|
||||
code="io_error",
|
||||
message="Filesystem operation failed",
|
||||
status_code=500,
|
||||
details={"reason": str(exc)},
|
||||
)
|
||||
|
||||
return DeleteResponse(path=resolved_target.relative)
|
||||
self._record_history_error(operation="delete", path=path, error=error)
|
||||
raise error
|
||||
|
||||
def view(self, path: str, for_edit: bool = False) -> ViewResponse:
|
||||
resolved_target = self._path_guard.resolve_existing_path(path)
|
||||
@@ -282,3 +312,54 @@ class FileOpsService:
|
||||
if special_name:
|
||||
return special_name
|
||||
return TEXT_CONTENT_TYPES.get(path.suffix.lower())
|
||||
|
||||
def _record_history(
|
||||
self,
|
||||
*,
|
||||
operation: str,
|
||||
status: str,
|
||||
source: str | None = None,
|
||||
destination: str | None = None,
|
||||
path: str | None = None,
|
||||
error_code: str | None = None,
|
||||
error_message: str | None = None,
|
||||
finished_at: str | None = None,
|
||||
) -> None:
|
||||
if not self._history_repository:
|
||||
return
|
||||
self._history_repository.create_entry(
|
||||
operation=operation,
|
||||
status=status,
|
||||
source=source,
|
||||
destination=destination,
|
||||
path=path,
|
||||
error_code=error_code,
|
||||
error_message=error_message,
|
||||
finished_at=finished_at,
|
||||
)
|
||||
|
||||
def _record_history_error(
|
||||
self,
|
||||
*,
|
||||
operation: str,
|
||||
error: AppError,
|
||||
source: str | None = None,
|
||||
destination: str | None = None,
|
||||
path: str | None = None,
|
||||
) -> None:
|
||||
self._record_history(
|
||||
operation=operation,
|
||||
status="failed",
|
||||
source=source,
|
||||
destination=destination,
|
||||
path=path,
|
||||
error_code=error.code,
|
||||
error_message=error.message,
|
||||
finished_at=self._now_iso(),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _now_iso() -> str:
|
||||
from datetime import datetime, timezone
|
||||
|
||||
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
||||
|
||||
Reference in New Issue
Block a user