Persist synastry report history in Supabase
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user