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)
124 lines
4.9 KiB
TypeScript
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 });
|
|
}
|
|
}
|