feat: frontend-ready backend — pagination, auto-derivatives, list endpoints, bulk ops, OpenAPI

Pagination: count_by_owner + count_search on AssetRepository,
timeline/search return real total count (not page len).

Auto-derivatives: worker enqueues GenerateDerivative when
ExtractMetadata job completes, closing the upload→thumbnail gap.

List endpoints: GET /albums, GET /stacks with user scoping.
ListAlbumsHandler, ListStacksHandler, find_by_owner on AssetStackRepository.

Tag filtering: tag_name field on AssetFilters, JOIN asset_tags+tags
in postgres search/count queries.

Bulk operations: POST /assets/bulk-delete, POST /assets/bulk-tag.

Album update: PUT /albums/{id} with UpdateAlbumHandler (title, description).

OpenAPI: utoipa annotations on all 47 endpoints + all request/response
schemas registered. Scalar UI at /scalar covers full API.
This commit is contained in:
2026-05-31 23:06:25 +02:00
parent bcaf49cc81
commit 7b5bb66b37
33 changed files with 1048 additions and 72 deletions

View File

@@ -88,6 +88,9 @@ pub fn build(
));
let get_stack = Arc::new(GetStackHandler::new(stack_repo.clone()));
let delete_stack = Arc::new(DeleteStackHandler::new(stack_repo.clone()));
let list_stacks = Arc::new(application::catalog::ListStacksHandler::new(
stack_repo.clone(),
));
let detect_live_photos = Arc::new(DetectLivePhotosHandler::new(asset_repo.clone(), stack_repo));
let register_asset = Arc::new(RegisterAssetHandler::new(
@@ -112,5 +115,6 @@ pub fn build(
get_stack,
delete_stack,
detect_live_photos,
list_stacks,
}
}

View File

@@ -4,7 +4,8 @@ use adapters_postgres::{
PgPool, PostgresAlbumRepository, PostgresAssetRepository, PostgresTagRepository,
};
use application::organization::{
CreateAlbumHandler, GetAlbumHandler, ManageAlbumEntriesHandler, TagAssetHandler,
CreateAlbumHandler, GetAlbumHandler, ListAlbumsHandler, ManageAlbumEntriesHandler,
TagAssetHandler, UpdateAlbumHandler,
};
use presentation::state::OrganizationHandlers;
@@ -15,13 +16,17 @@ pub fn build(pool: &PgPool) -> OrganizationHandlers {
let create_album = Arc::new(CreateAlbumHandler::new(album_repo.clone()));
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 manage_album_entries = Arc::new(ManageAlbumEntriesHandler::new(album_repo));
let tag_asset = Arc::new(TagAssetHandler::new(asset_repo, tag_repo));
OrganizationHandlers {
create_album,
get_album,
list_albums,
manage_album_entries,
update_album,
tag_asset,
}
}