Fix API regressions and refresh project docs

This commit is contained in:
Artem Kokos
2026-05-15 23:12:28 +07:00
parent 654f64bb90
commit 13fba2fa44
19 changed files with 3258 additions and 964 deletions

View File

@@ -4,10 +4,10 @@ from sqlalchemy import select
from app.core.database import async_session
from app.models.api_key import ApiKeyModel
from app.api.deps import require_admin, AuthContext
from app.api.deps import require_master
# Все операции с ключами -- только для админов (мастер-ключ)
router = APIRouter(dependencies=[Depends(require_admin)])
# Все операции с ключами доступны только мастер-ключу.
router = APIRouter(dependencies=[Depends(require_master)])
class KeyActionRequest(BaseModel):
@@ -16,16 +16,41 @@ class KeyActionRequest(BaseModel):
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.key,
"key": k.public_id,
"key_id": k.public_id,
"display_key": k.preview,
"name": k.name,
"is_admin": k.is_admin,
"active": k.active,
@@ -50,6 +75,8 @@ async def create_key(name: str, is_admin: bool = False):
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": "Сохраните ключ -- он больше не будет показан полностью",
@@ -60,10 +87,7 @@ async def create_key(name: str, is_admin: bool = False):
async def revoke_key(body: KeyActionRequest):
"""Деактивировать (отозвать) гостевой ключ. Ключ передаётся в body, не в URL."""
async with async_session() as session:
result = await session.execute(
select(ApiKeyModel).where(ApiKeyModel.key == body.key)
)
api_key = result.scalar_one_or_none()
api_key = await _find_key_by_secret_or_public_id(session, body.key)
if not api_key:
raise HTTPException(status_code=404, detail="Ключ не найден")
@@ -71,17 +95,14 @@ async def revoke_key(body: KeyActionRequest):
session.add(api_key)
await session.commit()
return {"status": "revoked", "name": api_key.name}
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:
result = await session.execute(
select(ApiKeyModel).where(ApiKeyModel.key == body.key)
)
api_key = result.scalar_one_or_none()
api_key = await _find_key_by_secret_or_public_id(session, body.key)
if not api_key:
raise HTTPException(status_code=404, detail="Ключ не найден")
@@ -89,4 +110,4 @@ async def activate_key(body: KeyActionRequest):
session.add(api_key)
await session.commit()
return {"status": "activated", "name": api_key.name}
return {"status": "activated", "name": api_key.name, "key_id": api_key.public_id}