ai-robot-core/ai-service/app/api/admin/metadata_schema.py

311 lines
8.7 KiB
Python

"""
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
}
)