use axum::Extension; use axum::extract::{FromRef, 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, TenantContext}; use crate::data_dto::{ AggregateItem, AggregateQueryParams, CountQueryParams, CreatePluginDataReq, PluginDataListParams, PluginDataResp, UpdatePluginDataReq, }; use crate::data_service::{PluginDataService, resolve_manifest_id}; use crate::state::PluginState; /// 获取当前用户对指定权限的 data_scope 等级 /// /// 查询 user_roles -> role_permissions -> permissions 链路, /// 返回匹配权限的 data_scope 设置,默认 "all"。 async fn get_data_scope( ctx: &TenantContext, permission_code: &str, db: &sea_orm::DatabaseConnection, ) -> Result { use sea_orm::{FromQueryResult, Statement}; #[derive(FromQueryResult)] struct ScopeResult { data_scope: Option, } let result = ScopeResult::find_by_statement(Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, r#"SELECT rp.data_scope FROM user_roles ur JOIN role_permissions rp ON rp.role_id = ur.role_id JOIN permissions p ON p.id = rp.permission_id WHERE ur.user_id = $1 AND ur.tenant_id = $2 AND p.code = $3 LIMIT 1"#, [ ctx.user_id.into(), ctx.tenant_id.into(), permission_code.into(), ], )) .one(db) .await .map_err(|e| AppError::Internal(e.to_string()))?; Ok(result .and_then(|r| r.data_scope) .unwrap_or_else(|| "all".to_string())) } /// 获取部门成员 ID 列表 /// /// 当前返回 TenantContext 中的 department_ids。 /// 未来实现递归查询部门树时将支持 include_sub_depts 参数。 async fn get_dept_members( ctx: &TenantContext, _include_sub_depts: bool, ) -> Vec { // 当前 department_ids 为空时返回空列表 // 未来实现递归查询部门树 if ctx.department_ids.is_empty() { return vec![]; } ctx.department_ids.clone() } /// 计算插件数据操作所需的权限码 /// 格式:{manifest_id}.{entity}.{action},如 erp-crm.customer.list fn compute_permission_code(manifest_id: &str, entity_name: &str, action: &str) -> String { let action_suffix = match action { "list" | "get" => "list", _ => "manage", }; format!("{}.{}.{}", manifest_id, entity_name, action_suffix) } #[utoipa::path( get, path = "/api/v1/plugins/{plugin_id}/{entity}", params(PluginDataListParams), responses( (status = 200, description = "成功", body = ApiResponse>), ), security(("bearer_auth" = [])), tag = "插件数据" )] /// GET /api/v1/plugins/{plugin_id}/{entity} — 列表 pub async fn list_plugin_data( State(state): State, Extension(ctx): Extension, Path((plugin_id, entity)): Path<(Uuid, String)>, Query(params): Query, ) -> Result>>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { let manifest_id = resolve_manifest_id(plugin_id, ctx.tenant_id, &state.db).await?; let fine_perm = compute_permission_code(&manifest_id, &entity, "list"); require_permission(&ctx, &fine_perm)?; // TODO(data_scope): 此处注入行级数据权限过滤 // 1. 解析 entity 定义检查 data_scope == Some(true) // 2. 调用 get_data_scope(&ctx, &fine_perm, &state.db) 获取当前用户的 scope 等级 // 3. 若 scope != "all",调用 get_dept_members 获取部门成员列表 // 4. 将 scope 条件合并到 filter 中传给 PluginDataService::list // 参考: crates/erp-plugin/src/dynamic_table.rs build_data_scope_condition() let page = params.page.unwrap_or(1); let page_size = params.page_size.unwrap_or(20); // 解析 filter JSON let filter: Option = params .filter .as_ref() .and_then(|f| serde_json::from_str(f).ok()); let (items, total) = PluginDataService::list( plugin_id, &entity, ctx.tenant_id, page, page_size, &state.db, filter, params.search, params.sort_by, params.sort_order, &state.entity_cache, ) .await?; Ok(Json(ApiResponse::ok(PaginatedResponse { data: items, total, page, page_size, total_pages: (total as f64 / page_size as f64).ceil() as u64, }))) } #[utoipa::path( post, path = "/api/v1/plugins/{plugin_id}/{entity}", request_body = CreatePluginDataReq, responses( (status = 200, description = "创建成功", body = ApiResponse), ), security(("bearer_auth" = [])), tag = "插件数据" )] /// POST /api/v1/plugins/{plugin_id}/{entity} — 创建 pub async fn create_plugin_data( State(state): State, Extension(ctx): Extension, Path((plugin_id, entity)): Path<(Uuid, String)>, Json(req): Json, ) -> Result>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { let manifest_id = resolve_manifest_id(plugin_id, ctx.tenant_id, &state.db).await?; let fine_perm = compute_permission_code(&manifest_id, &entity, "create"); require_permission(&ctx, &fine_perm)?; let result = PluginDataService::create( plugin_id, &entity, ctx.tenant_id, ctx.user_id, req.data, &state.db, &state.event_bus, ) .await?; Ok(Json(ApiResponse::ok(result))) } #[utoipa::path( get, path = "/api/v1/plugins/{plugin_id}/{entity}/{id}", responses( (status = 200, description = "成功", body = ApiResponse), ), security(("bearer_auth" = [])), tag = "插件数据" )] /// GET /api/v1/plugins/{plugin_id}/{entity}/{id} — 详情 pub async fn get_plugin_data( State(state): State, Extension(ctx): Extension, Path((plugin_id, entity, id)): Path<(Uuid, String, Uuid)>, ) -> Result>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { let manifest_id = resolve_manifest_id(plugin_id, ctx.tenant_id, &state.db).await?; let fine_perm = compute_permission_code(&manifest_id, &entity, "get"); require_permission(&ctx, &fine_perm)?; let result = PluginDataService::get_by_id(plugin_id, &entity, id, ctx.tenant_id, &state.db).await?; Ok(Json(ApiResponse::ok(result))) } #[utoipa::path( put, path = "/api/v1/plugins/{plugin_id}/{entity}/{id}", request_body = UpdatePluginDataReq, responses( (status = 200, description = "更新成功", body = ApiResponse), ), security(("bearer_auth" = [])), tag = "插件数据" )] /// PUT /api/v1/plugins/{plugin_id}/{entity}/{id} — 更新 pub async fn update_plugin_data( State(state): State, Extension(ctx): Extension, Path((plugin_id, entity, id)): Path<(Uuid, String, Uuid)>, Json(req): Json, ) -> Result>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { let manifest_id = resolve_manifest_id(plugin_id, ctx.tenant_id, &state.db).await?; let fine_perm = compute_permission_code(&manifest_id, &entity, "update"); require_permission(&ctx, &fine_perm)?; let result = PluginDataService::update( plugin_id, &entity, id, ctx.tenant_id, ctx.user_id, req.data, req.version, &state.db, &state.event_bus, ) .await?; Ok(Json(ApiResponse::ok(result))) } #[utoipa::path( delete, path = "/api/v1/plugins/{plugin_id}/{entity}/{id}", responses( (status = 200, description = "删除成功"), ), security(("bearer_auth" = [])), tag = "插件数据" )] /// DELETE /api/v1/plugins/{plugin_id}/{entity}/{id} — 删除 pub async fn delete_plugin_data( State(state): State, Extension(ctx): Extension, Path((plugin_id, entity, id)): Path<(Uuid, String, Uuid)>, ) -> Result>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { let manifest_id = resolve_manifest_id(plugin_id, ctx.tenant_id, &state.db).await?; let fine_perm = compute_permission_code(&manifest_id, &entity, "delete"); require_permission(&ctx, &fine_perm)?; PluginDataService::delete( plugin_id, &entity, id, ctx.tenant_id, &state.db, &state.event_bus, ) .await?; Ok(Json(ApiResponse::ok(()))) } #[utoipa::path( get, path = "/api/v1/plugins/{plugin_id}/{entity}/count", params(CountQueryParams), responses( (status = 200, description = "成功", body = ApiResponse), ), security(("bearer_auth" = [])), tag = "插件数据" )] /// GET /api/v1/plugins/{plugin_id}/{entity}/count — 统计计数 pub async fn count_plugin_data( State(state): State, Extension(ctx): Extension, Path((plugin_id, entity)): Path<(Uuid, String)>, Query(params): Query, ) -> Result>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { let manifest_id = resolve_manifest_id(plugin_id, ctx.tenant_id, &state.db).await?; let fine_perm = compute_permission_code(&manifest_id, &entity, "list"); require_permission(&ctx, &fine_perm)?; // 解析 filter JSON let filter: Option = params .filter .as_ref() .and_then(|f| serde_json::from_str(f).ok()); let total = PluginDataService::count( plugin_id, &entity, ctx.tenant_id, &state.db, filter, params.search, ) .await?; Ok(Json(ApiResponse::ok(total))) } #[utoipa::path( get, path = "/api/v1/plugins/{plugin_id}/{entity}/aggregate", params(AggregateQueryParams), responses( (status = 200, description = "成功", body = ApiResponse>), ), security(("bearer_auth" = [])), tag = "插件数据" )] /// GET /api/v1/plugins/{plugin_id}/{entity}/aggregate — 聚合查询 pub async fn aggregate_plugin_data( State(state): State, Extension(ctx): Extension, Path((plugin_id, entity)): Path<(Uuid, String)>, Query(params): Query, ) -> Result>>, AppError> where PluginState: FromRef, S: Clone + Send + Sync + 'static, { let manifest_id = resolve_manifest_id(plugin_id, ctx.tenant_id, &state.db).await?; let fine_perm = compute_permission_code(&manifest_id, &entity, "list"); require_permission(&ctx, &fine_perm)?; // 解析 filter JSON let filter: Option = params .filter .as_ref() .and_then(|f| serde_json::from_str(f).ok()); let rows = PluginDataService::aggregate( plugin_id, &entity, ctx.tenant_id, &state.db, ¶ms.group_by, filter, ) .await?; let items = rows .into_iter() .map(|(key, count)| AggregateItem { key, count }) .collect(); Ok(Json(ApiResponse::ok(items))) }