use axum::Extension; use axum::extract::{FromRef, Multipart, Path, Query, State}; use axum::response::Json; use uuid::Uuid; use erp_core::error::AppError; use erp_core::rbac::require_permission; use erp_core::types::{ApiResponse, PaginatedResponse, Pagination, TenantContext}; use crate::dto::{ PluginHealthResp, PluginListParams, PluginResp, UpdatePluginConfigReq, }; use crate::service::PluginService; use crate::state::PluginState; #[utoipa::path( post, path = "/api/v1/admin/plugins/upload", request_body(content_type = "multipart/form-data"), responses( (status = 200, description = "上传成功", body = ApiResponse), (status = 401, description = "未授权"), ), security(("bearer_auth" = [])), tag = "插件管理" )] /// POST /api/v1/admin/plugins/upload — 上传插件 (multipart: wasm + manifest) pub async fn upload_plugin( State(state): State, Extension(ctx): Extension, mut multipart: Multipart, ) -> Result>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "plugin.admin")?; let mut wasm_binary: Option> = None; let mut manifest_toml: Option = None; while let Some(field) = multipart.next_field().await.map_err(|e| { AppError::Validation(format!("Multipart 解析失败: {}", e)) })? { let name = field.name().unwrap_or(""); match name { "wasm" => { wasm_binary = Some(field.bytes().await.map_err(|e| { AppError::Validation(format!("读取 WASM 文件失败: {}", e)) })?.to_vec()); } "manifest" => { let text = field.text().await.map_err(|e| { AppError::Validation(format!("读取 Manifest 失败: {}", e)) })?; manifest_toml = Some(text); } _ => {} } } let wasm = wasm_binary.ok_or_else(|| { AppError::Validation("缺少 wasm 文件".to_string()) })?; let manifest = manifest_toml.ok_or_else(|| { AppError::Validation("缺少 manifest 文件".to_string()) })?; let result = PluginService::upload( ctx.tenant_id, ctx.user_id, wasm, &manifest, &state.db, ) .await?; Ok(Json(ApiResponse::ok(result))) } #[utoipa::path( get, path = "/api/v1/admin/plugins", params(PluginListParams), responses( (status = 200, description = "成功", body = ApiResponse>), ), security(("bearer_auth" = [])), tag = "插件管理" )] /// GET /api/v1/admin/plugins — 列表 pub async fn list_plugins( State(state): State, Extension(ctx): Extension, Query(params): Query, ) -> Result>>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "plugin.list")?; let pagination = Pagination { page: params.page, page_size: params.page_size, }; let (plugins, total) = PluginService::list( ctx.tenant_id, pagination.page.unwrap_or(1), pagination.page_size.unwrap_or(20), params.status.as_deref(), params.search.as_deref(), &state.db, ) .await?; Ok(Json(ApiResponse::ok(PaginatedResponse { data: plugins, total, page: pagination.page.unwrap_or(1), page_size: pagination.page_size.unwrap_or(20), total_pages: (total as f64 / pagination.page_size.unwrap_or(20) as f64).ceil() as u64, }))) } #[utoipa::path( get, path = "/api/v1/admin/plugins/{id}", responses( (status = 200, description = "成功", body = ApiResponse), ), security(("bearer_auth" = [])), tag = "插件管理" )] /// GET /api/v1/admin/plugins/{id} — 详情 pub async fn get_plugin( State(state): State, Extension(ctx): Extension, Path(id): Path, ) -> Result>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "plugin.list")?; let result = PluginService::get_by_id(id, ctx.tenant_id, &state.db).await?; Ok(Json(ApiResponse::ok(result))) } #[utoipa::path( get, path = "/api/v1/admin/plugins/{id}/schema", responses( (status = 200, description = "成功"), ), security(("bearer_auth" = [])), tag = "插件管理" )] /// GET /api/v1/admin/plugins/{id}/schema — 实体 schema pub async fn get_plugin_schema( State(state): State, Extension(ctx): Extension, Path(id): Path, ) -> Result>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "plugin.list")?; let schema = PluginService::get_schema(id, ctx.tenant_id, &state.db).await?; Ok(Json(ApiResponse::ok(schema))) } #[utoipa::path( post, path = "/api/v1/admin/plugins/{id}/install", responses( (status = 200, description = "安装成功", body = ApiResponse), ), security(("bearer_auth" = [])), tag = "插件管理" )] /// POST /api/v1/admin/plugins/{id}/install — 安装 pub async fn install_plugin( State(state): State, Extension(ctx): Extension, Path(id): Path, ) -> Result>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "plugin.admin")?; let result = PluginService::install( id, ctx.tenant_id, ctx.user_id, &state.db, &state.engine, ) .await .map_err(|e| { tracing::error!(error = %e, "Install failed"); e })?; Ok(Json(ApiResponse::ok(result))) } #[utoipa::path( post, path = "/api/v1/admin/plugins/{id}/enable", responses( (status = 200, description = "启用成功", body = ApiResponse), ), security(("bearer_auth" = [])), tag = "插件管理" )] /// POST /api/v1/admin/plugins/{id}/enable — 启用 pub async fn enable_plugin( State(state): State, Extension(ctx): Extension, Path(id): Path, ) -> Result>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "plugin.admin")?; let result = PluginService::enable( id, ctx.tenant_id, ctx.user_id, &state.db, &state.engine, ) .await?; Ok(Json(ApiResponse::ok(result))) } #[utoipa::path( post, path = "/api/v1/admin/plugins/{id}/disable", responses( (status = 200, description = "停用成功", body = ApiResponse), ), security(("bearer_auth" = [])), tag = "插件管理" )] /// POST /api/v1/admin/plugins/{id}/disable — 停用 pub async fn disable_plugin( State(state): State, Extension(ctx): Extension, Path(id): Path, ) -> Result>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "plugin.admin")?; let result = PluginService::disable( id, ctx.tenant_id, ctx.user_id, &state.db, &state.engine, ) .await?; Ok(Json(ApiResponse::ok(result))) } #[utoipa::path( post, path = "/api/v1/admin/plugins/{id}/uninstall", responses( (status = 200, description = "卸载成功", body = ApiResponse), ), security(("bearer_auth" = [])), tag = "插件管理" )] /// POST /api/v1/admin/plugins/{id}/uninstall — 卸载 pub async fn uninstall_plugin( State(state): State, Extension(ctx): Extension, Path(id): Path, ) -> Result>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "plugin.admin")?; let result = PluginService::uninstall( id, ctx.tenant_id, ctx.user_id, &state.db, &state.engine, ) .await?; Ok(Json(ApiResponse::ok(result))) } #[utoipa::path( delete, path = "/api/v1/admin/plugins/{id}", responses( (status = 200, description = "清除成功"), ), security(("bearer_auth" = [])), tag = "插件管理" )] /// DELETE /api/v1/admin/plugins/{id} — 清除(软删除) pub async fn purge_plugin( State(state): State, Extension(ctx): Extension, Path(id): Path, ) -> Result>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "plugin.admin")?; PluginService::purge(id, ctx.tenant_id, ctx.user_id, &state.db).await?; Ok(Json(ApiResponse::ok(()))) } #[utoipa::path( get, path = "/api/v1/admin/plugins/{id}/health", responses( (status = 200, description = "健康检查", body = ApiResponse), ), security(("bearer_auth" = [])), tag = "插件管理" )] /// GET /api/v1/admin/plugins/{id}/health — 健康检查 pub async fn health_check_plugin( State(state): State, Extension(ctx): Extension, Path(id): Path, ) -> Result>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "plugin.list")?; let result = PluginService::health_check(id, ctx.tenant_id, &state.db, &state.engine).await?; Ok(Json(ApiResponse::ok(result))) } #[utoipa::path( put, path = "/api/v1/admin/plugins/{id}/config", request_body = UpdatePluginConfigReq, responses( (status = 200, description = "更新成功", body = ApiResponse), ), security(("bearer_auth" = [])), tag = "插件管理" )] /// PUT /api/v1/admin/plugins/{id}/config — 更新配置 pub async fn update_plugin_config( State(state): State, Extension(ctx): Extension, Path(id): Path, Json(req): Json, ) -> Result>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "plugin.admin")?; let result = PluginService::update_config( id, ctx.tenant_id, ctx.user_id, req.config, req.version, &state.db, ) .await?; Ok(Json(ApiResponse::ok(result))) }