Update production deployment references
This commit is contained in:
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
PolyWeather Pro — a paid institutional weather-intelligence terminal. 50 monitored cities with real-time METAR/AMOS/MADIS observations, DEB multi-model temperature blending, Mu probability calibration, and intraday bias correction. Pure meteorological decision workspace; no market/price layer. Next.js 15 + React 19 (Vercel) frontend, FastAPI backend (VPS), Telegram bot.
|
PolyWeather Pro — a paid institutional weather-intelligence terminal. 50 monitored cities with real-time METAR/AMOS/MADIS observations, DEB multi-model temperature blending, Mu probability calibration, and intraday bias correction. Pure meteorological decision workspace; no market/price layer. Next.js 15 + React 19 frontend (Docker / VPS, behind Cloudflare + Nginx), FastAPI backend (VPS), Telegram bot.
|
||||||
|
|
||||||
**Business model**: Paid-only, $10/month, no free tier, no trial. Landing page is public; `/terminal` requires login + active subscription.
|
**Business model**: Paid-only, $10/month, no free tier, no trial. Landing page is public; `/terminal` requires login + active subscription.
|
||||||
|
|
||||||
@@ -47,10 +47,12 @@ docker compose down && docker compose up -d --build
|
|||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
Users → Next.js (Vercel) → FastAPI :8000 (VPS)
|
Users → Cloudflare → Nginx → Docker Compose (VPS)
|
||||||
/terminal (paid gate) Weather Collector
|
├── Next.js frontend → FastAPI :8000
|
||||||
/ (landing page) Analysis (DEB + Mu)
|
│ /terminal (paid gate) Weather Collector
|
||||||
Payment Layer (USDC on Polygon)
|
│ / (landing page) Analysis (DEB + Mu)
|
||||||
|
│ Payment Layer (USDC on Polygon)
|
||||||
|
└── Redis (SSE event store)
|
||||||
Telegram Bot → bot_listener.py
|
Telegram Bot → bot_listener.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ See: [AGPL-3.0 & Commercial Boundary](docs/OPEN_CORE_POLICY.md)
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
U["Users (Web / Telegram)"] --> FE["Next.js Frontend (Vercel)"]
|
U["Users (Web / Telegram)"] --> FE["Next.js Frontend (Docker / VPS)"]
|
||||||
U --> BOT["Telegram Bot (VPS)"]
|
U --> BOT["Telegram Bot (VPS)"]
|
||||||
FE --> API["FastAPI /web/app.py"]
|
FE --> API["FastAPI /web/app.py"]
|
||||||
BOT --> API
|
BOT --> API
|
||||||
|
|||||||
+2
-2
@@ -83,7 +83,7 @@
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
U["用户(Web / Telegram)"] --> FE["Next.js 前端(Vercel)"]
|
U["用户(Web / Telegram)"] --> FE["Next.js 前端(Docker / VPS)"]
|
||||||
U --> BOT["Telegram Bot(VPS)"]
|
U --> BOT["Telegram Bot(VPS)"]
|
||||||
FE --> API["FastAPI /web/app.py"]
|
FE --> API["FastAPI /web/app.py"]
|
||||||
BOT --> API
|
BOT --> API
|
||||||
@@ -218,7 +218,7 @@ POLYWEATHER_OPS_ADMIN_EMAILS=yhrsc30@gmail.com
|
|||||||
- AGPL-3.0 边界:[docs/OPEN_CORE_POLICY.md](docs/OPEN_CORE_POLICY.md)
|
- AGPL-3.0 边界:[docs/OPEN_CORE_POLICY.md](docs/OPEN_CORE_POLICY.md)
|
||||||
- Supabase 接入:[docs/SUPABASE_SETUP_ZH.md](docs/SUPABASE_SETUP_ZH.md)
|
- Supabase 接入:[docs/SUPABASE_SETUP_ZH.md](docs/SUPABASE_SETUP_ZH.md)
|
||||||
- 配置与密钥管理:[docs/CONFIGURATION_ZH.md](docs/CONFIGURATION_ZH.md)
|
- 配置与密钥管理:[docs/CONFIGURATION_ZH.md](docs/CONFIGURATION_ZH.md)
|
||||||
- 前端部署(Vercel):[docs/FRONTEND_DEPLOYMENT_ZH.md](docs/FRONTEND_DEPLOYMENT_ZH.md)
|
- 前端部署(Docker / VPS):[docs/FRONTEND_DEPLOYMENT_ZH.md](docs/FRONTEND_DEPLOYMENT_ZH.md)
|
||||||
- 技术债:[docs/TECH_DEBT_ZH.md](docs/TECH_DEBT_ZH.md)
|
- 技术债:[docs/TECH_DEBT_ZH.md](docs/TECH_DEBT_ZH.md)
|
||||||
- 机场实时数据源:[docs/AIRPORT_REALTIME_SOURCES.md](docs/AIRPORT_REALTIME_SOURCES.md)
|
- 机场实时数据源:[docs/AIRPORT_REALTIME_SOURCES.md](docs/AIRPORT_REALTIME_SOURCES.md)
|
||||||
- 机场市场监控(中文):[docs/AIRPORT_MARKET_MONITOR_ZH.md](docs/AIRPORT_MARKET_MONITOR_ZH.md)
|
- 机场市场监控(中文):[docs/AIRPORT_MARKET_MONITOR_ZH.md](docs/AIRPORT_MARKET_MONITOR_ZH.md)
|
||||||
|
|||||||
+1
-1
@@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
首次建议打开扩展"选项页"并确认:
|
首次建议打开扩展"选项页"并确认:
|
||||||
|
|
||||||
- `网站基础地址`:你的前端域名(例如 `https://polyweather-pro.vercel.app`)
|
- `网站基础地址`:你的前端域名(例如 `https://polyweather.top`)
|
||||||
- `API 基础地址`:你的后端 API 域名(若同域也可填前端域名)
|
- `API 基础地址`:你的后端 API 域名(若同域也可填前端域名)
|
||||||
- `Bearer Token`:后端开启鉴权时填写
|
- `Bearer Token`:后端开启鉴权时填写
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,12 @@
|
|||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span id="siteBaseLabel">Site Base URL</span>
|
<span id="siteBaseLabel">Site Base URL</span>
|
||||||
<input id="siteBaseInput" type="text" placeholder="https://polyweather-pro.vercel.app" />
|
<input id="siteBaseInput" type="text" placeholder="https://polyweather.top" />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span id="apiBaseLabel">API Base URL</span>
|
<span id="apiBaseLabel">API Base URL</span>
|
||||||
<input id="apiBaseInput" type="text" placeholder="https://polyweather-pro.vercel.app" />
|
<input id="apiBaseInput" type="text" placeholder="https://polyweather.top" />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const DEFAULT_CONFIG = {
|
const DEFAULT_CONFIG = {
|
||||||
apiBase: "https://polyweather-pro.vercel.app",
|
apiBase: "https://polyweather.top",
|
||||||
siteBase: "https://polyweather-pro.vercel.app",
|
siteBase: "https://polyweather.top",
|
||||||
authToken: "",
|
authToken: "",
|
||||||
selectedCity: ""
|
selectedCity: ""
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const DEFAULT_CONFIG = {
|
const DEFAULT_CONFIG = {
|
||||||
apiBase: "https://polyweather-pro.vercel.app",
|
apiBase: "https://polyweather.top",
|
||||||
authToken: "",
|
authToken: "",
|
||||||
selectedCity: "",
|
selectedCity: "",
|
||||||
siteBase: "https://polyweather-pro.vercel.app"
|
siteBase: "https://polyweather.top"
|
||||||
};
|
};
|
||||||
const CACHE_VERSION = "v3";
|
const CACHE_VERSION = "v3";
|
||||||
const locale = String(navigator.language || "en").toLowerCase().startsWith("zh")
|
const locale = String(navigator.language || "en").toLowerCase().startsWith("zh")
|
||||||
|
|||||||
@@ -55,10 +55,7 @@ export async function POST(req: NextRequest) {
|
|||||||
...(body ?? {}),
|
...(body ?? {}),
|
||||||
payload: {
|
payload: {
|
||||||
...payload,
|
...payload,
|
||||||
cf_country:
|
cf_country: req.headers.get("cf-ipcountry") || "",
|
||||||
req.headers.get("cf-ipcountry") ||
|
|
||||||
req.headers.get("x-vercel-ip-country") ||
|
|
||||||
"",
|
|
||||||
user_agent: req.headers.get("user-agent") || "",
|
user_agent: req.headers.get("user-agent") || "",
|
||||||
referer_header: req.headers.get("referer") || "",
|
referer_header: req.headers.get("referer") || "",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -165,10 +165,7 @@ async function trackAuthDiagnosticEvent(
|
|||||||
response_mode: responseMode,
|
response_mode: responseMode,
|
||||||
user_id: normalizedUserId || undefined,
|
user_id: normalizedUserId || undefined,
|
||||||
email_domain: email?.includes("@") ? email.split("@").pop() : undefined,
|
email_domain: email?.includes("@") ? email.split("@").pop() : undefined,
|
||||||
cf_country:
|
cf_country: req.headers.get("cf-ipcountry") || "",
|
||||||
req.headers.get("cf-ipcountry") ||
|
|
||||||
req.headers.get("x-vercel-ip-country") ||
|
|
||||||
"",
|
|
||||||
user_agent: req.headers.get("user-agent") || "",
|
user_agent: req.headers.get("user-agent") || "",
|
||||||
referer_header: req.headers.get("referer") || "",
|
referer_header: req.headers.get("referer") || "",
|
||||||
captured_at: new Date().toISOString(),
|
captured_at: new Date().toISOString(),
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ async function findSupabaseUserIdByEmail(email: string) {
|
|||||||
.replace(/\/$/, "");
|
.replace(/\/$/, "");
|
||||||
const serviceRoleKey = String(process.env.SUPABASE_SERVICE_ROLE_KEY || "").trim();
|
const serviceRoleKey = String(process.env.SUPABASE_SERVICE_ROLE_KEY || "").trim();
|
||||||
if (!supabaseUrl || !serviceRoleKey) {
|
if (!supabaseUrl || !serviceRoleKey) {
|
||||||
throw new Error("Supabase service role is not configured on Vercel");
|
throw new Error("Supabase service role is not configured on the server");
|
||||||
}
|
}
|
||||||
const profileRes = await fetch(
|
const profileRes = await fetch(
|
||||||
`${supabaseUrl}/rest/v1/profiles?select=id&email=eq.${encodeURIComponent(email)}&limit=1`,
|
`${supabaseUrl}/rest/v1/profiles?select=id&email=eq.${encodeURIComponent(email)}&limit=1`,
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ self.addEventListener("fetch", (event) => {
|
|||||||
|
|
||||||
// Do not cache-first Next.js build chunks. A user can keep an old app shell
|
// Do not cache-first Next.js build chunks. A user can keep an old app shell
|
||||||
// open across deployments; if checkout UI is loaded later, stale/missing
|
// open across deployments; if checkout UI is loaded later, stale/missing
|
||||||
// chunks surface as a generic page fault. Let the browser/Vercel handle
|
// chunks surface as a generic page fault. Let the browser handle immutable
|
||||||
// immutable _next/static asset caching instead.
|
// _next/static asset caching instead.
|
||||||
if (CACHEABLE_PUBLIC_ASSETS.includes(url.pathname)) {
|
if (CACHEABLE_PUBLIC_ASSETS.includes(url.pathname)) {
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
caches.open(CACHE_NAME).then((cache) =>
|
caches.open(CACHE_NAME).then((cache) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user