feat(end-user-api): add authenticated API endpoint for end user creation
- Should be merged after v0.2.9 - Create new end_user_api_controller.py with POST /end_user/create endpoint - Implement API Key authentication requirement with memory scope - Add support for optional memory_config_id parameter with workspace default fallback - Update memory_api_schema.py to remove workspace_id from request (now derived from API key auth) - Add memory_config_id field to CreateEndUserResponse schema - Register end_user_api_controller router in service module - Migrate end user creation from unauthenticated to authenticated API flow
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
认证方式: API Key
|
认证方式: API Key
|
||||||
"""
|
"""
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from . import app_api_controller, rag_api_knowledge_controller, rag_api_document_controller, rag_api_file_controller, rag_api_chunk_controller, memory_api_controller
|
from . import app_api_controller, rag_api_knowledge_controller, rag_api_document_controller, rag_api_file_controller, rag_api_chunk_controller, memory_api_controller, end_user_api_controller
|
||||||
|
|
||||||
# 创建 V1 API 路由器
|
# 创建 V1 API 路由器
|
||||||
service_router = APIRouter()
|
service_router = APIRouter()
|
||||||
@@ -16,5 +16,6 @@ service_router.include_router(rag_api_document_controller.router)
|
|||||||
service_router.include_router(rag_api_file_controller.router)
|
service_router.include_router(rag_api_file_controller.router)
|
||||||
service_router.include_router(rag_api_chunk_controller.router)
|
service_router.include_router(rag_api_chunk_controller.router)
|
||||||
service_router.include_router(memory_api_controller.router)
|
service_router.include_router(memory_api_controller.router)
|
||||||
|
service_router.include_router(end_user_api_controller.router)
|
||||||
|
|
||||||
__all__ = ["service_router"]
|
__all__ = ["service_router"]
|
||||||
|
|||||||
92
api/app/controllers/service/end_user_api_controller.py
Normal file
92
api/app/controllers/service/end_user_api_controller.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
"""End User 服务接口 - 基于 API Key 认证"""
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Body, Depends, Request
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app.core.api_key_auth import require_api_key
|
||||||
|
from app.core.error_codes import BizCode
|
||||||
|
from app.core.exceptions import BusinessException
|
||||||
|
from app.core.logging_config import get_business_logger
|
||||||
|
from app.core.response_utils import success
|
||||||
|
from app.db import get_db
|
||||||
|
from app.repositories.end_user_repository import EndUserRepository
|
||||||
|
from app.schemas.api_key_schema import ApiKeyAuth
|
||||||
|
from app.schemas.memory_api_schema import CreateEndUserRequest, CreateEndUserResponse
|
||||||
|
from app.services.memory_config_service import MemoryConfigService
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/end_user", tags=["V1 - End User API"])
|
||||||
|
logger = get_business_logger()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/create")
|
||||||
|
@require_api_key(scopes=["memory"])
|
||||||
|
async def create_end_user(
|
||||||
|
request: Request,
|
||||||
|
api_key_auth: ApiKeyAuth = None,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
message: str = Body(..., description="Request body"),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Create or retrieve an end user for the workspace.
|
||||||
|
|
||||||
|
Creates a new end user and connects it to a memory configuration.
|
||||||
|
If an end user with the same other_id already exists in the workspace,
|
||||||
|
returns the existing one.
|
||||||
|
|
||||||
|
Optionally accepts a memory_config_id to connect the end user to a specific
|
||||||
|
memory configuration. If not provided, falls back to the workspace default config.
|
||||||
|
"""
|
||||||
|
body = await request.json()
|
||||||
|
payload = CreateEndUserRequest(**body)
|
||||||
|
workspace_id = api_key_auth.workspace_id
|
||||||
|
|
||||||
|
logger.info(f"Create end user request - other_id: {payload.other_id}, workspace_id: {workspace_id}")
|
||||||
|
|
||||||
|
# Resolve memory_config_id: explicit > workspace default
|
||||||
|
memory_config_id = None
|
||||||
|
config_service = MemoryConfigService(db)
|
||||||
|
|
||||||
|
if payload.memory_config_id:
|
||||||
|
try:
|
||||||
|
memory_config_id = uuid.UUID(payload.memory_config_id)
|
||||||
|
except ValueError:
|
||||||
|
raise BusinessException(
|
||||||
|
f"Invalid memory_config_id format: {payload.memory_config_id}",
|
||||||
|
BizCode.INVALID_PARAMETER
|
||||||
|
)
|
||||||
|
config = config_service.get_config_with_fallback(memory_config_id, workspace_id)
|
||||||
|
if not config:
|
||||||
|
raise BusinessException(
|
||||||
|
f"Memory config not found: {payload.memory_config_id}",
|
||||||
|
BizCode.MEMORY_CONFIG_NOT_FOUND
|
||||||
|
)
|
||||||
|
memory_config_id = config.config_id
|
||||||
|
else:
|
||||||
|
default_config = config_service.get_workspace_default_config(workspace_id)
|
||||||
|
if default_config:
|
||||||
|
memory_config_id = default_config.config_id
|
||||||
|
logger.info(f"Using workspace default memory config: {memory_config_id}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"No default memory config found for workspace: {workspace_id}")
|
||||||
|
|
||||||
|
end_user_repo = EndUserRepository(db)
|
||||||
|
end_user = end_user_repo.get_or_create_end_user_with_config(
|
||||||
|
app_id=api_key_auth.resource_id,
|
||||||
|
workspace_id=workspace_id,
|
||||||
|
other_id=payload.other_id,
|
||||||
|
memory_config_id=memory_config_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"End user ready: {end_user.id}")
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"id": str(end_user.id),
|
||||||
|
"other_id": end_user.other_id or "",
|
||||||
|
"other_name": end_user.other_name or "",
|
||||||
|
"workspace_id": str(end_user.workspace_id),
|
||||||
|
"memory_config_id": str(end_user.memory_config_id) if end_user.memory_config_id else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
return success(data=CreateEndUserResponse(**result).model_dump(), msg="End user created successfully")
|
||||||
@@ -138,21 +138,13 @@ class CreateEndUserRequest(BaseModel):
|
|||||||
"""Request schema for creating an end user.
|
"""Request schema for creating an end user.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
workspace_id: Workspace ID (required)
|
|
||||||
other_id: External user identifier (required)
|
other_id: External user identifier (required)
|
||||||
other_name: Display name for the end user
|
other_name: Display name for the end user
|
||||||
|
memory_config_id: Optional memory config ID. If not provided, uses workspace default.
|
||||||
"""
|
"""
|
||||||
workspace_id: str = Field(..., description="Workspace ID (required)")
|
|
||||||
other_id: str = Field(..., description="External user identifier (required)")
|
other_id: str = Field(..., description="External user identifier (required)")
|
||||||
other_name: Optional[str] = Field("", description="Display name")
|
other_name: Optional[str] = Field("", description="Display name")
|
||||||
|
memory_config_id: Optional[str] = Field(None, description="Memory config ID. Falls back to workspace default if not provided.")
|
||||||
@field_validator("workspace_id")
|
|
||||||
@classmethod
|
|
||||||
def validate_workspace_id(cls, v: str) -> str:
|
|
||||||
"""Validate that workspace_id is not empty."""
|
|
||||||
if not v or not v.strip():
|
|
||||||
raise ValueError("workspace_id is required and cannot be empty")
|
|
||||||
return v.strip()
|
|
||||||
|
|
||||||
@field_validator("other_id")
|
@field_validator("other_id")
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -171,11 +163,13 @@ class CreateEndUserResponse(BaseModel):
|
|||||||
other_id: External user identifier
|
other_id: External user identifier
|
||||||
other_name: Display name
|
other_name: Display name
|
||||||
workspace_id: Workspace the user belongs to
|
workspace_id: Workspace the user belongs to
|
||||||
|
memory_config_id: Connected memory config ID
|
||||||
"""
|
"""
|
||||||
id: str = Field(..., description="End user UUID")
|
id: str = Field(..., description="End user UUID")
|
||||||
other_id: str = Field(..., description="External user identifier")
|
other_id: str = Field(..., description="External user identifier")
|
||||||
other_name: str = Field("", description="Display name")
|
other_name: str = Field("", description="Display name")
|
||||||
workspace_id: str = Field(..., description="Workspace ID")
|
workspace_id: str = Field(..., description="Workspace ID")
|
||||||
|
memory_config_id: Optional[str] = Field(None, description="Connected memory config ID")
|
||||||
|
|
||||||
|
|
||||||
class MemoryConfigItem(BaseModel):
|
class MemoryConfigItem(BaseModel):
|
||||||
|
|||||||
Reference in New Issue
Block a user