Persist synastry report history in Supabase

This commit is contained in:
732642856
2026-07-17 19:29:35 +08:00
parent aeb6852aa7
commit efaa8fa9b6
4 changed files with 217 additions and 3 deletions
@@ -0,0 +1,72 @@
import { NextResponse } from "next/server";
import { isSupabaseConfigurationError } from "@/lib/supabase/config";
import { createServerSupabaseClient } from "@/lib/supabase/server";
type SynastryReportPayload = {
id?: string;
partnerName?: string;
report?: unknown;
};
function errorMessage(error: unknown, fallback: string) {
return error instanceof Error ? error.message : fallback;
}
export async function GET() {
try {
const supabase = await createServerSupabaseClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) return NextResponse.json({ error: "请先登录" }, { status: 401 });
const { data, error } = await supabase
.from("synastry_reports")
.select("id, partner_name, report, created_at")
.eq("user_id", user.id)
.order("created_at", { ascending: false })
.limit(10);
if (error) throw error;
return NextResponse.json({ reports: data ?? [] });
} catch (error) {
if (isSupabaseConfigurationError(error)) {
return NextResponse.json({ error: "Supabase 尚未配置", code: "SUPABASE_NOT_CONFIGURED" }, { status: 503 });
}
return NextResponse.json({ error: errorMessage(error, "合盘历史暂时不可用") }, { status: 500 });
}
}
export async function POST(request: Request) {
try {
const body = await request.json().catch(() => null) as SynastryReportPayload | null;
if (!body?.report || typeof body.report !== "object" || Array.isArray(body.report)) {
return NextResponse.json({ error: "合盘报告格式不正确" }, { status: 400 });
}
const supabase = await createServerSupabaseClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) return NextResponse.json({ error: "请先登录" }, { status: 401 });
const partnerName = body.partnerName?.trim() || "对方";
const record = {
...(body.id ? { id: body.id } : {}),
user_id: user.id,
partner_name: partnerName,
report: body.report,
created_at: new Date().toISOString(),
};
const { data, error } = await supabase
.from("synastry_reports")
.insert(record)
.select("id, partner_name, report, created_at")
.single();
if (error) throw error;
return NextResponse.json({ report: data });
} catch (error) {
if (isSupabaseConfigurationError(error)) {
return NextResponse.json({ error: "Supabase 尚未配置", code: "SUPABASE_NOT_CONFIGURED" }, { status: 503 });
}
return NextResponse.json({ error: errorMessage(error, "合盘历史保存失败") }, { status: 500 });
}
}
+61 -3
View File
@@ -58,6 +58,12 @@ type SynastryReportCard = {
nextEvidence?: string[];
createdAt: number;
};
type SynastryReportApiRecord = {
id: string;
partner_name?: string;
report?: SynastryReportCard;
created_at?: string;
};
type ChatSession = { id: string; title: string; theme: Theme; modelId: string; messages: Message[]; updatedAt: number };
type RequestError = { sessionId: string; message: string };
type StreamingReply = { sessionId: string; text: string };
@@ -229,6 +235,38 @@ function readSynastryHistory(accountId: string): SynastryReportCard[] {
}
}
function writeSynastryHistory(accountId: string, history: SynastryReportCard[]) {
localStorage.setItem(synastryHistoryStorageKey(accountId), JSON.stringify(history.slice(0, 10)));
}
function normalizeSynastryReportApiRecord(record: SynastryReportApiRecord): SynastryReportCard | null {
if (!record.report || typeof record.report !== "object") return null;
return {
...record.report,
id: record.id,
partnerName: record.partner_name || record.report.partnerName || "对方",
createdAt: Date.parse(record.created_at || "") || record.report.createdAt || timestamp(),
};
}
async function fetchCloudSynastryHistory() {
const response = await fetch("/api/synastry-reports", { cache: "no-store" });
if (!response.ok) throw new Error("cloud_synastry_history_unavailable");
const payload = await response.json().catch(() => null) as { reports?: SynastryReportApiRecord[] } | null;
return (payload?.reports || []).map(normalizeSynastryReportApiRecord).filter(Boolean) as SynastryReportCard[];
}
async function saveCloudSynastryReport(report: SynastryReportCard) {
const response = await fetch("/api/synastry-reports", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ partnerName: report.partnerName, report }),
});
if (!response.ok) throw new Error("cloud_synastry_report_save_failed");
const payload = await response.json().catch(() => null) as { report?: SynastryReportApiRecord } | null;
return payload?.report ? normalizeSynastryReportApiRecord(payload.report) || report : report;
}
function normalizeChartLibraryApiRecord(record: ChartLibraryApiRecord): ChartLibraryRecord {
return {
id: record.role === "self" ? "self" : record.id,
@@ -636,6 +674,18 @@ export default function Home() {
.catch(() => {
// Cloud chart library is best-effort; local library remains usable.
});
void fetchCloudSynastryHistory()
.then((cloudHistory) => {
setSynastryHistory((current) => {
const byId = new Map([...current, ...cloudHistory].map((record) => [record.id, record] as const));
const next = [...byId.values()].sort((a, b) => b.createdAt - a.createdAt).slice(0, 10);
writeSynastryHistory(accountId, next);
return next;
});
})
.catch(() => {
// Cloud synastry history is best-effort; local history remains usable.
});
}, [accountId, profile]);
useEffect(() => {
@@ -1303,11 +1353,19 @@ export default function Home() {
nextEvidence: payload.relationshipReport?.nextEvidence,
createdAt: Date.now(),
};
setSynastryReportCard(reportCard);
let savedReportCard = reportCard;
if (accountId) {
try {
savedReportCard = await saveCloudSynastryReport(reportCard);
} catch {
// Local history remains the fallback when cloud persistence is unavailable.
}
}
setSynastryReportCard(savedReportCard);
if (accountId) {
setSynastryHistory((current) => {
const next = [reportCard, ...current].slice(0, 10);
localStorage.setItem(synastryHistoryStorageKey(accountId), JSON.stringify(next));
const next = [savedReportCard, ...current.filter((item) => item.id !== savedReportCard.id)].slice(0, 10);
writeSynastryHistory(accountId, next);
return next;
});
}