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:
@@ -86,10 +86,14 @@ impl InsightDao for SqliteInsightDao {
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
|
||||
// Mark all existing insights for this file as no longer current
|
||||
diesel::update(photo_insights.filter(file_path.eq(&insight.file_path)))
|
||||
.set(is_current.eq(false))
|
||||
.execute(connection.deref_mut())
|
||||
.map_err(|_| anyhow::anyhow!("Update is_current error"))?;
|
||||
diesel::update(
|
||||
photo_insights
|
||||
.filter(library_id.eq(insight.library_id))
|
||||
.filter(rel_path.eq(&insight.file_path)),
|
||||
)
|
||||
.set(is_current.eq(false))
|
||||
.execute(connection.deref_mut())
|
||||
.map_err(|_| anyhow::anyhow!("Update is_current error"))?;
|
||||
|
||||
// Insert the new insight as current
|
||||
diesel::insert_into(photo_insights)
|
||||
@@ -99,7 +103,8 @@ impl InsightDao for SqliteInsightDao {
|
||||
|
||||
// Retrieve the inserted record (is_current = true)
|
||||
photo_insights
|
||||
.filter(file_path.eq(&insight.file_path))
|
||||
.filter(library_id.eq(insight.library_id))
|
||||
.filter(rel_path.eq(&insight.file_path))
|
||||
.filter(is_current.eq(true))
|
||||
.first::<PhotoInsight>(connection.deref_mut())
|
||||
.map_err(|_| anyhow::anyhow!("Query error"))
|
||||
@@ -118,7 +123,7 @@ impl InsightDao for SqliteInsightDao {
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
|
||||
photo_insights
|
||||
.filter(file_path.eq(path))
|
||||
.filter(rel_path.eq(path))
|
||||
.filter(is_current.eq(true))
|
||||
.first::<PhotoInsight>(connection.deref_mut())
|
||||
.optional()
|
||||
@@ -138,7 +143,7 @@ impl InsightDao for SqliteInsightDao {
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
|
||||
photo_insights
|
||||
.filter(file_path.eq(path))
|
||||
.filter(rel_path.eq(path))
|
||||
.order(generated_at.desc())
|
||||
.load::<PhotoInsight>(connection.deref_mut())
|
||||
.map_err(|_| anyhow::anyhow!("Query error"))
|
||||
@@ -156,7 +161,7 @@ impl InsightDao for SqliteInsightDao {
|
||||
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
|
||||
diesel::delete(photo_insights.filter(file_path.eq(path)))
|
||||
diesel::delete(photo_insights.filter(rel_path.eq(path)))
|
||||
.execute(connection.deref_mut())
|
||||
.map(|_| ())
|
||||
.map_err(|_| anyhow::anyhow!("Delete error"))
|
||||
@@ -195,7 +200,7 @@ impl InsightDao for SqliteInsightDao {
|
||||
|
||||
diesel::update(
|
||||
photo_insights
|
||||
.filter(file_path.eq(path))
|
||||
.filter(rel_path.eq(path))
|
||||
.filter(is_current.eq(true)),
|
||||
)
|
||||
.set(approved.eq(Some(is_approved)))
|
||||
|
||||
@@ -550,8 +550,8 @@ impl KnowledgeDao for SqliteKnowledgeDao {
|
||||
|
||||
// 3. Copy photo links to target (INSERT OR IGNORE to skip duplicates)
|
||||
let links_updated = diesel::sql_query(
|
||||
"INSERT OR IGNORE INTO entity_photo_links (entity_id, file_path, role) \
|
||||
SELECT ?, file_path, role FROM entity_photo_links WHERE entity_id = ?",
|
||||
"INSERT OR IGNORE INTO entity_photo_links (entity_id, library_id, rel_path, role) \
|
||||
SELECT ?, library_id, rel_path, role FROM entity_photo_links WHERE entity_id = ?",
|
||||
)
|
||||
.bind::<diesel::sql_types::Integer, _>(target_id)
|
||||
.bind::<diesel::sql_types::Integer, _>(source_id)
|
||||
@@ -781,11 +781,12 @@ impl KnowledgeDao for SqliteKnowledgeDao {
|
||||
) -> Result<(), DbError> {
|
||||
trace_db_call(cx, "insert", "upsert_photo_link", |_span| {
|
||||
let mut conn = self.connection.lock().expect("KnowledgeDao lock");
|
||||
// INSERT OR IGNORE respects the UNIQUE(entity_id, file_path, role) constraint
|
||||
// INSERT OR IGNORE respects the UNIQUE(entity_id, library_id, rel_path, role) constraint
|
||||
diesel::sql_query(
|
||||
"INSERT OR IGNORE INTO entity_photo_links (entity_id, file_path, role) VALUES (?, ?, ?)"
|
||||
"INSERT OR IGNORE INTO entity_photo_links (entity_id, library_id, rel_path, role) VALUES (?, ?, ?, ?)"
|
||||
)
|
||||
.bind::<diesel::sql_types::Integer, _>(link.entity_id)
|
||||
.bind::<diesel::sql_types::Integer, _>(link.library_id)
|
||||
.bind::<diesel::sql_types::Text, _>(&link.file_path)
|
||||
.bind::<diesel::sql_types::Text, _>(&link.role)
|
||||
.execute(conn.deref_mut())
|
||||
@@ -803,7 +804,7 @@ impl KnowledgeDao for SqliteKnowledgeDao {
|
||||
trace_db_call(cx, "delete", "delete_photo_links_for_file", |_span| {
|
||||
use schema::entity_photo_links::dsl::*;
|
||||
let mut conn = self.connection.lock().expect("KnowledgeDao lock");
|
||||
diesel::delete(entity_photo_links.filter(file_path.eq(file_path_val)))
|
||||
diesel::delete(entity_photo_links.filter(rel_path.eq(file_path_val)))
|
||||
.execute(conn.deref_mut())
|
||||
.map(|_| ())
|
||||
.map_err(|e| anyhow::anyhow!("Delete error: {}", e))
|
||||
@@ -820,7 +821,7 @@ impl KnowledgeDao for SqliteKnowledgeDao {
|
||||
use schema::entity_photo_links::dsl::*;
|
||||
let mut conn = self.connection.lock().expect("KnowledgeDao lock");
|
||||
entity_photo_links
|
||||
.filter(file_path.eq(file_path_val))
|
||||
.filter(rel_path.eq(file_path_val))
|
||||
.load::<EntityPhotoLink>(conn.deref_mut())
|
||||
.map_err(|e| anyhow::anyhow!("Query error: {}", e))
|
||||
})
|
||||
|
||||
@@ -184,7 +184,7 @@ impl FavoriteDao for SqliteFavoriteDao {
|
||||
let mut connection = self.connection.lock().expect("Unable to get FavoriteDao");
|
||||
|
||||
if favorites
|
||||
.filter(userid.eq(user_id).and(path.eq(&favorite_path)))
|
||||
.filter(userid.eq(user_id).and(rel_path.eq(&favorite_path)))
|
||||
.first::<Favorite>(connection.deref_mut())
|
||||
.is_err()
|
||||
{
|
||||
@@ -204,7 +204,7 @@ impl FavoriteDao for SqliteFavoriteDao {
|
||||
use schema::favorites::dsl::*;
|
||||
|
||||
diesel::delete(favorites)
|
||||
.filter(userid.eq(user_id).and(path.eq(favorite_path)))
|
||||
.filter(userid.eq(user_id).and(rel_path.eq(favorite_path)))
|
||||
.execute(self.connection.lock().unwrap().deref_mut())
|
||||
.unwrap();
|
||||
}
|
||||
@@ -221,8 +221,8 @@ impl FavoriteDao for SqliteFavoriteDao {
|
||||
fn update_path(&mut self, old_path: &str, new_path: &str) -> Result<(), DbError> {
|
||||
use schema::favorites::dsl::*;
|
||||
|
||||
diesel::update(favorites.filter(path.eq(old_path)))
|
||||
.set(path.eq(new_path))
|
||||
diesel::update(favorites.filter(rel_path.eq(old_path)))
|
||||
.set(rel_path.eq(new_path))
|
||||
.execute(self.connection.lock().unwrap().deref_mut())
|
||||
.map_err(|_| DbError::new(DbErrorKind::UpdateError))?;
|
||||
Ok(())
|
||||
@@ -232,7 +232,7 @@ impl FavoriteDao for SqliteFavoriteDao {
|
||||
use schema::favorites::dsl::*;
|
||||
|
||||
favorites
|
||||
.select(path)
|
||||
.select(rel_path)
|
||||
.distinct()
|
||||
.load(self.connection.lock().unwrap().deref_mut())
|
||||
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
||||
@@ -349,7 +349,8 @@ impl ExifDao for SqliteExifDao {
|
||||
.map_err(|_| anyhow::anyhow!("Insert error"))?;
|
||||
|
||||
image_exif
|
||||
.filter(file_path.eq(&exif_data.file_path))
|
||||
.filter(library_id.eq(exif_data.library_id))
|
||||
.filter(rel_path.eq(&exif_data.file_path))
|
||||
.first::<ImageExif>(connection.deref_mut())
|
||||
.map_err(|_| anyhow::anyhow!("Query error"))
|
||||
})
|
||||
@@ -372,7 +373,7 @@ impl ExifDao for SqliteExifDao {
|
||||
let windows_path = path.replace('/', "\\");
|
||||
|
||||
match image_exif
|
||||
.filter(file_path.eq(&normalized).or(file_path.eq(&windows_path)))
|
||||
.filter(rel_path.eq(&normalized).or(rel_path.eq(&windows_path)))
|
||||
.first::<ImageExif>(connection.deref_mut())
|
||||
{
|
||||
Ok(exif) => Ok(Some(exif)),
|
||||
@@ -393,29 +394,34 @@ impl ExifDao for SqliteExifDao {
|
||||
|
||||
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
||||
|
||||
diesel::update(image_exif.filter(file_path.eq(&exif_data.file_path)))
|
||||
.set((
|
||||
camera_make.eq(&exif_data.camera_make),
|
||||
camera_model.eq(&exif_data.camera_model),
|
||||
lens_model.eq(&exif_data.lens_model),
|
||||
width.eq(&exif_data.width),
|
||||
height.eq(&exif_data.height),
|
||||
orientation.eq(&exif_data.orientation),
|
||||
gps_latitude.eq(&exif_data.gps_latitude),
|
||||
gps_longitude.eq(&exif_data.gps_longitude),
|
||||
gps_altitude.eq(&exif_data.gps_altitude),
|
||||
focal_length.eq(&exif_data.focal_length),
|
||||
aperture.eq(&exif_data.aperture),
|
||||
shutter_speed.eq(&exif_data.shutter_speed),
|
||||
iso.eq(&exif_data.iso),
|
||||
date_taken.eq(&exif_data.date_taken),
|
||||
last_modified.eq(&exif_data.last_modified),
|
||||
))
|
||||
.execute(connection.deref_mut())
|
||||
.map_err(|_| anyhow::anyhow!("Update error"))?;
|
||||
diesel::update(
|
||||
image_exif
|
||||
.filter(library_id.eq(exif_data.library_id))
|
||||
.filter(rel_path.eq(&exif_data.file_path)),
|
||||
)
|
||||
.set((
|
||||
camera_make.eq(&exif_data.camera_make),
|
||||
camera_model.eq(&exif_data.camera_model),
|
||||
lens_model.eq(&exif_data.lens_model),
|
||||
width.eq(&exif_data.width),
|
||||
height.eq(&exif_data.height),
|
||||
orientation.eq(&exif_data.orientation),
|
||||
gps_latitude.eq(&exif_data.gps_latitude),
|
||||
gps_longitude.eq(&exif_data.gps_longitude),
|
||||
gps_altitude.eq(&exif_data.gps_altitude),
|
||||
focal_length.eq(&exif_data.focal_length),
|
||||
aperture.eq(&exif_data.aperture),
|
||||
shutter_speed.eq(&exif_data.shutter_speed),
|
||||
iso.eq(&exif_data.iso),
|
||||
date_taken.eq(&exif_data.date_taken),
|
||||
last_modified.eq(&exif_data.last_modified),
|
||||
))
|
||||
.execute(connection.deref_mut())
|
||||
.map_err(|_| anyhow::anyhow!("Update error"))?;
|
||||
|
||||
image_exif
|
||||
.filter(file_path.eq(&exif_data.file_path))
|
||||
.filter(library_id.eq(exif_data.library_id))
|
||||
.filter(rel_path.eq(&exif_data.file_path))
|
||||
.first::<ImageExif>(connection.deref_mut())
|
||||
.map_err(|_| anyhow::anyhow!("Query error"))
|
||||
})
|
||||
@@ -426,7 +432,7 @@ impl ExifDao for SqliteExifDao {
|
||||
trace_db_call(context, "delete", "delete_exif", |_span| {
|
||||
use schema::image_exif::dsl::*;
|
||||
|
||||
diesel::delete(image_exif.filter(file_path.eq(path)))
|
||||
diesel::delete(image_exif.filter(rel_path.eq(path)))
|
||||
.execute(self.connection.lock().unwrap().deref_mut())
|
||||
.map(|_| ())
|
||||
.map_err(|_| anyhow::anyhow!("Delete error"))
|
||||
@@ -444,7 +450,7 @@ impl ExifDao for SqliteExifDao {
|
||||
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
||||
|
||||
image_exif
|
||||
.select((file_path, date_taken))
|
||||
.select((rel_path, date_taken))
|
||||
.filter(date_taken.is_not_null())
|
||||
.load::<(String, Option<i64>)>(connection.deref_mut())
|
||||
.map(|records| {
|
||||
@@ -473,7 +479,7 @@ impl ExifDao for SqliteExifDao {
|
||||
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
||||
|
||||
image_exif
|
||||
.filter(file_path.eq_any(file_paths))
|
||||
.filter(rel_path.eq_any(file_paths))
|
||||
.load::<ImageExif>(connection.deref_mut())
|
||||
.map_err(|_| anyhow::anyhow!("Query error"))
|
||||
})
|
||||
@@ -572,8 +578,8 @@ impl ExifDao for SqliteExifDao {
|
||||
|
||||
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
||||
|
||||
diesel::update(image_exif.filter(file_path.eq(old_path)))
|
||||
.set(file_path.eq(new_path))
|
||||
diesel::update(image_exif.filter(rel_path.eq(old_path)))
|
||||
.set(rel_path.eq(new_path))
|
||||
.execute(connection.deref_mut())
|
||||
.map_err(|_| anyhow::anyhow!("Update error"))?;
|
||||
Ok(())
|
||||
@@ -591,7 +597,7 @@ impl ExifDao for SqliteExifDao {
|
||||
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
||||
|
||||
image_exif
|
||||
.select(file_path)
|
||||
.select(rel_path)
|
||||
.load(connection.deref_mut())
|
||||
.map_err(|_| anyhow::anyhow!("Query error"))
|
||||
})
|
||||
@@ -627,7 +633,7 @@ impl ExifDao for SqliteExifDao {
|
||||
// Otherwise filter by path prefix
|
||||
if !base_path.is_empty() && base_path != "/" {
|
||||
// Match base path as prefix (with wildcard)
|
||||
query = query.filter(file_path.like(format!("{}%", base_path)));
|
||||
query = query.filter(rel_path.like(format!("{}%", base_path)));
|
||||
|
||||
span.set_attribute(KeyValue::new("path_filter_applied", true));
|
||||
} else {
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -84,6 +84,7 @@ impl PreviewDao for SqlitePreviewDao {
|
||||
|
||||
diesel::insert_or_ignore_into(video_preview_clips)
|
||||
.values(InsertVideoPreviewClip {
|
||||
library_id: 1,
|
||||
file_path: file_path_val.to_string(),
|
||||
status: status_val.to_string(),
|
||||
created_at: now.clone(),
|
||||
@@ -111,7 +112,7 @@ impl PreviewDao for SqlitePreviewDao {
|
||||
let mut connection = self.connection.lock().expect("Unable to get PreviewDao");
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
|
||||
diesel::update(video_preview_clips.filter(file_path.eq(file_path_val)))
|
||||
diesel::update(video_preview_clips.filter(rel_path.eq(file_path_val)))
|
||||
.set((
|
||||
status.eq(status_val),
|
||||
duration_seconds.eq(duration),
|
||||
@@ -137,7 +138,7 @@ impl PreviewDao for SqlitePreviewDao {
|
||||
let mut connection = self.connection.lock().expect("Unable to get PreviewDao");
|
||||
|
||||
match video_preview_clips
|
||||
.filter(file_path.eq(file_path_val))
|
||||
.filter(rel_path.eq(file_path_val))
|
||||
.first::<VideoPreviewClip>(connection.deref_mut())
|
||||
{
|
||||
Ok(clip) => Ok(Some(clip)),
|
||||
@@ -163,7 +164,7 @@ impl PreviewDao for SqlitePreviewDao {
|
||||
let mut connection = self.connection.lock().expect("Unable to get PreviewDao");
|
||||
|
||||
video_preview_clips
|
||||
.filter(file_path.eq_any(file_paths))
|
||||
.filter(rel_path.eq_any(file_paths))
|
||||
.load::<VideoPreviewClip>(connection.deref_mut())
|
||||
.map_err(|e| anyhow::anyhow!("Query error: {}", e))
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user