feat(api): production security, cors hardening, latency optimizations, postman artifacts & frontend documentation

This commit is contained in:
2026-08-31 13:48:54 +05:30
parent 4e21d617ce
commit e91cdbae6e
48 changed files with 3742 additions and 65 deletions
+78 -9
View File
@@ -1,4 +1,5 @@
import json
import time
from uuid import uuid4
from fastapi import APIRouter, Depends, HTTPException
@@ -36,6 +37,10 @@ from app.services.result_extraction_service import (
from app.services.voice_service import VoiceService
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.orm import Session
from app.core.security import get_auth_context, AuthContext, check_rate_limit
router = APIRouter(
prefix="/v1/onboarding",
tags=["Patient Onboarding"],
@@ -48,14 +53,22 @@ router = APIRouter(
)
async def start_onboarding(
request: StartOnboardingRequest,
req_obj: Request = None,
auth: AuthContext = Depends(get_auth_context),
db: Session = Depends(get_db),
):
if req_obj and req_obj.client:
check_rate_limit(req_obj.client.host, limit=30, window_sec=60)
onboarding_service = OnboardingService()
session_id = f"ONB-{uuid4().hex[:8].upper()}"
# Use authenticated patient identity
patient_id = request.patient.id or auth.patient_id
session = OnboardingSession(
session_id=session_id,
patient_id=request.patient.id,
patient_id=patient_id,
patient_name=request.patient.name,
age=request.patient.age,
gender=request.patient.gender,
@@ -108,16 +121,24 @@ async def start_onboarding(
async def send_message(
session_id: str,
request: SendMessageRequest,
req_obj: Request = None,
auth: AuthContext = Depends(get_auth_context),
db: Session = Depends(get_db),
):
if req_obj and req_obj.client:
check_rate_limit(req_obj.client.host, limit=30, window_sec=60)
t0 = time.perf_counter()
onboarding_service = OnboardingService()
ai_service = AIOnboardingService()
# 1. Get session
t_sess_start = time.perf_counter()
session = onboarding_service.get_session(
db,
session_id,
)
t_sess_end = time.perf_counter()
if not session:
raise HTTPException(
@@ -125,6 +146,13 @@ async def send_message(
detail="Onboarding session not found",
)
# 1b. Verify Session Ownership (IDOR Prevention)
if session.patient_id != auth.patient_id and auth.role != "admin":
raise HTTPException(
status_code=403,
detail="Forbidden: You do not have authorization to access or modify this patient onboarding session.",
)
# 2. Check session
if session.status != "in_progress":
raise HTTPException(
@@ -142,12 +170,15 @@ async def send_message(
)
# 4. Get state
t_state_start = time.perf_counter()
state = onboarding_service.get_state(
db,
session_id,
)
t_state_end = time.perf_counter()
# 5. Get conversation history
t_hist_start = time.perf_counter()
messages = (
db.query(OnboardingMessage)
.filter(
@@ -167,14 +198,17 @@ async def send_message(
}
for message in messages
]
t_hist_end = time.perf_counter()
# 6. Send to AI
t_ai_start = time.perf_counter()
ai_response = await ai_service.process_message(
session=session,
state=state,
conversation_history=conversation_history,
user_message=request.message,
)
t_ai_end = time.perf_counter()
# 7. Update state
current_state = json.loads(
@@ -205,7 +239,9 @@ async def send_message(
if ai_response.get("completed"):
session.status = "completed"
t_db_start = time.perf_counter()
db.commit()
t_db_end = time.perf_counter()
# 10. Save AI message
onboarding_service.save_message(
@@ -216,12 +252,20 @@ async def send_message(
stage=session.stage,
)
t_total_end = time.perf_counter()
total_ms = round((t_total_end - t0) * 1000, 2)
total_sec = round(t_total_end - t0, 3)
print(f"[PERF PROFILE - send_message] SESSION_LOOKUP: {(t_sess_end - t_sess_start)*1000:.2f}ms | STATE_LOAD: {(t_state_end - t_state_start)*1000:.2f}ms | HIST_LOAD: {(t_hist_end - t_hist_start)*1000:.2f}ms | AI_PROCESS: {(t_ai_end - t_ai_start)*1000:.2f}ms | DB_COMMIT: {(t_db_end - t_db_start)*1000:.2f}ms | TOTAL: {total_ms}ms")
return SendMessageResponse(
success=True,
session_id=session_id,
stage=session.stage,
message=ai_response["reply"],
status=session.status,
response_time_ms=total_ms,
response_time_sec=total_sec,
)
@@ -232,8 +276,10 @@ async def send_message(
async def complete_onboarding(
session_id: str,
request: CompleteOnboardingRequest,
auth: AuthContext = Depends(get_auth_context),
db: Session = Depends(get_db),
):
start_time = time.time()
onboarding_service = OnboardingService()
completion_service = CompletionService()
result_service = ResultExtractionService()
@@ -251,6 +297,13 @@ async def complete_onboarding(
detail="Onboarding session not found",
)
# 1b. Verify Session Ownership (IDOR Prevention)
if session.patient_id != auth.patient_id and auth.role != "admin":
raise HTTPException(
status_code=403,
detail="Forbidden: You do not have authorization to complete this onboarding session.",
)
# 2. Load state
state = onboarding_service.get_state(
db,
@@ -297,19 +350,35 @@ async def complete_onboarding(
# 7. Patient confirmed -> submit to existing patient / hospital API
submission = await submission_service.submit(result)
if submission.get("success"):
# 8. Successful submission -> clean up temporary SQLite session
db.query(OnboardingMessage).filter(OnboardingMessage.session_id == session_id).delete()
db.query(OnboardingState).filter(OnboardingState.session_id == session_id).delete()
db.delete(session)
db.commit()
if isinstance(result, dict):
result["onboarding_completed"] = True
if session.status == "completed":
return CompleteOnboardingResponse(
success=True,
session_id=session_id,
status="submitted_and_cleaned",
status="completed",
result=result,
message="Onboarding successfully completed, saved to patient record, and temporary session cleaned.",
message="Onboarding session is already completed.",
response_time_ms=round((time.time() - start_time) * 1000, 2),
response_time_sec=round(time.time() - start_time, 3),
)
if submission.get("success"):
# Mark session completed in DB instead of hard-deleting
session.status = "completed"
session.stage = "confirmation"
db.commit()
elapsed = time.time() - start_time
return CompleteOnboardingResponse(
success=True,
session_id=session_id,
status="completed",
result=result,
message="Onboarding successfully completed and saved to patient record.",
response_time_ms=round(elapsed * 1000, 2),
response_time_sec=round(elapsed, 3),
)
# If submission failed, preserve SQLite session for retry