use std::sync::Arc;
use axum::{
extract::{Path, Query, State},
Json,
};
use oxios_kernel::{ClawHubSearchResult, ClawHubSkillDetail};
use crate::error::AppError;
use crate::server::AppState;
#[derive(Debug, serde::Deserialize)]
pub struct SearchQuery {
pub q: String,
#[serde(default = "default_limit")]
pub limit: usize,
}
fn default_limit() -> usize {
20
}
#[derive(Debug, serde::Deserialize)]
pub struct SkillsShListQuery {
#[serde(default = "default_view")]
pub view: String,
#[serde(default)]
pub page: Option<i64>,
#[serde(default = "default_per_page")]
pub per_page: i64,
}
fn default_view() -> String {
"all-time".to_string()
}
fn default_per_page() -> i64 {
50
}
#[derive(Debug, serde::Deserialize)]
pub struct InstallBody {
pub version: Option<String>,
}
pub(crate) async fn handle_marketplace_search(
state: State<Arc<AppState>>,
Query(query): Query<SearchQuery>,
) -> Result<Json<Vec<ClawHubSearchResult>>, AppError> {
let results = state
.kernel
.marketplace_api()
.search(&query.q, Some(query.limit))
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(Json(results))
}
pub(crate) async fn handle_marketplace_skill_detail(
state: State<Arc<AppState>>,
Path(slug): Path<String>,
) -> Result<Json<ClawHubSkillDetail>, AppError> {
let detail = state
.kernel
.marketplace_api()
.get_skill(&slug)
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(Json(detail))
}
pub(crate) async fn handle_marketplace_install(
state: State<Arc<AppState>>,
Path(slug): Path<String>,
Json(body): Json<InstallBody>,
) -> Result<Json<serde_json::Value>, AppError> {
let result = state
.kernel
.marketplace_api()
.install(&slug, body.version.as_deref())
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(Json(serde_json::json!({
"ok": result.ok,
"slug": result.slug,
"version": result.version,
"targetDir": result.target_dir.to_string_lossy(),
"changelog": result.changelog,
})))
}
pub(crate) async fn handle_marketplace_updates(
state: State<Arc<AppState>>,
) -> Result<Json<Vec<serde_json::Value>>, AppError> {
let updates = state
.kernel
.marketplace_api()
.check_updates()
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
let results: Vec<serde_json::Value> = updates
.into_iter()
.map(|u| {
serde_json::json!({
"slug": u.slug,
"currentVersion": u.current_version,
"latestVersion": u.latest_version,
"changelog": u.changelog,
})
})
.collect();
Ok(Json(results))
}
pub(crate) async fn handle_skills_sh_search(
state: State<Arc<AppState>>,
Query(query): Query<SearchQuery>,
) -> Result<Json<serde_json::Value>, AppError> {
let resp = state
.kernel
.marketplace_api()
.search_skills_sh(&query.q, Some(query.limit))
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(Json(serde_json::json!({
"data": resp.data,
"query": resp.query,
"searchType": resp.search_type,
"count": resp.count,
"durationMs": resp.duration_ms,
})))
}
pub(crate) async fn handle_skills_sh_list(
state: State<Arc<AppState>>,
Query(query): Query<SkillsShListQuery>,
) -> Result<Json<serde_json::Value>, AppError> {
let resp = state
.kernel
.marketplace_api()
.list_skills_sh(Some(&query.view), query.page, Some(query.per_page))
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(Json(serde_json::json!({
"data": resp.data,
"pagination": {
"page": resp.pagination.page,
"perPage": resp.pagination.per_page,
"total": resp.pagination.total,
"hasMore": resp.pagination.has_more,
},
})))
}
pub(crate) async fn handle_skills_sh_skill_detail(
state: State<Arc<AppState>>,
Path(id): Path<String>,
) -> Result<Json<serde_json::Value>, AppError> {
let detail = state
.kernel
.marketplace_api()
.get_skills_sh_skill(&id)
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(Json(serde_json::to_value(&detail).unwrap_or_default()))
}
pub(crate) async fn handle_skills_sh_skill_audit(
state: State<Arc<AppState>>,
Path(id): Path<String>,
) -> Result<Json<serde_json::Value>, AppError> {
let audit = state
.kernel
.marketplace_api()
.audit_skills_sh(&id)
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(Json(serde_json::to_value(&audit).unwrap_or_default()))
}
pub(crate) async fn handle_skills_sh_install(
state: State<Arc<AppState>>,
Path(id): Path<String>,
) -> Result<Json<serde_json::Value>, AppError> {
let result = state
.kernel
.marketplace_api()
.install_skills_sh(&id)
.await
.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(Json(serde_json::json!({
"ok": result.ok,
"slug": result.slug,
"source": result.source,
"skillId": result.skill_id,
"targetDir": result.target_dir.to_string_lossy(),
"fileCount": result.file_count,
"installs": result.installs,
"hash": result.hash,
})))
}