From 1b14e7b23e18d56dd23564c9d36405268b8a53f7 Mon Sep 17 00:00:00 2001 From: Thigazhezhilan J Date: Fri, 1 May 2026 13:38:45 +0530 Subject: [PATCH] Fix broker session showing connected after Zerodha token expiry - Set connected=FALSE (was TRUE) when expiring broker session so the dashboard correctly reflects disconnected state - Notify user by email when their Zerodha session expires so they know to reconnect before the next SIP execution Co-Authored-By: Claude Sonnet 4.6 --- backend/app/broker_store.py | 2 +- backend/app/routers/zerodha.py | 25 ++++++++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/backend/app/broker_store.py b/backend/app/broker_store.py index 7e55378..272090f 100644 --- a/backend/app/broker_store.py +++ b/backend/app/broker_store.py @@ -162,7 +162,7 @@ def expire_user_broker_session(user_id: str): cur.execute( """ UPDATE user_broker - SET connected = TRUE, + SET connected = FALSE, access_token = NULL, auth_state = 'EXPIRED' WHERE user_id = %s diff --git a/backend/app/routers/zerodha.py b/backend/app/routers/zerodha.py index afd74b2..f64fe35 100644 --- a/backend/app/routers/zerodha.py +++ b/backend/app/routers/zerodha.py @@ -5,6 +5,7 @@ from fastapi.responses import HTMLResponse from app.broker_store import expire_user_broker_session from app.services.auth_service import get_user_for_session +from app.services.email_service import send_email_async from app.services.live_equity_service import ( capture_live_equity_snapshot, get_live_equity_curve, @@ -48,14 +49,24 @@ def _capture_request_token(request: Request, request_token: str): store_request_token(user["id"], token) -def _clear_broker_session(user_id: str): +def _clear_broker_session(user_id: str, email: str | None = None): expire_user_broker_session(user_id) clear_session(user_id) + if email: + try: + body = ( + "Your Zerodha session has expired and your broker connection has been disconnected.\n\n" + "Please log in to QuantFortune and reconnect your Zerodha account to resume your strategy.\n\n" + "If your strategy was running, it has been paused until you reconnect." + ) + send_email_async(email, "Action required: Zerodha session expired", body) + except Exception: + pass -def _raise_kite_error(user_id: str, exc: KiteApiError): +def _raise_kite_error(user_id: str, exc: KiteApiError, email: str | None = None): if isinstance(exc, KiteTokenError): - _clear_broker_session(user_id) + _clear_broker_session(user_id, email=email) raise HTTPException( status_code=401, detail="Zerodha session expired. Please reconnect." ) from exc @@ -139,7 +150,7 @@ async def holdings(request: Request): try: data = fetch_holdings(session["api_key"], session["access_token"]) except KiteApiError as exc: - _raise_kite_error(user["id"], exc) + _raise_kite_error(user["id"], exc, email=user["username"]) return {"holdings": [normalize_holding(item) for item in data]} @@ -152,7 +163,7 @@ async def funds(request: Request): try: data = fetch_funds(session["api_key"], session["access_token"]) except KiteApiError as exc: - _raise_kite_error(user["id"], exc) + _raise_kite_error(user["id"], exc, email=user["username"]) equity = data.get("equity", {}) if isinstance(data, dict) else {} return {"funds": {**equity, "raw": data}} @@ -168,7 +179,7 @@ async def equity_curve(request: Request, from_: str = Query("", alias="from")): holdings = fetch_holdings(session["api_key"], session["access_token"]) funds_data = fetch_funds(session["api_key"], session["access_token"]) except KiteApiError as exc: - _raise_kite_error(user["id"], exc) + _raise_kite_error(user["id"], exc, email=user["username"]) try: capture_live_equity_snapshot( @@ -177,7 +188,7 @@ async def equity_curve(request: Request, from_: str = Query("", alias="from")): funds_data=funds_data, ) except KiteApiError as exc: - _raise_kite_error(user["id"], exc) + _raise_kite_error(user["id"], exc, email=user["username"]) now = datetime.utcnow() default_start = (now - timedelta(days=90)).date()