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:
@@ -1,6 +1,6 @@
|
||||
use crate::database::schema::{
|
||||
entities, entity_facts, entity_photo_links, favorites, image_exif, photo_insights, users,
|
||||
video_preview_clips,
|
||||
entities, entity_facts, entity_photo_links, favorites, image_exif, libraries, photo_insights,
|
||||
users, video_preview_clips,
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -23,6 +23,7 @@ pub struct User {
|
||||
#[diesel(table_name = favorites)]
|
||||
pub struct InsertFavorite<'a> {
|
||||
pub userid: &'a i32,
|
||||
#[diesel(column_name = rel_path)]
|
||||
pub path: &'a str,
|
||||
}
|
||||
|
||||
@@ -30,12 +31,15 @@ pub struct InsertFavorite<'a> {
|
||||
pub struct Favorite {
|
||||
pub id: i32,
|
||||
pub userid: i32,
|
||||
#[diesel(column_name = rel_path)]
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
#[diesel(table_name = image_exif)]
|
||||
pub struct InsertImageExif {
|
||||
pub library_id: i32,
|
||||
#[diesel(column_name = rel_path)]
|
||||
pub file_path: String,
|
||||
pub camera_make: Option<String>,
|
||||
pub camera_model: Option<String>,
|
||||
@@ -53,11 +57,16 @@ pub struct InsertImageExif {
|
||||
pub date_taken: Option<i64>,
|
||||
pub created_time: i64,
|
||||
pub last_modified: i64,
|
||||
pub content_hash: Option<String>,
|
||||
pub size_bytes: Option<i64>,
|
||||
}
|
||||
|
||||
// Field order matches the post-migration column order in `image_exif`.
|
||||
#[derive(Serialize, Queryable, Clone, Debug)]
|
||||
pub struct ImageExif {
|
||||
pub id: i32,
|
||||
pub library_id: i32,
|
||||
#[diesel(column_name = rel_path)]
|
||||
pub file_path: String,
|
||||
pub camera_make: Option<String>,
|
||||
pub camera_model: Option<String>,
|
||||
@@ -75,11 +84,15 @@ pub struct ImageExif {
|
||||
pub date_taken: Option<i64>,
|
||||
pub created_time: i64,
|
||||
pub last_modified: i64,
|
||||
pub content_hash: Option<String>,
|
||||
pub size_bytes: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
#[diesel(table_name = photo_insights)]
|
||||
pub struct InsertPhotoInsight {
|
||||
pub library_id: i32,
|
||||
#[diesel(column_name = rel_path)]
|
||||
pub file_path: String,
|
||||
pub title: String,
|
||||
pub summary: String,
|
||||
@@ -92,6 +105,8 @@ pub struct InsertPhotoInsight {
|
||||
#[derive(Serialize, Queryable, Clone, Debug)]
|
||||
pub struct PhotoInsight {
|
||||
pub id: i32,
|
||||
pub library_id: i32,
|
||||
#[diesel(column_name = rel_path)]
|
||||
pub file_path: String,
|
||||
pub title: String,
|
||||
pub summary: String,
|
||||
@@ -102,6 +117,24 @@ pub struct PhotoInsight {
|
||||
pub approved: Option<bool>,
|
||||
}
|
||||
|
||||
// --- Libraries ---
|
||||
|
||||
#[derive(Serialize, Queryable, Clone, Debug)]
|
||||
pub struct LibraryRow {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub root_path: String,
|
||||
pub created_at: i64,
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
#[diesel(table_name = libraries)]
|
||||
pub struct InsertLibrary<'a> {
|
||||
pub name: &'a str,
|
||||
pub root_path: &'a str,
|
||||
pub created_at: i64,
|
||||
}
|
||||
|
||||
// --- Knowledge memory models ---
|
||||
|
||||
#[derive(Insertable)]
|
||||
@@ -162,6 +195,8 @@ pub struct EntityFact {
|
||||
#[diesel(table_name = entity_photo_links)]
|
||||
pub struct InsertEntityPhotoLink {
|
||||
pub entity_id: i32,
|
||||
pub library_id: i32,
|
||||
#[diesel(column_name = rel_path)]
|
||||
pub file_path: String,
|
||||
pub role: String,
|
||||
}
|
||||
@@ -170,6 +205,8 @@ pub struct InsertEntityPhotoLink {
|
||||
pub struct EntityPhotoLink {
|
||||
pub id: i32,
|
||||
pub entity_id: i32,
|
||||
pub library_id: i32,
|
||||
#[diesel(column_name = rel_path)]
|
||||
pub file_path: String,
|
||||
pub role: String,
|
||||
}
|
||||
@@ -177,6 +214,8 @@ pub struct EntityPhotoLink {
|
||||
#[derive(Insertable)]
|
||||
#[diesel(table_name = video_preview_clips)]
|
||||
pub struct InsertVideoPreviewClip {
|
||||
pub library_id: i32,
|
||||
#[diesel(column_name = rel_path)]
|
||||
pub file_path: String,
|
||||
pub status: String,
|
||||
pub created_at: String,
|
||||
@@ -186,6 +225,8 @@ pub struct InsertVideoPreviewClip {
|
||||
#[derive(Serialize, Queryable, Clone, Debug)]
|
||||
pub struct VideoPreviewClip {
|
||||
pub id: i32,
|
||||
pub library_id: i32,
|
||||
#[diesel(column_name = rel_path)]
|
||||
pub file_path: String,
|
||||
pub status: String,
|
||||
pub duration_seconds: Option<f32>,
|
||||
|
||||
Reference in New Issue
Block a user