feat: frontend MVP — auth, timeline, upload, albums, admin, image viewer
Backend: - user roles (DB + JWT + first-user-is-admin) - volume-aware file resolver (multi-volume asset serving) - directory scanner uses volume URI directly - date-summary endpoint (capture date from EXIF) - timeline ordered by capture date - list endpoints: volumes, plugins, pipelines, library paths - delete endpoints: volumes, library paths - configurable upload body limit (MAX_UPLOAD_BYTES) Frontend: - auth: login/register, token refresh, role-based admin gate - timeline: date-grouped grid, infinite scroll, date scrubber - image viewer: fullscreen zoom/pan/pinch, metadata sidebar - upload: drag-drop, sequential upload, progress tracking - albums: create, add/remove photos, asset picker dialog - admin: storage (import library), jobs (pagination, error details), plugins (list + toggle), pipelines, sidecars, duplicates - multi-select mode with add-to-album action - TanStack Query for all data fetching
This commit is contained in:
32
crates/application/src/catalog/queries/get_date_summary.rs
Normal file
32
crates/application/src/catalog/queries/get_date_summary.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use domain::{errors::DomainError, ports::AssetRepository, value_objects::SystemId};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct GetDateSummaryQuery {
|
||||
pub owner_id: SystemId,
|
||||
}
|
||||
|
||||
pub struct DateSummaryEntry {
|
||||
pub date: chrono::NaiveDate,
|
||||
pub count: u64,
|
||||
}
|
||||
|
||||
pub struct GetDateSummaryHandler {
|
||||
asset_repo: Arc<dyn AssetRepository>,
|
||||
}
|
||||
|
||||
impl GetDateSummaryHandler {
|
||||
pub fn new(asset_repo: Arc<dyn AssetRepository>) -> Self {
|
||||
Self { asset_repo }
|
||||
}
|
||||
|
||||
pub async fn execute(
|
||||
&self,
|
||||
query: GetDateSummaryQuery,
|
||||
) -> Result<Vec<DateSummaryEntry>, DomainError> {
|
||||
let rows = self.asset_repo.date_summary(&query.owner_id).await?;
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(|(date, count)| DateSummaryEntry { date, count })
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod get_asset;
|
||||
pub mod get_date_summary;
|
||||
pub mod get_stack;
|
||||
pub mod get_timeline;
|
||||
pub mod list_stacks;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
ports::{AssetRepository, DataStream, FileStoragePort},
|
||||
ports::{AssetRepository, DataStream, VolumeFileResolver},
|
||||
value_objects::SystemId,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
@@ -20,17 +20,17 @@ pub struct AssetFileResult {
|
||||
|
||||
pub struct ReadAssetFileHandler {
|
||||
asset_repo: Arc<dyn AssetRepository>,
|
||||
file_storage: Arc<dyn FileStoragePort>,
|
||||
volume_resolver: Arc<dyn VolumeFileResolver>,
|
||||
}
|
||||
|
||||
impl ReadAssetFileHandler {
|
||||
pub fn new(
|
||||
asset_repo: Arc<dyn AssetRepository>,
|
||||
file_storage: Arc<dyn FileStoragePort>,
|
||||
volume_resolver: Arc<dyn VolumeFileResolver>,
|
||||
) -> Self {
|
||||
Self {
|
||||
asset_repo,
|
||||
file_storage,
|
||||
volume_resolver,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +46,11 @@ impl ReadAssetFileHandler {
|
||||
}
|
||||
|
||||
let (stream, size) = self
|
||||
.file_storage
|
||||
.open_file(&asset.source_reference.relative_path)
|
||||
.volume_resolver
|
||||
.open_by_volume(
|
||||
&asset.source_reference.volume_id,
|
||||
&asset.source_reference.relative_path,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let filename = asset
|
||||
|
||||
Reference in New Issue
Block a user