from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from sqlalchemy import select from app.core.database import async_session from app.models.api_key import ApiKeyModel from app.api.deps import require_master # Все операции с ключами доступны только мастер-ключу. router = APIRouter(dependencies=[Depends(require_master)]) class KeyActionRequest(BaseModel): """Тело запроса для операций с ключом (чтобы токен не летел в URL).""" key: str async def _find_key_by_secret_or_public_id( session, key_or_id: str ) -> ApiKeyModel | None: result = await session.execute( select(ApiKeyModel).where(ApiKeyModel.key == key_or_id) ) api_key = result.scalar_one_or_none() if api_key: return api_key result = await session.execute(select(ApiKeyModel)) for candidate in result.scalars().all(): if candidate.public_id == key_or_id: return candidate return None @router.get("") async def list_keys(): """ Список всех гостевых ключей. В ответе поле `key` содержит публичный идентификатор, а не сам секрет. Это сохраняет совместимость с текущим UI и не раскрывает токены повторно. """ async with async_session() as session: result = await session.execute(select(ApiKeyModel)) keys = result.scalars().all() return [ { "key": k.public_id, "key_id": k.public_id, "display_key": k.preview, "name": k.name, "is_admin": k.is_admin, "active": k.active, "created_at": k.created_at, } for k in keys ] @router.post("") async def create_key(name: str, is_admin: bool = False): """Создать гостевой ключ. Возвращает сгенерированный токен.""" new_key = ApiKeyModel( key=ApiKeyModel.generate_key(), name=name, is_admin=is_admin, ) async with async_session() as session: session.add(new_key) await session.commit() return { "key": new_key.key, "key_id": new_key.public_id, "display_key": new_key.preview, "name": new_key.name, "is_admin": new_key.is_admin, "message": "Сохраните ключ -- он больше не будет показан полностью", } @router.post("/revoke") async def revoke_key(body: KeyActionRequest): """Деактивировать (отозвать) гостевой ключ. Ключ передаётся в body, не в URL.""" async with async_session() as session: api_key = await _find_key_by_secret_or_public_id(session, body.key) if not api_key: raise HTTPException(status_code=404, detail="Ключ не найден") api_key.active = False session.add(api_key) await session.commit() return {"status": "revoked", "name": api_key.name, "key_id": api_key.public_id} @router.post("/activate") async def activate_key(body: KeyActionRequest): """Повторно активировать ключ. Ключ передаётся в body, не в URL.""" async with async_session() as session: api_key = await _find_key_by_secret_or_public_id(session, body.key) if not api_key: raise HTTPException(status_code=404, detail="Ключ не найден") api_key.active = True session.add(api_key) await session.commit() return {"status": "activated", "name": api_key.name, "key_id": api_key.public_id}