Skip to content

Commit 9ac6d09

Browse files
committed
feat: updated error handling in job status retrieval
1 parent b7a91fb commit 9ac6d09

File tree

3 files changed

+103
-31
lines changed

3 files changed

+103
-31
lines changed

app/auth.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from jwt import PyJWKClient
77
from loguru import logger
88

9-
from app.error import AuthException
9+
from app.error import AuthException, DispatcherException
10+
from app.schemas.websockets import WSStatusMessage
1011

1112
from .config.settings import settings
1213

@@ -57,6 +58,7 @@ async def websocket_authenticate(websocket: WebSocket) -> str | None:
5758
"""
5859
logger.debug("Authenticating websocket")
5960
token = websocket.query_params.get("token")
61+
6062
if not token:
6163
logger.error("Token is missing from websocket authentication")
6264
await websocket.close(code=1008, reason="Missing token")
@@ -65,9 +67,20 @@ async def websocket_authenticate(websocket: WebSocket) -> str | None:
6567
try:
6668
await websocket.accept()
6769
return token
70+
except DispatcherException as ae:
71+
logger.error(f"Dispatcher exception detected: {ae.message}")
72+
await websocket.send_json(
73+
WSStatusMessage(type="error", message=ae.message).model_dump()
74+
)
75+
await websocket.close(code=1008, reason=ae.error_code)
76+
return None
6877
except Exception as e:
69-
logger.error(f"Invalid token in websocket authentication: {e}")
70-
await websocket.close(code=1008, reason="Invalid token")
78+
logger.error(f"Unexpected error occurred during websocket authentication: {e}")
79+
await WSStatusMessage(
80+
type="error",
81+
message="Something went wrong during authentication. Please try again.",
82+
).model_dump()
83+
await websocket.close(code=1008, reason="INTERNAL_ERROR")
7184
return None
7285

7386

@@ -107,9 +120,12 @@ async def exchange_token_for_provider(
107120
resp = await client.post(token_url, data=payload)
108121
except httpx.RequestError as exc:
109122
logger.error(f"Token exchange network error for provider={provider}: {exc}")
110-
raise HTTPException(
111-
status_code=status.HTTP_502_BAD_GATEWAY,
112-
detail="Failed to contact the identity provider for token exchange.",
123+
raise AuthException(
124+
http_status=status.HTTP_502_BAD_GATEWAY,
125+
message=(
126+
f"Could not authenticate with {provider}. Please contact APEx support or reach out "
127+
"through the <a href='https://forum.apex.esa.int/'>APEx User Forum</a>."
128+
),
113129
)
114130

115131
# Parse response
@@ -119,9 +135,12 @@ async def exchange_token_for_provider(
119135
logger.error(
120136
f"Token exchange invalid JSON response (status={resp.status_code})"
121137
)
122-
raise HTTPException(
123-
status_code=status.HTTP_502_BAD_GATEWAY,
124-
detail="Invalid response from identity provider during token exchange.",
138+
raise AuthException(
139+
http_status=status.HTTP_502_BAD_GATEWAY,
140+
message=(
141+
f"Could not authenticate with {provider}. Please contact APEx support or reach out "
142+
"through the <a href='https://forum.apex.esa.int/'>APEx User Forum</a>."
143+
),
125144
)
126145

127146
if resp.status_code != 200:
@@ -138,7 +157,14 @@ async def exchange_token_for_provider(
138157
else status.HTTP_502_BAD_GATEWAY
139158
)
140159

141-
raise HTTPException(client_status, detail=body)
160+
raise AuthException(
161+
http_status=client_status,
162+
message=(
163+
f"Please link your account with {provider} in your <a href='https://{settings.keycloak_host}/realms/{settings.keycloak_realm}/account'>Account Dashboard</a>"
164+
if body.get("error", "") == "not_linked"
165+
else f"Could not authenticate with {provider}: {err}"
166+
),
167+
)
142168

143169
# Successful exchange, return token response (access_token, expires_in, etc.)
144170
return body

app/middleware/error_handling.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from fastapi import Request, status
2+
from fastapi.exceptions import RequestValidationError
23
from fastapi.responses import JSONResponse
34
from app.error import DispatcherException, ErrorResponse
45
from app.middleware.correlation_id import correlation_id_ctx
@@ -33,19 +34,31 @@ async def generic_exception_handler(request: Request, exc: Exception):
3334
request_id=correlation_id_ctx.get(),
3435
)
3536

36-
# Log exception to server logs for debugging
37-
print(f"[ERROR] Request ID: {exc}")
38-
37+
logger.exception(f"GenericException raised: {exc}")
3938
return JSONResponse(
4039
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=content.dict()
4140
)
4241

4342

43+
async def validation_exception_handler(request: Request, exc: RequestValidationError):
44+
45+
content = ErrorResponse(
46+
error_code="VALIDATION_ERROR",
47+
message="Request validation failed.",
48+
details={"errors": exc.errors()},
49+
request_id=correlation_id_ctx.get(),
50+
)
51+
52+
return JSONResponse(
53+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content=content.dict()
54+
)
55+
56+
4457
def register_exception_handlers(app):
4558
"""
4659
Call this in main.py after creating the FastAPI() instance.
4760
"""
4861

4962
app.add_exception_handler(DispatcherException, dispatch_exception_handler)
50-
# app.add_exception_handler(RequestValidationError, validation_exception_handler)
63+
app.add_exception_handler(RequestValidationError, validation_exception_handler)
5164
app.add_exception_handler(Exception, generic_exception_handler)

app/routers/jobs_status.py

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from loguru import logger
88

99
from app.database.db import SessionLocal, get_db
10+
from app.error import DispatcherException, ErrorResponse, InternalException
11+
from app.middleware.error_handling import get_dispatcher_error_response
1012
from app.schemas.jobs_status import JobsFilter, JobsStatusResponse
1113
from app.schemas.websockets import WSStatusMessage
1214
from app.services.processing import get_processing_jobs_by_user_id
@@ -22,6 +24,19 @@
2224
"/jobs_status",
2325
tags=["Upscale Tasks", "Unit Jobs"],
2426
summary="Get a list of all upscaling tasks & processing jobs for the authenticated user",
27+
responses={
28+
InternalException.http_status: {
29+
"description": "Internal server error",
30+
"model": ErrorResponse,
31+
"content": {
32+
"application/json": {
33+
"example": get_dispatcher_error_response(
34+
InternalException(), "request-id"
35+
)
36+
}
37+
},
38+
},
39+
},
2540
)
2641
async def get_jobs_status(
2742
db: Session = Depends(get_db),
@@ -34,21 +49,29 @@ async def get_jobs_status(
3449
"""
3550
Return combined list of upscaling tasks and processing jobs for the authenticated user.
3651
"""
37-
logger.debug("Fetching jobs list")
38-
upscaling_tasks = (
39-
await get_upscaling_tasks_by_user_id(token, db)
40-
if JobsFilter.upscaling in filter
41-
else []
42-
)
43-
processing_jobs = (
44-
await get_processing_jobs_by_user_id(token, db)
45-
if JobsFilter.processing in filter
46-
else []
47-
)
48-
return JobsStatusResponse(
49-
upscaling_tasks=upscaling_tasks,
50-
processing_jobs=processing_jobs,
51-
)
52+
try:
53+
logger.debug("Fetching jobs list")
54+
upscaling_tasks = (
55+
await get_upscaling_tasks_by_user_id(token, db)
56+
if JobsFilter.upscaling in filter
57+
else []
58+
)
59+
processing_jobs = (
60+
await get_processing_jobs_by_user_id(token, db)
61+
if JobsFilter.processing in filter
62+
else []
63+
)
64+
return JobsStatusResponse(
65+
upscaling_tasks=upscaling_tasks,
66+
processing_jobs=processing_jobs,
67+
)
68+
except DispatcherException as de:
69+
raise de
70+
except Exception as e:
71+
logger.error(f"Error retrieving job status: {e}")
72+
raise InternalException(
73+
message="An error occurred while retrieving the job status."
74+
)
5275

5376

5477
@router.websocket(
@@ -91,8 +114,18 @@ async def ws_jobs_status(
91114

92115
except WebSocketDisconnect:
93116
logger.info("WebSocket disconnected")
117+
except DispatcherException as ae:
118+
logger.error(f"Dispatcher exception detected: {ae.message}")
119+
await websocket.send_json(
120+
WSStatusMessage(type="error", message=ae.message).model_dump()
121+
)
122+
await websocket.close(code=1008, reason=ae.error_code)
94123
except Exception as e:
95-
logger.exception(f"Error in jobs_status_ws: {e}")
96-
await websocket.close(code=1011, reason="Error in job status websocket: {e}")
124+
logger.error(f"Unexpected error occurred during websocket authentication: {e}")
125+
await WSStatusMessage(
126+
type="error",
127+
message="Something went wrong during authentication. Please try again.",
128+
).model_dump()
129+
await websocket.close(code=1008, reason="INTERNAL_ERROR")
97130
finally:
98131
db.close()

0 commit comments

Comments
 (0)