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:
2026-06-01 01:35:43 +02:00
parent 49f77a78b9
commit 957737ac9b
101 changed files with 4679 additions and 109 deletions

View File

@@ -6,6 +6,7 @@ pub struct UserResponse {
pub id: Uuid,
pub username: String,
pub email: String,
pub role: String,
pub created_at: DateTime<Utc>,
}
@@ -22,6 +23,7 @@ impl UserResponse {
id: *user.id.as_uuid(),
username: user.username.clone(),
email: user.email.to_string(),
role: user.role.clone(),
created_at: user.created_at,
}
}
@@ -34,6 +36,7 @@ pub struct AlbumResponse {
pub description: String,
pub creator_id: Uuid,
pub asset_count: usize,
pub asset_ids: Vec<Uuid>,
pub created_at: DateTime<Utc>,
}
@@ -45,6 +48,7 @@ impl AlbumResponse {
description: album.description.clone(),
creator_id: *album.creator_user_id.as_uuid(),
asset_count: album.asset_count(),
asset_ids: album.entries.iter().map(|e| *e.asset_id.as_uuid()).collect(),
created_at: *album.created_at.as_datetime(),
}
}
@@ -84,6 +88,17 @@ impl AssetResponse {
}
}
#[derive(Debug, serde::Serialize, utoipa::ToSchema)]
pub struct DateSummaryResponse {
pub dates: Vec<DateCountEntry>,
}
#[derive(Debug, serde::Serialize, utoipa::ToSchema)]
pub struct DateCountEntry {
pub date: String,
pub count: u64,
}
#[derive(Debug, serde::Serialize, utoipa::ToSchema)]
pub struct TimelineResponse {
pub assets: Vec<AssetResponse>,
@@ -349,6 +364,7 @@ pub struct JobResponse {
pub status: String,
pub priority: u32,
pub created_at: DateTime<Utc>,
pub error_message: Option<String>,
}
impl JobResponse {
@@ -359,6 +375,7 @@ impl JobResponse {
status: format!("{:?}", job.status),
priority: job.priority,
created_at: *job.created_at.as_datetime(),
error_message: job.error_message.clone(),
}
}
}