From 1b11cdf25cef0f8aeabeef2747606f44614bb046 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Apr 2026 07:43:00 +0000 Subject: [PATCH] Fix document loading: circular FK, patch validation 500, and token expiry redirect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - documents.py: fix root_version_id never being saved due to SQLAlchemy deferring the circular FK (CvDocument↔CvVersion). Use flush-based approach: flush doc, flush version, then set root_version_id before final commit. - config.py: add validator to coerce empty MINIO_ENDPOINT string to None, preventing boto3 ValueError: Invalid endpoint at startup. - versions.py: catch PatchValidationError and return 422 instead of 500 when a patch violates ATS guard rules. - api.ts: on 401, clear stale OIDC cookies and redirect to /login instead of showing the "Failed to load documents" error. https://claude.ai/code/session_01KKbzWYz8fLyG2qcwiDZ8fy --- .../fastapi/app/api/routes/versions.py | 20 +++++++++++-------- apps/backend/fastapi/app/core/config.py | 7 +++++++ .../backend/fastapi/app/services/documents.py | 12 ++++++----- apps/webapp/src/libs/api.ts | 5 +++++ 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/apps/backend/fastapi/app/api/routes/versions.py b/apps/backend/fastapi/app/api/routes/versions.py index 9699781..ed53426 100644 --- a/apps/backend/fastapi/app/api/routes/versions.py +++ b/apps/backend/fastapi/app/api/routes/versions.py @@ -7,6 +7,7 @@ from app.api.deps import get_current_user, get_db from app.schemas import BranchCreateRequest, VersionResponse from app.services.versions import create_branch, delete_version from dlib.auth import AuthenticatedUser +from dlib.cv.ats_guard import PatchValidationError router = APIRouter(prefix="/versions", tags=["versions"]) @@ -18,14 +19,17 @@ async def create_version_branch( session: AsyncSession = Depends(get_db), user: AuthenticatedUser = Depends(get_current_user), ): - version = await create_branch( - session, - owner_id=user.sub, - parent_version_id=payload.parent_version_id, - branch_name=payload.branch_name, - version_label=payload.version_label, - patches=payload.patches, - ) + try: + version = await create_branch( + session, + owner_id=user.sub, + parent_version_id=payload.parent_version_id, + branch_name=payload.branch_name, + version_label=payload.version_label, + patches=payload.patches, + ) + except PatchValidationError as exc: + raise HTTPException(status_code=422, detail=str(exc)) from exc if not version: raise HTTPException(status_code=404, detail="Parent version not found") return VersionResponse.model_validate(version) diff --git a/apps/backend/fastapi/app/core/config.py b/apps/backend/fastapi/app/core/config.py index 879d0da..3975c55 100644 --- a/apps/backend/fastapi/app/core/config.py +++ b/apps/backend/fastapi/app/core/config.py @@ -57,6 +57,13 @@ class Settings(BaseSettings): return [origin.strip() for origin in value.split(",") if origin.strip()] return value + @field_validator("storage_endpoint_url", mode="before") + @classmethod + def _empty_endpoint_to_none(cls, value): + if isinstance(value, str) and not value.strip(): + return None + return value + @lru_cache(maxsize=1) def get_settings() -> Settings: diff --git a/apps/backend/fastapi/app/services/documents.py b/apps/backend/fastapi/app/services/documents.py index 6bc0022..710cc8c 100644 --- a/apps/backend/fastapi/app/services/documents.py +++ b/apps/backend/fastapi/app/services/documents.py @@ -23,20 +23,22 @@ async def create_document( structured = parse_docx_bytes(file_bytes, version_label="root") doc = CvDocument(owner_id=owner_id, title=title, description=description) + session.add(doc) + await session.flush() # persist doc so version FK is satisfied + version = CvVersion( - document=doc, + document_id=doc.id, branch_name="root", version_label="root", artifact_docx_key=artifact_key, structured_blocks=[block.model_dump() for block in structured.blocks], metadata_json={"ingested": True}, ) - doc.versions.append(version) - doc.root_version_id = version.id + session.add(version) + await session.flush() # persist version so root_version_id FK is satisfied - session.add(doc) + doc.root_version_id = version.id await session.commit() - await session.refresh(doc) stmt = ( select(CvDocument) diff --git a/apps/webapp/src/libs/api.ts b/apps/webapp/src/libs/api.ts index c744664..c50c201 100644 --- a/apps/webapp/src/libs/api.ts +++ b/apps/webapp/src/libs/api.ts @@ -92,6 +92,11 @@ async function req(path: string, init?: RequestInit): Promise { headers: { accept: 'application/json', ...getAuthHeader(), ...init?.headers }, }); if (!res.ok) { + if (res.status === 401 && typeof window !== 'undefined') { + document.cookie = 'oidc_token_pub=; max-age=0; path=/'; + document.cookie = 'oidc_token=; max-age=0; path=/'; + window.location.href = '/login'; + } const detail = await res.text().catch(() => res.statusText); throw new Error(detail || `HTTP ${res.status}`); }