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

@@ -126,6 +126,7 @@ pub struct User {
pub username: String,
pub email: Email,
pub password_hash: PasswordHash,
pub role: String,
pub created_at: DateTime<Utc>,
}
@@ -136,9 +137,14 @@ impl User {
username: username.into(),
email,
password_hash,
role: "user".to_string(),
created_at: Utc::now(),
}
}
pub fn is_admin(&self) -> bool {
self.role == "admin"
}
}
// --- RefreshToken ---

View File

@@ -12,6 +12,7 @@ pub trait UserRepository: Send + Sync {
async fn find_by_username(&self, username: &str) -> Result<Option<User>, DomainError>;
async fn save(&self, user: &User) -> Result<(), DomainError>;
async fn delete(&self, id: &SystemId) -> Result<(), DomainError>;
async fn count(&self) -> Result<u64, DomainError>;
}
// --- RoleRepository ---