""" Metadata Schema API. 动态元数据模式管理接口。 """ import logging from typing import Annotated, Any from fastapi import APIRouter, Depends, HTTPException from fastapi.responses import JSONResponse from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_session from app.models.entities import ( MetadataField, MetadataSchema, MetadataSchemaCreate, MetadataSchemaUpdate, ) from app.services.metadata_schema_service import MetadataSchemaService logger = logging.getLogger(__name__) router = APIRouter(prefix="/admin/metadata-schemas", tags=["Metadata Schemas"]) def get_current_tenant_id() -> str: """Get current tenant ID from context.""" from app.core.tenant import get_tenant_id tenant_id = get_tenant_id() if not tenant_id: from app.core.exceptions import MissingTenantIdException raise MissingTenantIdException() return tenant_id @router.get( "", operation_id="listMetadataSchemas", summary="List metadata schemas", description="获取租户所有元数据模式配置", ) async def list_schemas( tenant_id: Annotated[str, Depends(get_current_tenant_id)], session: Annotated[AsyncSession, Depends(get_session)], include_disabled: bool = False, ) -> JSONResponse: """ 列出租户所有元数据模式 """ service = MetadataSchemaService(session) schemas = await service.list_schemas(tenant_id, include_disabled) return JSONResponse( content={ "schemas": [ { "id": str(s.id), "name": s.name, "description": s.description, "fields": s.fields, "isDefault": s.is_default, "isEnabled": s.is_enabled, "createdAt": s.created_at.isoformat(), "updatedAt": s.updated_at.isoformat(), } for s in schemas ] } ) @router.get( "/default", operation_id="getDefaultMetadataSchema", summary="Get default metadata schema", description="获取租户默认的元数据模式配置", ) async def get_default_schema( tenant_id: Annotated[str, Depends(get_current_tenant_id)], session: Annotated[AsyncSession, Depends(get_session)], ) -> JSONResponse: """ 获取租户默认的元数据模式 """ service = MetadataSchemaService(session) schema = await service.get_schema(tenant_id) if not schema: return JSONResponse( content={ "schema": None, "message": "No default schema configured", } ) return JSONResponse( content={ "schema": { "id": str(schema.id), "name": schema.name, "description": schema.description, "fields": schema.fields, "isDefault": schema.is_default, "isEnabled": schema.is_enabled, "createdAt": schema.created_at.isoformat(), "updatedAt": schema.updated_at.isoformat(), } } ) @router.get( "/{schema_id}", operation_id="getMetadataSchema", summary="Get metadata schema by ID", description="根据 ID 获取元数据模式配置", ) async def get_schema( tenant_id: Annotated[str, Depends(get_current_tenant_id)], session: Annotated[AsyncSession, Depends(get_session)], schema_id: str, ) -> JSONResponse: """ 根据 ID 获取元数据模式 """ service = MetadataSchemaService(session) schema = await service.get_schema(tenant_id, schema_id) if not schema: raise HTTPException(status_code=404, detail="Schema not found") return JSONResponse( content={ "schema": { "id": str(schema.id), "name": schema.name, "description": schema.description, "fields": schema.fields, "isDefault": schema.is_default, "isEnabled": schema.is_enabled, "createdAt": schema.created_at.isoformat(), "updatedAt": schema.updated_at.isoformat(), } } ) @router.post( "", operation_id="createMetadataSchema", summary="Create metadata schema", description="创建新的元数据模式配置", ) async def create_schema( tenant_id: Annotated[str, Depends(get_current_tenant_id)], session: Annotated[AsyncSession, Depends(get_session)], schema_create: MetadataSchemaCreate, ) -> JSONResponse: """ 创建元数据模式 """ service = MetadataSchemaService(session) for field in schema_create.fields: if isinstance(field, MetadataField): field_dict = field.model_dump() else: field_dict = field field_type = field_dict.get("field_type", "string") if field_type in ["select", "multi_select"]: if not field_dict.get("options"): raise HTTPException( status_code=400, detail=f"Field '{field_dict.get('name')}' is {field_type} type but has no options" ) schema = await service.create_schema(tenant_id, schema_create) await session.commit() return JSONResponse( status_code=201, content={ "id": str(schema.id), "name": schema.name, "description": schema.description, "fields": schema.fields, "isDefault": schema.is_default, "isEnabled": schema.is_enabled, } ) @router.put( "/{schema_id}", operation_id="updateMetadataSchema", summary="Update metadata schema", description="更新元数据模式配置", ) async def update_schema( tenant_id: Annotated[str, Depends(get_current_tenant_id)], session: Annotated[AsyncSession, Depends(get_session)], schema_id: str, schema_update: MetadataSchemaUpdate, ) -> JSONResponse: """ 更新元数据模式 """ service = MetadataSchemaService(session) schema = await service.update_schema(tenant_id, schema_id, schema_update) if not schema: raise HTTPException(status_code=404, detail="Schema not found") await session.commit() return JSONResponse( content={ "id": str(schema.id), "name": schema.name, "description": schema.description, "fields": schema.fields, "isDefault": schema.is_default, "isEnabled": schema.is_enabled, } ) @router.delete( "/{schema_id}", operation_id="deleteMetadataSchema", summary="Delete metadata schema", description="删除元数据模式配置", ) async def delete_schema( tenant_id: Annotated[str, Depends(get_current_tenant_id)], session: Annotated[AsyncSession, Depends(get_session)], schema_id: str, ) -> JSONResponse: """ 删除元数据模式 """ service = MetadataSchemaService(session) success = await service.delete_schema(tenant_id, schema_id) if not success: raise HTTPException( status_code=400, detail="Cannot delete schema (not found or is default)" ) await session.commit() return JSONResponse( content={ "success": True, "message": "Schema deleted" } ) @router.get( "/default/field-definitions", operation_id="getFieldDefinitions", summary="Get field definitions", description="获取字段定义映射,用于前端动态渲染表单", ) async def get_field_definitions( tenant_id: Annotated[str, Depends(get_current_tenant_id)], session: Annotated[AsyncSession, Depends(get_session)], schema_id: str | None = None, ) -> JSONResponse: """ 获取字段定义映射 """ service = MetadataSchemaService(session) field_defs = await service.get_field_definitions(tenant_id, schema_id) return JSONResponse( content={ "fieldDefinitions": field_defs } ) @router.post( "/default/validate", operation_id="validateMetadata", summary="Validate metadata", description="验证元数据是否符合模式定义", ) async def validate_metadata( tenant_id: Annotated[str, Depends(get_current_tenant_id)], session: Annotated[AsyncSession, Depends(get_session)], metadata: dict[str, Any], schema_id: str | None = None, ) -> JSONResponse: """ 验证元数据 """ service = MetadataSchemaService(session) is_valid, errors = await service.validate_metadata(tenant_id, metadata, schema_id) return JSONResponse( content={ "isValid": is_valid, "errors": errors } )