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>
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
use actix_web::{HttpResponse, Responder, get, web::Data};
|
||||
use chrono::Utc;
|
||||
use diesel::prelude::*;
|
||||
use diesel::sqlite::SqliteConnection;
|
||||
use log::{info, warn};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::data::Claims;
|
||||
use crate::database::models::{InsertLibrary, LibraryRow};
|
||||
use crate::database::schema::libraries;
|
||||
use crate::state::AppState;
|
||||
|
||||
/// Id of the primary library row seeded by the multi-library migration.
|
||||
/// Used as the default `library_id` during the Phase 2 transitional shim,
|
||||
@@ -116,6 +119,43 @@ pub fn seed_or_patch_from_env(conn: &mut SqliteConnection, base_path: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a library request parameter (accepts numeric id as string or name)
|
||||
/// against the configured libraries. Returns `Ok(None)` when the param is
|
||||
/// absent, meaning "span all libraries". Returns `Err` when a value is
|
||||
/// provided but does not match any library.
|
||||
pub fn resolve_library_param<'a>(
|
||||
state: &'a AppState,
|
||||
param: Option<&str>,
|
||||
) -> Result<Option<&'a Library>, String> {
|
||||
let Some(raw) = param.map(str::trim).filter(|s| !s.is_empty()) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
if let Ok(id) = raw.parse::<i32>() {
|
||||
return state
|
||||
.library_by_id(id)
|
||||
.map(Some)
|
||||
.ok_or_else(|| format!("unknown library id: {}", id));
|
||||
}
|
||||
|
||||
state
|
||||
.library_by_name(raw)
|
||||
.map(Some)
|
||||
.ok_or_else(|| format!("unknown library name: {}", raw))
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct LibrariesResponse {
|
||||
pub libraries: Vec<Library>,
|
||||
}
|
||||
|
||||
#[get("/libraries")]
|
||||
pub async fn list_libraries(_claims: Claims, app_state: Data<AppState>) -> impl Responder {
|
||||
HttpResponse::Ok().json(LibrariesResponse {
|
||||
libraries: app_state.libraries.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user