from __future__ import annotations from datetime import datetime from typing import List, Optional from fastapi import APIRouter, HTTPException from fastapi.responses import Response from pydantic import BaseModel from app.db import get_pool router = APIRouter(tags=["models"]) async def _bump_all_domain_versions(conn) -> None: """api_key 有变动时,所有域名的版本号 +1,让客户端感知到数据变化。""" await conn.execute( "UPDATE domain_version SET version = version + 1, updated_at = NOW()" ) await conn.execute( "UPDATE price_snapshot_version SET version = GREATEST(version + 1, 1), updated_at = NOW() WHERE id = 1" ) class ModelIn(BaseModel): name: str url: str api_key_id: Optional[int] = None group_id: Optional[int] = None class ModelUpdate(BaseModel): name: Optional[str] = None url: Optional[str] = None api_key_id: Optional[int] = None group_id: Optional[int] = None class ModelOut(BaseModel): id: int name: str url: str api_key_id: Optional[int] api_key_name: Optional[str] = None group_id: Optional[int] = None group_name: Optional[str] = None created_at: datetime @router.get("/models", response_model=List[ModelOut]) async def list_models() -> List[ModelOut]: pool = get_pool() async with pool.acquire() as conn: rows = await conn.fetch( """ SELECT m.id, m.name, m.url, m.api_key_id, m.group_id, m.created_at, k.name AS api_key_name, g.name AS group_name FROM models m LEFT JOIN api_keys k ON k.id = m.api_key_id LEFT JOIN model_groups g ON g.id = m.group_id ORDER BY m.created_at DESC """ ) return [ModelOut(**dict(r)) for r in rows] @router.post("/models", response_model=ModelOut, status_code=201) async def create_model(body: ModelIn) -> ModelOut: pool = get_pool() async with pool.acquire() as conn: try: row = await conn.fetchrow( """ INSERT INTO models (name, url, api_key_id, group_id) VALUES ($1, $2, $3, $4) RETURNING id, name, url, api_key_id, group_id, created_at """, body.name, body.url, body.api_key_id, body.group_id, ) api_key_name = None if row["api_key_id"]: k = await conn.fetchrow("SELECT name FROM api_keys WHERE id = $1", row["api_key_id"]) api_key_name = k["name"] if k else None group_name = None if row["group_id"]: g = await conn.fetchrow("SELECT name FROM model_groups WHERE id = $1", row["group_id"]) group_name = g["name"] if g else None except Exception: raise HTTPException(status_code=409, detail="该 URL 已存在") return ModelOut(**dict(row), api_key_name=api_key_name, group_name=group_name) @router.put("/models/{model_id}", response_model=ModelOut) async def update_model(model_id: int, body: ModelUpdate) -> ModelOut: pool = get_pool() async with pool.acquire() as conn: existing = await conn.fetchrow( "SELECT id, name, url, api_key_id, group_id, created_at FROM models WHERE id = $1", model_id, ) if existing is None: raise HTTPException(status_code=404, detail="模型不存在") new_name = body.name if body.name is not None else existing["name"] new_url = body.url if body.url is not None else existing["url"] new_api_key_id = body.api_key_id if body.api_key_id is not None else existing["api_key_id"] new_group_id = body.group_id if body.group_id is not None else existing["group_id"] api_key_changed = new_api_key_id != existing["api_key_id"] try: async with conn.transaction(): row = await conn.fetchrow( """ UPDATE models SET name = $1, url = $2, api_key_id = $3, group_id = $4 WHERE id = $5 RETURNING id, name, url, api_key_id, group_id, created_at """, new_name, new_url, new_api_key_id, new_group_id, model_id, ) if api_key_changed: await _bump_all_domain_versions(conn) api_key_name = None if row["api_key_id"]: k = await conn.fetchrow("SELECT name FROM api_keys WHERE id = $1", row["api_key_id"]) api_key_name = k["name"] if k else None group_name = None if row["group_id"]: g = await conn.fetchrow("SELECT name FROM model_groups WHERE id = $1", row["group_id"]) group_name = g["name"] if g else None except HTTPException: raise except Exception: raise HTTPException(status_code=409, detail="该 URL 已存在") return ModelOut(**dict(row), api_key_name=api_key_name, group_name=group_name) @router.delete("/models/{model_id}", status_code=204, response_model=None) async def delete_model(model_id: int) -> Response: pool = get_pool() async with pool.acquire() as conn: result = await conn.execute("DELETE FROM models WHERE id = $1", model_id) if result == "DELETE 0": raise HTTPException(status_code=404, detail="模型不存在") return Response(status_code=204)