fix(report): stop listing reports via PostgREST JSON paths (BUG-574)

Staging PostgREST rejects the executiveSummary JSON-path alias, so GET /api/reports 500s. Persist a plain card_summary column from the Markdown excerpt instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-07 11:08:00 +08:00
co-authored by Cursor
parent ad9283d1bd
commit b466a6fc8c
15 changed files with 517 additions and 10 deletions
+3 -2
View File
@@ -28,6 +28,7 @@ import { loadReportCandidateRange } from "@/lib/report-candidate-range";
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
import { authorizeUsage, completeUsage, releaseUsage } from "@/lib/consultation-billing";
import { FeaturePricingError, resolveFeaturePricing } from "@/lib/feature-pricing";
import { sanitizedErrorReason } from "@/lib/safe-error-reason";
import { isSupabaseConfigurationError } from "@/lib/supabase/config";
import { createServerSupabaseClient } from "@/lib/supabase/server";
@@ -45,7 +46,7 @@ const REPORT_LIST_COLUMNS = [
"failure_code",
"created_at",
"completed_at",
"card_summary:report_document->executiveSummary->>summary",
"card_summary",
].join(",");
function sanitizedErrorCode(error: unknown): string {
@@ -139,7 +140,7 @@ export async function GET() {
{ status: 503 },
);
}
console.error(`[reports] list failed reason=${sanitizedErrorCode(error)}`);
console.error(`[reports] list failed reason=${sanitizedErrorReason(error)}`);
return NextResponse.json(
{ error: "报告列表暂时无法读取", code: REPORT_STABLE_CODES.generationFailed },
{ status: 500 },
@@ -4,7 +4,10 @@ import {
REPORT_DOCUMENT_V2_SCHEMA_VERSION,
type ReportDocumentV2,
} from "./personal-report-contract.ts";
import { extractLongformSummary } from "./personal-report-longform-outline.ts";
import {
PERSONAL_REPORT_CARD_SUMMARY_MAX_CHARS,
extractLongformSummary,
} from "./personal-report-longform-outline.ts";
import type { PersonalReportRecord } from "./personal-report-service-core.ts";
type CoverSkillSnapshot = Readonly<{
@@ -52,7 +55,7 @@ function coverHouses(): ReportDocumentV2["charts"][number]["houses"] {
}
export function buildLongformCoverDocument(input: LongformCoverInput): ReportDocumentV2 {
const summary = extractLongformSummary(input.markdown);
const summary = extractLongformSummary(input.markdown, PERSONAL_REPORT_CARD_SUMMARY_MAX_CHARS);
const skillName = input.skillSnapshot?.name ?? input.report.skillName;
const skillVersion = input.skillSnapshot?.version ?? input.report.skillVersion;
const skillSnapshotSha256 = input.skillSnapshot?.sha256 ?? input.report.skillSnapshotSha256;
@@ -23,6 +23,9 @@ export type LongformOutline = Readonly<{
const EAGER_H2 = /成品阅读导航|质量验收矩阵/;
const SUMMARY_TITLE = /^(?:解读摘要|摘要)$/;
/** Dedicated `personal_reports.card_summary` column length. */
export const PERSONAL_REPORT_CARD_SUMMARY_MAX_CHARS = 500;
export function slugifyHeading(title: string, used: Map<string, number>): string {
const base = title
.trim()
+20
View File
@@ -0,0 +1,20 @@
/**
* Operational log reason for unknown catch values.
*
* PostgREST / supabase-js may throw a plain object with `code` and `hint`
* instead of an Error instance. Never include message, details, or row data.
*/
export function sanitizedErrorReason(error: unknown): string {
const record = error !== null && typeof error === "object"
? error as Record<string, unknown>
: null;
const name = error instanceof Error && error.name.trim()
? error.name.trim()
: typeof record?.name === "string" && record.name.trim()
? record.name.trim()
: "UnknownError";
const code = typeof record?.code === "string" ? record.code.trim() : "";
const hint = typeof record?.hint === "string" ? record.hint.trim() : "";
if (!code && !hint) return name;
return `${name} code=${code || "none"} hint=${hint || "none"}`;
}