Route main SSE traffic to backend
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -49,6 +49,49 @@ function buildRuntimeContext(extra: Record<string, unknown>) {
|
||||
};
|
||||
}
|
||||
|
||||
function looksLikeHtmlDocument(value: string) {
|
||||
const text = String(value || "").trim().toLowerCase();
|
||||
return (
|
||||
text.startsWith("<!doctype html") ||
|
||||
text.startsWith("<html") ||
|
||||
text.includes("<html ") ||
|
||||
text.includes("cloudflare")
|
||||
);
|
||||
}
|
||||
|
||||
function extractFeedbackErrorMessage(raw: string) {
|
||||
try {
|
||||
const parsed = JSON.parse(String(raw || "")) as {
|
||||
detail?: unknown;
|
||||
error?: unknown;
|
||||
message?: unknown;
|
||||
};
|
||||
const value = [parsed.detail, parsed.error, parsed.message].find((item) => {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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\"") &&
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user