Commit Graph

32 Commits

Author SHA1 Message Date
c251a5c41f perf: concurrent worker with claim/execute split + graceful shutdown
- JobRepository::claim_next() — atomic SELECT FOR UPDATE SKIP LOCKED +
  UPDATE status=processing in one query, no duplicate claims
- ExecutePipelineHandler skips start() for already-claimed jobs
- Sweep spawns N concurrent tasks via JoinSet, claims are fast+sequential,
  execution is slow+concurrent
- Graceful shutdown: stop claiming, await all in-flight JoinSet tasks
- WORKER_CONCURRENCY env (default: CPU cores)
- DB_MAX_CONNECTIONS env (default: 20, was hardcoded 10)
- VolumeFileResolver impl for InMemoryFileStorage (test fix)
2026-06-01 02:14:44 +02:00
0077caa743 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
2026-06-01 01:57:53 +02:00
957737ac9b 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
2026-06-01 01:35:43 +02:00
7b5bb66b37 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.
2026-05-31 23:06:25 +02:00
bcaf49cc81 perf: scale fixes for 1M+ photo libraries
Indexes: share_targets.target_id, duplicate_groups.status,
GIN on stacks members + duplicate candidates JSONB,
composite (owner_user_id, created_at DESC) on assets.

N+1 elimination: batch metadata loading via find_by_assets(ids)
using WHERE asset_id = ANY($1), used in timeline + sidecar export.

Visibility: cache find_targets_for_user per request via OnceCell,
extract filter_visible helper to reduce duplication.

Streaming: FileStoragePort.open_file() returns (DataStream, u64),
LocalFileStorage uses ReaderStream instead of loading full file.
serve_file/serve_derivative use Body::from_stream().

Unbounded queries: sidecar full_export/import batched in 500-row
chunks instead of u32::MAX. find_unresolved paginated with
limit/offset. list_duplicates API accepts pagination params.
2026-05-31 22:40:25 +02:00
c6f82090d2 feat: auth hardening + codebase quality sweep
Refresh tokens: RefreshToken entity, PostgresRefreshTokenRepository,
login returns refresh token, POST /auth/refresh (rotation), POST /auth/logout,
JWT expiry 24h→1h, configurable via with_expiry().

Route protection: require_auth middleware on protected routes,
public routes split (register, login, refresh, sharing/access).

Authorization: caller_id added to ReadAssetFileQuery, ReadDerivativeQuery,
GetStackQuery, DeleteStackCommand with ownership checks. Admin-only gates
on processing, storage, sidecar, duplicates handlers.

Quality fixes: visibility filtering bypass in search(), unwrap panics in
date parsing, DRY auth header parsing, centralized parsers module,
email validation via email_address crate, value objects (Username, MimeType,
RelativePath), domain events (UserCreated, UserDeleted, AlbumCreated,
TagCreated, DuplicateDetected), postgres error mapping for constraint
violations, OptionExt::or_not_found helper, in_memory_repo! macro,
GetStackQuery moved to queries, album add_entry 200→201.
2026-05-31 22:26:02 +02:00
d379f3d3c8 refactor: code smell fixes — tests, events, naming
- Tests for ExecutePipelineHandler (happy path, fallback, disabled skip, failure retry, not found)
- Tests for ProcessNextJobHandler (empty queue, process, drain multiple)
- DerivativeGenerated domain event + event-payload mapping + event_store aggregate
- Renamed event-payload → adapters-event-payload, event-transport → adapters-event-transport
2026-05-31 21:00:50 +02:00
35d5baf7be feat: thumbnail generator plugin with configurable size/format
- ThumbnailGeneratorPort in domain (bytes + config → resized bytes)
- adapters-thumbnail: ImageThumbnailGenerator using image crate
- ThumbnailGeneratorPlugin reads width/height/format/profile from step config
- PostgresDerivativeRepository + 012_derivatives migration
- Seeded in extract_metadata pipeline as step 2 (300x300 webp)
- Standalone generate_derivative pipeline for on-demand use
2026-05-31 20:44:55 +02:00
45669ec848 feat: real EXIF extraction via adapters-exif crate
- MetadataExtractorPort in domain (bytes → StructuredData)
- adapters-exif: NomExifExtractor using nom-exif, handles EXIF + TrackInfo
- Worker's MetadataExtractorPlugin delegates to port, no longer knows nom-exif
- Filters noisy binary tags (U8Array, Undefined, Unknown)
2026-05-31 20:28:50 +02:00
d1c7243f5b feat: seed default plugins/pipelines, auto-enqueue jobs on asset ingest
- Migration seeds metadata_extractor, sidecar_sync, no_op plugins
- Pipelines: extract_metadata → metadata_extractor, sync_sidecar → sidecar_sync
- Worker reacts to AssetIngested → enqueues ExtractMetadata job
- Worker reacts to SidecarSyncRequested → enqueues SyncSidecar job
- Closes the ingest-to-processing loop end-to-end
2026-05-31 20:12:42 +02:00
0b2237860e refactor: introduce IngestTransaction port to reduce IngestAssetHandler from 7 to 4 ports 2026-05-31 18:44:51 +02:00
aa09aec66b feat: event store — persist domain events to Postgres event_log table via composite publisher 2026-05-31 18:36:10 +02:00
0e9911ebfc feat: event infrastructure — payload, transport, NATS adapter
- EventPublisher now takes &DomainEvent (11 call sites + 3 impls updated)
- EventEnvelope + EventConsumer port in domain
- event-payload: serializable DomainEvent mirror with subject routing
- event-transport: generic Transport/MessageSource traits, publisher/consumer adapters
- adapters-nats: JetStream publish + durable pull consumer
2026-05-31 11:50:16 +02:00
dacfc3d453 feat: worker plugin system — domain ports, pipeline executor, built-in plugins
- PluginExecutor + PluginRegistry ports in domain
- ExecutePipelineCommand orchestrates job→pipeline→plugin steps
- ProcessNextJobCommand polls + executes next queued job
- InMemoryPluginRegistry, NoOp/MetadataExtractor/SidecarSync plugins
- Worker main rewritten with poll loop, factories module for DI
- Deleted template job/runner/jobs remnants
2026-05-31 11:35:05 +02:00
34b231a8f6 refactor: move business logic out of presentation — ReadAssetFile, checksum, auth checks, MetadataValue conversions 2026-05-31 06:10:07 +02:00
c2ebca0da0 style: cargo fmt --all 2026-05-31 05:31:42 +02:00
de93373b43 refactor: restructure domain crate by bounded context 2026-05-31 04:44:48 +02:00
2b62d1ec81 fix: resolve clippy warnings in share_link, invite_code, asset_stack 2026-05-31 04:27:03 +02:00
b67e595280 domain: add Processing entities and ports (Job, JobBatch, Plugin, Pipeline) 2026-05-31 03:35:41 +02:00
ee79be0351 domain: add Sidecar Sync entities and ports (SidecarRecord, SidecarConfig, SidecarWriterPort) 2026-05-31 03:34:04 +02:00
ba53e0fa70 domain: add Sharing entities and ports (ShareScope, ShareTarget, ShareLink, InviteCode, VisibilityFilter) 2026-05-31 03:33:00 +02:00
1d3060fa12 domain: add Organization entities and ports (Album, Tag, Collection, FilterCriteria) 2026-05-31 03:31:33 +02:00
ccb61b72d7 domain: add Media Catalog ports and MetadataResolver service 2026-05-31 03:27:48 +02:00
147206d8a5 domain: add Media Catalog entities (Asset, Metadata, Stack, Derivative, Duplicate) 2026-05-31 03:27:41 +02:00
ed6eb0c28a domain: add Storage ports (BYOS, Quota, LibraryPath) and QuotaChecker service 2026-05-31 03:23:38 +02:00
3c5c4ed9b1 domain: add Storage & Sources entities (StorageVolume, LibraryPath, IngestSession, Quota) 2026-05-31 03:23:34 +02:00
04811ff436 domain: add PermissionChecker service with additive role evaluation 2026-05-31 03:20:28 +02:00
cebb4cdaf1 domain: add Identity & Access ports (UserRepo, RoleRepo, GroupRepo, EventPublisher) 2026-05-31 03:20:22 +02:00
656da7e945 domain: add Identity & Access entities (User, Role, Permission, Group) 2026-05-31 03:20:18 +02:00
aa432e6594 domain: add domain errors, events, and services module scaffold 2026-05-31 03:16:40 +02:00
3571c94bec domain: add cross-cutting value objects (SystemId, DateTimeStamp, Checksum, StructuredData) 2026-05-31 03:16:28 +02:00
f9cb142c3b init: scaffold from k-template with postgres + worker 2026-05-31 03:08:38 +02:00