Files
cms-service/src/api/handlers/column.rs
2026-02-11 10:56:04 +08:00

250 lines
7.0 KiB
Rust

use axum::{
Json, Router,
extract::{Path, Query, State},
routing::{get, post},
};
use common_telemetry::{AppError, AppResponse};
use utoipa::IntoParams;
use uuid::Uuid;
use crate::api::{AppState, handlers::common::extract_bearer_token};
use auth_kit::middleware::{auth::AuthContext, tenant::TenantId};
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
pub struct CreateColumnRequest {
pub name: String,
pub slug: String,
pub description: Option<String>,
pub parent_id: Option<Uuid>,
pub sort_order: Option<i32>,
}
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
pub struct UpdateColumnRequest {
pub name: Option<String>,
pub slug: Option<String>,
pub description: Option<Option<String>>,
pub parent_id: Option<Option<Uuid>>,
pub sort_order: Option<i32>,
}
#[derive(Debug, serde::Deserialize, IntoParams)]
pub struct ListColumnsQuery {
pub page: Option<u32>,
pub page_size: Option<u32>,
pub search: Option<String>,
pub parent_id: Option<Uuid>,
}
pub fn router() -> Router<AppState> {
Router::new()
.route("/", post(create_column_handler).get(list_columns_handler))
.route(
"/{id}",
get(get_column_handler)
.patch(update_column_handler)
.delete(delete_column_handler),
)
}
#[utoipa::path(
post,
path = "/columns",
tag = "Column",
request_body = CreateColumnRequest,
security(
("bearer_auth" = [])
),
responses(
(status = 200, description = "创建栏目", body = crate::domain::models::Column),
(status = 401, description = "未认证"),
(status = 403, description = "无权限")
)
)]
pub async fn create_column_handler(
TenantId(tenant_id): TenantId,
AuthContext { user_id, .. }: AuthContext,
State(state): State<AppState>,
headers: axum::http::HeaderMap,
Json(body): Json<CreateColumnRequest>,
) -> Result<AppResponse<crate::domain::models::Column>, AppError> {
let token = extract_bearer_token(&headers)?;
state
.iam_client
.require_permission(tenant_id, user_id, "cms:column:write", &token)
.await?;
let column = state
.services
.create_column(
tenant_id,
body.name,
body.slug,
body.description,
body.parent_id,
body.sort_order.unwrap_or(0),
)
.await?;
Ok(AppResponse::ok(column))
}
#[utoipa::path(
get,
path = "/columns",
tag = "Column",
params(ListColumnsQuery),
security(
("bearer_auth" = [])
),
responses(
(status = 200, description = "栏目列表", body = crate::domain::models::Paged<crate::domain::models::Column>),
(status = 401, description = "未认证"),
(status = 403, description = "无权限")
)
)]
pub async fn list_columns_handler(
TenantId(tenant_id): TenantId,
AuthContext { user_id, .. }: AuthContext,
State(state): State<AppState>,
headers: axum::http::HeaderMap,
Query(query): Query<ListColumnsQuery>,
) -> Result<
AppResponse<crate::infrastructure::repositories::column::Paged<crate::domain::models::Column>>,
AppError,
> {
let token = extract_bearer_token(&headers)?;
state
.iam_client
.require_permission(tenant_id, user_id, "cms:column:read", &token)
.await?;
let result = state
.services
.list_columns(
tenant_id,
crate::infrastructure::repositories::column::ListColumnsQuery {
page: query.page.unwrap_or(1),
page_size: query.page_size.unwrap_or(20),
search: query.search,
parent_id: query.parent_id,
},
)
.await?;
Ok(AppResponse::ok(result))
}
#[utoipa::path(
get,
path = "/columns/{id}",
tag = "Column",
params(
("id" = String, Path, description = "栏目ID")
),
security(
("bearer_auth" = [])
),
responses(
(status = 200, description = "栏目详情", body = crate::domain::models::Column),
(status = 401, description = "未认证"),
(status = 403, description = "无权限"),
(status = 404, description = "不存在")
)
)]
pub async fn get_column_handler(
TenantId(tenant_id): TenantId,
AuthContext { user_id, .. }: AuthContext,
State(state): State<AppState>,
headers: axum::http::HeaderMap,
Path(id): Path<Uuid>,
) -> Result<AppResponse<crate::domain::models::Column>, AppError> {
let token = extract_bearer_token(&headers)?;
state
.iam_client
.require_permission(tenant_id, user_id, "cms:column:read", &token)
.await?;
let column = state.services.get_column(tenant_id, id).await?;
Ok(AppResponse::ok(column))
}
#[utoipa::path(
patch,
path = "/columns/{id}",
tag = "Column",
request_body = UpdateColumnRequest,
params(
("id" = String, Path, description = "栏目ID")
),
security(
("bearer_auth" = [])
),
responses(
(status = 200, description = "更新栏目", body = crate::domain::models::Column),
(status = 401, description = "未认证"),
(status = 403, description = "无权限"),
(status = 404, description = "不存在")
)
)]
pub async fn update_column_handler(
TenantId(tenant_id): TenantId,
AuthContext { user_id, .. }: AuthContext,
State(state): State<AppState>,
headers: axum::http::HeaderMap,
Path(id): Path<Uuid>,
Json(body): Json<UpdateColumnRequest>,
) -> Result<AppResponse<crate::domain::models::Column>, AppError> {
let token = extract_bearer_token(&headers)?;
state
.iam_client
.require_permission(tenant_id, user_id, "cms:column:write", &token)
.await?;
let column = state
.services
.update_column(
tenant_id,
id,
body.name,
body.slug,
body.description,
body.parent_id,
body.sort_order,
)
.await?;
Ok(AppResponse::ok(column))
}
#[utoipa::path(
delete,
path = "/columns/{id}",
tag = "Column",
params(
("id" = String, Path, description = "栏目ID")
),
security(
("bearer_auth" = [])
),
responses(
(status = 200, description = "删除成功"),
(status = 401, description = "未认证"),
(status = 403, description = "无权限"),
(status = 404, description = "不存在")
)
)]
pub async fn delete_column_handler(
TenantId(tenant_id): TenantId,
AuthContext { user_id, .. }: AuthContext,
State(state): State<AppState>,
headers: axum::http::HeaderMap,
Path(id): Path<Uuid>,
) -> Result<AppResponse<serde_json::Value>, AppError> {
let token = extract_bearer_token(&headers)?;
state
.iam_client
.require_permission(tenant_id, user_id, "cms:column:write", &token)
.await?;
state.services.delete_column(tenant_id, id).await?;
Ok(AppResponse::ok(serde_json::json!({"deleted": true})))
}