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:
@@ -5,8 +5,8 @@ use api_types::{
|
||||
responses::AlbumResponse,
|
||||
};
|
||||
use application::organization::{
|
||||
AlbumAction, CreateAlbumCommand, GetAlbumQuery, ListAlbumsQuery, ManageAlbumEntriesCommand,
|
||||
UpdateAlbumCommand,
|
||||
AlbumAction, CreateAlbumCommand, DeleteAlbumCommand, GetAlbumQuery, ListAlbumsQuery,
|
||||
ManageAlbumEntriesCommand, UpdateAlbumCommand,
|
||||
};
|
||||
use axum::{
|
||||
Json,
|
||||
@@ -108,6 +108,19 @@ pub async fn update_album(
|
||||
Ok(Json(AlbumResponse::from_domain(&album)))
|
||||
}
|
||||
|
||||
pub async fn delete_album(
|
||||
State(state): State<AppState>,
|
||||
claims: JwtClaims,
|
||||
Path((album_id,)): Path<(uuid::Uuid,)>,
|
||||
) -> Result<StatusCode, AppError> {
|
||||
let cmd = DeleteAlbumCommand {
|
||||
album_id: SystemId::from_uuid(album_id),
|
||||
user_id: claims.user_id,
|
||||
};
|
||||
state.organization.delete_album.execute(cmd).await?;
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post, path = "/api/v1/albums/{id}/entries",
|
||||
request_body = AlbumEntryRequest,
|
||||
|
||||
@@ -14,8 +14,9 @@ use api_types::{
|
||||
};
|
||||
use application::{
|
||||
catalog::{
|
||||
DeleteAssetCommand, GetAssetQuery, GetDateSummaryQuery, GetTimelineQuery, ReadAssetFileQuery,
|
||||
ReadDerivativeQuery, RegisterAssetCommand, SearchAssetsQuery, UpdateMetadataCommand,
|
||||
DeleteAssetCommand, GetAssetQuery, GetDateSummaryQuery, GetTimelineQuery, ListTrashQuery,
|
||||
ReadAssetFileQuery, ReadDerivativeQuery, RegisterAssetCommand, RestoreAssetCommand,
|
||||
SearchAssetsQuery, UpdateMetadataCommand,
|
||||
},
|
||||
organization::TagAssetCommand,
|
||||
storage::IngestAssetCommand,
|
||||
@@ -473,3 +474,46 @@ pub async fn bulk_tag(
|
||||
}
|
||||
Ok(Json(serde_json::json!({ "tagged": tagged })))
|
||||
}
|
||||
|
||||
pub async fn restore_asset(
|
||||
State(state): State<AppState>,
|
||||
claims: JwtClaims,
|
||||
Path((asset_id,)): Path<(uuid::Uuid,)>,
|
||||
) -> Result<StatusCode, AppError> {
|
||||
let cmd = RestoreAssetCommand {
|
||||
asset_id: SystemId::from_uuid(asset_id),
|
||||
user_id: claims.user_id,
|
||||
};
|
||||
state.catalog.restore_asset.execute(cmd).await?;
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct TrashParams {
|
||||
pub limit: Option<u32>,
|
||||
pub offset: Option<u32>,
|
||||
}
|
||||
|
||||
pub async fn list_trash(
|
||||
State(state): State<AppState>,
|
||||
claims: JwtClaims,
|
||||
Query(params): Query<TrashParams>,
|
||||
) -> Result<Json<TimelineResponse>, AppError> {
|
||||
let limit = params.limit.unwrap_or(DEFAULT_PAGE_SIZE).min(MAX_PAGE_SIZE);
|
||||
let offset = params.offset.unwrap_or(0);
|
||||
let query = ListTrashQuery {
|
||||
owner_id: claims.user_id,
|
||||
limit,
|
||||
offset,
|
||||
};
|
||||
let result = state.catalog.list_trash.execute(query).await?;
|
||||
let items = result
|
||||
.assets
|
||||
.iter()
|
||||
.map(|a| AssetResponse::from_domain(a, &StructuredData::new()))
|
||||
.collect();
|
||||
Ok(Json(TimelineResponse {
|
||||
assets: items,
|
||||
total: result.total,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ pub fn routes() -> Router<AppState> {
|
||||
get(assets::serve_derivative),
|
||||
)
|
||||
.route("/assets/{id}/tags", post(assets::tag_asset))
|
||||
.route("/assets/trash", get(assets::list_trash))
|
||||
.route("/assets/{id}/restore", post(assets::restore_asset))
|
||||
.route("/assets/bulk-delete", post(assets::bulk_delete))
|
||||
.route("/assets/bulk-tag", post(assets::bulk_tag))
|
||||
.route(
|
||||
|
||||
@@ -12,7 +12,9 @@ pub fn routes() -> Router<AppState> {
|
||||
)
|
||||
.route(
|
||||
"/albums/{id}",
|
||||
get(albums::get_album).put(albums::update_album),
|
||||
get(albums::get_album)
|
||||
.put(albums::update_album)
|
||||
.delete(albums::delete_album),
|
||||
)
|
||||
.route("/albums/{id}/entries", post(albums::add_entry))
|
||||
.route(
|
||||
|
||||
@@ -4,17 +4,17 @@ use application::{
|
||||
catalog::{
|
||||
CreateStackHandler, DeleteAssetHandler, DeleteStackHandler, DetectLivePhotosHandler,
|
||||
GetAssetHandler, GetDateSummaryHandler, GetStackHandler, GetTimelineHandler,
|
||||
ListDuplicatesHandler, ListStacksHandler, ReadAssetFileHandler, ReadDerivativeHandler,
|
||||
RegisterAssetHandler, ResolveDuplicateHandler, SearchAssetsHandler,
|
||||
UpdateMetadataHandler,
|
||||
ListDuplicatesHandler, ListStacksHandler, ListTrashHandler, ReadAssetFileHandler,
|
||||
ReadDerivativeHandler, RegisterAssetHandler, ResolveDuplicateHandler,
|
||||
RestoreAssetHandler, SearchAssetsHandler, UpdateMetadataHandler,
|
||||
},
|
||||
identity::{
|
||||
GetProfileHandler, LoginUserHandler, LogoutHandler, RefreshTokenHandler,
|
||||
RegisterUserHandler,
|
||||
},
|
||||
organization::{
|
||||
CreateAlbumHandler, GetAlbumHandler, ListAlbumsHandler, ManageAlbumEntriesHandler,
|
||||
TagAssetHandler, UpdateAlbumHandler,
|
||||
CreateAlbumHandler, DeleteAlbumHandler, GetAlbumHandler, ListAlbumsHandler,
|
||||
ManageAlbumEntriesHandler, TagAssetHandler, UpdateAlbumHandler,
|
||||
},
|
||||
processing::{
|
||||
CompleteJobHandler, ConfigurePipelineHandler, EnqueueJobHandler, FailJobHandler,
|
||||
@@ -58,6 +58,8 @@ pub struct CatalogHandlers {
|
||||
pub read_derivative: Arc<ReadDerivativeHandler>,
|
||||
pub register_asset: Arc<RegisterAssetHandler>,
|
||||
pub delete_asset: Arc<DeleteAssetHandler>,
|
||||
pub restore_asset: Arc<RestoreAssetHandler>,
|
||||
pub list_trash: Arc<ListTrashHandler>,
|
||||
pub search_assets: Arc<SearchAssetsHandler>,
|
||||
pub list_duplicates: Arc<ListDuplicatesHandler>,
|
||||
pub resolve_duplicate: Arc<ResolveDuplicateHandler>,
|
||||
@@ -71,6 +73,7 @@ pub struct CatalogHandlers {
|
||||
#[derive(Clone)]
|
||||
pub struct OrganizationHandlers {
|
||||
pub create_album: Arc<CreateAlbumHandler>,
|
||||
pub delete_album: Arc<DeleteAlbumHandler>,
|
||||
pub get_album: Arc<GetAlbumHandler>,
|
||||
pub list_albums: Arc<ListAlbumsHandler>,
|
||||
pub manage_album_entries: Arc<ManageAlbumEntriesHandler>,
|
||||
|
||||
Reference in New Issue
Block a user