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, pub parent_id: Option, pub sort_order: Option, } #[derive(Debug, serde::Deserialize, utoipa::ToSchema)] pub struct UpdateColumnRequest { pub name: Option, pub slug: Option, pub description: Option>, pub parent_id: Option>, pub sort_order: Option, } #[derive(Debug, serde::Deserialize, IntoParams)] pub struct ListColumnsQuery { pub page: Option, pub page_size: Option, pub search: Option, pub parent_id: Option, } pub fn router() -> Router { 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, headers: axum::http::HeaderMap, Json(body): Json, ) -> Result, 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), (status = 401, description = "未认证"), (status = 403, description = "无权限") ) )] pub async fn list_columns_handler( TenantId(tenant_id): TenantId, AuthContext { user_id, .. }: AuthContext, State(state): State, headers: axum::http::HeaderMap, Query(query): Query, ) -> Result< AppResponse>, 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, headers: axum::http::HeaderMap, Path(id): Path, ) -> Result, 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, headers: axum::http::HeaderMap, Path(id): Path, Json(body): Json, ) -> Result, 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, headers: axum::http::HeaderMap, Path(id): Path, ) -> Result, 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}))) }