import logging import httpx from datetime import datetime from fastapi import APIRouter, Request, Depends, HTTPException from fastapi.responses import RedirectResponse, JSONResponse from web.templates_env import templates from pydantic import BaseModel from sqlalchemy.orm import Session from web.auth import get_current_user from web.config import settings from web.database import get_db from web.models import User, EvotorConnection logger = logging.getLogger(__name__) router = APIRouter(prefix="/evotor") EVOTOR_APP_URL = "https://market.evotor.ru/store/apps/{app_id}" EVOTOR_STORES_URL = "https://api.evotor.ru/stores" @router.get("") def evotor_page( request: Request, db: Session = Depends(get_db), user: User | None = Depends(get_current_user), ): if not user: return RedirectResponse("/login", 303) connection = db.query(EvotorConnection).filter(EvotorConnection.user_id == user.id).first() error = request.query_params.get("error") app_url = EVOTOR_APP_URL.format(app_id=settings.EVOTOR_APP_ID) if settings.EVOTOR_APP_ID else None return templates.TemplateResponse("evotor.html", { "request": request, "user": user, "connection": connection, "error": error, "app_url": app_url, }) class EvotorTokenPayload(BaseModel): userId: str token: str @router.post("/callback") async def evotor_callback( request: Request, payload: EvotorTokenPayload, db: Session = Depends(get_db), ): """ Webhook endpoint: Evotor POSTs {"userId": "...", "token": "..."} here after the user authorizes the app in their Evotor account. """ # Verify the Authorization header matches our configured webhook secret if settings.EVOTOR_WEBHOOK_SECRET: auth_header = request.headers.get("Authorization", "") expected = f"Bearer {settings.EVOTOR_WEBHOOK_SECRET}" if auth_header != expected: logger.warning("Evotor webhook: invalid Authorization header") raise HTTPException(status_code=401, detail="Unauthorized") now = datetime.utcnow() # Fetch store info using the received token store_id = None store_name = None try: async with httpx.AsyncClient() as client: stores_response = await client.get( EVOTOR_STORES_URL, headers={"Authorization": f"Bearer {payload.token}"}, timeout=15, ) if stores_response.status_code == 200: stores = stores_response.json() items = stores.get("items", stores) if isinstance(stores, dict) else stores if items: store_id = items[0].get("uuid") or items[0].get("id") store_name = items[0].get("name") except Exception: pass # Store info is optional # Upsert by evotor_user_id (user_id stays NULL until /evotor/link is called) connection = db.query(EvotorConnection).filter( EvotorConnection.evotor_user_id == payload.userId ).first() if connection: connection.access_token = payload.token connection.store_id = store_id connection.store_name = store_name connection.is_online = True connection.last_checked_at = now connection.updated_at = now else: connection = EvotorConnection( evotor_user_id=payload.userId, access_token=payload.token, store_id=store_id, store_name=store_name, is_online=True, last_checked_at=now, ) db.add(connection) db.commit() logger.info("Evotor webhook: saved token for evotor_user_id=%s", payload.userId) return JSONResponse({"status": "ok"}) @router.post("/token") async def evotor_token_manual( request: Request, db: Session = Depends(get_db), user: User | None = Depends(get_current_user), ): """Allow user to manually paste their Evotor token.""" if not user: return RedirectResponse("/login", 303) form = await request.form() token = (form.get("token") or "").strip() if not token: return RedirectResponse("/evotor?error=empty_token", 303) now = datetime.utcnow() # Fetch store info store_id = None store_name = None try: async with httpx.AsyncClient() as client: stores_response = await client.get( EVOTOR_STORES_URL, headers={"Authorization": f"Bearer {token}"}, timeout=15, ) if stores_response.status_code == 200: stores = stores_response.json() items = stores.get("items", stores) if isinstance(stores, dict) else stores if items: store_id = items[0].get("uuid") or items[0].get("id") store_name = items[0].get("name") elif stores_response.status_code == 401: return RedirectResponse("/evotor?error=invalid_token", 303) except Exception: pass connection = db.query(EvotorConnection).filter(EvotorConnection.user_id == user.id).first() if connection: connection.access_token = token connection.store_id = store_id connection.store_name = store_name connection.is_online = True connection.last_checked_at = now connection.updated_at = now else: connection = EvotorConnection( user_id=user.id, access_token=token, store_id=store_id, store_name=store_name, is_online=True, last_checked_at=now, ) db.add(connection) db.commit() return RedirectResponse("/connections", 303) @router.post("/disconnect") async def evotor_disconnect( request: Request, db: Session = Depends(get_db), user: User | None = Depends(get_current_user), ): if not user: return RedirectResponse("/login", 303) connection = db.query(EvotorConnection).filter(EvotorConnection.user_id == user.id).first() if connection: db.delete(connection) db.commit() return RedirectResponse("/connections", 303)