Skip to content
Clone
f2e745a
215 lines 5.9 KB Rust
use export::http::{Json, Path, Query, State};
use export::rest::{AppResponseData, NoData};
use jwt::Session;
use serde::{Deserialize, Serialize};
use utoipa::IntoParams;
use crate::errors::{ApiError, GitApiError};
use crate::handler::{RepoPath, require_writer, resolve_reader};
use crate::service::GitService;
use crate::service::topics::{CreateTopicCmd, TopicDto, TopicRepoDto};
#[derive(Debug, Clone, Deserialize, IntoParams)]
pub struct TopicNamePath {
pub name: String,
}
#[derive(Debug, Clone, Deserialize, IntoParams)]
pub struct TopicListQuery {
pub limit: Option<u64>,
pub offset: Option<u64>,
}
#[derive(Debug, Clone, Deserialize, IntoParams, utoipa::ToSchema)]
pub struct RepoTopicPath {
pub space_name: String,
pub repo_name: String,
pub topic_name: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct TopicOkResp {
pub topic: TopicDto,
}
#[utoipa::path(
post,
path = "/topics",
tag = "topics",
description = "创建全局标签;同名时幂等返回已有标签。",
request_body = CreateTopicCmd,
responses(
(status = 200, description = "create topic", body = AppResponseData<TopicDto>)
)
)]
pub async fn create_topic(
State(service): State<GitService>,
Json(cmd): Json<CreateTopicCmd>,
) -> Result<AppResponseData<TopicDto>, ApiError> {
let out = service
.create_topic(cmd.name)
.await
.map_err(GitApiError::from)?;
Ok(AppResponseData::ok(out))
}
#[utoipa::path(
get,
path = "/topics",
tag = "topics",
description = "分页列出系统全部标签。",
params(TopicListQuery),
responses(
(status = 200, description = "list topics", body = AppResponseData<Vec<TopicDto>>)
)
)]
pub async fn list_topics(
State(service): State<GitService>,
Query(q): Query<TopicListQuery>,
) -> Result<AppResponseData<Vec<TopicDto>>, ApiError> {
let limit = q.limit.unwrap_or(50).clamp(1, 100);
let offset = q
.offset
.unwrap_or(0)
.clamp(0, export::pagination::MAX_OFFSET);
let out = service
.list_topics(limit, offset)
.await
.map_err(GitApiError::from)?;
Ok(AppResponseData::ok(out))
}
#[utoipa::path(
get,
path = "/topics/{name}",
tag = "topics",
description = "按名称读取单个标签。",
params(TopicNamePath),
responses(
(status = 200, description = "get topic", body = AppResponseData<TopicDto>)
)
)]
pub async fn get_topic(
State(service): State<GitService>,
Path(p): Path<TopicNamePath>,
) -> Result<AppResponseData<TopicDto>, ApiError> {
let out = service
.get_topic(&p.name)
.await
.map_err(GitApiError::from)?;
Ok(AppResponseData::ok(out))
}
#[utoipa::path(
get,
path = "/topics/{name}/repos",
tag = "topics",
description = "分页列出关联某标签的仓库记录。",
params(TopicNamePath, TopicListQuery),
responses(
(status = 200, description = "repos using topic", body = AppResponseData<Vec<TopicRepoDto>>)
)
)]
pub async fn list_repos_by_topic(
State(service): State<GitService>,
Path(p): Path<TopicNamePath>,
Query(q): Query<TopicListQuery>,
) -> Result<AppResponseData<Vec<TopicRepoDto>>, ApiError> {
let limit = q.limit.unwrap_or(50).clamp(1, 100);
let offset = q
.offset
.unwrap_or(0)
.clamp(0, export::pagination::MAX_OFFSET);
let out = service
.list_repos_by_topic(&p.name, limit, offset)
.await
.map_err(GitApiError::from)?;
Ok(AppResponseData::ok(out))
}
#[utoipa::path(
put,
path = "/repos/{space_name}/{repo_name}/topics/{topic_name}",
tag = "repos",
description = "为仓库绑定标签,并在标签不存在时自动创建。",
params(RepoTopicPath),
responses(
(status = 200, description = "add topic to repo", body = AppResponseData<TopicDto>)
)
)]
pub async fn add_topic_to_repo(
State(service): State<GitService>,
session: Session,
Path(p): Path<RepoTopicPath>,
) -> Result<AppResponseData<TopicDto>, ApiError> {
let (ctx, _uid) = require_writer(
&service,
&session,
p.space_name.clone(),
p.repo_name.clone(),
)
.await?;
let out = service
.add_topic_to_repo(&ctx, &p.topic_name)
.await
.map_err(GitApiError::from)?;
Ok(AppResponseData::ok(out))
}
#[utoipa::path(
delete,
path = "/repos/{space_name}/{repo_name}/topics/{topic_name}",
tag = "repos",
description = "解除仓库与指定标签的关联,但保留标签本身。",
params(RepoTopicPath),
responses(
(status = 200, description = "remove topic from repo", body = AppResponseData<NoData>)
)
)]
pub async fn remove_topic_from_repo(
State(service): State<GitService>,
session: Session,
Path(p): Path<RepoTopicPath>,
) -> Result<AppResponseData<NoData>, ApiError> {
let (ctx, _uid) = require_writer(
&service,
&session,
p.space_name.clone(),
p.repo_name.clone(),
)
.await?;
service
.remove_topic_from_repo(&ctx, &p.topic_name)
.await
.map_err(GitApiError::from)?;
Ok(AppResponseData::empty())
}
#[utoipa::path(
get,
path = "/repos/{space_name}/{repo_name}/topics",
tag = "repos",
description = "列出仓库当前绑定的标签。",
params(RepoPath),
responses(
(status = 200, description = "list repo topics", body = AppResponseData<Vec<TopicDto>>)
)
)]
pub async fn list_repo_topics(
State(service): State<GitService>,
session: Session,
Path(path): Path<RepoPath>,
) -> Result<AppResponseData<Vec<TopicDto>>, ApiError> {
let (ctx, _uid) = resolve_reader(
&service,
&session,
path.space_name.clone(),
path.repo_name.clone(),
)
.await?;
let out = service
.list_repo_topics(&ctx)
.await
.map_err(GitApiError::from)?;
Ok(AppResponseData::ok(out))
}