From 98026e510a086e7062efb91a9db01cf83101d846 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Wed, 10 Jun 2026 12:31:14 +0800 Subject: [PATCH] Route main SSE traffic to backend --- deploy/nginx/polyweather.conf | 13 +++++ .../scan-terminal/UserFeedbackModal.tsx | 50 +++++++++++++++++-- .../__tests__/feedbackWorkflow.test.ts | 6 +++ .../__tests__/ssePatchArchitecture.test.ts | 9 ++++ 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/deploy/nginx/polyweather.conf b/deploy/nginx/polyweather.conf index c06a0871..ab112c82 100644 --- a/deploy/nginx/polyweather.conf +++ b/deploy/nginx/polyweather.conf @@ -9,6 +9,19 @@ server { proxy_buffers 8 16k; proxy_busy_buffers_size 32k; + location /api/events { + proxy_pass http://127.0.0.1:8000; + proxy_http_version 1.1; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400s; + proxy_set_header Connection ''; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + location / { proxy_pass http://127.0.0.1:3001; proxy_http_version 1.1; diff --git a/frontend/components/dashboard/scan-terminal/UserFeedbackModal.tsx b/frontend/components/dashboard/scan-terminal/UserFeedbackModal.tsx index d85473b1..7e33eaf9 100644 --- a/frontend/components/dashboard/scan-terminal/UserFeedbackModal.tsx +++ b/frontend/components/dashboard/scan-terminal/UserFeedbackModal.tsx @@ -49,6 +49,49 @@ function buildRuntimeContext(extra: Record) { }; } +function looksLikeHtmlDocument(value: string) { + const text = String(value || "").trim().toLowerCase(); + return ( + text.startsWith(" { + return typeof item === "string" && item.trim(); + }); + if (typeof value !== "string") return ""; + const message = value.replace(/\s+/g, " ").trim(); + return looksLikeHtmlDocument(message) ? "" : message; + } catch { + return ""; + } +} + +function formatFeedbackSubmitError(status: number, raw: string, isEn: boolean) { + const jsonMessage = extractFeedbackErrorMessage(raw); + if (jsonMessage) return jsonMessage.slice(0, 180); + + if (looksLikeHtmlDocument(raw)) { + return isEn + ? `Service is temporarily unavailable. Please retry in a moment. (HTTP ${status})` + : `服务暂时不可用,请稍后重试。(HTTP ${status})`; + } + + const message = String(raw || "").replace(/\s+/g, " ").trim(); + if (message) return `${message.slice(0, 180)} (HTTP ${status})`; + return `HTTP ${status}`; +} + export function UserFeedbackModal({ draft, isEn, @@ -134,8 +177,8 @@ export function UserFeedbackModal({ }), }); if (!res.ok) { - const detail = (await res.text().catch(() => "")).slice(0, 200); - throw new Error(detail || `HTTP ${res.status}`); + const raw = await res.text().catch(() => ""); + throw new Error(formatFeedbackSubmitError(res.status, raw, isEn)); } const payload = (await res.json().catch(() => null)) as { feedback?: UserFeedbackEntry; @@ -143,7 +186,8 @@ export function UserFeedbackModal({ setSubmitted(true); onSubmitted?.(payload?.feedback); } catch (err) { - setError(String(err).slice(0, 220)); + const message = err instanceof Error ? err.message : String(err); + setError(message.slice(0, 220)); } finally { setSubmitting(false); } diff --git a/frontend/components/dashboard/scan-terminal/__tests__/feedbackWorkflow.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/feedbackWorkflow.test.ts index 616f3824..ff11d06c 100644 --- a/frontend/components/dashboard/scan-terminal/__tests__/feedbackWorkflow.test.ts +++ b/frontend/components/dashboard/scan-terminal/__tests__/feedbackWorkflow.test.ts @@ -57,6 +57,12 @@ export function runTests() { modalSource.includes("type=\"textarea\"") === false, "feedback modal must submit to the feedback API, lock contact to the login email when available, notify the dashboard after success, and attach client/session diagnostics without using invalid textarea input types", ); + assert( + modalSource.includes("formatFeedbackSubmitError") && + modalSource.includes("looksLikeHtmlDocument") && + !modalSource.includes("throw new Error(detail || `HTTP ${res.status}`)"), + "feedback modal must not expose raw Cloudflare/HTML error pages to users", + ); assert( feedbackRouteSource.includes("export async function GET") && feedbackRouteSource.includes("method: \"GET\"") && diff --git a/frontend/components/dashboard/scan-terminal/__tests__/ssePatchArchitecture.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/ssePatchArchitecture.test.ts index e93f1af6..f700dd97 100644 --- a/frontend/components/dashboard/scan-terminal/__tests__/ssePatchArchitecture.test.ts +++ b/frontend/components/dashboard/scan-terminal/__tests__/ssePatchArchitecture.test.ts @@ -78,6 +78,15 @@ export function runTests() { assert(nginx.includes("location /api/events"), "Nginx deploy config must route /api/events separately"); assert(nginx.includes("proxy_buffering off"), "Nginx /api/events must disable proxy buffering for SSE"); assert(nginx.includes("proxy_read_timeout 86400s"), "Nginx /api/events must keep SSE connections open"); + const frontendServerStart = nginx.indexOf("server_name polyweather.top www.polyweather.top"); + const apiServerStart = nginx.indexOf("server_name api.polyweather.top"); + const frontendServer = nginx.slice(frontendServerStart, apiServerStart); + assert( + frontendServer.includes("location /api/events") && + frontendServer.includes("proxy_pass http://127.0.0.1:8000") && + frontendServer.indexOf("location /api/events") < frontendServer.indexOf("location / {"), + "Nginx frontend host must route /api/events directly to FastAPI before the generic Next.js location", + ); const weatherSources = readRepoFile("src", "data_collection", "weather_sources.py"); assert(weatherSources.includes("_emit_temperature_patch_if_changed"), "collector must centralize temperature patch emission");