feat: update core API, database layer, and worker modules
- Remove unused imports (n8n_search, universal_search, Client, Arc, etc.) - Update API endpoints for identity, face recognition, search - Fix postgres_db.rs search_videos parent_uuid column - Add snapshot API and identity agent API - Clean up backup files (.bak, .bak2)
This commit is contained in:
@@ -8,17 +8,27 @@ use axum::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::core::db::{Database, PostgresDb, ResourceRecord};
|
||||
use crate::core::db::ResourceRecord;
|
||||
|
||||
pub fn identity_routes() -> Router<crate::api::server::AppState> {
|
||||
Router::new()
|
||||
.route("/api/v1/people", get(list_people))
|
||||
.route("/api/v1/people/search", post(search_people))
|
||||
.route("/api/v1/people/candidates", get(list_candidates))
|
||||
.route("/api/v1/people/{identity_id}/confirm-candidate", post(confirm_candidate))
|
||||
.route("/api/v1/people/{identity_id}/reject-candidate", post(reject_candidate))
|
||||
.route(
|
||||
"/api/v1/people/:identity_id/confirm-candidate",
|
||||
post(confirm_candidate),
|
||||
)
|
||||
.route(
|
||||
"/api/v1/people/:identity_id/reject-candidate",
|
||||
post(reject_candidate),
|
||||
)
|
||||
.route("/api/v1/files", get(list_files))
|
||||
.route("/api/v1/files/{uuid}", get(get_file_detail))
|
||||
.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/resources", get(list_resources))
|
||||
@@ -59,18 +69,24 @@ async fn list_people(
|
||||
let page_size = params.page_size.unwrap_or(20);
|
||||
let offset = ((page - 1) as i64) * (page_size as i64);
|
||||
|
||||
let records = state.db.list_people(page_size as i32, offset).await
|
||||
let records = state
|
||||
.db
|
||||
.list_people(page_size as i32, offset)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
// TODO: Get total count
|
||||
let total = 100; // Placeholder
|
||||
|
||||
let data = records.into_iter().map(|r| PeopleItem {
|
||||
identity_id: r.uuid,
|
||||
name: r.name,
|
||||
metadata: r.metadata,
|
||||
created_at: r.created_at,
|
||||
}).collect();
|
||||
let data = records
|
||||
.into_iter()
|
||||
.map(|r| PeopleItem {
|
||||
identity_id: r.uuid,
|
||||
name: r.name,
|
||||
metadata: r.metadata,
|
||||
created_at: r.created_at,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(PeopleResponse {
|
||||
success: true,
|
||||
@@ -96,15 +112,21 @@ async fn search_people(
|
||||
let page_size = req.page_size.unwrap_or(20);
|
||||
let offset = ((page - 1) as i64) * (page_size as i64);
|
||||
|
||||
let records = state.db.search_people(&req.query, page_size as i32, offset).await
|
||||
let records = state
|
||||
.db
|
||||
.search_people(&req.query, page_size as i32, offset)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
let data: Vec<PeopleItem> = records.into_iter().map(|r| PeopleItem {
|
||||
identity_id: r.uuid,
|
||||
name: r.name,
|
||||
metadata: r.metadata,
|
||||
created_at: r.created_at,
|
||||
}).collect();
|
||||
let data: Vec<PeopleItem> = records
|
||||
.into_iter()
|
||||
.map(|r| PeopleItem {
|
||||
identity_id: r.uuid,
|
||||
name: r.name,
|
||||
metadata: r.metadata,
|
||||
created_at: r.created_at,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(PeopleResponse {
|
||||
success: true,
|
||||
@@ -145,14 +167,20 @@ async fn list_candidates(
|
||||
let page_size = params.page_size.unwrap_or(20);
|
||||
let offset = ((page - 1) as i64) * (page_size as i64);
|
||||
|
||||
let records = state.db.get_people_candidates(page_size as i32, offset).await
|
||||
let records = state
|
||||
.db
|
||||
.get_people_candidates(page_size as i32, offset)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
let data = records.into_iter().map(|r| CandidateItem {
|
||||
pre_chunk_id: r.id,
|
||||
file_uuid: r.file_uuid,
|
||||
data: r.data,
|
||||
}).collect();
|
||||
let data = records
|
||||
.into_iter()
|
||||
.map(|r| CandidateItem {
|
||||
pre_chunk_id: r.id,
|
||||
file_uuid: r.file_uuid,
|
||||
data: r.data,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(CandidatesResponse {
|
||||
success: true,
|
||||
@@ -184,7 +212,10 @@ async fn confirm_candidate(
|
||||
let identity_id = Uuid::parse_str(&identity_id_str)
|
||||
.map_err(|e| (StatusCode::BAD_REQUEST, format!("Invalid UUID: {}", e)))?;
|
||||
|
||||
state.db.confirm_candidate(req.pre_chunk_id, identity_id).await
|
||||
state
|
||||
.db
|
||||
.confirm_candidate(req.pre_chunk_id, identity_id)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
Ok(Json(ConfirmCandidateResponse {
|
||||
@@ -198,7 +229,10 @@ async fn reject_candidate(
|
||||
Path(_identity_id_str): Path<String>, // Unused, but consistent with route
|
||||
Json(req): Json<ConfirmCandidateRequest>,
|
||||
) -> Result<Json<ConfirmCandidateResponse>, (StatusCode, String)> {
|
||||
state.db.reject_candidate(req.pre_chunk_id).await
|
||||
state
|
||||
.db
|
||||
.reject_candidate(req.pre_chunk_id)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
Ok(Json(ConfirmCandidateResponse {
|
||||
@@ -240,15 +274,21 @@ async fn list_files(
|
||||
let page_size = params.page_size.unwrap_or(20);
|
||||
let offset = ((page - 1) as i64) * (page_size as i64);
|
||||
|
||||
let records = state.db.list_files(page_size as i32, offset).await
|
||||
let records = state
|
||||
.db
|
||||
.list_files(page_size as i32, offset)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
let data = records.into_iter().map(|r| FileItem {
|
||||
file_uuid: r.uuid,
|
||||
file_name: r.file_name,
|
||||
file_path: r.file_path,
|
||||
status: "ready".to_string(),
|
||||
}).collect();
|
||||
let data = records
|
||||
.into_iter()
|
||||
.map(|r| FileItem {
|
||||
file_uuid: r.file_uuid,
|
||||
file_name: r.file_name,
|
||||
file_path: r.file_path,
|
||||
status: "ready".to_string(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(FilesResponse {
|
||||
success: true,
|
||||
@@ -261,23 +301,349 @@ async fn list_files(
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct FileDetailResponse {
|
||||
pub success: bool,
|
||||
pub file_uuid: String,
|
||||
pub file_name: String,
|
||||
pub file_path: String,
|
||||
pub metadata: serde_json::Value,
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
pub created_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
}
|
||||
|
||||
async fn get_file_detail(
|
||||
State(state): State<crate::api::server::AppState>,
|
||||
Path(uuid): Path<String>,
|
||||
) -> Result<Json<FileDetailResponse>, (StatusCode, String)> {
|
||||
// Need a method to get single file
|
||||
// For now, placeholder
|
||||
Ok(Json(FileDetailResponse {
|
||||
let file = state
|
||||
.db
|
||||
.get_file_by_uuid(&uuid)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
match file {
|
||||
Some(f) => Ok(Json(FileDetailResponse {
|
||||
success: true,
|
||||
file_uuid: f.file_uuid,
|
||||
file_name: f.file_name,
|
||||
file_path: f.file_path,
|
||||
metadata: f.probe_json,
|
||||
created_at: f.created_at,
|
||||
})),
|
||||
None => Err((StatusCode::NOT_FOUND, format!("File not found: {}", uuid))),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct FileIdentitiesResponse {
|
||||
pub success: bool,
|
||||
pub file_uuid: String,
|
||||
pub total: i64,
|
||||
pub page: usize,
|
||||
pub page_size: usize,
|
||||
pub data: Vec<FileIdentityItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct FileIdentityItem {
|
||||
pub identity_id: i32,
|
||||
pub name: String,
|
||||
pub metadata: serde_json::Value,
|
||||
pub face_count: Option<i32>,
|
||||
pub speaker_count: Option<i32>,
|
||||
pub first_appearance: Option<f64>,
|
||||
pub last_appearance: Option<f64>,
|
||||
pub confidence: Option<f64>,
|
||||
}
|
||||
|
||||
async fn get_file_identities(
|
||||
State(state): State<crate::api::server::AppState>,
|
||||
Path(uuid): Path<String>,
|
||||
Query(params): Query<FilesQuery>,
|
||||
) -> Result<Json<FileIdentitiesResponse>, (StatusCode, String)> {
|
||||
let page = params.page.unwrap_or(1);
|
||||
let page_size = params.page_size.unwrap_or(20);
|
||||
let offset = ((page - 1) as i64) * (page_size as i64);
|
||||
|
||||
let records = state
|
||||
.db
|
||||
.get_file_identities(&uuid, page_size as i32, offset)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
let data: Vec<FileIdentityItem> = records
|
||||
.into_iter()
|
||||
.map(|r| FileIdentityItem {
|
||||
identity_id: r.identity_id,
|
||||
name: r.name,
|
||||
metadata: r.metadata,
|
||||
face_count: r.face_count,
|
||||
speaker_count: r.speaker_count,
|
||||
first_appearance: r.first_appearance,
|
||||
last_appearance: r.last_appearance,
|
||||
confidence: r.confidence,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(FileIdentitiesResponse {
|
||||
success: true,
|
||||
file_uuid: uuid,
|
||||
file_name: "Unknown".to_string(),
|
||||
file_path: "/path/to/file".to_string(),
|
||||
metadata: serde_json::json!({}),
|
||||
total: data.len() as i64,
|
||||
page,
|
||||
page_size,
|
||||
data,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct IdentityDetailResponse {
|
||||
pub success: bool,
|
||||
pub uuid: Uuid,
|
||||
pub name: String,
|
||||
pub identity_type: Option<String>,
|
||||
pub source: Option<String>,
|
||||
pub status: Option<String>,
|
||||
pub metadata: serde_json::Value,
|
||||
pub reference_data: serde_json::Value,
|
||||
pub tmdb_id: Option<i32>,
|
||||
pub tmdb_profile: Option<String>,
|
||||
pub created_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub updated_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
}
|
||||
|
||||
async fn get_identity_detail(
|
||||
State(state): State<crate::api::server::AppState>,
|
||||
Path(uuid_str): Path<String>,
|
||||
) -> Result<Json<IdentityDetailResponse>, (StatusCode, String)> {
|
||||
let uuid = Uuid::parse_str(&uuid_str)
|
||||
.map_err(|e| (StatusCode::BAD_REQUEST, format!("Invalid UUID: {}", e)))?;
|
||||
|
||||
let identity = state
|
||||
.db
|
||||
.get_identity_by_uuid(&uuid)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
match identity {
|
||||
Some(i) => Ok(Json(IdentityDetailResponse {
|
||||
success: true,
|
||||
uuid: i.uuid,
|
||||
name: i.name,
|
||||
identity_type: i.identity_type,
|
||||
source: i.source,
|
||||
status: i.status,
|
||||
metadata: i.metadata,
|
||||
reference_data: i.reference_data,
|
||||
tmdb_id: i.tmdb_id,
|
||||
tmdb_profile: i.tmdb_profile,
|
||||
created_at: i.created_at,
|
||||
updated_at: i.updated_at,
|
||||
})),
|
||||
None => Err((
|
||||
StatusCode::NOT_FOUND,
|
||||
format!("Identity not found: {}", uuid),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct IdentityFilesResponse {
|
||||
pub success: bool,
|
||||
pub identity_uuid: Uuid,
|
||||
pub total: i64,
|
||||
pub page: usize,
|
||||
pub page_size: usize,
|
||||
pub data: Vec<IdentityFileItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct IdentityFileItem {
|
||||
pub file_uuid: String,
|
||||
pub file_name: String,
|
||||
pub file_path: String,
|
||||
pub status: String,
|
||||
pub face_count: Option<i32>,
|
||||
pub speaker_count: Option<i32>,
|
||||
pub first_appearance: Option<f64>,
|
||||
pub last_appearance: Option<f64>,
|
||||
pub confidence: Option<f64>,
|
||||
}
|
||||
|
||||
async fn get_identity_files(
|
||||
State(state): State<crate::api::server::AppState>,
|
||||
Path(uuid_str): Path<String>,
|
||||
Query(params): Query<FilesQuery>,
|
||||
) -> Result<Json<IdentityFilesResponse>, (StatusCode, String)> {
|
||||
let uuid = Uuid::parse_str(&uuid_str)
|
||||
.map_err(|e| (StatusCode::BAD_REQUEST, format!("Invalid UUID: {}", e)))?;
|
||||
|
||||
let page = params.page.unwrap_or(1);
|
||||
let page_size = params.page_size.unwrap_or(20);
|
||||
let offset = ((page - 1) as i64) * (page_size as i64);
|
||||
|
||||
let records = state
|
||||
.db
|
||||
.get_identity_files(&uuid, page_size as i32, offset)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
let data: Vec<IdentityFileItem> = records
|
||||
.into_iter()
|
||||
.map(|r| IdentityFileItem {
|
||||
file_uuid: r.file_uuid,
|
||||
file_name: r.file_name,
|
||||
file_path: r.file_path,
|
||||
status: r.status,
|
||||
face_count: r.face_count,
|
||||
speaker_count: r.speaker_count,
|
||||
first_appearance: r.first_appearance,
|
||||
last_appearance: r.last_appearance,
|
||||
confidence: r.confidence,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(IdentityFilesResponse {
|
||||
success: true,
|
||||
identity_uuid: uuid,
|
||||
total: data.len() as i64,
|
||||
page,
|
||||
page_size,
|
||||
data,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct IdentityFacesResponse {
|
||||
pub success: bool,
|
||||
pub identity_uuid: Uuid,
|
||||
pub total: i64,
|
||||
pub page: usize,
|
||||
pub page_size: usize,
|
||||
pub data: Vec<IdentityFaceItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct IdentityFaceItem {
|
||||
pub id: i64,
|
||||
pub file_uuid: String,
|
||||
pub frame_number: i64,
|
||||
pub timestamp_secs: f64,
|
||||
pub face_id: Option<String>,
|
||||
pub bbox: BBox,
|
||||
pub confidence: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct BBox {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub width: f64,
|
||||
pub height: f64,
|
||||
}
|
||||
|
||||
async fn get_identity_faces(
|
||||
State(state): State<crate::api::server::AppState>,
|
||||
Path(uuid_str): Path<String>,
|
||||
Query(params): Query<FilesQuery>,
|
||||
) -> Result<Json<IdentityFacesResponse>, (StatusCode, String)> {
|
||||
let uuid = Uuid::parse_str(&uuid_str)
|
||||
.map_err(|e| (StatusCode::BAD_REQUEST, format!("Invalid UUID: {}", e)))?;
|
||||
|
||||
let page = params.page.unwrap_or(1);
|
||||
let page_size = params.page_size.unwrap_or(50);
|
||||
let offset = ((page - 1) as i64) * (page_size as i64);
|
||||
|
||||
let records = state
|
||||
.db
|
||||
.get_identity_faces(&uuid, page_size as i32, offset)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
let data: Vec<IdentityFaceItem> = records
|
||||
.into_iter()
|
||||
.map(|r| IdentityFaceItem {
|
||||
id: r.id,
|
||||
file_uuid: r.file_uuid,
|
||||
frame_number: r.frame_number,
|
||||
timestamp_secs: r.timestamp_secs,
|
||||
face_id: r.face_id,
|
||||
bbox: BBox {
|
||||
x: r.x,
|
||||
y: r.y,
|
||||
width: r.width,
|
||||
height: r.height,
|
||||
},
|
||||
confidence: r.confidence,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(IdentityFacesResponse {
|
||||
success: true,
|
||||
identity_uuid: uuid,
|
||||
total: data.len() as i64,
|
||||
page,
|
||||
page_size,
|
||||
data,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct IdentityChunksResponse {
|
||||
pub success: bool,
|
||||
pub identity_uuid: Uuid,
|
||||
pub total: i64,
|
||||
pub page: usize,
|
||||
pub page_size: usize,
|
||||
pub data: Vec<IdentityChunkItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct IdentityChunkItem {
|
||||
pub id: i64,
|
||||
pub file_uuid: String,
|
||||
pub chunk_id: String,
|
||||
pub chunk_type: String,
|
||||
pub start_time: Option<f64>,
|
||||
pub end_time: Option<f64>,
|
||||
pub text_content: Option<String>,
|
||||
}
|
||||
|
||||
async fn get_identity_chunks(
|
||||
State(state): State<crate::api::server::AppState>,
|
||||
Path(uuid_str): Path<String>,
|
||||
Query(params): Query<FilesQuery>,
|
||||
) -> Result<Json<IdentityChunksResponse>, (StatusCode, String)> {
|
||||
let uuid = Uuid::parse_str(&uuid_str)
|
||||
.map_err(|e| (StatusCode::BAD_REQUEST, format!("Invalid UUID: {}", e)))?;
|
||||
|
||||
let page = params.page.unwrap_or(1);
|
||||
let page_size = params.page_size.unwrap_or(20);
|
||||
let offset = ((page - 1) as i64) * (page_size as i64);
|
||||
|
||||
let records = state
|
||||
.db
|
||||
.get_identity_chunks(&uuid, page_size as i32, offset)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
let data: Vec<IdentityChunkItem> = records
|
||||
.into_iter()
|
||||
.map(|r| IdentityChunkItem {
|
||||
id: r.id,
|
||||
file_uuid: r.file_uuid,
|
||||
chunk_id: r.chunk_id,
|
||||
chunk_type: r.chunk_type,
|
||||
start_time: r.start_time,
|
||||
end_time: r.end_time,
|
||||
text_content: r.text_content,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(IdentityChunksResponse {
|
||||
success: true,
|
||||
identity_uuid: uuid,
|
||||
total: data.len() as i64,
|
||||
page,
|
||||
page_size,
|
||||
data,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -326,7 +692,10 @@ async fn register_resource(
|
||||
created_at: None,
|
||||
};
|
||||
|
||||
state.db.register_resource(resource).await
|
||||
state
|
||||
.db
|
||||
.register_resource(resource)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
Ok(Json(ResourceResponse {
|
||||
@@ -347,7 +716,10 @@ async fn heartbeat_resource(
|
||||
Json(req): Json<HeartbeatRequest>,
|
||||
) -> Result<Json<ResourceResponse>, (StatusCode, String)> {
|
||||
let status = req.status.unwrap_or("online".to_string());
|
||||
state.db.heartbeat_resource(&req.resource_id, &status).await
|
||||
state
|
||||
.db
|
||||
.heartbeat_resource(&req.resource_id, &status)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
Ok(Json(ResourceResponse {
|
||||
@@ -360,17 +732,23 @@ async fn heartbeat_resource(
|
||||
async fn list_resources(
|
||||
State(state): State<crate::api::server::AppState>,
|
||||
) -> Result<Json<ResourceResponse>, (StatusCode, String)> {
|
||||
let records = state.db.list_resources().await
|
||||
let records = state
|
||||
.db
|
||||
.list_resources()
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
let data: Vec<ResourceItem> = records.into_iter().map(|r| ResourceItem {
|
||||
resource_id: r.resource_id,
|
||||
resource_type: r.resource_type,
|
||||
category: r.category,
|
||||
capabilities: r.capabilities,
|
||||
status: r.status,
|
||||
last_heartbeat: r.last_heartbeat,
|
||||
}).collect();
|
||||
let data: Vec<ResourceItem> = records
|
||||
.into_iter()
|
||||
.map(|r| ResourceItem {
|
||||
resource_id: r.resource_id,
|
||||
resource_type: r.resource_type,
|
||||
category: r.category,
|
||||
capabilities: r.capabilities,
|
||||
status: r.status,
|
||||
last_heartbeat: r.last_heartbeat,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(ResourceResponse {
|
||||
success: true,
|
||||
|
||||
Reference in New Issue
Block a user