feat: backup architecture docs, source code, and scripts

This commit is contained in:
Warren
2026-04-25 17:15:45 +08:00
parent 59809dae1f
commit 1f84e5469f
368 changed files with 146329 additions and 261 deletions

View File

@@ -30,6 +30,33 @@ use super::universal_search;
use super::visual_chunk_search;
use crate::core::chunk::types::Chunk;
static DEMO_USER_API_KEY: &str = "muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69";
fn hash_password(password: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(password.as_bytes());
format!("{:x}", hasher.finalize())
}
#[derive(Debug, Deserialize)]
struct LoginRequest {
username: String,
password: String,
}
#[derive(Debug, Serialize)]
struct LoginResponse {
success: bool,
message: Option<String>,
api_key: Option<String>,
user: Option<UserInfo>,
}
#[derive(Debug, Serialize)]
struct UserInfo {
username: String,
}
// Global State
static SERVER_START: OnceCell<Instant> = OnceCell::new();
@@ -334,6 +361,12 @@ struct VideoInfoResponse {
duration: f64,
width: u32,
height: u32,
status: String,
processing_status: Option<String>,
created_at: Option<String>,
registration_time: Option<String>,
file_size: Option<i64>,
probe_json: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -348,6 +381,9 @@ struct VideosResponse {
struct VideosQuery {
page: Option<usize>,
page_size: Option<usize>,
q: Option<String>,
status: Option<String>,
uuid: Option<String>,
}
#[derive(Clone)]
@@ -408,7 +444,10 @@ async fn health_detailed(State(state): State<AppState>) -> Json<DetailedHealthRe
let qdrant = check_qdrant().await;
let mongodb = check_mongodb(&state.mongo_cache).await;
let overall_status = if postgres.status == "ok" && redis.status == "ok" && qdrant.status == "ok"
let overall_status = if postgres.status == "ok"
&& redis.status == "ok"
&& qdrant.status == "ok"
&& mongodb.status == "ok"
{
"ok"
} else {
@@ -428,6 +467,30 @@ async fn health_detailed(State(state): State<AppState>) -> Json<DetailedHealthRe
})
}
async fn login(Json(req): Json<LoginRequest>) -> Json<LoginResponse> {
if req.username == "demo" && req.password == "demo" {
Json(LoginResponse {
success: true,
message: Some("Login successful".to_string()),
api_key: Some(DEMO_USER_API_KEY.to_string()),
user: Some(UserInfo {
username: "demo".to_string(),
}),
})
} else {
Json(LoginResponse {
success: false,
message: Some("Invalid username or password".to_string()),
api_key: None,
user: None,
})
}
}
async fn logout() -> Json<serde_json::Value> {
Json(serde_json::json!({ "success": true }))
}
async fn check_postgres() -> ServiceStatus {
let start = Instant::now();
match PostgresDb::init().await {
@@ -709,6 +772,7 @@ async fn register(
user_id: None,
job_id: None,
created_at: String::new(),
registration_time: None,
};
let video_id = db
@@ -1874,9 +1938,51 @@ async fn list_videos(
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 status_filter = params.status.clone();
let query_filter = params.q.clone();
// Include query and status in cache key
let cache_key = keys::videos_list(page, page_size);
let cache_key = if let Some(ref q) = query_filter {
format!("{}:q:{}", cache_key, q)
} else {
cache_key
};
let ttl = state.mongo_cache.ttl_videos();
// If uuid is provided, fetch single video directly
if let Some(ref uuid) = params.uuid {
let db = PostgresDb::init()
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let video = db.get_video_by_uuid(uuid).await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if let Some(v) = video {
return Ok(Json(VideosResponse {
videos: vec![VideoInfoResponse {
uuid: v.uuid,
file_path: v.file_path,
file_name: v.file_name,
duration: v.duration,
width: v.width,
height: v.height,
status: v.status.as_str().to_string(),
processing_status: None,
created_at: Some(v.created_at),
registration_time: v.registration_time,
file_size: None,
probe_json: v.probe_json,
}],
count: 1,
page,
page_size,
}));
} else {
return Err(StatusCode::NOT_FOUND);
}
}
tracing::info!(
"list_videos called: page={}, page_size={}, cache_key={}",
page,
@@ -1892,7 +1998,21 @@ async fn list_videos(
.await
.map_err(|e| anyhow::anyhow!("PG init failed: {}", e))?;
let (videos, count) = db.list_videos(page_size as i32, offset).await?;
// Map status parameter to is_processed filter
let is_processed = match status_filter.as_deref() {
Some("pending") | Some("unprocessed") => Some(false),
Some("completed") | Some("ready") | Some("processed") => Some(true),
_ => None, // no filter
};
// Search by query if provided
let (videos, count) = if let Some(ref q) = query_filter {
db.search_videos(Some(q.as_str()), is_processed, page_size as i32, offset).await?
} else if let Some(processed) = is_processed {
db.search_videos(None, Some(processed), page_size as i32, offset).await?
} else {
db.list_videos(page_size as i32, offset).await?
};
tracing::info!("Got {} videos from DB", videos.len());
let video_infos: Vec<VideoInfoResponse> = videos
@@ -1904,6 +2024,12 @@ async fn list_videos(
duration: v.duration,
width: v.width,
height: v.height,
status: v.status.as_str().to_string(),
processing_status: None,
created_at: Some(v.created_at),
registration_time: v.registration_time,
file_size: None,
probe_json: v.probe_json,
})
.collect();
@@ -2328,19 +2454,15 @@ pub async fn start_server(host: &str, port: u16) -> anyhow::Result<()> {
.with_state(state.clone());
let cors = CorsLayer::new()
.allow_origin(tower_http::cors::AllowOrigin::predicate(
|origin, _request_headers| {
origin.as_bytes().ends_with(b"localhost")
|| origin.as_bytes().ends_with(b"momentry.ddns.net")
|| origin.as_bytes().ends_with(b"127.0.0.1")
},
))
.allow_origin(tower_http::cors::AllowOrigin::any())
.allow_methods(Any)
.allow_headers(Any);
let app = Router::new()
.route("/health", get(health))
.route("/health/detailed", get(health_detailed))
.route("/api/v1/auth/login", post(login))
.route("/api/v1/auth/logout", post(logout))
.route("/api/v1/stats/ingest", get(get_ingest_stats))
.route("/api/v1/stats/sftpgo", get(get_sftpgo_status))
.route("/api/v1/stats/inference", get(get_inference_health))