From b5dbc6a39b281e424a49b0cbd91869a0271d3ce1 Mon Sep 17 00:00:00 2001 From: Murat Can Nergiz Date: Thu, 2 Jul 2026 15:06:28 +0200 Subject: [PATCH 1/2] feat: add .dockerignore to reduce build context size --- .dockerignore | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c3e9a7e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.git +.gitignore +*.md +frontend/ +n8n/ +docs/ +*.db +*.pdf +__pycache__/ +*.pyc +.env From 06343301722a423e6fceb7368592626109b1407d Mon Sep 17 00:00:00 2001 From: Murat Can Nergiz Date: Thu, 16 Jul 2026 18:09:27 +0200 Subject: [PATCH 2/2] fix: surface the real reason a contract email fails The API sits behind a CDN that rewrites upstream 5xx into its own plain 'error code: 502' page, so the coordinator only ever saw 'Failed to fetch' and the actual cause never reached the browser or the logs. - log the failure server-side, including the exception type (connect/DNS errors often carry an empty message) - return 424 Failed Dependency instead of 502, and 409 instead of 503, so the JSON detail passes through the CDN untouched --- backend/app/routers/applications.py | 6 ++++-- backend/app/services/email_service.py | 12 +++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/backend/app/routers/applications.py b/backend/app/routers/applications.py index 4dfe4a6..35a3f09 100644 --- a/backend/app/routers/applications.py +++ b/backend/app/routers/applications.py @@ -190,9 +190,11 @@ def send_contract(index: int, request: SendContractRequest): candidate_name=application.name, ) except email_service.EmailNotConfiguredError as exc: - raise HTTPException(status_code=503, detail=str(exc)) from exc + raise HTTPException(status_code=409, detail=str(exc)) from exc except email_service.EmailSendError as exc: - raise HTTPException(status_code=502, detail=str(exc)) from exc + # 424 (not 502): a CDN in front of the API rewrites upstream 5xx into its + # own error page, which would hide the reason from the coordinator. + raise HTTPException(status_code=424, detail=str(exc)) from exc application.contract_sent_to = request.to application.contract_sent_at = datetime.now(timezone.utc).isoformat() diff --git a/backend/app/services/email_service.py b/backend/app/services/email_service.py index 979c28e..931e1f2 100644 --- a/backend/app/services/email_service.py +++ b/backend/app/services/email_service.py @@ -73,10 +73,16 @@ def send_signed_contract( ) response.raise_for_status() except httpx.HTTPStatusError as exc: - raise EmailSendError( + message = ( f"n8n webhook returned {exc.response.status_code}: {exc.response.text[:200]}" - ) from exc + ) + logger.error("Contract email failed — %s", message) + raise EmailSendError(message) from exc except httpx.HTTPError as exc: - raise EmailSendError(f"Could not reach the n8n webhook: {exc}") from exc + # Log the exception type too: a bare message like "" is common for + # connect/DNS errors and tells the operator nothing on its own. + message = f"Could not reach the n8n webhook ({type(exc).__name__}): {exc}" + logger.error("Contract email failed — %s", message) + raise EmailSendError(message) from exc logger.info("Signed contract emailed via n8n to %s", to)