Admin CRUD router + non-breaking analyze_parcel integration. 46 tests pass. Refs: #114 Co-authored-by: lekss361 <claudestars@proton.me>
102 lines
3.8 KiB
Python
102 lines
3.8 KiB
Python
"""Admin endpoints для управления weight profiles.
|
||
|
||
Per #114 (Макс feedback: садики ≠ Мегамарт). Endpoints под X-Admin-Token.
|
||
|
||
GET /api/v1/admin/site-finder/weight-profiles?user_id=... → list
|
||
POST /api/v1/admin/site-finder/weight-profiles → create
|
||
GET /api/v1/admin/site-finder/weight-profiles/{id}?user_id= → get one
|
||
PUT /api/v1/admin/site-finder/weight-profiles/{id}?user_id= → update
|
||
DELETE /api/v1/admin/site-finder/weight-profiles/{id}?user_id= → delete
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Annotated, Any
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.core.db import get_db
|
||
from app.core.deps import AdminTokenAuth
|
||
from app.services.site_finder.weight_profiles import (
|
||
WeightProfile,
|
||
WeightProfileCreate,
|
||
WeightProfileUpdate,
|
||
create_profile,
|
||
delete_profile,
|
||
get_profile,
|
||
list_profiles,
|
||
update_profile,
|
||
)
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
@router.get("", response_model=list[WeightProfile])
|
||
def list_user_profiles(
|
||
user_id: Annotated[str, Query(min_length=1, description="user_id для фильтра профилей")],
|
||
db: Annotated[Session, Depends(get_db)],
|
||
_: AdminTokenAuth,
|
||
) -> Any:
|
||
"""Список всех weight profiles для заданного user_id. Default-профиль первым."""
|
||
return list_profiles(db, user_id)
|
||
|
||
|
||
@router.post("", response_model=WeightProfile, status_code=status.HTTP_201_CREATED)
|
||
def create_user_profile(
|
||
payload: WeightProfileCreate,
|
||
db: Annotated[Session, Depends(get_db)],
|
||
_: AdminTokenAuth,
|
||
) -> Any:
|
||
"""Создать новый weight profile.
|
||
|
||
При is_default=True снимает is_default у всех существующих профилей
|
||
пользователя в одной транзакции.
|
||
"""
|
||
return create_profile(db, payload)
|
||
|
||
|
||
@router.get("/{profile_id}", response_model=WeightProfile)
|
||
def get_user_profile(
|
||
profile_id: int,
|
||
user_id: Annotated[str, Query(min_length=1, description="Владелец профиля")],
|
||
db: Annotated[Session, Depends(get_db)],
|
||
_: AdminTokenAuth,
|
||
) -> Any:
|
||
"""Получить один weight profile по id (scoped к user_id)."""
|
||
profile = get_profile(db, user_id, profile_id)
|
||
if profile is None:
|
||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found")
|
||
return profile
|
||
|
||
|
||
@router.put("/{profile_id}", response_model=WeightProfile)
|
||
def update_user_profile(
|
||
profile_id: int,
|
||
user_id: Annotated[str, Query(min_length=1, description="Владелец профиля")],
|
||
payload: WeightProfileUpdate,
|
||
db: Annotated[Session, Depends(get_db)],
|
||
_: AdminTokenAuth,
|
||
) -> Any:
|
||
"""PATCH-style обновление weight profile.
|
||
|
||
Передавай только изменяемые поля. Не переданные поля остаются прежними.
|
||
При is_default=True снимает is_default у остальных профилей пользователя.
|
||
"""
|
||
profile = update_profile(db, user_id, profile_id, payload)
|
||
if profile is None:
|
||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found")
|
||
return profile
|
||
|
||
|
||
@router.delete("/{profile_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||
def delete_user_profile(
|
||
profile_id: int,
|
||
user_id: Annotated[str, Query(min_length=1, description="Владелец профиля")],
|
||
db: Annotated[Session, Depends(get_db)],
|
||
_: AdminTokenAuth,
|
||
) -> None:
|
||
"""Удалить weight profile. 404 если не найден."""
|
||
deleted = delete_profile(db, user_id, profile_id)
|
||
if not deleted:
|
||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found")
|