gitdataai/libs/api/skill.rs
2026-04-14 19:02:01 +08:00

245 lines
6.7 KiB
Rust

//! Skill management API endpoints.
use actix_web::{HttpResponse, Result, web};
use service::AppService;
use session::Session;
use crate::{ApiResponse, error::ApiError};
#[derive(serde::Deserialize, utoipa::IntoParams)]
pub struct SkillPath {
pub project_name: String,
pub slug: String,
}
#[derive(Debug, serde::Deserialize, utoipa::IntoParams)]
pub struct SkillQuery {
pub source: Option<String>,
pub enabled: Option<bool>,
}
#[utoipa::path(
get,
path = "/api/projects/{project_name}/skills",
params(
("project_name" = String, Path),
),
responses(
(status = 200, description = "List skills", body = ApiResponse<Vec<service::skill::info::SkillResponse>>),
(status = 401, description = "Unauthorized"),
(status = 404, description = "Project not found"),
),
tag = "Skill"
)]
pub async fn skill_list(
service: web::Data<AppService>,
session: Session,
path: web::Path<String>,
query: web::Query<SkillQuery>,
) -> Result<HttpResponse, ApiError> {
let project_name = path.into_inner();
let project = service
.project_info(&session, project_name.clone())
.await?;
let q = service::skill::info::SkillListQuery {
source: query.source.clone(),
enabled: query.enabled,
};
let skills = service
.skill_list(project.uid.to_string(), q, &session)
.await?;
Ok(ApiResponse::ok(skills).to_response())
}
#[utoipa::path(
get,
path = "/api/projects/{project_name}/skills/{slug}",
params(
("project_name" = String, Path),
("slug" = String, Path),
),
responses(
(status = 200, description = "Get skill", body = ApiResponse<service::skill::info::SkillResponse>),
(status = 401, description = "Unauthorized"),
(status = 404, description = "Not found"),
),
tag = "Skill"
)]
pub async fn skill_get(
service: web::Data<AppService>,
session: Session,
path: web::Path<SkillPath>,
) -> Result<HttpResponse, ApiError> {
let SkillPath {
project_name,
slug,
} = path.into_inner();
let project = service
.project_info(&session, project_name.clone())
.await?;
let skill = service
.skill_get(project.uid.to_string(), slug, &session)
.await?;
Ok(ApiResponse::ok(skill).to_response())
}
#[utoipa::path(
post,
path = "/api/projects/{project_name}/skills",
params(("project_name" = String, Path)),
request_body = service::skill::manage::CreateSkillRequest,
responses(
(status = 200, description = "Create skill", body = ApiResponse<service::skill::info::SkillResponse>),
(status = 401, description = "Unauthorized"),
(status = 409, description = "Skill already exists"),
),
tag = "Skill"
)]
pub async fn skill_create(
service: web::Data<AppService>,
session: Session,
path: web::Path<String>,
body: web::Json<service::skill::manage::CreateSkillRequest>,
) -> Result<HttpResponse, ApiError> {
let project_name = path.into_inner();
let project = service
.project_info(&session, project_name.clone())
.await?;
let skill = service
.skill_create(project.uid.to_string(), body.into_inner(), &session)
.await?;
Ok(ApiResponse::ok(skill).to_response())
}
#[utoipa::path(
patch,
path = "/api/projects/{project_name}/skills/{slug}",
params(
("project_name" = String, Path),
("slug" = String, Path),
),
request_body = service::skill::manage::UpdateSkillRequest,
responses(
(status = 200, description = "Update skill", body = ApiResponse<service::skill::info::SkillResponse>),
(status = 401, description = "Unauthorized"),
(status = 404, description = "Not found"),
),
tag = "Skill"
)]
pub async fn skill_update(
service: web::Data<AppService>,
session: Session,
path: web::Path<SkillPath>,
body: web::Json<service::skill::manage::UpdateSkillRequest>,
) -> Result<HttpResponse, ApiError> {
let SkillPath {
project_name,
slug,
} = path.into_inner();
let project = service
.project_info(&session, project_name.clone())
.await?;
let skill = service
.skill_update(project.uid.to_string(), slug, body.into_inner(), &session)
.await?;
Ok(ApiResponse::ok(skill).to_response())
}
#[utoipa::path(
delete,
path = "/api/projects/{project_name}/skills/{slug}",
params(
("project_name" = String, Path),
("slug" = String, Path),
),
responses(
(status = 200, description = "Delete skill", body = ApiResponse<service::skill::manage::DeleteSkillResponse>),
(status = 401, description = "Unauthorized"),
(status = 404, description = "Not found"),
),
tag = "Skill"
)]
pub async fn skill_delete(
service: web::Data<AppService>,
session: Session,
path: web::Path<SkillPath>,
) -> Result<HttpResponse, ApiError> {
let SkillPath {
project_name,
slug,
} = path.into_inner();
let project = service
.project_info(&session, project_name.clone())
.await?;
let result = service
.skill_delete(project.uid.to_string(), slug, &session)
.await?;
Ok(ApiResponse::ok(result).to_response())
}
#[utoipa::path(
post,
path = "/api/projects/{project_name}/skills/scan",
params(("project_name" = String, Path)),
responses(
(status = 200, description = "Scan repos for skills", body = ApiResponse<ScanResponse>),
(status = 401, description = "Unauthorized"),
),
tag = "Skill"
)]
pub async fn skill_scan(
service: web::Data<AppService>,
session: Session,
path: web::Path<String>,
) -> Result<HttpResponse, ApiError> {
let project_name = path.into_inner();
let project = service
.project_info(&session, project_name)
.await?;
let result = service
.skill_scan_repos(project.uid, project.uid)
.await?;
Ok(ApiResponse::ok(ScanResponse {
discovered: result.discovered,
created: result.created,
updated: result.updated,
removed: result.removed,
}).to_response())
}
#[derive(serde::Serialize, utoipa::ToSchema)]
pub struct ScanResponse {
pub discovered: i64,
pub created: i64,
pub updated: i64,
pub removed: i64,
}
pub fn init_skill_routes(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/projects/{project_name}/skills")
.route("", web::get().to(skill_list))
.route("", web::post().to(skill_create))
.route("/scan", web::post().to(skill_scan))
.route("/{slug}", web::get().to(skill_get))
.route("/{slug}", web::patch().to(skill_update))
.route("/{slug}", web::delete().to(skill_delete)),
);
}