Files
Jyotisha/frontend/src/app/api/chart-profiles/route.ts
T
jesse-ux 16f9f86843 fix(consult): date the full-data cache and slim the model card
The cache key now includes the request date, and files older than two days are removed. The model card keeps values only. Parashari aspects are copied from the engine. Saving a chart warms the packet without blocking the response.

Biography severe conflicts rose from 1 to 3, so this is not accepted for merge. Not pushed.
2026-10-09 10:00:45 +08:00

124 lines
4.8 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) => 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 });
}
}