feat: media API (video/bbox/thumbnail), UUID unification, dot matrix text, portal fixes, API dictionary V1.3

This commit is contained in:
Warren
2026-05-06 13:34:49 +08:00
parent e75c4d6f07
commit 74b6182eba
197 changed files with 17511 additions and 8759 deletions

View File

@@ -13,13 +13,25 @@ use crate::core::db::ResourceRecord;
pub fn identity_routes() -> Router<crate::api::server::AppState> {
Router::new()
.route("/api/v1/files", get(list_files))
.route("/api/v1/files/:uuid", get(get_file_detail))
.route("/api/v1/files/:uuid/identities", get(get_file_identities))
.route("/api/v1/identities/:uuid", get(get_identity_detail))
.route("/api/v1/identities/:uuid/files", get(get_identity_files))
.route("/api/v1/identities/:uuid/chunks", get(get_identity_chunks))
.route("/api/v1/resources/register", post(register_resource))
.route("/api/v1/resources/heartbeat", post(heartbeat_resource))
.route("/api/v1/file/:file_uuid", get(get_file_detail))
.route(
"/api/v1/file/:file_uuid/identities",
get(get_file_identities),
)
.route(
"/api/v1/identity/:identity_uuid",
get(get_identity_detail).delete(delete_identity),
)
.route(
"/api/v1/identity/:identity_uuid/files",
get(get_identity_files),
)
.route(
"/api/v1/identity/:identity_uuid/chunks",
get(get_identity_chunks),
)
.route("/api/v1/resource/register", post(register_resource))
.route("/api/v1/resource/heartbeat", post(heartbeat_resource))
.route("/api/v1/resources", get(list_resources))
}
@@ -124,11 +136,11 @@ pub struct FileDetailResponse {
async fn get_file_detail(
State(state): State<crate::api::server::AppState>,
Path(uuid): Path<String>,
Path(file_uuid): Path<String>,
) -> Result<Json<FileDetailResponse>, (StatusCode, String)> {
let file = state
.db
.get_file_by_uuid(&uuid)
.get_file_by_uuid(&file_uuid)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
@@ -141,7 +153,10 @@ async fn get_file_detail(
metadata: f.probe_json,
created_at: f.created_at,
})),
None => Err((StatusCode::NOT_FOUND, format!("File not found: {}", uuid))),
None => Err((
StatusCode::NOT_FOUND,
format!("File not found: {}", file_uuid),
)),
}
}
@@ -169,7 +184,7 @@ pub struct FileIdentityItem {
async fn get_file_identities(
State(state): State<crate::api::server::AppState>,
Path(uuid): Path<String>,
Path(file_uuid): Path<String>,
Query(params): Query<FilesQuery>,
) -> Result<Json<FileIdentitiesResponse>, (StatusCode, String)> {
let page = params.page.unwrap_or(1);
@@ -178,7 +193,7 @@ async fn get_file_identities(
let records = state
.db
.get_file_identities(&uuid, page_size as i32, offset)
.get_file_identities(&file_uuid, page_size as i32, offset)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
@@ -198,7 +213,7 @@ async fn get_file_identities(
Ok(Json(FileIdentitiesResponse {
success: true,
file_uuid: uuid,
file_uuid: file_uuid,
total: data.len() as i64,
page,
page_size,
@@ -222,10 +237,15 @@ pub struct IdentityDetailResponse {
pub updated_at: Option<chrono::DateTime<chrono::Utc>>,
}
fn strip_uuid(u: &uuid::Uuid) -> String {
u.to_string().replace('-', "")
}
async fn get_identity_detail(
State(state): State<crate::api::server::AppState>,
Path(uuid_str): Path<String>,
Path(identity_uuid): Path<String>,
) -> Result<Json<IdentityDetailResponse>, (StatusCode, String)> {
let uuid_str = identity_uuid;
let uuid = Uuid::parse_str(&uuid_str)
.map_err(|e| (StatusCode::BAD_REQUEST, format!("Invalid UUID: {}", e)))?;
@@ -267,6 +287,45 @@ pub struct IdentityFilesResponse {
pub data: Vec<IdentityFileItem>,
}
async fn delete_identity(
State(state): State<crate::api::server::AppState>,
Path(identity_uuid): Path<String>,
) -> Result<StatusCode, StatusCode> {
let table = crate::core::db::schema::table_name("face_detections");
let id_table = crate::core::db::schema::table_name("identities");
// Get identity_id from identity_uuid
let row: Option<(i32,)> = sqlx::query_as(&format!(
"SELECT id FROM {} WHERE replace(uuid::text, '-', '') = $1",
id_table
))
.bind(&identity_uuid)
.fetch_optional(state.db.pool())
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let (identity_id,) = row.ok_or(StatusCode::NOT_FOUND)?;
// Unbind all faces
sqlx::query(&format!(
"UPDATE {} SET identity_id = NULL, identity_confidence = NULL WHERE identity_id = $1",
table
))
.bind(identity_id)
.execute(state.db.pool())
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// Delete identity
sqlx::query(&format!("DELETE FROM {} WHERE id = $1", id_table))
.bind(identity_id)
.execute(state.db.pool())
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(StatusCode::NO_CONTENT)
}
#[derive(Debug, Serialize)]
pub struct IdentityFileItem {
pub file_uuid: String,
@@ -282,9 +341,10 @@ pub struct IdentityFileItem {
async fn get_identity_files(
State(state): State<crate::api::server::AppState>,
Path(uuid_str): Path<String>,
Path(identity_uuid): Path<String>,
Query(params): Query<FilesQuery>,
) -> Result<Json<IdentityFilesResponse>, (StatusCode, String)> {
let uuid_str = identity_uuid;
let uuid = Uuid::parse_str(&uuid_str)
.map_err(|e| (StatusCode::BAD_REQUEST, format!("Invalid UUID: {}", e)))?;
@@ -421,9 +481,10 @@ pub struct IdentityChunkItem {
async fn get_identity_chunks(
State(state): State<crate::api::server::AppState>,
Path(uuid_str): Path<String>,
Path(identity_uuid): Path<String>,
Query(params): Query<FilesQuery>,
) -> Result<Json<IdentityChunksResponse>, (StatusCode, String)> {
let uuid_str = identity_uuid;
let uuid = Uuid::parse_str(&uuid_str)
.map_err(|e| (StatusCode::BAD_REQUEST, format!("Invalid UUID: {}", e)))?;