feat: Initial v0.9 release with API Key authentication

## v0.9.20260325_144654

### Features
- API Key Authentication System
- Job Worker System
- V2 Backup Versioning

### Bug Fixes
- get_processor_results_by_job column mapping

Co-authored-by: OpenCode
This commit is contained in:
accusys
2026-03-25 14:52:51 +08:00
parent 47e86b696f
commit 383201cacd
193 changed files with 40268 additions and 422 deletions

View File

@@ -7,12 +7,15 @@ use axum::{
};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::sync::Arc;
use std::time::Instant;
use crate::core::cache::{keys, MongoCache, RedisCache};
use crate::core::db::{Database, PostgresDb, QdrantDb, RedisClient, VideoRecord, VideoStatus};
use crate::{Embedder, FileManager};
use super::middleware::api_key_validation;
#[derive(Debug, Serialize)]
struct HealthResponse {
status: String,
@@ -59,6 +62,7 @@ struct AppState {
embedder_model: String,
mongo_cache: MongoCache,
redis_cache: RedisCache,
api_state: super::middleware::ApiState,
}
#[derive(Debug, Deserialize)]
@@ -982,14 +986,23 @@ async fn list_videos(
let cache_key = keys::videos_list(page, limit);
let ttl = state.mongo_cache.ttl_videos();
tracing::info!(
"list_videos called: page={}, limit={}, cache_key={}",
page,
limit,
cache_key
);
let video_infos = state
.mongo_cache
.get_or_fetch(&cache_key, ttl, keys::CATEGORY_VIDEOS, || async {
tracing::info!("Fetching videos from database...");
let db = PostgresDb::init()
.await
.map_err(|e| anyhow::anyhow!("PG init failed: {}", e))?;
let videos = db.list_videos().await?;
tracing::info!("Got {} videos from DB", videos.len());
let video_infos: Vec<VideoInfoResponse> = videos
.into_iter()
@@ -1003,12 +1016,17 @@ async fn list_videos(
})
.collect();
tracing::info!("Mapped to {} video infos", video_infos.len());
Ok::<VideosResponse, anyhow::Error>(VideosResponse {
videos: video_infos,
})
})
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
.map_err(|e| {
tracing::error!("Error in list_videos: {}", e);
StatusCode::INTERNAL_SERVER_ERROR
})?;
Ok(Json(video_infos))
}
@@ -1222,20 +1240,34 @@ async fn list_jobs() -> Result<Json<JobListResponse>, StatusCode> {
async fn get_job(
axum::extract::Path(uuid): axum::extract::Path<String>,
) -> Result<Json<JobDetailResponse>, StatusCode> {
let pg = PostgresDb::init()
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
tracing::info!("[get_job] START - uuid: {}", uuid);
let pg = PostgresDb::init().await.map_err(|e| {
tracing::error!("[get_job] ERROR - Failed to init PostgresDb: {}", e);
StatusCode::INTERNAL_SERVER_ERROR
})?;
tracing::info!("[get_job] PostgresDb initialized");
let job = pg
.get_monitor_job_by_uuid(&uuid)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.ok_or(StatusCode::NOT_FOUND)?;
.map_err(|e| {
tracing::error!("[get_job] ERROR - Failed to get monitor job: {}", e);
StatusCode::INTERNAL_SERVER_ERROR
})?
.ok_or_else(|| {
tracing::warn!("[get_job] Job not found: {}", uuid);
StatusCode::NOT_FOUND
})?;
let results = pg
.get_processor_results_by_job(job.id)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
tracing::info!("[get_job] Found job: id={}, uuid={}", job.id, job.uuid);
let results = pg.get_processor_results_by_job(job.id).await.map_err(|e| {
tracing::error!("[get_job] ERROR - Failed to get processor results: {}", e);
StatusCode::INTERNAL_SERVER_ERROR
})?;
tracing::info!("[get_job] Got {} processor results", results.len());
let processors: Vec<ProcessorInfoResponse> = results
.into_iter()
@@ -1249,7 +1281,9 @@ async fn get_job(
})
.collect();
Ok(Json(JobDetailResponse {
tracing::info!("[get_job] Mapped {} processors", processors.len());
let response = JobDetailResponse {
id: job.id,
uuid: job.uuid,
status: job.status.as_str().to_string(),
@@ -1260,7 +1294,10 @@ async fn get_job(
created_at: job.created_at.to_string(),
started_at: job.started_at.map(|t| t.to_string()),
updated_at: job.updated_at.map(|t| t.to_string()),
}))
};
tracing::info!("[get_job] SUCCESS - returning response");
Ok(Json(response))
}
pub async fn start_server(host: &str, port: u16) -> anyhow::Result<()> {
@@ -1269,17 +1306,18 @@ pub async fn start_server(host: &str, port: u16) -> anyhow::Result<()> {
let embedder = std::sync::Arc::new(Embedder::new("nomic-embed-text:v1.5".to_string()));
let mongo_cache = MongoCache::init().await?;
let redis_cache = RedisCache::new()?;
let db = PostgresDb::init().await?;
let api_state = super::middleware::ApiState { db: Arc::new(db) };
let state = AppState {
embedder,
embedder_model: "nomic-embed-text:v1.5".to_string(),
mongo_cache,
redis_cache,
api_state,
};
let app = Router::new()
.route("/health", get(health))
.route("/health/detailed", get(health_detailed))
let protected_routes = Router::new()
.route("/api/v1/register", post(register))
.route("/api/v1/probe", post(probe))
.route("/api/v1/search", post(search))
@@ -1290,6 +1328,16 @@ pub async fn start_server(host: &str, port: u16) -> anyhow::Result<()> {
.route("/api/v1/progress/:uuid", get(get_progress))
.route("/api/v1/jobs", get(list_jobs))
.route("/api/v1/jobs/:uuid", get(get_job))
.layer(axum::middleware::from_fn_with_state(
state.api_state.clone(),
api_key_validation,
))
.with_state(state.clone());
let app = Router::new()
.route("/health", get(health))
.route("/health/detailed", get(health_detailed))
.merge(protected_routes)
.with_state(state);
let addr: std::net::SocketAddr = format!("{}:{}", host, port).parse().unwrap();