refactor(routers): 统一完善所有外部系统接口的参数校验和类型约束
1. 为所有查询参数添加max_length长度限制,规范参数输入范围 2. 使用Literal类型替换普通字符串参数,限定合法取值范围 3. 为路径参数添加Path校验,确保ID参数合法有效 4. 优化请求体参数的声明,补充缺失的Body注解和校验规则 5. 统一分页参数的offset/limit使用方式,替换旧的page/page_size模式
This commit is contained in:
parent
e5e9f45411
commit
aabde54f2e
@ -13,7 +13,7 @@ from __future__ import annotations
|
||||
from datetime import datetime
|
||||
from typing import Any, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from fastapi import APIRouter, Depends, Path, Query
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
|
||||
@ -115,7 +115,7 @@ class BatchToggleAccessRulesRequest(BaseModel):
|
||||
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
rule_ids: list[int] = Field(min_length=1)
|
||||
rule_ids: list[int] = Field(..., min_length=1, max_length=100)
|
||||
enabled: bool
|
||||
|
||||
|
||||
@ -133,7 +133,7 @@ class ReorderAccessRulesRequest(BaseModel):
|
||||
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
orders: list[ReorderItem] = Field(min_length=1)
|
||||
orders: list[ReorderItem] = Field(..., min_length=1, max_length=100)
|
||||
|
||||
|
||||
class UpdateConditionsRequest(BaseModel):
|
||||
@ -149,7 +149,7 @@ class BatchCloneAccessRulesRequest(BaseModel):
|
||||
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
source_rule_ids: list[int] = Field(min_length=1)
|
||||
source_rule_ids: list[int] = Field(..., min_length=1, max_length=100)
|
||||
override_config: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
@ -160,12 +160,12 @@ class BatchCloneAccessRulesRequest(BaseModel):
|
||||
async def list_access_rules(
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
offset: int = Query(0, ge=0),
|
||||
system_id: int | None = Query(None),
|
||||
tool_slug: str | None = Query(None),
|
||||
principal_type: str | None = Query(None),
|
||||
principal_id: str | None = Query(None),
|
||||
env_key: str | None = Query(None),
|
||||
effect: str | None = Query(None),
|
||||
system_id: int | None = Query(None, ge=1),
|
||||
tool_slug: str | None = Query(None, max_length=128),
|
||||
principal_type: str | None = Query(None, max_length=32),
|
||||
principal_id: str | None = Query(None, max_length=64),
|
||||
env_key: str | None = Query(None, max_length=32),
|
||||
effect: Literal["allow", "deny"] | None = Query(None, description="规则效果:allow/deny"),
|
||||
enabled: bool | None = Query(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
@ -296,7 +296,7 @@ async def batch_clone_access_rules(
|
||||
|
||||
@access_rule_router.get("/{rule_id}", response_model=dict)
|
||||
async def get_access_rule(
|
||||
rule_id: int,
|
||||
rule_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -309,8 +309,8 @@ async def get_access_rule(
|
||||
|
||||
@access_rule_router.put("/{rule_id}", response_model=dict)
|
||||
async def update_access_rule(
|
||||
rule_id: int,
|
||||
payload: UpdateAccessRuleRequest,
|
||||
rule_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -327,7 +327,7 @@ async def update_access_rule(
|
||||
|
||||
@access_rule_router.delete("/{rule_id}", response_model=dict)
|
||||
async def delete_access_rule(
|
||||
rule_id: int,
|
||||
rule_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -343,7 +343,7 @@ async def delete_access_rule(
|
||||
|
||||
@access_rule_router.get("/{rule_id}/conditions", response_model=dict)
|
||||
async def get_conditions(
|
||||
rule_id: int,
|
||||
rule_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -356,8 +356,8 @@ async def get_conditions(
|
||||
|
||||
@access_rule_router.put("/{rule_id}/conditions", response_model=dict)
|
||||
async def update_conditions(
|
||||
rule_id: int,
|
||||
payload: UpdateConditionsRequest,
|
||||
rule_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
|
||||
@ -14,9 +14,9 @@ dedup_key 过滤参数。Request Schema 与 Input DTO 不共享类,Router 内
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from typing import Any, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from fastapi import APIRouter, Body, Depends, Path, Query
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
|
||||
@ -48,20 +48,20 @@ class FireAlertRequest(BaseModel):
|
||||
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
system_id: int | None
|
||||
alert_type: str
|
||||
title: str
|
||||
system_id: int | None = Field(default=None, ge=1)
|
||||
alert_type: str = Field(..., min_length=1, max_length=64)
|
||||
title: str = Field(..., min_length=1, max_length=256)
|
||||
triggered_at: datetime
|
||||
env_key: str | None = None
|
||||
severity: str = "warning"
|
||||
env_key: str | None = Field(default=None, max_length=32)
|
||||
severity: Literal["info", "warning", "critical", "fatal"] = "warning"
|
||||
description: str | None = None
|
||||
detail: dict[str, Any] = Field(default_factory=dict)
|
||||
resource_type: str | None = None
|
||||
resource_id: str | None = None
|
||||
related_execution_id: str | None = None
|
||||
related_trace_id: str | None = None
|
||||
resource_type: str | None = Field(default=None, max_length=32)
|
||||
resource_id: str | None = Field(default=None, max_length=128)
|
||||
related_execution_id: str | None = Field(default=None, max_length=64)
|
||||
related_trace_id: str | None = Field(default=None, max_length=64)
|
||||
metric_snapshot: dict[str, Any] | None = None
|
||||
dedup_key: str | None = None
|
||||
dedup_key: str | None = Field(default=None, max_length=256)
|
||||
|
||||
|
||||
class AcknowledgeAlertRequest(BaseModel):
|
||||
@ -85,7 +85,7 @@ class BatchAcknowledgeAlertsRequest(BaseModel):
|
||||
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
alert_ids: list[int]
|
||||
alert_ids: list[int] = Field(..., min_length=1, max_length=100)
|
||||
note: str | None = None
|
||||
|
||||
|
||||
@ -94,7 +94,7 @@ class BatchResolveAlertsRequest(BaseModel):
|
||||
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
alert_ids: list[int]
|
||||
alert_ids: list[int] = Field(..., min_length=1, max_length=100)
|
||||
resolution_note: str | None = None
|
||||
|
||||
|
||||
@ -105,18 +105,22 @@ class BatchResolveAlertsRequest(BaseModel):
|
||||
async def list_alerts(
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
offset: int = Query(0, ge=0),
|
||||
system_id: int | None = Query(None),
|
||||
env_key: str | None = Query(None),
|
||||
alert_type: str | None = Query(None),
|
||||
severity: str | None = Query(None),
|
||||
status: str | None = Query(None),
|
||||
start_at: datetime | None = Query(None, description="ISO8601 开始时间"),
|
||||
end_at: datetime | None = Query(None, description="ISO8601 结束时间"),
|
||||
system_id: int | None = Query(None, ge=1),
|
||||
env_key: str | None = Query(None, max_length=32),
|
||||
alert_type: str | None = Query(None, max_length=64),
|
||||
severity: Literal["info", "warning", "critical", "fatal"] | None = Query(
|
||||
None, description="严重度:info/warning/critical/fatal"
|
||||
),
|
||||
status: Literal["firing", "acknowledged", "resolved", "suppressed"] | None = Query(
|
||||
None, description="告警状态:firing/acknowledged/resolved/suppressed"
|
||||
),
|
||||
start_time: datetime | None = Query(None, description="ISO8601 开始时间"),
|
||||
end_time: datetime | None = Query(None, description="ISO8601 结束时间"),
|
||||
# 新增查询参数
|
||||
resource_type: str | None = Query(None),
|
||||
resource_id: str | None = Query(None),
|
||||
related_trace_id: str | None = Query(None),
|
||||
dedup_key: str | None = Query(None),
|
||||
resource_type: str | None = Query(None, max_length=32),
|
||||
resource_id: str | None = Query(None, max_length=128),
|
||||
related_trace_id: str | None = Query(None, max_length=64),
|
||||
dedup_key: str | None = Query(None, max_length=256),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -130,8 +134,8 @@ async def list_alerts(
|
||||
alert_type=alert_type,
|
||||
severity=severity,
|
||||
status=status,
|
||||
start_at=start_at,
|
||||
end_at=end_at,
|
||||
start_at=start_time,
|
||||
end_at=end_time,
|
||||
resource_type=resource_type,
|
||||
resource_id=resource_id,
|
||||
related_trace_id=related_trace_id,
|
||||
@ -143,7 +147,7 @@ async def list_alerts(
|
||||
|
||||
@alert_router.get("/stats", response_model=dict)
|
||||
async def get_alert_stats(
|
||||
system_id: int | None = Query(None),
|
||||
system_id: int | None = Query(None, ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -209,7 +213,7 @@ async def batch_resolve_alerts(
|
||||
|
||||
@alert_router.get("/{alert_id}", response_model=dict)
|
||||
async def get_alert(
|
||||
alert_id: int,
|
||||
alert_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -235,8 +239,8 @@ async def fire_alert(
|
||||
|
||||
@alert_router.post("/{alert_id}/acknowledge", response_model=dict)
|
||||
async def acknowledge_alert(
|
||||
alert_id: int,
|
||||
payload: AcknowledgeAlertRequest,
|
||||
alert_id: int = Path(ge=1),
|
||||
payload: AcknowledgeAlertRequest = Body(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -253,8 +257,8 @@ async def acknowledge_alert(
|
||||
|
||||
@alert_router.post("/{alert_id}/resolve", response_model=dict)
|
||||
async def resolve_alert(
|
||||
alert_id: int,
|
||||
payload: ResolveAlertRequest,
|
||||
alert_id: int = Path(ge=1),
|
||||
payload: ResolveAlertRequest = Body(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -271,7 +275,7 @@ async def resolve_alert(
|
||||
|
||||
@alert_router.get("/{alert_id}/context", response_model=dict)
|
||||
async def get_alert_context(
|
||||
alert_id: int,
|
||||
alert_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -284,7 +288,7 @@ async def get_alert_context(
|
||||
|
||||
@alert_router.get("/{alert_id}/notifications", response_model=dict)
|
||||
async def get_alert_notifications(
|
||||
alert_id: int,
|
||||
alert_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
|
||||
@ -161,13 +161,13 @@ class ProbeAssetRequest(BaseModel):
|
||||
async def list_assets(
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
offset: int = Query(0, ge=0),
|
||||
adapter_type: str | None = Query(None),
|
||||
asset_type: str | None = Query(None),
|
||||
status: str | None = Query(None),
|
||||
keyword: str | None = Query(None),
|
||||
adapter_type: str | None = Query(None, max_length=32),
|
||||
asset_type: str | None = Query(None, max_length=32),
|
||||
status: str | None = Query(None, max_length=32),
|
||||
keyword: str | None = Query(None, max_length=128),
|
||||
start_time: datetime | None = Query(None, description="创建时间起始(UTC ISO 8601)"),
|
||||
end_time: datetime | None = Query(None, description="创建时间结束(UTC ISO 8601)"),
|
||||
created_by: str | None = Query(None, description="按创建人过滤"),
|
||||
created_by: str | None = Query(None, max_length=64, description="按创建人过滤"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -334,7 +334,7 @@ async def probe_asset(
|
||||
|
||||
@asset_router.get("/{asset_id}", response_model=dict)
|
||||
async def get_asset(
|
||||
asset_id: int,
|
||||
asset_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -347,8 +347,8 @@ async def get_asset(
|
||||
|
||||
@asset_router.put("/{asset_id}", response_model=dict)
|
||||
async def update_asset(
|
||||
asset_id: int,
|
||||
payload: UpdateAssetRequest,
|
||||
asset_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -369,7 +369,7 @@ async def update_asset(
|
||||
|
||||
@asset_router.delete("/{asset_id}", response_model=dict)
|
||||
async def delete_asset(
|
||||
asset_id: int,
|
||||
asset_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -382,7 +382,7 @@ async def delete_asset(
|
||||
|
||||
@asset_router.post("/{asset_id}/validate", response_model=dict)
|
||||
async def validate_asset(
|
||||
asset_id: int,
|
||||
asset_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -395,7 +395,7 @@ async def validate_asset(
|
||||
|
||||
@asset_router.get("/{asset_id}/preview", response_model=dict)
|
||||
async def preview_asset(
|
||||
asset_id: int,
|
||||
asset_id: int = Path(ge=1),
|
||||
max_lines: int = Query(200, ge=1, le=10000),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
@ -414,7 +414,7 @@ async def preview_asset(
|
||||
|
||||
@asset_router.get("/{asset_id}/generate-preview", response_model=dict)
|
||||
async def generate_preview(
|
||||
asset_id: int,
|
||||
asset_id: int = Path(ge=1),
|
||||
max_lines: int = Query(200, ge=1, le=10000),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
@ -433,7 +433,7 @@ async def generate_preview(
|
||||
|
||||
@asset_router.get("/{asset_id}/references", response_model=dict)
|
||||
async def get_asset_references(
|
||||
asset_id: int,
|
||||
asset_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
|
||||
@ -13,7 +13,7 @@ import json
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from fastapi import APIRouter, Depends, Path, Query
|
||||
from fastapi.responses import StreamingResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
|
||||
@ -44,12 +44,12 @@ audit_log_router = APIRouter(
|
||||
|
||||
@audit_log_router.get("/export")
|
||||
async def export_audit_logs(
|
||||
system_id: int | None = Query(None),
|
||||
env_key: str | None = Query(None),
|
||||
event_type: str | None = Query(None),
|
||||
action_type: str | None = Query(None),
|
||||
resource: str | None = Query(None),
|
||||
user: str | None = Query(None),
|
||||
system_id: int | None = Query(None, ge=1),
|
||||
env_key: str | None = Query(None, max_length=32),
|
||||
event_type: str | None = Query(None, max_length=64),
|
||||
action_type: str | None = Query(None, max_length=64),
|
||||
resource: str | None = Query(None, max_length=256),
|
||||
user: str | None = Query(None, max_length=64),
|
||||
start_time: datetime | None = Query(None),
|
||||
end_time: datetime | None = Query(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
@ -95,12 +95,12 @@ async def export_audit_logs(
|
||||
async def list_audit_logs(
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
offset: int = Query(0, ge=0),
|
||||
system_id: int | None = Query(None),
|
||||
env_key: str | None = Query(None),
|
||||
event_type: str | None = Query(None),
|
||||
action_type: str | None = Query(None),
|
||||
resource: str | None = Query(None),
|
||||
user: str | None = Query(None),
|
||||
system_id: int | None = Query(None, ge=1),
|
||||
env_key: str | None = Query(None, max_length=32),
|
||||
event_type: str | None = Query(None, max_length=64),
|
||||
action_type: str | None = Query(None, max_length=64),
|
||||
resource: str | None = Query(None, max_length=256),
|
||||
user: str | None = Query(None, max_length=64),
|
||||
start_time: datetime | None = Query(None),
|
||||
end_time: datetime | None = Query(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
@ -126,7 +126,7 @@ async def list_audit_logs(
|
||||
|
||||
@audit_log_router.get("/stats", response_model=dict)
|
||||
async def get_audit_log_stats(
|
||||
system_id: int | None = Query(None),
|
||||
system_id: int | None = Query(None, ge=1),
|
||||
start_time: datetime | None = Query(None),
|
||||
end_time: datetime | None = Query(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
@ -147,11 +147,11 @@ async def get_audit_log_stats(
|
||||
async def list_config_changes(
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
offset: int = Query(0, ge=0),
|
||||
system_id: int | None = Query(None),
|
||||
env_key: str | None = Query(None),
|
||||
action_type: str | None = Query(None),
|
||||
resource: str | None = Query(None),
|
||||
user: str | None = Query(None),
|
||||
system_id: int | None = Query(None, ge=1),
|
||||
env_key: str | None = Query(None, max_length=32),
|
||||
action_type: str | None = Query(None, max_length=64),
|
||||
resource: str | None = Query(None, max_length=256),
|
||||
user: str | None = Query(None, max_length=64),
|
||||
start_time: datetime | None = Query(None),
|
||||
end_time: datetime | None = Query(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
@ -220,7 +220,7 @@ async def list_operators(
|
||||
|
||||
@audit_log_router.get("/{log_id}", response_model=dict)
|
||||
async def get_audit_log_detail(
|
||||
log_id: int,
|
||||
log_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from typing import Any, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@ -87,10 +87,9 @@ async def get_executions_stats(
|
||||
|
||||
@dashboard_router.get("/alerts", response_model=dict)
|
||||
async def get_alerts_stats(
|
||||
severity_status: str = Query(
|
||||
severity_status: Literal["firing", "acknowledged", "resolved", "suppressed"] = Query(
|
||||
"firing",
|
||||
description="count_by_severity 的状态过滤",
|
||||
pattern="^(firing|acknowledged|resolved|suppressed)$",
|
||||
description="count_by_severity 的状态过滤,告警状态:firing/acknowledged/resolved/suppressed",
|
||||
),
|
||||
top_limit: int = Query(10, ge=1, le=50, description="Top 告警去重键数量"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
|
||||
@ -53,7 +53,7 @@ class CleanupExecutionsRequest(BaseModel):
|
||||
|
||||
system_id: int | None = None
|
||||
before_at: datetime | None = None
|
||||
status: str | None = None
|
||||
status: Literal["pending", "running", "success", "failed", "timeout", "throttled", "cancelled"] | None = None
|
||||
batch_size: int = Field(default=1000, ge=1, le=10000)
|
||||
|
||||
|
||||
@ -84,17 +84,19 @@ async def cleanup_old_executions(
|
||||
async def paginate_executions(
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
offset: int = Query(0, ge=0),
|
||||
system_id: int | None = Query(None),
|
||||
env_key: str | None = Query(None),
|
||||
tool_slug: str | None = Query(None),
|
||||
status: str | None = Query(None, description="按状态过滤:success/failed/running/timeout"),
|
||||
caller: str | None = Query(None),
|
||||
caller_id: str | None = Query(None, description="按调用方实体过滤"),
|
||||
operation: str | None = Query(None, description="按操作类型过滤"),
|
||||
tag_key: str | None = Query(None, description="按业务标签键过滤"),
|
||||
tag_value: str | None = Query(None, description="按业务标签值过滤"),
|
||||
trace_id: str | None = Query(None),
|
||||
correlation_id: str | None = Query(None),
|
||||
system_id: int | None = Query(None, ge=1),
|
||||
env_key: str | None = Query(None, max_length=32),
|
||||
tool_slug: str | None = Query(None, max_length=128),
|
||||
status: Literal["pending", "running", "success", "failed", "timeout", "throttled", "cancelled"] | None = Query(
|
||||
None, description="执行状态:pending/running/success/failed/timeout/throttled/cancelled"
|
||||
),
|
||||
caller: str | None = Query(None, max_length=32),
|
||||
caller_id: str | None = Query(None, max_length=64, description="按调用方实体过滤"),
|
||||
operation: str | None = Query(None, max_length=32, description="按操作类型过滤"),
|
||||
tag_key: str | None = Query(None, max_length=64, description="按业务标签键过滤"),
|
||||
tag_value: str | None = Query(None, max_length=256, description="按业务标签值过滤"),
|
||||
trace_id: str | None = Query(None, max_length=64),
|
||||
correlation_id: str | None = Query(None, max_length=64),
|
||||
start_time: datetime | None = Query(None, description="ISO8601 开始时间"),
|
||||
end_time: datetime | None = Query(None, description="ISO8601 结束时间"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
@ -130,9 +132,9 @@ async def paginate_executions(
|
||||
|
||||
@execution_router.get("/stats", response_model=dict)
|
||||
async def get_execution_stats(
|
||||
system_id: int | None = Query(None, description="系统过滤"),
|
||||
env_key: str | None = Query(None, description="环境过滤"),
|
||||
adapter_type: str | None = Query(None, description="适配器类型过滤"),
|
||||
system_id: int | None = Query(None, ge=1, description="系统过滤"),
|
||||
env_key: str | None = Query(None, max_length=32, description="环境过滤"),
|
||||
adapter_type: str | None = Query(None, max_length=32, description="适配器类型过滤"),
|
||||
start_time: datetime | None = Query(None, description="起始时间"),
|
||||
end_time: datetime | None = Query(None, description="结束时间"),
|
||||
tool_limit: int = Query(20, ge=1, le=100, description="Top 工具数量"),
|
||||
@ -156,9 +158,9 @@ async def get_execution_stats(
|
||||
@execution_router.get("/stats/grouped", response_model=dict)
|
||||
async def get_grouped_stats(
|
||||
group_by: Literal["tool"] = Query(..., description="分组维度,仅支持 tool"),
|
||||
system_id: int | None = Query(None, description="系统过滤"),
|
||||
env_key: str | None = Query(None, description="环境过滤"),
|
||||
adapter_type: str | None = Query(None, description="适配器类型过滤"),
|
||||
system_id: int | None = Query(None, ge=1, description="系统过滤"),
|
||||
env_key: str | None = Query(None, max_length=32, description="环境过滤"),
|
||||
adapter_type: str | None = Query(None, max_length=32, description="适配器类型过滤"),
|
||||
start_time: datetime | None = Query(None, description="起始时间"),
|
||||
end_time: datetime | None = Query(None, description="结束时间"),
|
||||
limit: int = Query(20, ge=1, le=100, description="返回数量"),
|
||||
@ -181,7 +183,7 @@ async def get_grouped_stats(
|
||||
|
||||
@execution_router.get("/stats/error-analysis", response_model=dict)
|
||||
async def get_error_analysis(
|
||||
system_id: int = Query(..., description="系统 ID(必填)"),
|
||||
system_id: int = Query(..., ge=1, description="系统 ID(必填)"),
|
||||
start_time: datetime | None = Query(None, description="起始时间"),
|
||||
end_time: datetime | None = Query(None, description="结束时间"),
|
||||
limit: int = Query(5, ge=1, le=50, description="返回数量"),
|
||||
@ -202,9 +204,9 @@ async def get_error_analysis(
|
||||
|
||||
@execution_router.get("/stats/trend", response_model=dict)
|
||||
async def get_execution_trend(
|
||||
tool_slug: str = Query(..., description="工具标识(必填)"),
|
||||
system_id: int | None = Query(None, description="系统过滤"),
|
||||
env_key: str | None = Query(None, description="环境过滤"),
|
||||
tool_slug: str = Query(..., min_length=1, max_length=128, description="工具标识(必填)"),
|
||||
system_id: int | None = Query(None, ge=1, description="系统过滤"),
|
||||
env_key: str | None = Query(None, max_length=32, description="环境过滤"),
|
||||
start_time: datetime | None = Query(None, description="起始时间"),
|
||||
end_time: datetime | None = Query(None, description="结束时间"),
|
||||
interval: Literal["hour", "day"] = Query("day", description="时间桶粒度:hour / day"),
|
||||
@ -227,7 +229,7 @@ async def get_execution_trend(
|
||||
|
||||
@execution_router.get("/slow", response_model=dict)
|
||||
async def list_slow_executions(
|
||||
system_id: int = Query(..., description="系统 ID(必填)"),
|
||||
system_id: int = Query(..., ge=1, description="系统 ID(必填)"),
|
||||
min_duration_ms: int = Query(..., ge=0, description="耗时阈值(毫秒,必填)"),
|
||||
limit: int = Query(50, ge=1, le=200, description="返回数量"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
|
||||
@ -17,9 +17,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from typing import Any, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from fastapi import APIRouter, Depends, Path, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
|
||||
from yuxi.external_systems.use_cases.dto.health_check import (
|
||||
@ -47,9 +47,11 @@ health_check_router = APIRouter(prefix="/health-checks", tags=["external-systems
|
||||
@health_check_router.get("/latest", response_model=dict)
|
||||
async def list_latest_health(
|
||||
system_ids: list[int] | None = Query(None, description="按系统 ID 过滤(逗号分隔)"),
|
||||
health_status: str | None = Query(None, description="按健康状态过滤"),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
health_status: Literal["healthy", "auth_failure", "connectivity_issue", "not_checked"] | None = Query(
|
||||
None, description="健康状态:healthy/auth_failure/connectivity_issue/not_checked"
|
||||
),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
offset: int = Query(0, ge=0),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -58,8 +60,8 @@ async def list_latest_health(
|
||||
input_dto = ListLatestHealthInput(
|
||||
system_ids=system_ids,
|
||||
health_status=health_status,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
page=offset // limit + 1,
|
||||
page_size=limit,
|
||||
)
|
||||
output = await use_cases.health_check_service.list_latest(input_dto)
|
||||
return {"success": True, "data": output.model_dump()}
|
||||
@ -80,10 +82,9 @@ async def count_by_status(
|
||||
|
||||
@health_check_router.get("/failing", response_model=dict)
|
||||
async def list_failing(
|
||||
health_status: str = Query(
|
||||
health_status: Literal["auth_failure", "connectivity_issue"] = Query(
|
||||
"auth_failure",
|
||||
description="故障状态过滤:auth_failure / connectivity_issue",
|
||||
pattern="^(auth_failure|connectivity_issue)$",
|
||||
),
|
||||
system_ids: list[int] | None = Query(None, description="按系统 ID 过滤(逗号分隔)"),
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
@ -132,8 +133,8 @@ async def delete_old_records(
|
||||
|
||||
@health_check_router.get("/by-system/{system_id}", response_model=dict)
|
||||
async def list_by_system(
|
||||
system_id: int,
|
||||
env_key: str | None = Query(None, description="按环境过滤"),
|
||||
system_id: int = Path(ge=1),
|
||||
env_key: str | None = Query(None, max_length=32, description="按环境过滤"),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
@ -151,8 +152,8 @@ async def list_by_system(
|
||||
|
||||
@health_check_router.get("/by-system/{system_id}/latest", response_model=dict)
|
||||
async def get_latest_by_system(
|
||||
system_id: int,
|
||||
env_key: str | None = Query(None, description="环境标识(不传则跨 env 取最近一条)"),
|
||||
system_id: int = Path(ge=1),
|
||||
env_key: str | None = Query(None, max_length=32, description="环境标识(不传则跨 env 取最近一条)"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
|
||||
@ -38,9 +38,11 @@ _SUMMARY_EXCLUDED_FIELDS = ("connection_extra_schema", "example_config", "depend
|
||||
|
||||
@integration_router.get("", response_model=dict)
|
||||
async def list_integrations(
|
||||
adapter_type: str | None = Query(None, description="按适配器类型精确过滤"),
|
||||
tags: str | None = Query(None, description="按标签过滤,多个标签以逗号分隔(OR 关系)"),
|
||||
keyword: str | None = Query(None, description="模糊匹配 display_name / description(大小写不敏感)"),
|
||||
adapter_type: str | None = Query(None, max_length=32, description="按适配器类型精确过滤"),
|
||||
tags: str | None = Query(None, max_length=256, description="按标签过滤,多个标签以逗号分隔(OR 关系)"),
|
||||
keyword: str | None = Query(
|
||||
None, max_length=128, description="模糊匹配 display_name / description(大小写不敏感)"
|
||||
),
|
||||
limit: int = Query(50, ge=1, le=200, description="分页大小,最大 200"),
|
||||
offset: int = Query(0, ge=0, description="分页偏移"),
|
||||
current_user: User = Depends(get_required_user),
|
||||
|
||||
@ -101,10 +101,10 @@ async def list_generated_tools(
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
offset: int = Query(0, ge=0),
|
||||
system_id: int | None = Query(None),
|
||||
category: str | None = Query(None),
|
||||
adapter_type: str | None = Query(None),
|
||||
category: str | None = Query(None, max_length=64),
|
||||
adapter_type: str | None = Query(None, max_length=32),
|
||||
enabled: bool | None = Query(None),
|
||||
keyword: str | None = Query(None),
|
||||
keyword: str | None = Query(None, max_length=128),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from typing import Any, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, Response
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@ -48,8 +48,8 @@ metric_router = APIRouter(prefix="/metrics", tags=["external-systems-metric"])
|
||||
@metric_router.get("/aggregate", response_model=dict)
|
||||
async def aggregate_metrics(
|
||||
system_id: int | None = Query(None),
|
||||
env_key: str | None = Query(None),
|
||||
adapter_type: str | None = Query(None),
|
||||
env_key: str | None = Query(None, max_length=32),
|
||||
adapter_type: str | None = Query(None, max_length=32),
|
||||
start_time: datetime | None = Query(None),
|
||||
end_time: datetime | None = Query(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
@ -71,11 +71,11 @@ async def aggregate_metrics(
|
||||
@metric_router.get("/timeseries", response_model=dict)
|
||||
async def get_metrics_timeseries(
|
||||
system_id: int | None = Query(None),
|
||||
env_key: str | None = Query(None),
|
||||
adapter_type: str | None = Query(None),
|
||||
env_key: str | None = Query(None, max_length=32),
|
||||
adapter_type: str | None = Query(None, max_length=32),
|
||||
start_time: datetime = Query(..., description="起始时间(必填)"),
|
||||
end_time: datetime = Query(..., description="结束时间(必填)"),
|
||||
interval: str = Query("hour", pattern="^(hour|day)$", description="聚合间隔:hour / day"),
|
||||
interval: Literal["hour", "day"] = Query("hour", description="聚合间隔:hour / day"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -95,8 +95,8 @@ async def get_metrics_timeseries(
|
||||
|
||||
@metric_router.get("/by-system", response_model=dict)
|
||||
async def get_metrics_by_system(
|
||||
env_key: str | None = Query(None),
|
||||
adapter_type: str | None = Query(None),
|
||||
env_key: str | None = Query(None, max_length=32),
|
||||
adapter_type: str | None = Query(None, max_length=32),
|
||||
start_time: datetime = Query(..., description="起始时间(必填)"),
|
||||
end_time: datetime = Query(..., description="结束时间(必填)"),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
@ -138,7 +138,7 @@ async def get_metrics_by_env(
|
||||
@metric_router.get("/latest", response_model=dict)
|
||||
async def get_latest_metric_bucket(
|
||||
system_id: int = Query(..., description="系统 ID(必填)"),
|
||||
env_key: str = Query("default", description="环境标识"),
|
||||
env_key: str = Query("default", max_length=32, description="环境标识"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -153,7 +153,7 @@ async def get_latest_metric_bucket(
|
||||
async def export_metrics_prometheus(
|
||||
start_time: datetime | None = Query(None),
|
||||
end_time: datetime | None = Query(None),
|
||||
env_key: str | None = Query(None),
|
||||
env_key: str | None = Query(None, max_length=32),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> Response:
|
||||
@ -183,7 +183,7 @@ async def delete_old_metric_buckets(
|
||||
@metric_router.get("", response_model=dict)
|
||||
async def list_metric_buckets(
|
||||
system_id: int | None = Query(None),
|
||||
env_key: str | None = Query(None),
|
||||
env_key: str | None = Query(None, max_length=32),
|
||||
start_time: datetime | None = Query(None),
|
||||
end_time: datetime | None = Query(None),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
|
||||
@ -17,7 +17,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from fastapi import APIRouter, Depends, Path, Query
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
|
||||
@ -79,9 +79,9 @@ class UpdateNotificationChannelRequest(BaseModel):
|
||||
async def list_notification_channels(
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
offset: int = Query(0, ge=0),
|
||||
channel_type: str | None = Query(None),
|
||||
channel_type: str | None = Query(None, max_length=32),
|
||||
enabled: bool | None = Query(None),
|
||||
keyword: str | None = Query(None),
|
||||
keyword: str | None = Query(None, max_length=128),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -112,7 +112,7 @@ async def get_notification_channel_stats(
|
||||
|
||||
@notification_channel_router.get("/{channel_id}", response_model=dict)
|
||||
async def get_notification_channel(
|
||||
channel_id: int,
|
||||
channel_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -145,8 +145,8 @@ async def create_notification_channel(
|
||||
|
||||
@notification_channel_router.put("/{channel_id}", response_model=dict)
|
||||
async def update_notification_channel(
|
||||
channel_id: int,
|
||||
payload: UpdateNotificationChannelRequest,
|
||||
channel_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -163,7 +163,7 @@ async def update_notification_channel(
|
||||
|
||||
@notification_channel_router.delete("/{channel_id}", response_model=dict)
|
||||
async def delete_notification_channel(
|
||||
channel_id: int,
|
||||
channel_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
|
||||
@ -17,9 +17,9 @@ Request Schema 与 Input DTO 不共享类,Router 内显式构造 DTO。
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from typing import Any, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from fastapi import APIRouter, Depends, Path, Query
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
|
||||
@ -170,8 +170,8 @@ class UpsertQuotaRequest(BaseModel):
|
||||
@quota_router.get("/thresholds", response_model=dict)
|
||||
async def get_quota_threshold(
|
||||
system_id: int = Query(...),
|
||||
env_key: str | None = Query(None),
|
||||
quota_key: str | None = Query(None),
|
||||
env_key: str | None = Query(None, max_length=32),
|
||||
quota_key: str | None = Query(None, max_length=128),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -192,8 +192,8 @@ async def get_quota_threshold(
|
||||
@quota_router.get("/by-key", response_model=dict)
|
||||
async def get_quota_by_key(
|
||||
system_id: int = Query(...),
|
||||
env_key: str = Query("default"),
|
||||
quota_key: str = Query(...),
|
||||
env_key: str = Query("default", max_length=32),
|
||||
quota_key: str = Query(..., max_length=128),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -318,7 +318,7 @@ async def upsert_quota(
|
||||
|
||||
@quota_router.delete("/by-system/{system_id}", response_model=dict)
|
||||
async def delete_quotas_by_system(
|
||||
system_id: int,
|
||||
system_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -337,14 +337,18 @@ async def list_quotas(
|
||||
limit: int = Query(100, ge=1, le=500, description="返回记录数上限"),
|
||||
offset: int = Query(0, ge=0, description="偏移量(与 limit 配合分页)"),
|
||||
system_id: int | None = Query(None, description="按系统过滤"),
|
||||
env_key: str | None = Query(None, description="按环境键过滤"),
|
||||
quota_key: str | None = Query(None, description="按配额键过滤"),
|
||||
quota_window: str | None = Query(None, description="按窗口类型过滤(day/month/total 等)"),
|
||||
source: str | None = Query(None, description="按来源过滤(config/response_header 等)"),
|
||||
env_key: str | None = Query(None, max_length=32, description="按环境键过滤"),
|
||||
quota_key: str | None = Query(None, max_length=128, description="按配额键过滤"),
|
||||
quota_window: Literal["daily", "hourly", "rolling_24h", "minute", "custom"] | None = Query(
|
||||
None, description="窗口类型:daily/hourly/rolling_24h/minute/custom"
|
||||
),
|
||||
source: Literal["response_header", "manual", "config"] | None = Query(
|
||||
None, description="配额来源:response_header/manual/config"
|
||||
),
|
||||
near_warning: bool = Query(False, description="仅返回接近告警阈值的配额"),
|
||||
near_critical: bool = Query(False, description="仅返回接近临界阈值的配额"),
|
||||
expiring_before: datetime | None = Query(None, description="仅返回 window_end <= 该时间的配额"),
|
||||
keyword: str | None = Query(None, description="按 quota_name / quota_key 模糊匹配"),
|
||||
keyword: str | None = Query(None, max_length=128, description="按 quota_name / quota_key 模糊匹配"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -385,7 +389,7 @@ async def create_quota(
|
||||
|
||||
@quota_router.get("/{quota_id}", response_model=dict)
|
||||
async def get_quota(
|
||||
quota_id: int,
|
||||
quota_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -398,8 +402,8 @@ async def get_quota(
|
||||
|
||||
@quota_router.put("/{quota_id}", response_model=dict)
|
||||
async def update_quota(
|
||||
quota_id: int,
|
||||
payload: UpdateQuotaRequest,
|
||||
quota_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -416,8 +420,8 @@ async def update_quota(
|
||||
|
||||
@quota_router.patch("/{quota_id}/threshold", response_model=dict)
|
||||
async def set_quota_threshold(
|
||||
quota_id: int,
|
||||
payload: SetQuotaThresholdRequest,
|
||||
quota_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -434,8 +438,8 @@ async def set_quota_threshold(
|
||||
|
||||
@quota_router.post("/{quota_id}/reset", response_model=dict)
|
||||
async def reset_quota(
|
||||
quota_id: int,
|
||||
payload: ResetQuotaRequest,
|
||||
quota_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -452,7 +456,7 @@ async def reset_quota(
|
||||
|
||||
@quota_router.delete("/{quota_id}", response_model=dict)
|
||||
async def delete_quota(
|
||||
quota_id: int,
|
||||
quota_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
|
||||
@ -14,7 +14,7 @@ import json
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from fastapi import APIRouter, Body, Depends, Path, Query
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@ -123,7 +123,7 @@ class ImportTestCasesRequest(BaseModel):
|
||||
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
items: list[CreateTestCaseRequest] = Field(..., min_length=1)
|
||||
items: list[CreateTestCaseRequest] = Field(..., min_length=1, max_length=100)
|
||||
|
||||
|
||||
class UpdateAlertConfigRequest(BaseModel):
|
||||
@ -142,10 +142,10 @@ class UpdateAlertConfigRequest(BaseModel):
|
||||
async def list_test_cases(
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
offset: int = Query(0, ge=0),
|
||||
tool_slug: str | None = Query(None),
|
||||
env_key: str | None = Query(None),
|
||||
tool_slug: str | None = Query(None, max_length=128),
|
||||
env_key: str | None = Query(None, max_length=32),
|
||||
enabled: bool | None = Query(None),
|
||||
schedule_cron: str | None = Query(None),
|
||||
schedule_cron: str | None = Query(None, max_length=64),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -269,7 +269,7 @@ async def export_test_cases(
|
||||
|
||||
@test_case_router.get("/{case_id}", response_model=dict)
|
||||
async def get_test_case(
|
||||
case_id: int,
|
||||
case_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -282,8 +282,8 @@ async def get_test_case(
|
||||
|
||||
@test_case_router.put("/{case_id}", response_model=dict)
|
||||
async def update_test_case(
|
||||
case_id: int,
|
||||
payload: UpdateTestCaseRequest,
|
||||
case_id: int = Path(ge=1),
|
||||
payload: UpdateTestCaseRequest = Body(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -300,7 +300,7 @@ async def update_test_case(
|
||||
|
||||
@test_case_router.delete("/{case_id}", response_model=dict)
|
||||
async def delete_test_case(
|
||||
case_id: int,
|
||||
case_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -316,7 +316,7 @@ async def delete_test_case(
|
||||
|
||||
@test_case_router.post("/{case_id}/run", response_model=dict)
|
||||
async def run_test_case(
|
||||
case_id: int,
|
||||
case_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -332,8 +332,8 @@ async def run_test_case(
|
||||
|
||||
@test_case_router.post("/{case_id}/clone", response_model=dict)
|
||||
async def clone_test_case(
|
||||
case_id: int,
|
||||
payload: CloneTestCaseRequest,
|
||||
case_id: int = Path(ge=1),
|
||||
payload: CloneTestCaseRequest = Body(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -350,7 +350,7 @@ async def clone_test_case(
|
||||
|
||||
@test_case_router.get("/{case_id}/alert-config", response_model=dict)
|
||||
async def get_alert_config(
|
||||
case_id: int,
|
||||
case_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -363,8 +363,8 @@ async def get_alert_config(
|
||||
|
||||
@test_case_router.put("/{case_id}/alert-config", response_model=dict)
|
||||
async def update_alert_config(
|
||||
case_id: int,
|
||||
payload: UpdateAlertConfigRequest,
|
||||
case_id: int = Path(ge=1),
|
||||
payload: UpdateAlertConfigRequest = Body(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
|
||||
@ -18,9 +18,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from typing import Any, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, Header, Query, Request
|
||||
from fastapi import APIRouter, Body, Depends, Header, Path, Query, Request
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
|
||||
@ -194,7 +194,9 @@ async def list_subscriptions(
|
||||
system_id: int | None = Query(None, ge=1),
|
||||
env_key: str | None = Query(None, max_length=32),
|
||||
source_event_type: str | None = Query(None, max_length=128),
|
||||
status: str | None = Query(None, max_length=32),
|
||||
status: Literal["pending", "active", "paused", "expired", "disabled"] | None = Query(
|
||||
None, description="订阅状态:pending/active/paused/expired/disabled"
|
||||
),
|
||||
enabled: bool | None = Query(None),
|
||||
target_handler_type: str | None = Query(None, max_length=32),
|
||||
target_handler_id: str | None = Query(None, max_length=64),
|
||||
@ -276,7 +278,9 @@ async def list_events(
|
||||
subscription_id: int | None = Query(None, ge=1),
|
||||
subscription_slug: str | None = Query(None, max_length=128),
|
||||
event_type: str | None = Query(None, max_length=128),
|
||||
processing_status: str | None = Query(None, max_length=32),
|
||||
processing_status: Literal["pending", "processing", "processed", "failed", "ignored", "duplicate"] | None = Query(
|
||||
None, description="处理状态:pending/processing/processed/failed/ignored/duplicate"
|
||||
),
|
||||
correlation_id: str | None = Query(None, max_length=128),
|
||||
triggered_execution_id: str | None = Query(None, max_length=128),
|
||||
signature_verified: bool | None = Query(None),
|
||||
@ -369,7 +373,7 @@ async def list_renewal_failures(
|
||||
|
||||
@webhook_router.get("/subscriptions/{subscription_id}", response_model=dict)
|
||||
async def get_subscription(
|
||||
subscription_id: int,
|
||||
subscription_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -382,8 +386,8 @@ async def get_subscription(
|
||||
|
||||
@webhook_router.put("/subscriptions/{subscription_id}", response_model=dict)
|
||||
async def update_subscription(
|
||||
subscription_id: int,
|
||||
payload: UpdateWebhookSubscriptionRequest,
|
||||
subscription_id: int = Path(ge=1),
|
||||
payload: UpdateWebhookSubscriptionRequest = Body(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -400,7 +404,7 @@ async def update_subscription(
|
||||
|
||||
@webhook_router.delete("/subscriptions/{subscription_id}", response_model=dict)
|
||||
async def delete_subscription(
|
||||
subscription_id: int,
|
||||
subscription_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -413,7 +417,7 @@ async def delete_subscription(
|
||||
|
||||
@webhook_router.post("/subscriptions/{subscription_id}/renew", response_model=dict)
|
||||
async def renew_subscription(
|
||||
subscription_id: int,
|
||||
subscription_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -426,7 +430,7 @@ async def renew_subscription(
|
||||
|
||||
@webhook_router.get("/events/{event_id}/deliveries", response_model=dict)
|
||||
async def get_event_deliveries(
|
||||
event_id: int,
|
||||
event_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -439,7 +443,7 @@ async def get_event_deliveries(
|
||||
|
||||
@webhook_router.post("/events/{event_id}/replay", response_model=dict)
|
||||
async def replay_event(
|
||||
event_id: int,
|
||||
event_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -452,7 +456,7 @@ async def replay_event(
|
||||
|
||||
@webhook_router.get("/subscriptions/{subscription_id}/filter-rules", response_model=dict)
|
||||
async def get_filter_rules(
|
||||
subscription_id: int,
|
||||
subscription_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -465,8 +469,8 @@ async def get_filter_rules(
|
||||
|
||||
@webhook_router.put("/subscriptions/{subscription_id}/filter-rules", response_model=dict)
|
||||
async def update_filter_rules(
|
||||
subscription_id: int,
|
||||
payload: UpdateFilterRulesRequest,
|
||||
subscription_id: int = Path(ge=1),
|
||||
payload: UpdateFilterRulesRequest = Body(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -483,7 +487,7 @@ async def update_filter_rules(
|
||||
|
||||
@webhook_router.get("/subscriptions/{subscription_id}/transform-rules", response_model=dict)
|
||||
async def get_transform_rules(
|
||||
subscription_id: int,
|
||||
subscription_id: int = Path(ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -496,8 +500,8 @@ async def get_transform_rules(
|
||||
|
||||
@webhook_router.put("/subscriptions/{subscription_id}/transform-rules", response_model=dict)
|
||||
async def update_transform_rules(
|
||||
subscription_id: int,
|
||||
payload: UpdateTransformRulesRequest,
|
||||
subscription_id: int = Path(ge=1),
|
||||
payload: UpdateTransformRulesRequest = Body(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -514,8 +518,8 @@ async def update_transform_rules(
|
||||
|
||||
@webhook_router.post("/subscriptions/{subscription_id}/test", response_model=dict)
|
||||
async def test_subscription(
|
||||
subscription_id: int,
|
||||
payload: TestWebhookSubscriptionRequest,
|
||||
subscription_id: int = Path(ge=1),
|
||||
payload: TestWebhookSubscriptionRequest = Body(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
@ -533,8 +537,8 @@ async def test_subscription(
|
||||
|
||||
@webhook_router.post("/subscriptions/{subscription_id}/verify-signature", response_model=dict)
|
||||
async def verify_signature(
|
||||
subscription_id: int,
|
||||
payload: VerifySignatureRequest,
|
||||
subscription_id: int = Path(ge=1),
|
||||
payload: VerifySignatureRequest = Body(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
) -> dict[str, Any]:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user