perf(struct): ddd
This commit is contained in:
359
src/presentation/http/handlers/app.rs
Normal file
359
src/presentation/http/handlers/app.rs
Normal 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!({})))
|
||||
}
|
||||
Reference in New Issue
Block a user