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

@@ -27,6 +27,8 @@ pub struct Asset {
pub is_processed: bool,
pub owner_user_id: SystemId,
pub created_at: DateTimeStamp,
pub deleted_at: Option<DateTimeStamp>,
pub deleted_by: Option<SystemId>,
}
impl Asset {
@@ -46,12 +48,28 @@ impl Asset {
is_processed: false,
owner_user_id: owner,
created_at: DateTimeStamp::now(),
deleted_at: None,
deleted_by: None,
}
}
pub fn mark_processed(&mut self) {
self.is_processed = true;
}
pub fn is_deleted(&self) -> bool {
self.deleted_at.is_some()
}
pub fn trash(&mut self, by: SystemId) {
self.deleted_at = Some(DateTimeStamp::now());
self.deleted_by = Some(by);
}
pub fn restore(&mut self) {
self.deleted_at = None;
self.deleted_by = None;
}
}
// --- AssetFilters ---

View File

@@ -38,6 +38,23 @@ pub trait AssetRepository: Send + Sync {
) -> Result<Vec<(chrono::NaiveDate, u64)>, DomainError>;
async fn save(&self, asset: &Asset) -> Result<(), DomainError>;
async fn delete(&self, id: &SystemId) -> Result<(), DomainError>;
async fn soft_delete(
&self,
id: &SystemId,
deleted_by: &SystemId,
) -> Result<(), DomainError>;
async fn restore(&self, id: &SystemId) -> Result<(), DomainError>;
async fn find_trashed_before(
&self,
cutoff: chrono::DateTime<chrono::Utc>,
) -> Result<Vec<Asset>, DomainError>;
async fn count_trashed(&self, owner_id: &SystemId) -> Result<u64, DomainError>;
async fn find_trashed_by_owner(
&self,
owner_id: &SystemId,
limit: u32,
offset: u32,
) -> Result<Vec<Asset>, DomainError>;
}
// --- AssetMetadataRepository ---