"""店铺管理:绑定 Ozon Client-Id/Api-Key(加密落库)+ 连通性校验。""" from __future__ import annotations from datetime import datetime, timezone from uuid import UUID from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from core.security import decrypt_secret, encrypt_secret from db import get_db from deps import get_current_user from models import Shop from models.enums import ShopStatus from schemas.shop import ShopCreate, ShopListItem, ShopUpdate from services.ozon_client import OzonClient, OzonAPIError router = APIRouter(prefix="/api/shops", tags=["shops"]) def _mask(client_id: str) -> str: return f"…{client_id[-4:]}" if len(client_id) > 4 else "…" @router.get("", response_model=list[ShopListItem]) async def list_shops( db: AsyncSession = Depends(get_db), _user: dict = Depends(get_current_user), ): rows = (await db.scalars(select(Shop).order_by(Shop.created_at))).all() items = [] for s in rows: item = ShopListItem.model_validate(s) try: item.client_id_masked = _mask(decrypt_secret(s.client_id_enc)) except Exception: # noqa: BLE001 item.client_id_masked = "…" items.append(item) return items @router.post("", response_model=ShopListItem) async def create_shop( body: ShopCreate, db: AsyncSession = Depends(get_db), _user: dict = Depends(get_current_user), ): shop = Shop( name=body.name, client_id_enc=encrypt_secret(body.client_id), api_key_enc=encrypt_secret(body.api_key), currency_code=body.currency_code or "RUB", status=ShopStatus.active, ) db.add(shop) await db.commit() await db.refresh(shop) item = ShopListItem.model_validate(shop) item.client_id_masked = _mask(body.client_id) return item @router.patch("/{shop_id}", response_model=ShopListItem) async def update_shop( shop_id: str, body: ShopUpdate, db: AsyncSession = Depends(get_db), _user: dict = Depends(get_current_user), ): shop = await db.get(Shop, UUID(shop_id)) if shop is None: raise HTTPException(status_code=404, detail="店铺不存在") if body.name is not None: shop.name = body.name if body.currency_code is not None: shop.currency_code = body.currency_code if body.client_id: shop.client_id_enc = encrypt_secret(body.client_id) if body.api_key: shop.api_key_enc = encrypt_secret(body.api_key) await db.commit() await db.refresh(shop) item = ShopListItem.model_validate(shop) item.client_id_masked = _mask(decrypt_secret(shop.client_id_enc)) return item @router.delete("/{shop_id}") async def delete_shop( shop_id: str, db: AsyncSession = Depends(get_db), _user: dict = Depends(get_current_user), ): shop = await db.get(Shop, UUID(shop_id)) if shop is None: raise HTTPException(status_code=404, detail="店铺不存在") await db.delete(shop) await db.commit() return {"deleted": True} @router.post("/{shop_id}/test") async def test_shop( shop_id: str, db: AsyncSession = Depends(get_db), _user: dict = Depends(get_current_user), ): shop = await db.get(Shop, UUID(shop_id)) if shop is None: raise HTTPException(status_code=404, detail="店铺不存在") client = OzonClient(decrypt_secret(shop.client_id_enc), decrypt_secret(shop.api_key_enc)) try: result = await client.test_credentials() except OzonAPIError as exc: shop.status = ShopStatus.invalid await db.commit() return {"ok": False, "error": exc.detail, "roles": []} shop.status = ShopStatus.active shop.last_checked_at = datetime.now(timezone.utc) await db.commit() roles = [r.get("name") for r in result.get("roles", [])] return {"ok": True, "roles": roles}