- Unify API: lib/api.ts uses /api/v1, inbox uses /api/inbox (rewrites) - Remove localhost refs: openapi, inbox page - Add rewrites: /api/inbox|tmc -> inbox-server, /api/v1 -> FastAPI - Add stub routes: knowledge/insights, recommendations, search, log-error - Transfer from PAPA: prompts (inspection, tmc), scripts, supabase, data/tmc-requests - Fix inbox-server: ORDER BY created_at, package.json - Remove redundant app/api/inbox/files route (rewrites handle it) - knowledge/ in gitignore (large PDFs) Co-authored-by: Cursor <cursoragent@cursor.com>
136 lines
5.6 KiB
Python
136 lines
5.6 KiB
Python
"""
|
||
API routes для управления организациями.
|
||
|
||
Соответствует требованиям ТЗ: управление организациями (операторы, MRO, органы власти).
|
||
"""
|
||
|
||
import logging
|
||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||
from sqlalchemy.orm import Session
|
||
from sqlalchemy.exc import IntegrityError
|
||
|
||
from app.api.deps import get_current_user, require_roles
|
||
from app.db.session import get_db
|
||
from app.models import Organization, User, Aircraft, CertApplication
|
||
from app.schemas.organization import OrganizationCreate, OrganizationOut, OrganizationUpdate
|
||
|
||
logger = logging.getLogger(__name__)
|
||
router = APIRouter(tags=["organizations"])
|
||
|
||
|
||
@router.get("/organizations", response_model=list[OrganizationOut])
|
||
def list_organizations(
|
||
q: str | None = Query(None, description="Search by organization name"),
|
||
db: Session = Depends(get_db),
|
||
user=Depends(get_current_user),
|
||
):
|
||
"""Получить список организаций.
|
||
|
||
Authority видит все; остальные видят только свою организацию.
|
||
Поддерживает поиск по названию организации.
|
||
"""
|
||
try:
|
||
query = db.query(Organization)
|
||
if user.role not in {"admin", "authority_inspector"} and user.organization_id:
|
||
query = query.filter(Organization.id == user.organization_id)
|
||
if q:
|
||
query = query.filter(Organization.name.ilike(f"%{q}%"))
|
||
orgs = query.order_by(Organization.name).all()
|
||
result = []
|
||
for org in orgs:
|
||
try:
|
||
result.append(OrganizationOut.model_validate(org))
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при сериализации организации {org.id}: {str(e)}", exc_info=True)
|
||
# Пропускаем проблемную организацию, но продолжаем обработку остальных
|
||
continue
|
||
logger.info(f"Успешно возвращено {len(result)} организаций из {len(orgs)}")
|
||
return result
|
||
except Exception as e:
|
||
import traceback
|
||
logger.error(f"Ошибка при получении списка организаций: {str(e)}", exc_info=True)
|
||
traceback.print_exc()
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"Ошибка при получении списка организаций: {str(e)}"
|
||
)
|
||
|
||
|
||
@router.post(
|
||
"/organizations",
|
||
response_model=OrganizationOut,
|
||
dependencies=[Depends(require_roles("admin", "authority_inspector"))],
|
||
)
|
||
def create_organization(payload: OrganizationCreate, db: Session = Depends(get_db)):
|
||
"""Создать новую организацию."""
|
||
org = Organization(**payload.model_dump())
|
||
db.add(org)
|
||
db.commit()
|
||
db.refresh(org)
|
||
return OrganizationOut.model_validate(org)
|
||
|
||
|
||
@router.get("/organizations/{org_id}", response_model=OrganizationOut)
|
||
def get_organization(org_id: str, db: Session = Depends(get_db), user=Depends(get_current_user)):
|
||
"""Получить детали организации по ID."""
|
||
org = db.query(Organization).filter(Organization.id == org_id).first()
|
||
if not org:
|
||
raise HTTPException(status_code=404, detail="Not found")
|
||
if user.role not in {"admin", "authority_inspector"} and user.organization_id != org_id:
|
||
raise HTTPException(status_code=403, detail="Forbidden")
|
||
return OrganizationOut.model_validate(org)
|
||
|
||
|
||
@router.patch(
|
||
"/organizations/{org_id}",
|
||
response_model=OrganizationOut,
|
||
dependencies=[Depends(require_roles("admin", "authority_inspector"))],
|
||
)
|
||
def update_organization(org_id: str, payload: OrganizationUpdate, db: Session = Depends(get_db)):
|
||
"""Обновить организацию."""
|
||
org = db.query(Organization).filter(Organization.id == org_id).first()
|
||
if not org:
|
||
raise HTTPException(status_code=404, detail="Not found")
|
||
|
||
data = payload.model_dump(exclude_unset=True)
|
||
if not data:
|
||
return OrganizationOut.model_validate(org)
|
||
|
||
for k, v in data.items():
|
||
setattr(org, k, v)
|
||
|
||
try:
|
||
db.commit()
|
||
except IntegrityError:
|
||
db.rollback()
|
||
raise HTTPException(status_code=409, detail="Conflict (duplicate fields)")
|
||
db.refresh(org)
|
||
return OrganizationOut.model_validate(org)
|
||
|
||
|
||
@router.delete(
|
||
"/organizations/{org_id}",
|
||
status_code=status.HTTP_204_NO_CONTENT,
|
||
dependencies=[Depends(require_roles("admin", "authority_inspector"))],
|
||
)
|
||
def delete_organization(org_id: str, db: Session = Depends(get_db)):
|
||
"""Удалить организацию.
|
||
|
||
Нельзя удалить, если есть связанные сущности (пользователи, ВС, заявки).
|
||
"""
|
||
org = db.query(Organization).filter(Organization.id == org_id).first()
|
||
if not org:
|
||
raise HTTPException(status_code=404, detail="Not found")
|
||
|
||
# Проверка связанных сущностей
|
||
if db.query(User).filter(User.organization_id == org_id).count() > 0:
|
||
raise HTTPException(status_code=409, detail="Organization has users")
|
||
if db.query(Aircraft).filter(Aircraft.operator_id == org_id).count() > 0:
|
||
raise HTTPException(status_code=409, detail="Organization has aircraft")
|
||
if db.query(CertApplication).filter(CertApplication.applicant_org_id == org_id).count() > 0:
|
||
raise HTTPException(status_code=409, detail="Organization has applications")
|
||
|
||
db.delete(org)
|
||
db.commit()
|
||
return None
|