import logging import httpx from datetime import datetime from fastapi import APIRouter, Request, Depends, HTTPException from fastapi.responses import RedirectResponse, JSONResponse from fastapi.templating import Jinja2Templates 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") templates = Jinja2Templates(directory="web/templates") 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)