from __future__ import annotations from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, Query from backend.database import db from backend.logging_setup import get_logger from backend.routers.auth import require_roles from backend.schemas import CaseListResponse, CaseResponse, CaseUpdate, DashboardResponse log = get_logger('admin') router = APIRouter(prefix='/api/v1/admin', tags=['admin'], dependencies=[Depends(require_roles(['admin']))]) @router.get('/cases', response_model=CaseListResponse) def admin_cases( page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), status: str | None = None, age_min: int | None = Query(default=None, ge=0), age_max: int | None = Query(default=None, ge=0), ) -> dict: cases = db.list_cases() if status: cases = [c for c in cases if c.status == status] if age_min is not None: cases = [c for c in cases if c.age is not None and c.age >= age_min] if age_max is not None: cases = [c for c in cases if c.age is not None and c.age <= age_max] start = (page - 1) * page_size end = start + page_size return {'items': [c.to_detail() for c in cases[start:end]], 'total': len(cases), 'page': page, 'page_size': page_size} @router.patch('/cases/{case_id}', response_model=CaseResponse) def admin_update_case(case_id: str, payload: CaseUpdate) -> dict: data = payload.model_dump(exclude_none=True) # default_factory-списки ([]) при exclude_none всё равно попадают в data и # ЗАТИРАЮТ реальные terrain/diagnosis_type — выкидываем пустые списки: data = {k: v for k, v in data.items() if not (isinstance(v, list) and not v)} note = data.pop('note', None) if note is not None and 'notes' not in data: data['notes'] = note case = db.update_case(case_id, **data) if not case: raise HTTPException(status_code=404, detail='Case not found') # B21-синхронизация: кейс закрыт вручную → его операция завершается автоматически if data.get('status') == 'closed': import uuid as _uuid from backend.models import SearchOperation as _Op from backend.database import SessionLocal as _SL case_uuid = _uuid.UUID(str(case.id)) if isinstance(case.id, str) else case.id with _SL() as session: ops = session.query(_Op).filter(_Op.case_id == case_uuid, _Op.status.in_(['planned', 'active', 'paused'])).all() for op in ops: op.status = 'completed' op.closed_at = datetime.now(timezone.utc) session.commit() closed_ops = len(ops) if closed_ops: log.info('admin closed case %s → %d операция(ий) завершены автоматически', case_id, closed_ops) return case.to_detail() @router.get('/dashboard', response_model=DashboardResponse) def admin_dashboard() -> dict: return db.stats()