Saxo connector
This commit is contained in:
59
backend/routers/saxo.py
Normal file
59
backend/routers/saxo.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
|
||||
from services import saxo_auth
|
||||
from services.saxo_scheduler import get_watchlist, set_watchlist
|
||||
from services.database import get_saxo_snapshots
|
||||
|
||||
router = APIRouter(prefix="/api/saxo", tags=["saxo"])
|
||||
|
||||
|
||||
class WatchlistRequest(BaseModel):
|
||||
symbols: List[str]
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
def status():
|
||||
return saxo_auth.get_status()
|
||||
|
||||
|
||||
@router.post("/disconnect")
|
||||
def disconnect():
|
||||
saxo_auth.disconnect()
|
||||
return {"disconnected": True}
|
||||
|
||||
|
||||
@router.get("/watchlist")
|
||||
def watchlist():
|
||||
return {"symbols": get_watchlist()}
|
||||
|
||||
|
||||
@router.put("/watchlist")
|
||||
def update_watchlist(req: WatchlistRequest):
|
||||
set_watchlist(req.symbols)
|
||||
return {"symbols": get_watchlist()}
|
||||
|
||||
|
||||
@router.post("/snapshot-now/{symbol}")
|
||||
def snapshot_now(symbol: str):
|
||||
from services.saxo_client import snapshot_options_chain, SaxoNotConnected
|
||||
from services.database import save_saxo_snapshot_rows
|
||||
try:
|
||||
rows = snapshot_options_chain(symbol)
|
||||
except SaxoNotConnected as e:
|
||||
raise HTTPException(status_code=401, detail=str(e))
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=502, detail=str(e))
|
||||
save_saxo_snapshot_rows(rows)
|
||||
return {"symbol": symbol.upper(), "rows_saved": len(rows)}
|
||||
|
||||
|
||||
@router.get("/history")
|
||||
def history(
|
||||
symbol: Optional[str] = Query(None),
|
||||
date_from: Optional[str] = Query(None),
|
||||
date_to: Optional[str] = Query(None),
|
||||
):
|
||||
return get_saxo_snapshots(symbol, date_from, date_to)
|
||||
41
backend/routers/saxo_oauth.py
Normal file
41
backend/routers/saxo_oauth.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""OAuth2 login/callback for Saxo — paths outside /api, matching the callback URL
|
||||
registered on the Saxo app dashboard (https://openfin.open-squared.tech/oauth/saxo/callback)."""
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import RedirectResponse
|
||||
|
||||
from services import saxo_auth
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(tags=["saxo-oauth"])
|
||||
|
||||
FRONTEND_CONFIG_URL = "/config"
|
||||
|
||||
|
||||
@router.get("/oauth/saxo/login")
|
||||
def saxo_login():
|
||||
if not saxo_auth.is_configured():
|
||||
raise HTTPException(status_code=400, detail="SAXO_APP_KEY / SAXO_APP_SECRET manquants dans backend/.env")
|
||||
state = saxo_auth.new_login_state()
|
||||
return RedirectResponse(saxo_auth.build_authorize_url(state))
|
||||
|
||||
|
||||
@router.get("/oauth/saxo/callback")
|
||||
def saxo_callback(code: str = Query(...), state: str = Query(...)):
|
||||
if not saxo_auth.consume_and_verify_state(state):
|
||||
raise HTTPException(status_code=400, detail="État OAuth invalide (CSRF) — relancez la connexion depuis Config.")
|
||||
try:
|
||||
saxo_auth.exchange_code_for_tokens(code)
|
||||
except Exception as e:
|
||||
logger.error(f"[Saxo OAuth] Token exchange failed: {e}")
|
||||
return RedirectResponse(f"{FRONTEND_CONFIG_URL}?saxo_error=1")
|
||||
return RedirectResponse(f"{FRONTEND_CONFIG_URL}?saxo_connected=1")
|
||||
|
||||
|
||||
@router.post("/oauth/saxo/refresh")
|
||||
def saxo_manual_refresh():
|
||||
result = saxo_auth.refresh_tokens()
|
||||
if not result:
|
||||
raise HTTPException(status_code=400, detail="Saxo n'est pas connecté")
|
||||
return {"refreshed": True}
|
||||
Reference in New Issue
Block a user