feat: multi-library foundation (schema + libraries module)

Adds a `libraries` registry table and threads library_id through
per-instance metadata tables (image_exif, photo_insights,
entity_photo_links, video_preview_clips). File-path columns renamed to
rel_path to make the relative-to-root semantics explicit. Adds
content_hash + size_bytes on image_exif to support future hash-keyed
thumbnail/HLS dedup. Tags and favorites stay library-agnostic so they
share across libraries by rel_path.

Behavior is unchanged: a single primary library (id=1) is seeded from
BASE_PATH on first boot; all handlers and DAOs route through it as a
transitional shim until the API gains a library query param.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Cameron
2026-04-17 15:28:30 -04:00
committed by cameron
parent 2f4edba08c
commit ffcddbb843
17 changed files with 750 additions and 108 deletions

View File

@@ -64,7 +64,8 @@ diesel::table! {
entity_photo_links (id) {
id -> Integer,
entity_id -> Integer,
file_path -> Text,
library_id -> Integer,
rel_path -> Text,
role -> Text,
}
}
@@ -73,14 +74,15 @@ diesel::table! {
favorites (id) {
id -> Integer,
userid -> Integer,
path -> Text,
rel_path -> Text,
}
}
diesel::table! {
image_exif (id) {
id -> Integer,
file_path -> Text,
library_id -> Integer,
rel_path -> Text,
camera_make -> Nullable<Text>,
camera_model -> Nullable<Text>,
lens_model -> Nullable<Text>,
@@ -97,18 +99,17 @@ diesel::table! {
date_taken -> Nullable<BigInt>,
created_time -> BigInt,
last_modified -> BigInt,
content_hash -> Nullable<Text>,
size_bytes -> Nullable<BigInt>,
}
}
diesel::table! {
knowledge_embeddings (id) {
libraries (id) {
id -> Integer,
keyword -> Text,
description -> Text,
category -> Nullable<Text>,
embedding -> Binary,
name -> Text,
root_path -> Text,
created_at -> BigInt,
model_version -> Text,
}
}
@@ -129,23 +130,11 @@ diesel::table! {
}
}
diesel::table! {
message_embeddings (id) {
id -> Integer,
contact -> Text,
body -> Text,
timestamp -> BigInt,
is_sent -> Bool,
embedding -> Binary,
created_at -> BigInt,
model_version -> Text,
}
}
diesel::table! {
photo_insights (id) {
id -> Integer,
file_path -> Text,
library_id -> Integer,
rel_path -> Text,
title -> Text,
summary -> Text,
generated_at -> BigInt,
@@ -171,7 +160,7 @@ diesel::table! {
diesel::table! {
tagged_photo (id) {
id -> Integer,
photo_name -> Text,
rel_path -> Text,
tag_id -> Integer,
created_time -> BigInt,
}
@@ -196,7 +185,8 @@ diesel::table! {
diesel::table! {
video_preview_clips (id) {
id -> Integer,
file_path -> Text,
library_id -> Integer,
rel_path -> Text,
status -> Text,
duration_seconds -> Nullable<Float>,
file_size_bytes -> Nullable<Integer>,
@@ -208,7 +198,11 @@ diesel::table! {
diesel::joinable!(entity_facts -> photo_insights (source_insight_id));
diesel::joinable!(entity_photo_links -> entities (entity_id));
diesel::joinable!(entity_photo_links -> libraries (library_id));
diesel::joinable!(image_exif -> libraries (library_id));
diesel::joinable!(photo_insights -> libraries (library_id));
diesel::joinable!(tagged_photo -> tags (tag_id));
diesel::joinable!(video_preview_clips -> libraries (library_id));
diesel::allow_tables_to_appear_in_same_query!(
calendar_events,
@@ -218,9 +212,8 @@ diesel::allow_tables_to_appear_in_same_query!(
entity_photo_links,
favorites,
image_exif,
knowledge_embeddings,
libraries,
location_history,
message_embeddings,
photo_insights,
search_history,
tagged_photo,