Initial commit: AI Video Assistant fullstack platform.
Add pipecat-based backend with WebRTC/WS voice routes, Next.js frontend, and Docker Compose orchestration. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
106
backend/routes/credentials.py
Normal file
106
backend/routes/credentials.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""模型资源凭证 CRUD。前端 ComponentsModelsPage 对接这里。
|
||||
|
||||
字段对齐前端 ModelResource。读:api_key 打码;写:占位符表示不改(写时哨兵)。
|
||||
设默认时清掉同 type 其它默认。
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from db.models import ProviderCredential
|
||||
from db.session import get_session
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from schemas import CredentialOut, CredentialUpsert
|
||||
from services.masking import mask, resolve_incoming_key
|
||||
from sqlalchemy import select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
router = APIRouter(prefix="/api/credentials", tags=["credentials"])
|
||||
|
||||
|
||||
def _to_out(c: ProviderCredential) -> CredentialOut:
|
||||
return CredentialOut(
|
||||
id=c.id,
|
||||
name=c.name,
|
||||
model_id=c.model_id,
|
||||
type=c.type,
|
||||
interface_type=c.interface_type,
|
||||
api_url=c.api_url,
|
||||
api_key=mask(c.api_key), # 永远打码
|
||||
is_default=c.is_default,
|
||||
)
|
||||
|
||||
|
||||
async def _clear_other_defaults(session: AsyncSession, type_: str, keep_id: str):
|
||||
await session.execute(
|
||||
update(ProviderCredential)
|
||||
.where(ProviderCredential.type == type_, ProviderCredential.id != keep_id)
|
||||
.values(is_default=False)
|
||||
)
|
||||
|
||||
|
||||
@router.get("", response_model=list[CredentialOut])
|
||||
async def list_credentials(session: AsyncSession = Depends(get_session)):
|
||||
rows = (
|
||||
await session.execute(
|
||||
select(ProviderCredential).order_by(ProviderCredential.type)
|
||||
)
|
||||
).scalars().all()
|
||||
return [_to_out(c) for c in rows]
|
||||
|
||||
|
||||
@router.post("", response_model=CredentialOut)
|
||||
async def create_credential(
|
||||
body: CredentialUpsert, session: AsyncSession = Depends(get_session)
|
||||
):
|
||||
c = ProviderCredential(
|
||||
id=f"model_{uuid.uuid4().hex[:12]}",
|
||||
name=body.name,
|
||||
model_id=body.model_id,
|
||||
type=body.type,
|
||||
interface_type=body.interface_type,
|
||||
api_url=body.api_url,
|
||||
api_key=resolve_incoming_key(body.api_key, ""),
|
||||
is_default=body.is_default,
|
||||
)
|
||||
session.add(c)
|
||||
if c.is_default:
|
||||
await _clear_other_defaults(session, c.type, c.id)
|
||||
await session.commit()
|
||||
await session.refresh(c)
|
||||
return _to_out(c)
|
||||
|
||||
|
||||
@router.put("/{cred_id}", response_model=CredentialOut)
|
||||
async def update_credential(
|
||||
cred_id: str,
|
||||
body: CredentialUpsert,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
c = await session.get(ProviderCredential, cred_id)
|
||||
if not c:
|
||||
raise HTTPException(404, "凭证不存在")
|
||||
c.name = body.name
|
||||
c.model_id = body.model_id
|
||||
c.type = body.type
|
||||
c.interface_type = body.interface_type
|
||||
c.api_url = body.api_url
|
||||
c.is_default = body.is_default
|
||||
# 写时哨兵:打码占位符 → 保留旧 key
|
||||
c.api_key = resolve_incoming_key(body.api_key, c.api_key)
|
||||
if c.is_default:
|
||||
await _clear_other_defaults(session, c.type, c.id)
|
||||
await session.commit()
|
||||
await session.refresh(c)
|
||||
return _to_out(c)
|
||||
|
||||
|
||||
@router.delete("/{cred_id}")
|
||||
async def delete_credential(
|
||||
cred_id: str, session: AsyncSession = Depends(get_session)
|
||||
):
|
||||
c = await session.get(ProviderCredential, cred_id)
|
||||
if not c:
|
||||
raise HTTPException(404, "凭证不存在")
|
||||
await session.delete(c)
|
||||
await session.commit()
|
||||
return {"ok": True}
|
||||
Reference in New Issue
Block a user