Commit Graph
52 Commits
Author SHA1 Message Date
Cameron CordesandClaude Opus 5.5 4feffb62bb fix(memories): sort Month span by time within a day
The Month sort keyed on the display day only. The sort is stable, so a
day's photos kept DB order, merged across libraries, and their times
jumped back and forth. Month now sorts like Week: full date, then
timestamp (undated items last).

The sort moves into sort_memories so it can be tested directly; the new
test covers a multi-library, out-of-order day.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:03:56 -04:00
Cameron CordesandClaude Opus 5.5 e2f33684c8 Dates: serve the display wall clock (wall_clock) to clients
Every payload a client renders a capture time from now carries
`wall_clock`, a naive ISO-8601 string computed by the existing
`date_view::row_display_wall`: the camera's wall clock (true instant +
camera offset; the owner's display zone without an offset), with the
per-source fallback for unconverted rows. Clients render it verbatim and
do no zone math, which is the FileViewer-React #33 / Apollo #37 flip with
the rule kept server-side: the drain's permanent `date_utc IS NULL` floor
(#120: edited/exported files) would otherwise force every client to
carry the per-source legacy conventions forever, and insight prompts
already use the same function, so prompt and UI agree by construction.

- `/image/metadata` (+ the GPS / date / date-clear mutation responses):
  `file_wall_clock` — the row's wall, else the filename-class date, else
  the earliest fs time in the display zone (unindexed files).
- `/memories` items and `/photos/gps-summary` photos: `row_wall_clock`.
- `get_all_with_gps` returns full rows instead of a 7-tuple that dropped
  `date_taken_source`, which unconverted rows need.
- `date_view::owner_display_zone` resolves prefs -> app default per request.

Legacy fields are untouched, so current clients see byte-identical
responses (additive; safe to deploy ahead of the client flip, per #120).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 17:54:46 -04:00
Cameron CordesandClaude Opus 5.5 b6deca2a52 Tracing: DAO layer + handler spans on tracing, drop all Context params (#141)
trace_db_call no longer takes an OpenTelemetry Context: it opens a
db.<query_type>.<operation> span (same name via otel.name, same
db.query_type / db.operation attrs) on the caller's ambient tracing span
and runs the DAO body inside it. The cx / ctx / context parameter is gone
from every DAO trait method, impl and call site, and from the helpers and
background drains that only threaded it.

The remaining request-handler spans (files, tags, faces, image, video,
favorites, duplicates, memories) move to #[instrument]; the
Context::current_with_span / with_remote_span_context plumbing is gone, as
is extract_context_from_request (the tracing-actix-web root span covers
it). otel::set_span_attributes replaces BoxedSpan::set_attributes.

Background-job raw spans (thumbnails, video actors) are left for #142.

tests/otel_db_spans.rs pins name, attributes, status and nesting (across
an instrumented spawn); otel_traceparent now uses an #[instrument]
handler. CLAUDE.md documents the tracing conventions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 17:02:40 -04:00
Cameron Cordes a699c940b8 Prefs: owner defaults (model, backend, persona_id, reel span/length)
Closes #127.

Grows user_ai_prefs with five nullable owner-default columns and makes
them the server-side fallback layer: request param -> stored pref ->
hard default, at insight generation (agentic + standard), all three
insight-chat turn variants, and reel creation.

- PUT /prefs accepts the fields with the existing three-state partial
  update semantics (TriState generalized to TriState<T> for the numeric
  reel_max_segments); validation (canonical BackendKind::parse,
  MemoriesSpan::from_str) runs before any write.
- GET /prefs returns the new fields.
- capture_prefs reads the existing row and carries the AI owner-defaults
  through unchanged (the upsert overwrites all columns), mirroring only
  the effective reel span/segments.
- MemoriesSpan gains Display + FromStr so stored spans round-trip
  losslessly.

1463/0 tests (+27), clippy clean.
2026-09-27 19:48:45 -04:00
Cameron Cordes af87765bbb Switch read paths to the true-instant date contract (ticket #119)
- New src/date_view.rs: pure read-side functions (row_true_instant,
  row_display_wall, row_display_day, filename_true_instant) applying the
  per-source convention table for unconverted rows and the new-contract
  triple for converted rows (ADR-0001).
- /memories: grouping on the row's true instant rendered in the owner's
  display zone (prefs -> app default), not the client device offset.
  SQL is a coarse interval pre-filter over the full years-back window;
  exact month-day/week/month matching across past years happens in Rust
  (memories::day_in_span, sqlite_w_week).
- Reels: ReelSelector::Memories carries an IANA display_tz instead of a
  fixed minute offset; pre-gen params read REEL_PREGEN_DISPLAY_TZ; the
  tz_offset_minutes ledger column stores the zone offset at render time.
- Insights: prompt-facing dates render as the display wall; the
  proximity timestamp and chat context block use the true instant;
  InsightGenerator reads the owner's display zone from prefs per call.
- GpsPhotoSummary carries the new-contract triple alongside the frozen
  legacy date_taken.

Closes #119
2026-09-26 19:28:26 -04:00
Cameron Cordes 3aa6ce314c feat(dates): pre-cutover audit utility for the filename date class (#117)
One-shot, operator-run, strictly read-only utility that lists every
filename-class file with its stored date/source and the date/source/
offset-source the re-based resolver would assign it — the human
spot-check that gates the production backfill drain (#118).

- src/filename_audit.rs: StoredDate/AuditRow/SkippedLibrary; walk +
  PathExcluder (depth-0 guarded) + match_filename_date -> one
  resolve_dates_batch pass with EMPTY priors (mirrors the drain
  exactly); disabled and probe-stale libraries reported as skipped,
  never partially processed; output stable-sorted by (library, path).
- src/bin/filename_audit.rs: operator binary (dotenv, DATABASE_URL,
  EXCLUDED_DIRS, libraries::load_all, format_report).
- src/memories.rs: match_filename_date canonical matcher returning
  FilenameMatch { pattern, date }; extract_date_from_filename is now
  a thin wrapper (byte-for-byte behavior-preserving, verified by
  review).

4 audit tests (inversion + utc_direct + exclusions, offline/disabled
skip, stable sort, report determinism incl. no debug quotes) + 2
matcher tests. 645 lib + 718 integration pass; clippy/fmt clean.
Standards review applied: {:?}->{}, depth-0 walk guard, dead
per-file exclude check removed, unused Default derive removed,
pattern column widened, doc comment reworded.
2026-09-25 18:03:38 -04:00
Cameron Cordes a97bf53603 feat(dates): filename date class — per-pattern conventions + filename-over-EXIF priority (#116)
The filename date class is now first-class (ADR-0001):

- memories::extract_date_from_filename returns FilenameDate:
  Wall(NaiveDateTime) for the screenshot/dash/compact patterns,
  Utc(DateTime<Utc>) for the unix-timestamp patterns. No patterns
  added or removed; every existing pattern carries its convention.
- Waterfall inversion in both resolve_date_taken and
  resolve_dates_batch: an in-class name is the original date of
  record and resolves BEFORE EXIF / container dates are consulted
  (shared filename_class_resolved step; in-class files skip
  exiftool entirely). Wall patterns run the offset cascade over the
  kamadak prior's evidence (empty evidence -> server-zone
  assumption, so a class file always resolves); timestamp patterns
  are true instants (tz_source utc_direct, no offset).
- Legacy date_taken stays byte-identical: wall-as-UTC for wall
  patterns, epoch for timestamp patterns, via
  FilenameDate::legacy_datetime / legacy_timestamp at the seven
  consumer call sites.
- Out-of-class files keep the global EXIF-wins waterfall unchanged.
- CLAUDE.md waterfall section rewritten (step 0 = filename class;
  the EXIF-beats-filename note becomes out-of-class-only).

Tests: five new waterfall-seam tests (wall no-evidence ->
assumed_server; wall outranks EXIF with offset-tag evidence;
wall GPS evidence; timestamp utc_direct; in-class video name
resolves without exiftool) plus enum-shape assertions in every
extractor test. Code-reviewed on both axes (standards: no
violations, 3 of 4 P2 notes applied; spec: all acceptance
criteria verified, no scope creep).

716 integration + 639 lib tests pass; fmt + clippy clean.
2026-09-25 16:54:46 -04:00
Cameron Cordes f707353807 feat: nightly agentic pre-generation of memory reels
Implement end-to-end nightly pre-generation of memory reels with agentic
scripting that grounds narration in calendar, location, messages, and RAG.

Sections A-E from the plan:

A. Extract produce_reel pipeline core from run_reel_job with
   ScripterMode::Fast/Agentic and progress callbacks.

B. Agentic scripter: factor run_readonly_tool_loop from the insight
   generator, build read-only tool gate, prompt builder with GPS, and
   generate_script_agentic with fallback to fast path.

C. Precomputed reels ledger (SQLite table + DAO), GET /reels/precomputed
   handler with validity gate, GET /reels/by-key/{key}/video streaming,
   and normalize_library_key helper.

D. Nightly scheduler: spawn_pregen_scheduler with configurable hour,
   run_pregen_batch (day/week/month spans), pregen_one with dedup and
   disk-check, secs_until_next_run_hour time math.

E. user_ai_prefs passive mirror table + DAO for param capture in
   create_reel_handler and replay in the scheduler.

Also fixes resolve_library_param signature to take &[Library] and adds
resolve_library_param_state wrapper for AppState callers.

New files: migrations/2026-06-13-000000_add_precomputed_reels/,
  migrations/2026-06-13-000010_add_user_ai_prefs/,
  src/database/precomputed_reel_dao.rs,
  src/database/user_ai_prefs_dao.rs
2026-06-13 14:29:34 -04:00
Cameron CordesandClaude Fable 5 e3f731b3b2 Add memory-reel backend: on-demand narrated photo slideshow
New POST /reels + GET /reels/{id} (+ /video) build an MP4 slideshow of a
memory span (day/week/month), narrated by the LLM in a cloned voice.

Pipeline (src/reels/): a selector resolves which photos + reel metadata,
the scripter writes one narration line per photo via a single LLM call
(reusing each photo's cached insight as context — no fresh vision calls,
so reel generation stays off the GPU's vision slot), each line is
synthesized to speech, and the renderer assembles stills + narration via
ffmpeg. Jobs run in the background (mirroring the TTS speech-job
registry) since a reel takes minutes; the finished MP4 is cached on disk
keyed by the selection so a repeat request is instant.

The segment model is media-typed (Photo today) so a video-clip segment
(phase 2) and a nightly pre-render (phase 3) slot in without reworking
the pipeline. Ken Burns motion is implemented but defaulted off pending a
visual check on the GPU box.

Supporting changes:
- memories: extract gather_memory_items() so the reel selector reuses the
  exact window/exclusion/tz/sort logic behind /memories.
- ai::tts: add synthesize_serialized() so reel narration honors the same
  single-GPU permit + write lease as user TTS requests.
- video::ffmpeg: make get_duration_seconds() pub for narration timing.
- AppState: reels_path (REELS_DIRECTORY, defaults beside preview clips).

Pure logic (cache key, script parsing, ffmpeg arg/filter construction,
even sampling, segment timing) is unit-tested (26 tests). The runtime
path (ffmpeg render, TTS, LLM) needs a real run on the GPU host to verify
end-to-end — not exercisable in CI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 22:31:08 -04:00
Cameron CordesandClaude Opus 4.7 d32a7d7c3a memories: restore early-era Snapchat unix-epoch filenames
The recent blanket "snapchat-" prefix denylist (43f8f83) rejected ALL
Snapchat-prefixed filenames from timestamp parsing, which fixed the
sequential-ID false positives but also broke real unix-second
filenames from Snapchat's early era. `Snapchat-1383929602.jpg`
(2013-11-08 16:53:22 UTC) now falls through to fs_time — and on files
with broken filesystem metadata, fs_time pins to 1970.

Replace the blanket prefix denial with a tighter discriminator:
  - exactly 10 captured digits AND timestamp >= 2011-09-23 (Snapchat
    launch) → real unix epoch, accept
  - any other length under this prefix → sequential ID, reject

This keeps the existing rejections intact:
  Snapchat-1021849065.mp4          → 10 digits, 2002 < launch → reject
  Snapchat-1751031586660373917.jpg → 19 digits truncates to 16 → reject
And restores the regression case:
  Snapchat-1383929602.jpg          → 10 digits, 2013 ≥ launch → accept

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 16:22:57 -04:00
Cameron CordesandClaude Opus 4.7 b42acbb3f3 fmt: cargo fmt sweep across drifted files
No behavior change — purely whitespace/line-break cleanup that had
accumulated since the last format run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:42:41 -04:00
Cameron CordesandClaude Opus 4.7 43f8f83d80 memories: deny Snapchat-prefixed filenames from timestamp parsing
Snapchat assigns sequential IDs that happen to overlap real epoch
values, so the 10-16 digit timestamp regex matched and produced
2002-era dates for files actually saved in 2016/2021. The digits
themselves are indistinguishable from a unix timestamp, so we
dispatch on the source-app prefix instead. Case-insensitive,
extensible for future apps that exhibit the same pattern.

Reported cases:
  Snapchat-1021849065.mp4          → 2002-05-19 (actual 2021)
  Snapchat-1751031586660373917.jpg → 2002-09-09 (actual 2016)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:17:40 -04:00
Cameron CordesandClaude Opus 4.7 feaae9b6d3 memories: reject implausible filename-derived timestamps
Filenames like `000227580005.jpg` (film-scan ID) and
`IMG_21323906751390.jpeg` were matched by the 10-16 digit timestamp
regex and resolved to 1970 / 2037, then written into
`image_exif.date_taken` with `source = 'filename'`. EXIF-less
photos showed up under those bogus dates everywhere date_taken is
read.

Two new guards in `extract_date_from_filename`:
- leading zero → reject (real epoch values don't have one at any
  sane resolution).
- resolved year outside [1995, now+1y] → reject.

Both let the date_resolver waterfall fall through to fs_time,
which is a much better proxy for content age than a fake epoch
date. Regression tests cover the two reported filenames.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:02:07 -04:00
Cameron CordesandClaude Opus 4.7 65af7d999e memories: parse filename dates as UTC, not server local
`extract_date_from_filename` was calling `Local::from_local_datetime`
on the parsed YYYY-MM-DD-HH-MM-SS components, then `.timestamp()` was
shifting the result by the SERVER's TZ offset to produce real UTC
seconds. That made filename-sourced timestamps disagree with EXIF-
sourced timestamps by hours: kamadak-exif's `DateTimeOriginal` is a
naive string parsed AS-IF-UTC (the project's load-bearing
"naive local reinterpreted as UTC" convention), and Apollo's photo
matcher re-anchors that naive value through the BROWSER's TZ when
matching to the track. Anything stamped in server-local instead got
double-shifted on its way through the matcher and through any
`formatNaive*` display path on the client.

Visible symptom in the Apollo DETAILS modal: a photo's CURRENT date
read correctly (1:25 AM via exif) while FROM FILENAME read 4 hours
ahead (5:25 AM in EDT) for the same `IMG_20160710_012515.jpg`.

Switch to `Utc::from_utc_datetime` so `.timestamp()` returns the
wall-clock-as-UTC unix seconds — same convention as the EXIF path.
The /memories endpoint, the canonical-date waterfall (which feeds
`image_exif.date_taken` for filename-only files), and Apollo's
DETAILS modal `filename_date` field all now line up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 20:43:18 -04:00
Cameron CordesandClaude Opus 4.7 7f12890f4b memories: single-SQL rewrite + 20-year lookback
Replaces the EXIF-loop + WalkDir-fallback pipeline that powered
`/memories` with a single per-library SQL query
(`get_memories_in_window`) that uses `strftime('%m-%d' | '%W' | '%m',
date_taken, 'unixepoch', tz_offset)` for calendar matching in the
client's timezone, plus a `years_back` lower bound and a
no-future-dates upper bound. Returns only the matching rows; the
handler applies per-library `PathExcluder` post-query and sorts.

Drops:
- `collect_exif_memories` — replaced by the single SQL query.
- `collect_filesystem_memories` — the canonical-date pipeline now
  populates `date_taken` for every row at ingest, so the WalkDir
  fallback that scanned 14k+ files each request is no longer needed.
- `get_memory_date_with_priority` and friends — request-time waterfall
  superseded by `date_resolver` running at ingest. The associated
  three priority-tests are dropped; their replacement lives in
  `date_resolver::tests`.

On a ~14k-file library this drops `/memories` from 10–15 s
(dominated by `fs::metadata` per row) to single-digit ms.

Bumps `DEFAULT_YEARS_BACK` from 15 → 20 to surface deeper archives
on matching anniversaries.

Note vs. ISO weeks: the original Rust used `chrono::iso_week().week()`
for week-span matching. SQLite's `%W` is Monday-anchored but uses week
0 for days before the first Monday, so it can disagree with ISO at
year boundaries by ±1. Acceptable for nostalgia browsing.

Adds 3 new DAO tests covering month-span filter, library scoping, and
the unknown-span-token guard. Also adds a CLAUDE.md section describing
the canonical-date pipeline end-to-end and the new
`DATE_BACKFILL_MAX_PER_TICK` env var.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:04:09 -04:00
Cameron CordesandClaude Opus 4.7 814066551e multi-library: per-library excluded_dirs
Adds a nullable comma-separated TEXT column to the libraries table.
Effective excludes for a walk = (env-var globals) ∪
(library.excluded_dirs). Empty / NULL = no library-specific
extras; the global env var still applies.

Migration (2026-05-01-110000_libraries_excluded_dirs)

  ALTER TABLE libraries ADD COLUMN excluded_dirs TEXT. NULL on every
  existing row — no behavior change on upgrade.

Library struct + helpers (libraries.rs)

  - Library gains excluded_dirs: Vec<String>, parsed from the column
    by parse_excluded_dirs_column (drops empties / whitespace,
    matches the env-var parser).
  - Library::effective_excluded_dirs(globals) returns the union.
  - From<LibraryRow> hydrates the field on AppState construction so
    /libraries surfaces it.

Watcher / walkers / memories

  Every per-library walker now consults the effective set:
    - process_new_files (file-watch ingest, RAW/EXIF/face)
    - process_face_backlog (filter_excluded inherits)
    - create_thumbnails (startup + new-file branch)
    - update_media_counts (Prometheus gauge)
    - cleanup_orphaned_playlists (per-library source-existence check)
    - memories endpoint (PathExcluder)

  Effective set is computed once per per-library iteration in the
  watcher tick and threaded through; called functions retain their
  flat &[String] signature (no per-library awareness needed inside
  the walker primitives).

Use case: mount a parent directory while a sibling library covers
a child subtree, and exclude the child subtree from the parent so
the libraries don't double-walk / double-write image_exif. With
hash-keyed derived data (Branches B/C), the duplication-avoidance
is the only cost prevented — face / tag / insight sharing was
already correct via content_hash.

Tests: 228 pass (226 from previous + 2 new in libraries::tests:
parse_excluded_dirs_column edge cases,
effective_excluded_dirs_unions_global_and_per_library).

CLAUDE.md gains a "Per-library excludes" subsection of the
multi-library data model.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 19:54:17 +00:00
Cameron CordesandClaude Opus 4.7 f77e44b34d faces: fix PathExcluder false-positive + cover face_client/crop in tests
PathExcluder was iterating every component of the absolute path,
including the system prefix. Two of the existing memories tests had
been failing on master because tempdir() lives under /tmp on Linux
and a pattern like "tmp" then matched the system /tmp component
rather than anything the user actually asked to exclude. Phase 3's
file-watch hook will use the same code to skip @eaDir / .thumbnails
under each library's BASE_PATH, so the bug would hide every photo
on a host whose BASE_PATH passes through a directory named the same
as a user pattern.

Fix: store base in PathExcluder and strip it before scanning
components. A path that lives outside base falls through to the
no-match branch (defensive — nothing legit hits that today).

Also extracted the face_client error classification into a pure
classify_error_response(status, body) so the marker-row contract
with Apollo (422 → Permanent / 'failed', 5xx → Transient / defer)
is unit-testable without spinning up an HTTP server.

New tests:
  memories::tests::test_path_excluder_*           — 2 previously
    failing tests now pass.
  ai::face_client::tests::classify_*              — 4 cases:
    422 decode_failed → Permanent, 503 cuda_oom → Transient
    (handles both string and {code:..} detail shapes), 5xx →
    Transient + other 4xx → Permanent, unparseable HTML body still
    classifies on status.
  faces::tests::crop_*                            — 3 cases:
    invalid bbox rejected, valid bbox round-trips through JPEG
    decode, corner crop with 10% padding clamps inside source.

cargo test --lib: 165 passed / 0 failed (was 156 / 2 failed).
cargo fmt and clippy on new code clean. The remaining
sort_by clippy warnings in pre-existing files (memories.rs,
files.rs, exif.rs) are unrelated and present on master.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:09:44 +00:00
CameronandClaude Opus 4.7 dc2a96162e fix(dates): prefer earliest of fs created/modified as fallback
On copied or restored files (e.g. a backup library), the OS stamps
created at copy time while modified is preserved from the source, so
the earlier of the two is a better proxy for when the content
originated. Adds utils::earliest_fs_time and threads it through the
three spots that fall back to filesystem dates: photos-list sort,
memories grouping, and insight-generation timestamp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 17:20:12 -04:00
CameronandClaude Opus 4.7 2c8de8dcc6 feat: union /photos and /memories across libraries
When `library` is omitted, both endpoints now walk every configured
library root, interleave the results, and tag each row with its source
library via the parallel `photo_libraries` / per-row `library_id`
arrays. Previously the handlers fell back to the primary library,
silently hiding the rest.

Threads a parallel `file_libraries: Vec<i32>` through the sort/paginate
helpers so library attribution survives sorting and pagination.
Directory names are de-duplicated across libraries.

`get_all_with_date_taken` grows an optional library filter so memories
can scope its EXIF query per-library during the union walk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 01:55:07 +00:00
CameronandClaude Opus 4.7 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
CameronandClaude Opus 4.7 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
CameronandClaude Opus 4.7 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 7d2a3148bb Make Memories week span sorting chronological 2026-01-26 20:51:11 -05:00
Cameron e2d6cd7258 Run clippy fix 2026-01-14 13:17:58 -05:00
Cameron b2cc617bc2 Pass image as additional Insight context 2026-01-10 11:30:01 -05:00
Cameron 0dfec4c8c5 Fix memory filename date extraction 2026-01-02 19:29:42 -05:00
Cameron 2d02f00e7d Fix Memories Week span sorting 2025-12-29 18:49:52 -05:00
Cameron 9cb923df9e Fix memory date priority 2025-12-29 12:28:17 -05:00
Cameron be281130d5 Send timestamp from filename for Memories endpoint 2025-12-25 23:32:00 -05:00
Cameron c0021734b6 Try fixing Otel span propogation 2025-12-24 10:17:14 -05:00
Cameron c1cfda9df9 Fix memories week span sorting 2025-12-24 00:27:44 -05:00
Cameron c035678162 Add tracing to EXIF DAO methods 2025-12-23 22:57:24 -05:00
Cameron df94010d21 Fix tests and improve memories date error log 2025-12-19 14:20:51 -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 c7fd328925 Check Exif DB for memory collection 2025-12-17 22:10:23 -05:00
Cameron 3c894335ce Added file date format for memories 2025-12-01 13:51:17 -05:00
Cameron f02a858368 Bump to 0.3.1 and format/clippy 2025-12-01 13:04:55 -05:00
Cameron a7d065aadc Tests and improved pattern-excluding behavior 2025-12-01 12:54:40 -05:00
Cameron f5c53d1e0e Allow for pattern-based memories folder exclusion 2025-12-01 12:29:32 -05:00
Cameron 273b877e16 Update to Rust 2024 edition
Formatted code.
2025-09-01 13:36:27 -04:00
Cameron 2cc4124544 Just look for date format instead of screenshot text 2025-09-01 11:15:27 -04:00
Cameron 6f76a74b2e Add additional memories filename regex 2025-09-01 11:09:09 -04:00
Cameron 9c04fcb1d1 Add additional memories filename regex 2025-09-01 11:01:01 -04:00
Cameron e46953194e Allow for excluding directories from Memories endpoint 2025-08-27 16:02:32 -04:00
Cameron 34784a39f6 Use rayon for memories endpoint 2025-08-21 16:39:33 -04:00
Cameron 93957bf389 Refactor date parsing from filename by introducing reusable closure, remove redundant logging level, and simplify regex logic. 2025-08-15 23:20:07 -04:00
Cameron 7dcf89c47e Add conditional sorting logic for Month span in memories sorting 2025-08-15 17:22:01 -04:00
Cameron 4315744abb Improve Memory sorting 2025-08-13 13:23:32 -04:00
Cameron 85093ff0c7 Add parsing date from filename for memories 2025-08-12 20:55:22 -04:00
Cameron 8d9a5fd79f Try adding timezone awareness 2025-08-11 17:11:02 -04:00