fix: bind rectification state to current declaration
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import {
|
||||
parseRectificationPriceCredits,
|
||||
type AccountRectificationCaseState,
|
||||
} from "@/lib/birth-time-consultation-consent";
|
||||
import { resolveAccountRectificationCase } from "@/lib/account-rectification-case";
|
||||
import {
|
||||
accountProfilePatchSchema,
|
||||
applyAccountProfileConcurrencyGuards,
|
||||
@@ -16,7 +16,7 @@ import { createServerSupabaseClient } from "@/lib/supabase/server";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const rectificationStatuses = ["starting", "active", "paused", "confirming", "completed", "abandoned"] as const;
|
||||
const unfinishedRectificationStatuses = ["starting", "active", "paused", "confirming"] as const;
|
||||
|
||||
function isMissingProfileColumn(error: { code?: string; message?: string } | null) {
|
||||
const message = error?.message?.toLowerCase() ?? "";
|
||||
@@ -26,28 +26,6 @@ function isMissingProfileColumn(error: { code?: string; message?: string } | nul
|
||||
|| message.includes("column");
|
||||
}
|
||||
|
||||
function projectRectificationCase(value: unknown): AccountRectificationCaseState | null {
|
||||
if (value === null || typeof value !== "object") return null;
|
||||
const row = value as Record<string, unknown>;
|
||||
const status = rectificationStatuses.find((candidate) => candidate === row.status);
|
||||
if (typeof row.id !== "string"
|
||||
|| row.journey_protocol !== "conversational-evidence-v3"
|
||||
|| !status
|
||||
|| typeof row.turn_version !== "number"
|
||||
|| !Number.isSafeInteger(row.turn_version)
|
||||
|| row.turn_version < 0) {
|
||||
throw new Error("invalid rectification case projection");
|
||||
}
|
||||
return Object.freeze({
|
||||
caseId: row.id,
|
||||
journeyProtocol: "conversational-evidence-v3",
|
||||
status,
|
||||
turnVersion: row.turn_version,
|
||||
isRevision: typeof row.revision_of_case_id === "string",
|
||||
preservesActiveTime: typeof row.baseline_active_time === "string",
|
||||
});
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const supabase = await createServerSupabaseClient();
|
||||
@@ -61,29 +39,35 @@ export async function GET() {
|
||||
const rectificationPriceCredits = parseRectificationPriceCredits(
|
||||
process.env.RECTIFICATION_PRICE_CREDITS,
|
||||
);
|
||||
const admin = createAdminSupabaseClient();
|
||||
const { data: rectificationCaseRows, error: rectificationCaseError } = await admin
|
||||
.from("birth_time_rectification_cases")
|
||||
.select("id,journey_protocol,status,turn_version,revision_of_case_id,baseline_active_time,declared_birth_input,updated_at")
|
||||
.eq("user_id", user.id)
|
||||
.eq("journey_protocol", "conversational-evidence-v3")
|
||||
.in("status", [...unfinishedRectificationStatuses])
|
||||
.order("updated_at", { ascending: false })
|
||||
.limit(50);
|
||||
if (rectificationCaseError) {
|
||||
return NextResponse.json({ error: "暂时无法读取生时校正状态" }, { status: 500 });
|
||||
}
|
||||
|
||||
// Read the profile after the case snapshot. If a declaration edit races
|
||||
// with this request, matching uses the later profile and cannot resurrect
|
||||
// an older case. A concurrently created case simply appears on refresh.
|
||||
const { data: profile, error } = await supabase
|
||||
.from("profiles")
|
||||
.select("credits,active_birth_time,birth_time_status")
|
||||
.select("credits,active_birth_time,birth_time_status,birth_date,reported_birth_time,birth_time_source,birth_time_period,birth_time_clue,uncertainty_before_minutes,uncertainty_after_minutes,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset")
|
||||
.eq("id", userId)
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
return NextResponse.json({ error: "暂时无法读取账户余额" }, { status: 500 });
|
||||
}
|
||||
|
||||
const admin = createAdminSupabaseClient();
|
||||
const { data: rectificationCaseRow, error: rectificationCaseError } = await admin
|
||||
.from("birth_time_rectification_cases")
|
||||
.select("id,journey_protocol,status,turn_version,revision_of_case_id,baseline_active_time,updated_at")
|
||||
.eq("user_id", user.id)
|
||||
.eq("journey_protocol", "conversational-evidence-v3")
|
||||
.order("updated_at", { ascending: false })
|
||||
.limit(1)
|
||||
.maybeSingle();
|
||||
if (rectificationCaseError) {
|
||||
return NextResponse.json({ error: "暂时无法读取生时校正状态" }, { status: 500 });
|
||||
}
|
||||
const rectificationCase = projectRectificationCase(rectificationCaseRow);
|
||||
const rectificationCase = resolveAccountRectificationCase(
|
||||
profile,
|
||||
Array.isArray(rectificationCaseRows) ? rectificationCaseRows : [],
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
user: { id: user.id, email: user.email ?? null },
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
type BirthTimeSource,
|
||||
} from "@/lib/birth-time-intake-model";
|
||||
import {
|
||||
birthTimeConsultationOptionsCopy,
|
||||
canUseUnverifiedBirthTime,
|
||||
clearBirthTimeConsultationConsent,
|
||||
createLatestAccountRequestGuard,
|
||||
@@ -1584,10 +1585,12 @@ export default function Home() {
|
||||
setProfileDraft(profileDraft);
|
||||
if (declarationChanged) {
|
||||
setBirthTimeConsultationConsent(createBirthTimeConsultationConsentState());
|
||||
setAccount((current) => current ? { ...current, rectificationCase: null } : current);
|
||||
void refreshAccount();
|
||||
}
|
||||
setProfileNotice(profileDraft.birthTimeStatus === "confirmed"
|
||||
? "出生资料已保存到云端,可在同一账号的其他设备使用。"
|
||||
: "出生资料已保存。你可以先使用填报时间询问,也可以从首页卡片开始校正。");
|
||||
: `出生资料已保存。${birthTimeConsultationOptionsCopy(profileDraft)}`);
|
||||
} catch (caught) {
|
||||
setAccountError(friendlyError(caught instanceof Error ? caught.message : "出生资料保存失败"));
|
||||
} finally {
|
||||
@@ -2638,7 +2641,7 @@ export default function Home() {
|
||||
{birthTimeDisplay ? (
|
||||
<>
|
||||
<div><dt>{birthTimeDisplay.kind === "candidate" ? "待验证候选时间" : "当前排盘时间"}</dt><dd>{birthTimeDisplay.activeTime}</dd></div>
|
||||
<div><dt>结果状态</dt><dd>{birthTimeDisplay.kind === "candidate" ? "未确认;咨询时仅可临时使用原始填报时间" : "已确认"}</dd></div>
|
||||
<div><dt>结果状态</dt><dd>{birthTimeDisplay.kind === "candidate" ? `未确认;${birthTimeConsultationOptionsCopy(profile)}` : "已确认"}</dd></div>
|
||||
<div><dt>原始填报</dt><dd>{birthTimeDisplay.reportedLabel}</dd></div>
|
||||
</>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user