Files
Jyotisha/frontend/src/app/api/chart-profiles/route.ts
T
jesse-uxandGrok (agent loop) b184448871 fix(db): check callers against the adapter that actually runs
The self-hosted client was typed as a full Supabase client, so an old scoring poll called .single() on a plain promise and failed. The new DataClient only lists methods the adapter implements.

Co-Authored-By: Grok (agent loop)
2026-10-15 09:21:14 +08:00

124 lines
4.9 KiB
TypeScript

import { NextResponse } from "next/server";
import {
libraryProfileFromSubjectRow,
parseChartSubjectWrite,
subjectLimitReached,
} from "@/lib/chart-subject-write";
import { CHART_SUBJECT_API_SELECT, MAX_SAVED_SUBJECTS } from "@/lib/subject-birth";
import { scheduleConsultPacketWarm } from "@/lib/consult-packet-warm";
import { createServerSupabaseClient } from "@/lib/supabase/server";
import { isSupabaseConfigurationError } from "@/lib/supabase/config";
const SUBJECT_LIMIT_MESSAGE = "最多保存 5 个人的星盘";
function errorMessage(error: unknown, fallback: string) {
return error && typeof error === "object" && "message" in error && typeof error.message === "string"
? error.message : fallback;
}
function databaseFailure(error: unknown): NextResponse | null {
const message = errorMessage(error, "");
if (message.includes("subject_limit_reached")) {
return NextResponse.json({ error: SUBJECT_LIMIT_MESSAGE, code: "subject_limit_reached" }, { status: 409 });
}
if (message.includes("reported_birth_time_is_immutable")) {
return NextResponse.json({ error: "填报的出生时间不能再改", code: "reported_birth_time_is_immutable" }, { status: 409 });
}
if (message.includes("chart_subject_active_birth_is_locked") || message.includes("chart_subject_birth_status_locked")) {
return NextResponse.json({ error: "不能直接写入排盘采用时间", code: "active_birth_locked" }, { status: 403 });
}
if (message.includes("chart_subject_self_mirror_retired")) {
return NextResponse.json({ error: "本人资料保存在账户里", code: "self_mirror_retired" }, { status: 409 });
}
return null;
}
function toClientProfile(row: Record<string, unknown>) {
return {
id: row.id,
role: "other",
profile: libraryProfileFromSubjectRow(row),
updated_at: row.updated_at,
};
}
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("chart_profiles")
.select(CHART_SUBJECT_API_SELECT)
.eq("user_id", user.id)
.eq("role", "other")
.order("updated_at", { ascending: false });
if (error) throw error;
const profiles = (data ?? []).map((row: unknown) => toClientProfile(row as Record<string, unknown>));
return NextResponse.json({
profiles,
limit: MAX_SAVED_SUBJECTS,
atLimit: subjectLimitReached(profiles.length),
});
} catch (error) {
if (isSupabaseConfigurationError(error)) {
return NextResponse.json({ error: "数据库尚未配置", code: "DATABASE_NOT_CONFIGURED" }, { status: 503 });
}
return NextResponse.json({ error: errorMessage(error, "星盘库暂时不可用") }, { status: 500 });
}
}
export async function POST(request: Request) {
try {
const supabase = await createServerSupabaseClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) return NextResponse.json({ error: "请先登录" }, { status: 401 });
const body = await request.json().catch(() => null) as { role?: unknown; profile?: unknown } | null;
if (body?.role === "self") {
return NextResponse.json({ error: "本人资料保存在账户里", code: "self_mirror_retired" }, { status: 409 });
}
const parsed = parseChartSubjectWrite(body?.profile);
if (!parsed.ok) return NextResponse.json({ error: parsed.error }, { status: 400 });
const existing = await supabase
.from("chart_profiles")
.select("id")
.eq("user_id", user.id)
.eq("role", "other")
.limit(MAX_SAVED_SUBJECTS - 1);
if (existing.error) throw existing.error;
if (subjectLimitReached(Array.isArray(existing.data) ? existing.data.length : 0)) {
return NextResponse.json({ error: SUBJECT_LIMIT_MESSAGE, code: "subject_limit_reached" }, { status: 409 });
}
const { data, error } = await supabase
.from("chart_profiles")
.insert({
user_id: user.id,
role: "other",
profile: parsed.write.profile,
updated_at: new Date().toISOString(),
...parsed.write.columns,
})
.select(CHART_SUBJECT_API_SELECT)
.single();
if (error) {
const mapped = databaseFailure(error);
if (mapped) return mapped;
throw error;
}
scheduleConsultPacketWarm(parsed.write.columns);
return NextResponse.json({ profile: toClientProfile(data as Record<string, unknown>) });
} catch (error) {
if (isSupabaseConfigurationError(error)) {
return NextResponse.json({ error: "数据库尚未配置", code: "DATABASE_NOT_CONFIGURED" }, { status: 503 });
}
const mapped = databaseFailure(error);
if (mapped) return mapped;
return NextResponse.json({ error: errorMessage(error, "星盘保存失败") }, { status: 500 });
}
}