Commit Graph

57 Commits

Author SHA1 Message Date
cameron b9175e2718 image: add xlarge (4096px) on-demand preview tier
New `PhotoSize::XLarge` variant sits between `Large` (2048px) and
`Full` (original). On-demand generated and disk-cached at
`_xlarge/<hash>.jpg`, same waterfall as `Large` (embedded RAW preview
→ ffmpeg → image crate). Sources below 4096px serve at native size.

Reduces decoded bitmap memory from ~192MB (48MP full) to ~64MB for
the mobile viewer's zoom tier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 15:33:03 -04:00
Cameron Cordes 19798184f0 image: add on-demand size=large preview tier (~2048px JPEG q85)
Adds a third PhotoSize between Thumb (200px) and Full (original). The
viewer placeholder and map callout previously upscaled a 200px thumb
into a full-screen / full-width view, which looked visibly blocky on
3× devices. The new tier is generated on-demand, disk-cached, and
served via the existing /image endpoint.

Storage layout mirrors the Thumb branch's lookup chain:
  1. hash-keyed: <thumbs>/_large/<hash[..2]>/<hash>.jpg (shared across
     libraries when content_hash is known)
  2. library-scoped legacy: <thumbs>/_large/<lib_id>/<rel_path>

Generation pipeline mirrors generate_image_thumbnail:
  - RAW: decode the embedded JPEG preview, apply EXIF orientation,
         resize to 2048-long-edge, encode JPEG q85
  - HEIC/HEIF: ffmpeg with scale + q:v 5 (≈ q85)
  - everything else: image crate decode + thumbnail() + JpegEncoder
Never upscales — sources below the 2048 cap re-encode at native size.

Handler offloads decode/resize to web::block to keep the actix worker
free (a 24MP source takes 100–500ms). Writes via tempfile+rename so
concurrent readers can't observe a half-written JPEG. On any
generation failure, falls through to the Full branch (which itself
serves the RAW embedded preview for unrenderable RAW containers).

Video requests for size=large fall back to the existing thumb pipeline
since there's no useful 2048px video tier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:14:49 -04:00
Cameron Cordes 832b50d587 image_exif: manual date_taken override (set/clear endpoints)
Add `POST /image/exif/date` and `POST /image/exif/date/clear` so an
operator can correct a row whose canonical-date waterfall landed on the
wrong value (camera clock reset, fs_time fallback for a copied-from-
backup file, etc). New `original_date_taken` / `original_date_taken_source`
columns snapshot the prior value on first override so revert is lossless.

The waterfall source set is now `'exif' | 'exiftool' | 'filename' | 'fs_time' | 'manual'`.
The existing `idx_image_exif_date_backfill` partial index already filters
to `date_taken IS NULL OR date_taken_source = 'fs_time'`, so manual rows
are naturally excluded from the per-tick drain — no index change needed.

`ExifMetadata` now exposes `date_taken_source` + originals so a UI can
render "manually set; was X via filename".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:26:43 -04:00
Cameron Cordes 7584cd8792 duplicates: perceptual hash + soft-mark resolution + upload 409
Adds pHash + dHash columns alongside the existing blake3 content_hash so
near-duplicates (re-encoded, resized, format-converted copies) become
queryable. /duplicates/{exact,perceptual} return groups; /duplicates/
{resolve,unresolve} flip a duplicate_of_hash soft-mark on losing rows
and union perceptual-only tag sets onto the survivor. The default
/photos listing filters duplicate_of_hash IS NULL so demoted siblings
stop cluttering the grid; include_duplicates=true opts back in for
Apollo's review modal. Upload now hashes bytes pre-write and returns
409 with the canonical sibling when a file's bytes already exist.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 17:36:01 -04:00
Cameron Cordes 7621282419 Thumb orientation + library filter on /photos/exif
Two follow-ups on the same feature branch:

1. Bake EXIF orientation into generated thumbnails. The `image` crate
   doesn't apply Orientation on load, and `save_with_format(..Jpeg)`
   drops EXIF — so portrait phone shots ended up sideways in any client
   that displays the cached thumb directly (no EXIF tag for the browser
   to compensate from). New `exif::read_orientation` reads the tag
   cheaply (no full EXIF parse) and `exif::apply_orientation` does the
   rotate/flip via image's existing `rotate90/180/270` + `fliph/flipv`.
   Applied in both branches of `generate_image_thumbnail` (RAW embedded-
   JPEG path and the regular `image::open` path). Existing thumbnails
   in the cache are still wrong-orientation; wipe the thumb dir or run
   a one-off backfill once this lands.

2. Optional `library` query param on `/photos/exif`. Accepts numeric id
   or name (same shape as `/image?library=...`), resolved via the
   existing `resolve_library_param` helper so a bad value 400s before
   we touch the DAO. Filter is applied post-query in the handler
   rather than pushed into `query_by_exif` to keep the DAO trait
   (and its test mocks) unchanged. Cheap enough at typical library
   counts; can be moved into SQL later if it ever isn't.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 17:29:36 -04:00
Cameron Cordes c6f82ebaba Batch EXIF endpoint: GET /photos/exif
Adds a single round-trip projection of `image_exif` for every photo whose
`date_taken` falls in `[date_from, date_to]`. Wraps the existing
`ExifDao::query_by_exif` DAO method which already handles the SQL filter
in one query against the covering index — the only missing piece was
HTTP plumbing.

Designed for window-scoped consumers like Apollo's photo-to-track
matcher, which currently does N+1 (one `/photos` listing + one
`/image/metadata` per photo). Because `/image/metadata` serializes on
`Data<Mutex<dyn ExifDao>>`, that pattern can take 10s+ for windows with
hundreds of photos. The new endpoint takes one mutex acquisition for
the whole batch.

Response shape:
  { photos: [
      { file_path, library_id, library_name,
        camera_model, width, height,
        gps_latitude, gps_longitude, date_taken } ],
    total: N }

Two notes on scope:
- Photos with NULL `date_taken` are excluded by `query_by_exif`'s
  semantics. Filename-extracted dates are not synthesized here; rare
  callers that need that fallback can still hit `/image/metadata`.
- GPS columns are stored as f32 in image_exif to keep row size small;
  the JSON shape widens to f64 so clients don't have to know about the
  on-disk precision.

Library names are pre-mapped from `app_state.libraries` once and
stamped on each row, avoiding an O(rows × libraries) linear scan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 16:38:53 -04:00
Cameron 586b735af5 feat: include per-photo library id in /photos response
Adds a parallel `photo_libraries: Vec<i32>` array alongside `photos`
in `PhotosResponse` so clients can render per-thumbnail badges.
Populated with the scoped library id at the two main return sites;
left empty for `/favorites` since favorites are library-agnostic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 01:55:07 +00:00
Cameron c2ee3996be chore: apply cargo fmt + clippy cleanup across crate
Silence forward-looking dead_code on unused DAO modules, annotate
individual placeholder items, rewrite tautological assert!(true/false)
in token tests as panic! arms, and pick up fmt drift.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 01:55:07 +00:00
Cameron e6ee38edec fix: resolve media across libraries for video, metadata, and insights
The /video/generate and /image/metadata handlers assumed files live under
the resolved library only, which broke when a mobile client passed no
library (union mode) but the file lived in a non-primary library. Both
now fall back to scanning every configured library for an existing file.

InsightGenerator held a single base_path, so vision-model loads and
filename-date fallbacks failed for non-primary libraries. It now takes
Vec<Library> and probes each root in resolve_full_path.

/image/metadata responses now carry library_id/library_name so the
mobile viewer can surface which library a file belongs to.

Thumbnail generation at startup is now spawned on a background thread
so the HTTP server can accept traffic while large libraries backfill.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 01:55:07 +00:00
Cameron c01a0479b7 fix: honor library param in /image, /photos, /memories
The Phase 3 plumbing accepted `library=` but didn't actually route
requests through the scoped library once it was resolved. Three
concrete bugs surfaced when testing against a second mounted library:

- `/image` always resolved paths against AppState.base_path (primary),
  so thumbnails for non-primary libraries 400'd when their rel_paths
  didn't exist under primary. Now resolves against the scoped library
  and defaults to primary when the param is omitted.

- `/memories` walked the scoped library correctly but its helper
  functions hardcoded `library_id: PRIMARY_LIBRARY_ID` on every
  MemoryItem, causing clients to route thumbnails back to primary
  regardless of which library the memory actually came from.

- `/photos` non-recursive listing delegated to a `RealFileSystem`
  constructed from AppState.base_path at startup, so walks always
  hit primary even when `library=2` was passed. The non-primary
  path now uses list_files against the scoped library's root;
  primary still goes through FileSystemAccess to preserve the
  existing test mock plumbing.

Also adds `library` to ThumbnailRequest so the /image query param
is actually parsed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 01:55:07 +00:00
Cameron 48e5de6eab feat: add GET /libraries and library query param plumbing
New `/libraries` endpoint returns configured libraries so clients can
discover them. `FilesRequest` and `MemoriesRequest` gain an optional
`library` param (accepts name or numeric id). Unknown values are
rejected with 400; absent values span all libraries. `/memories`
now scopes its filesystem walk + EXIF query to the resolved library.
`MemoryItem` carries `library_id` so union-mode clients can render a
per-item source badge.

Behavior is unchanged in single-library mode: omitting `library` still
returns results from the primary library, which is the only one
configured until a second row is added to the libraries table.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 01:55:07 +00:00
Cameron da16fddce3 Address path traversal and other security fixes 2026-04-10 14:58:57 -04:00
Cameron 0d05033b38 Add comprehensive testing for preview clip and status handling
- Implement unit tests for PreviewClipRequest/PreviewStatusRequest serialization and deserialization.
- Add tests for PreviewDao (insert, update, batch retrieval, and status-based queries).
- Extend Actix-web integration tests for `/video/preview/status` endpoint scenarios.
- Introduce in-memory TestPreviewDao for mock database interactions.
- Update README with new config parameters for preview clips.
2026-02-26 10:06:21 -05:00
Cameron 19c099360e Add VideoWall feature: server-side preview clip generation and mobile grid view
Backend (Rust/Actix-web):
- Add video_preview_clips table and PreviewDao for tracking preview generation
- Add ffmpeg preview clip generator: 10 equally-spaced 1s segments at 480p with CUDA NVENC auto-detection
- Add PreviewClipGenerator actor with semaphore-limited concurrent processing
- Add GET /video/preview and POST /video/preview/status endpoints
- Extend file watcher to detect and queue previews for new videos
- Use relative paths consistently for DB storage (matching EXIF convention)

Frontend (React Native/Expo):
- Add VideoWall grid view with 2-3 column layout of looping preview clips
- Add VideoWallItem component with ActiveVideoPlayer sub-component for lifecycle management
- Add useVideoWall hook for batch status polling with 5s refresh
- Add navigation button in grid header (visible when videos exist)
- Use TextureView surface type to fix Android z-ordering issues
- Optimize memory: players only mount while visible via FlatList windowSize
- Configure ExoPlayer buffer options and caching for short clips
- Tap to toggle audio focus, long press to open in full viewer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 19:40:17 -05:00
Cameron 1d2f4e3441 Add circular thumbnail creation for Map view 2026-01-26 20:04:14 -05:00
Cameron 073b5ed418 Added gps-summary endpoint for Map integration 2026-01-26 11:58:24 -05:00
Cameron be483c9c1a Add database optimizations for photo search and pagination
Implement database-level sorting with composite indexes for efficient date and tag queries. Add pagination metadata support and optimize tag count queries using batch processing.
2026-01-18 19:17:10 -05:00
Cameron af35a996a3 Cleanup unused message embedding code
Fixup some warnings
2026-01-14 13:33:36 -05:00
Cameron d86b2c3746 Add Google Takeout data import infrastructure
Implements Phase 1 & 2 of Google Takeout RAG integration:
- Database migrations for calendar_events, location_history, search_history
- DAO implementations with hybrid time + semantic search
- Parsers for .ics, JSON, and HTML Google Takeout formats
- Import utilities with batch insert optimization

Features:
- CalendarEventDao: Hybrid time-range + semantic search for events
- LocationHistoryDao: GPS proximity with Haversine distance calculation
- SearchHistoryDao: Semantic-first search (queries are embedding-rich)
- Batch inserts for performance (1M+ records in minutes vs hours)
- OpenTelemetry tracing for all database operations

Import utilities:
- import_calendar: Parse .ics with optional embedding generation
- import_location_history: High-volume GPS data with batch inserts
- import_search_history: Always generates embeddings for semantic search

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-05 14:50:49 -05:00
Cameron 4d9addaf22 Add filename date to metadata if available 2025-12-29 21:54:25 -05:00
Cameron 2c52cffd65 Implement critical security improvements for authentication
This commit addresses several security vulnerabilities in the authentication
and authorization system:

1. JWT Encoding Panic Fix (Critical)
   - Replace .unwrap() with proper error handling in JWT token generation
   - Prevents server crashes from encoding failures
   - Returns HTTP 500 with error logging instead of panicking

2. Rate Limiting for Login Endpoint (Critical)
   - Add actix-governor dependency (v0.5)
   - Configure rate limiter: 2 requests/sec with burst of 5
   - Protects against brute-force authentication attacks

3. Strengthen Password Requirements
   - Minimum length increased from 6 to 12 characters
   - Require uppercase, lowercase, numeric, and special characters
   - Add comprehensive validation with clear error messages

4. Fix Token Parsing Vulnerability
   - Replace unsafe split().last().unwrap_or() pattern
   - Use strip_prefix() for proper Bearer token validation
   - Return InvalidToken error for malformed Authorization headers

5. Improve Authentication Logging
   - Sanitize error messages to avoid leaking user existence
   - Change from "User not found or incorrect password" to "Failed login attempt"

All changes tested and verified with existing test suite (65/65 tests passing).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 23:53:54 -05:00
Cameron eb8e08b9ff Add EXIF search infrastructure (Phase 1 & 2)
Implements foundation for EXIF-based photo search capabilities:

- Add geo.rs module with GPS distance calculations (Haversine + bounding box)
- Extend FilesRequest with EXIF search parameters (camera, GPS, date, media type)
- Add MediaType enum and DateTakenAsc/DateTakenDesc sort options
- Create date_taken index migration for efficient date queries
- Implement ExifDao methods: get_exif_batch, query_by_exif, get_camera_makes
- Add FileWithMetadata struct for date-aware sorting
- Implement date sorting with filename extraction fallback
- Make extract_date_from_filename public for reuse

Next: Integrate EXIF filtering into list_photos() and enhance get_all_tags()
2025-12-18 09:34:07 -05:00
Cameron e4d988a9fd Cargo formatting 2025-12-17 22:10:03 -05:00
Cameron 4082f1fdb8 Add Exif storing and update to Metadata endpoint 2025-12-17 16:55:48 -05:00
Cameron 273b877e16 Update to Rust 2024 edition
Formatted code.
2025-09-01 13:36:27 -04:00
Cameron b3a885de28 Run cargo fmt 2025-08-08 15:06:43 -04:00
Cameron e5afdd909b Serve video gifs when requested 2025-07-02 15:48:49 -04:00
Cameron 5b17fba51f Add sorting by a File's Tag Count 2025-05-17 13:41:14 -04:00
Cameron 9a32a1cfe7 Allow for excluding certain tags from a file search 2024-11-23 20:21:19 -05:00
Cameron 6986540295 Add sorting shuffle, and name asc/desc 2024-11-23 19:13:25 -05:00
Cameron Cordes 9a486b3f66 Use PhotoSize enum in file requests 2024-06-30 17:49:55 -04:00
Cameron Cordes 05a56ba0bd Fix Recursive searching with tags including Any and All filter modes 2024-03-09 13:11:55 -05:00
Cameron Cordes ef39359862 Add basic recursive tag searching support based on the search path 2024-03-07 17:56:50 -05:00
Cameron Cordes 2b9a741642 Account for current path for tag counts
Fix tag unit tests
2023-12-02 13:38:24 -05:00
Cameron Cordes a9109fc52d Add the ability to query files by tags independent of file path 2023-03-25 20:52:51 -04:00
Cameron Cordes 5a2dce85e8 Fix searching by tags and fixed All FilterMode
Manually parsing the tag_ids for the file filtering isn't amazing, but
this works in a more friendly format.

Also the All filter mode was set up in the wrong direction instead of
checking that the file had ALL the tag ids provided, it checked that all
the tag-ids were on a file, which is too restrictive and wouldn't show
many files. Perhaps an ONLY option could exist for being even more
specific.
2023-03-22 13:58:14 -04:00
Cameron Cordes 7d05c9b8bf Add filtering by Tag to /images endpoint 2023-03-20 08:14:12 -04:00
Cameron Cordes 68bfcbf85f Update and Migrate Diesel to 2.0
Almost have tag support working, still figuring out how to get photo
tags.
2023-03-18 14:43:41 -04:00
Cameron Cordes c8cae28c9f Merge branch 'master' into feature/tagging 2022-03-17 21:53:17 -04:00
Cameron Cordes 69fe307516 Update to Actix 4
Core Repos/ImageApi/pipeline/pr-master There was a failure building this commit
2022-03-01 20:38:41 -05:00
Cameron Cordes 8939ffbaf5 Create Tag tables and Add Tag endpoint 2021-10-11 21:52:06 -04:00
Cameron Cordes d6e4a01c88 Move list_photos to files module
Core Repos/ImageApi/pipeline/pr-master This commit looks good
Added some tests, refactored the error handling/logging, and refactored
the extension tests.
2021-10-07 20:32:36 -04:00
Cameron Cordes 2c50b4ae2f Add anyhow, Improve Auth token code
Moved test helper code to its own module.
2021-10-07 20:32:36 -04:00
Cameron Cordes 8622500a2f Use file metadata to sort files and directories 2021-06-02 08:32:20 -04:00
Cameron Cordes 9d823fdc51 Create file metadata endpoint
This allows retrieving create/modify date as well as file size for any
file in the BASE_PATH.
2021-05-19 08:53:20 -04:00
Cameron Cordes f9983240df Use log crate for logging instead of println
Core Repos/ImageApi/pipeline/head This commit looks good
Core Repos/ImageApi/pipeline/pr-master This commit looks good
2021-02-24 21:26:11 -05:00
Cameron Cordes fae7b2a962 Add tests for JWT decoding 2021-02-18 20:31:03 -05:00
Cameron Cordes b595bdd642 Add VideoStreamManager for keeping track of active streams
The stream manager should help prevent zombie processes and can later be
used for stopping video streams if the user exits the video before
finishing for example.
2021-02-09 21:30:27 -05:00
Cameron Cordes 659bad02c9 Fix clippy warnings
Core Repos/ImageApi/pipeline/head This commit looks good
2021-02-03 17:21:23 -05:00
Cameron Cordes f20a8a5842 Update dependencies, disable registration and improve path handling 2021-02-02 13:57:51 -05:00