perf(struct): ddd

This commit is contained in:
2026-02-03 17:31:08 +08:00
parent 202b5eaad5
commit 4a071bd7c8
64 changed files with 1214 additions and 1189 deletions

View File

@@ -0,0 +1,359 @@
use crate::middleware::auth::AuthContext;
use crate::models::{
App, AppStatusChangeRequest, ApproveAppStatusChangeRequest, CreateAppRequest, ListAppsQuery,
RequestAppStatusChangeRequest, UpdateAppRequest,
};
use crate::presentation::http::state::AppState;
use axum::{
Json,
extract::{Path, Query, State},
http::HeaderMap,
};
use common_telemetry::{AppError, AppResponse};
use tracing::instrument;
use uuid::Uuid;
fn require_sensitive_token(headers: &HeaderMap) -> Result<(), AppError> {
let Some(expected) = std::env::var("IAM_SENSITIVE_ACTION_TOKEN")
.ok()
.filter(|v| !v.is_empty())
else {
return Ok(());
};
let provided = headers
.get("X-Sensitive-Token")
.and_then(|v| v.to_str().ok())
.unwrap_or("")
.to_string();
if provided == expected {
Ok(())
} else {
Err(AppError::PermissionDenied(
"sensitive:token_required".into(),
))
}
}
#[utoipa::path(
post,
path = "/platform/apps",
tag = "App",
security(
("bearer_auth" = [])
),
request_body = CreateAppRequest,
responses(
(status = 201, description = "App created", body = App),
(status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized"),
(status = 403, description = "Forbidden")
),
params(
("Authorization" = String, Header, description = "Bearer <access_token>(访问令牌)")
)
)]
#[instrument(skip(state, payload))]
pub async fn create_app_handler(
State(state): State<AppState>,
AuthContext { user_id, .. }: AuthContext,
Json(payload): Json<CreateAppRequest>,
) -> Result<AppResponse<App>, AppError> {
state
.authorization_service
.require_platform_permission(user_id, "iam:app:write")
.await?;
let app = state.app_service.create_app(payload, user_id).await?;
Ok(AppResponse::created(app))
}
#[utoipa::path(
get,
path = "/platform/apps",
tag = "App",
security(
("bearer_auth" = [])
),
responses(
(status = 200, description = "Apps list", body = [App]),
(status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized"),
(status = 403, description = "Forbidden")
),
params(
("Authorization" = String, Header, description = "Bearer <access_token>(访问令牌)"),
ListAppsQuery
)
)]
#[instrument(skip(state))]
pub async fn list_apps_handler(
State(state): State<AppState>,
AuthContext { user_id, .. }: AuthContext,
Query(query): Query<ListAppsQuery>,
) -> Result<AppResponse<Vec<App>>, AppError> {
state
.authorization_service
.require_platform_permission(user_id, "iam:app:read")
.await?;
let apps = state.app_service.list_apps(query).await?;
Ok(AppResponse::ok(apps))
}
#[utoipa::path(
get,
path = "/platform/apps/{app_id}",
tag = "App",
security(
("bearer_auth" = [])
),
responses(
(status = 200, description = "App detail", body = App),
(status = 401, description = "Unauthorized"),
(status = 403, description = "Forbidden"),
(status = 404, description = "Not found")
),
params(
("Authorization" = String, Header, description = "Bearer <access_token>(访问令牌)"),
("app_id" = String, Path, description = "App id")
)
)]
#[instrument(skip(state))]
pub async fn get_app_handler(
State(state): State<AppState>,
AuthContext { user_id, .. }: AuthContext,
Path(app_id): Path<String>,
) -> Result<AppResponse<App>, AppError> {
state
.authorization_service
.require_platform_permission(user_id, "iam:app:read")
.await?;
let app = state.app_service.get_app(&app_id).await?;
Ok(AppResponse::ok(app))
}
#[utoipa::path(
patch,
path = "/platform/apps/{app_id}",
tag = "App",
security(
("bearer_auth" = [])
),
request_body = UpdateAppRequest,
responses(
(status = 200, description = "Updated", body = App),
(status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized"),
(status = 403, description = "Forbidden"),
(status = 404, description = "Not found")
),
params(
("Authorization" = String, Header, description = "Bearer <access_token>(访问令牌)"),
("app_id" = String, Path, description = "App id")
)
)]
#[instrument(skip(state, payload))]
pub async fn update_app_handler(
State(state): State<AppState>,
AuthContext { user_id, .. }: AuthContext,
Path(app_id): Path<String>,
Json(payload): Json<UpdateAppRequest>,
) -> Result<AppResponse<App>, AppError> {
state
.authorization_service
.require_platform_permission(user_id, "iam:app:write")
.await?;
let app = state
.app_service
.update_app(&app_id, payload, user_id)
.await?;
Ok(AppResponse::ok(app))
}
#[utoipa::path(
post,
path = "/platform/apps/{app_id}/status-change-requests",
tag = "App",
security(
("bearer_auth" = [])
),
request_body = RequestAppStatusChangeRequest,
responses(
(status = 201, description = "Request created", body = AppStatusChangeRequest),
(status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized"),
(status = 403, description = "Forbidden"),
(status = 404, description = "Not found")
),
params(
("Authorization" = String, Header, description = "Bearer <access_token>(访问令牌)"),
("app_id" = String, Path, description = "App id")
)
)]
#[instrument(skip(state, payload))]
pub async fn request_app_status_change_handler(
State(state): State<AppState>,
AuthContext { user_id, .. }: AuthContext,
Path(app_id): Path<String>,
Json(payload): Json<RequestAppStatusChangeRequest>,
) -> Result<AppResponse<AppStatusChangeRequest>, AppError> {
state
.authorization_service
.require_platform_permission(user_id, "iam:app:write")
.await?;
let req = state
.app_service
.request_status_change(&app_id, payload, user_id)
.await?;
Ok(AppResponse::created(req))
}
#[utoipa::path(
get,
path = "/platform/app-status-change-requests",
tag = "App",
security(
("bearer_auth" = [])
),
responses(
(status = 200, description = "Requests list", body = [AppStatusChangeRequest]),
(status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized"),
(status = 403, description = "Forbidden")
),
params(
("Authorization" = String, Header, description = "Bearer <access_token>(访问令牌)"),
("status" = Option<String>, Query, description = "pending/approved/applied/rejected"),
("page" = Option<u32>, Query, description = "页码,默认 1"),
("page_size" = Option<u32>, Query, description = "每页数量,默认 20最大 200")
)
)]
#[instrument(skip(state))]
pub async fn list_app_status_change_requests_handler(
State(state): State<AppState>,
AuthContext { user_id, .. }: AuthContext,
Query(params): Query<std::collections::HashMap<String, String>>,
) -> Result<AppResponse<Vec<AppStatusChangeRequest>>, AppError> {
state
.authorization_service
.require_platform_permission(user_id, "iam:app:read")
.await?;
let status = params.get("status").cloned();
let page = params.get("page").and_then(|v| v.parse::<u32>().ok());
let page_size = params.get("page_size").and_then(|v| v.parse::<u32>().ok());
let rows = state
.app_service
.list_status_change_requests(status, page, page_size)
.await?;
Ok(AppResponse::ok(rows))
}
#[utoipa::path(
post,
path = "/platform/app-status-change-requests/{request_id}/approve",
tag = "App",
security(
("bearer_auth" = [])
),
request_body = ApproveAppStatusChangeRequest,
responses(
(status = 200, description = "Approved", body = AppStatusChangeRequest),
(status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized"),
(status = 403, description = "Forbidden"),
(status = 404, description = "Not found")
),
params(
("Authorization" = String, Header, description = "Bearer <access_token>(访问令牌)"),
("request_id" = String, Path, description = "Request id (UUID)")
)
)]
#[instrument(skip(state, payload))]
pub async fn approve_app_status_change_handler(
State(state): State<AppState>,
AuthContext { user_id, .. }: AuthContext,
Path(request_id): Path<Uuid>,
Json(payload): Json<ApproveAppStatusChangeRequest>,
) -> Result<AppResponse<AppStatusChangeRequest>, AppError> {
state
.authorization_service
.require_platform_permission(user_id, "iam:app:approve")
.await?;
let row = state
.app_service
.approve_status_change(request_id, payload.effective_at, user_id)
.await?;
Ok(AppResponse::ok(row))
}
#[utoipa::path(
post,
path = "/platform/app-status-change-requests/{request_id}/reject",
tag = "App",
security(
("bearer_auth" = [])
),
responses(
(status = 200, description = "Rejected", body = AppStatusChangeRequest),
(status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized"),
(status = 403, description = "Forbidden"),
(status = 404, description = "Not found")
),
params(
("Authorization" = String, Header, description = "Bearer <access_token>(访问令牌)"),
("request_id" = String, Path, description = "Request id (UUID)"),
("reason" = Option<String>, Query, description = "Reject reason")
)
)]
#[instrument(skip(state))]
pub async fn reject_app_status_change_handler(
State(state): State<AppState>,
AuthContext { user_id, .. }: AuthContext,
Path(request_id): Path<Uuid>,
Query(params): Query<std::collections::HashMap<String, String>>,
) -> Result<AppResponse<AppStatusChangeRequest>, AppError> {
state
.authorization_service
.require_platform_permission(user_id, "iam:app:approve")
.await?;
let reason = params.get("reason").cloned();
let row = state
.app_service
.reject_status_change(request_id, reason, user_id)
.await?;
Ok(AppResponse::ok(row))
}
#[utoipa::path(
delete,
path = "/platform/apps/{app_id}",
tag = "App",
security(
("bearer_auth" = [])
),
responses(
(status = 200, description = "Deleted"),
(status = 401, description = "Unauthorized"),
(status = 403, description = "Forbidden"),
(status = 404, description = "Not found")
),
params(
("Authorization" = String, Header, description = "Bearer <access_token>(访问令牌)"),
("X-Sensitive-Token" = Option<String>, Header, description = "二次验证令牌(当 IAM_SENSITIVE_ACTION_TOKEN 设置时必填)"),
("app_id" = String, Path, description = "App id")
)
)]
#[instrument(skip(state, headers))]
pub async fn delete_app_handler(
State(state): State<AppState>,
AuthContext { user_id, .. }: AuthContext,
headers: HeaderMap,
Path(app_id): Path<String>,
) -> Result<AppResponse<serde_json::Value>, AppError> {
state
.authorization_service
.require_platform_permission(user_id, "iam:app:delete")
.await?;
require_sensitive_token(&headers)?;
state.app_service.delete_app(&app_id, user_id).await?;
Ok(AppResponse::ok(serde_json::json!({})))
}