feat: safe deletion, album/asset delete, trash, README update

- volume-aware deletion: read-only volumes remove DB only, writable
  volumes soft-delete to trash with configurable grace period
- trash page with restore, worker purge sweep (TRASH_RETENTION_DAYS)
- album delete endpoint + sidebar trash icon
- asset delete from timeline selection toolbar
- all listing queries exclude trashed assets (deleted_at IS NULL)
- timeline ordered by EXIF capture date, date-summary endpoint
- README rewritten with features, setup, full env var table
This commit is contained in:
2026-06-01 01:57:53 +02:00
parent 957737ac9b
commit 0077caa743
36 changed files with 752 additions and 125 deletions

View File

@@ -10,9 +10,9 @@ use domain::ports::FileStoragePort;
use application::catalog::{
CreateStackHandler, DeleteAssetHandler, DeleteStackHandler, DetectLivePhotosHandler,
GetAssetHandler, GetDateSummaryHandler, GetStackHandler, GetTimelineHandler,
ListDuplicatesHandler,
ListDuplicatesHandler, ListTrashHandler,
ReadAssetFileHandler, ReadDerivativeHandler, RegisterAssetHandler, ResolveDuplicateHandler,
SearchAssetsHandler, UpdateMetadataHandler,
RestoreAssetHandler, SearchAssetsHandler, UpdateMetadataHandler,
};
use application::storage::IngestAssetHandler;
use domain::ports::EventPublisher;
@@ -77,12 +77,16 @@ pub fn build(
let delete_asset = Arc::new(DeleteAssetHandler::new(
asset_repo.clone(),
storage_repos.volume_repo.clone(),
derivative_repo.clone(),
sidecar_repo,
file_storage.clone(),
event_publisher.clone(),
));
let restore_asset = Arc::new(RestoreAssetHandler::new(asset_repo.clone()));
let list_trash = Arc::new(ListTrashHandler::new(asset_repo.clone()));
let list_duplicates = Arc::new(ListDuplicatesHandler::new(duplicate_repo.clone()));
let resolve_duplicate = Arc::new(ResolveDuplicateHandler::new(
duplicate_repo.clone(),
@@ -116,6 +120,8 @@ pub fn build(
read_derivative,
register_asset,
delete_asset,
restore_asset,
list_trash,
search_assets,
list_duplicates,
resolve_duplicate,

View File

@@ -4,8 +4,8 @@ use adapters_postgres::{
PgPool, PostgresAlbumRepository, PostgresAssetRepository, PostgresTagRepository,
};
use application::organization::{
CreateAlbumHandler, GetAlbumHandler, ListAlbumsHandler, ManageAlbumEntriesHandler,
TagAssetHandler, UpdateAlbumHandler,
CreateAlbumHandler, DeleteAlbumHandler, GetAlbumHandler, ListAlbumsHandler,
ManageAlbumEntriesHandler, TagAssetHandler, UpdateAlbumHandler,
};
use presentation::state::OrganizationHandlers;
@@ -18,11 +18,13 @@ pub fn build(pool: &PgPool) -> OrganizationHandlers {
let get_album = Arc::new(GetAlbumHandler::new(album_repo.clone()));
let list_albums = Arc::new(ListAlbumsHandler::new(album_repo.clone()));
let update_album = Arc::new(UpdateAlbumHandler::new(album_repo.clone()));
let delete_album = Arc::new(DeleteAlbumHandler::new(album_repo.clone()));
let manage_album_entries = Arc::new(ManageAlbumEntriesHandler::new(album_repo));
let tag_asset = Arc::new(TagAssetHandler::new(asset_repo, tag_repo));
OrganizationHandlers {
create_album,
delete_album,
get_album,
list_albums,
manage_album_entries,