feat: add consultation and product domain registries

This commit is contained in:
Jesse_Chen
2026-08-15 06:04:23 +08:00
parent 5012ff7212
commit 6d192ad175
46 changed files with 1404 additions and 292 deletions
+2 -1
View File
@@ -8,6 +8,7 @@ import {
runConsultationWorkflow,
} from "@/mastra";
import { blocksPromptExtraction } from "@/lib/consult-safety";
import { consultationDomainSchema } from "@/lib/consultation-domain-registry";
import { parseAgentReply } from "@/lib/agent-reply";
import {
consultationEntrypointSchema,
@@ -70,7 +71,7 @@ const generalChatRequestSchema = z.object({
...chatRequestMetadataSchema.shape,
consultationMode: z.literal("general_no_birth_time"),
question: z.string().trim().min(1).max(500),
theme: z.enum(["career", "marriage", "wealth", "timing", "general"]),
theme: consultationDomainSchema,
entrypoint: z.undefined().optional(),
}).strict();
@@ -8,6 +8,7 @@ import { RECTIFICATION_SKILL_NAME, RECTIFICATION_SKILL_VERSION } from "@/lib/rec
import { blocksPromptExtraction } from "@/lib/consult-safety";
import { authorizeUsage, completeUsage, releaseUsage } from "@/lib/consultation-billing";
import { loadRuntimeFeatureFlags } from "@/lib/feature-flags";
import { isProductEnabled } from "@/lib/product-access";
import { resolveSessionLanguageModel } from "@/lib/model-catalog";
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
import { createServerSupabaseClient } from "@/lib/supabase/server";
@@ -81,6 +82,13 @@ export async function POST(request: Request) {
return NextResponse.json({ error: "请先登录" }, { status: 401 });
}
if (!await isProductEnabled("rectification")) {
return NextResponse.json(
{ error: "生时校正服务暂未开放", code: "rectification_product_disabled" },
{ status: 503 },
);
}
const parsed = agentRequestSchema.safeParse(await request.json().catch(() => null));
if (!parsed.success) {
return NextResponse.json(
@@ -1,6 +1,7 @@
import { NextResponse } from "next/server";
import { z } from "zod";
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
import { isProductEnabled } from "@/lib/product-access";
import { createServerSupabaseClient } from "@/lib/supabase/server";
import { RectificationToolServiceError } from "@/lib/rectification-agentic/v9/tool-service";
@@ -63,6 +64,13 @@ export async function POST(request: Request, context: RouteContext) {
return NextResponse.json({ error: "请先登录" }, { status: 401 });
}
if (!await isProductEnabled("rectification")) {
return NextResponse.json(
{ error: "生时校正服务暂未开放", code: "rectification_product_disabled" },
{ status: 503 },
);
}
const { caseId } = await context.params;
if (!z.string().uuid().safeParse(caseId).success) {
return NextResponse.json({ error: "请求内容不正确", code: "invalid_case_id" }, { status: 400 });
@@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
import { isProductEnabled } from "@/lib/product-access";
import { createServerSupabaseClient } from "@/lib/supabase/server";
import {
RectificationCaseServiceError,
@@ -47,6 +48,13 @@ export async function POST(request: Request) {
return NextResponse.json({ error: "请先登录" }, { status: 401 });
}
if (!await isProductEnabled("rectification")) {
return NextResponse.json(
{ error: "生时校正服务暂未开放", code: "rectification_product_disabled" },
{ status: 503 },
);
}
const parsed = parseOpenRectificationCaseRequest(
await request.json().catch(() => null),
);
+5 -1
View File
@@ -6,6 +6,7 @@ import {
resolveSkillSnapshot,
} from "@/lib/personal-report-generation";
import { REPORT_STABLE_CODES } from "@/lib/personal-report-codes";
import { isProductEnabled } from "@/lib/product-access";
import {
isPersonalReportFeatureEnabled,
readPersonalReportDailyLimit,
@@ -107,6 +108,9 @@ export async function POST(request: Request) {
const supabase = await createServerSupabaseClient();
const { data: { user }, error: authError } = await supabase.auth.getUser();
const userId = authError || !user ? null : user.id;
const reportProductEnabled = userId
? await isProductEnabled("report_center")
: false;
// Admin client (service_role / self-hosted admin DB): generation writes
// and counting. The authenticated client is forbidden by migration grants
@@ -157,7 +161,7 @@ export async function POST(request: Request) {
if (error) throw error;
return Boolean(data);
},
featureEnabled: isPersonalReportFeatureEnabled(process.env),
featureEnabled: reportProductEnabled && isPersonalReportFeatureEnabled(process.env),
dailyLimit: readPersonalReportDailyLimit(process.env),
counts: {
countGenerating: async () => {
+17 -4
View File
@@ -1,4 +1,5 @@
import { NextResponse } from "next/server";
import { isProductEnabled } from "@/lib/product-access";
import { isSupabaseConfigurationError } from "@/lib/supabase/config";
import { createServerSupabaseClient } from "@/lib/supabase/server";
@@ -17,6 +18,12 @@ export async function GET() {
const supabase = await createServerSupabaseClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) return NextResponse.json({ error: "请先登录" }, { status: 401 });
if (!await isProductEnabled("compatibility")) {
return NextResponse.json(
{ error: "合盘服务暂未开放", code: "compatibility_product_disabled" },
{ status: 503 },
);
}
const { data, error } = await supabase
.from("synastry_reports")
@@ -37,15 +44,21 @@ export async function GET() {
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 });
if (!await isProductEnabled("compatibility")) {
return NextResponse.json(
{ error: "合盘服务暂未开放", code: "compatibility_product_disabled" },
{ status: 503 },
);
}
const body = await request.json().catch(() => null) as SynastryReportPayload | null;
if (!body?.report || typeof body.report !== "object" || Array.isArray(body.report)) {
return NextResponse.json({ error: "合盘报告格式不正确" }, { status: 400 });
}
const supabase = await createServerSupabaseClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) return NextResponse.json({ error: "请先登录" }, { status: 401 });
const partnerName = body.partnerName?.trim() || "对方";
const record = {
...(body.id ? { id: body.id } : {}),
+14
View File
@@ -1,4 +1,6 @@
import { NextResponse } from "next/server";
import { isProductEnabled } from "@/lib/product-access";
import { createServerSupabaseClient } from "@/lib/supabase/server";
import {
synastryBirthPayload,
type GlobalBirthProfile as Profile,
@@ -116,6 +118,18 @@ async function postPython(path: string, body: unknown) {
export async function POST(request: Request) {
try {
const supabase = await createServerSupabaseClient();
const { data: { user }, error: authError } = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json({ error: "请先登录" }, { status: 401 });
}
if (!await isProductEnabled("compatibility")) {
return NextResponse.json(
{ error: "合盘服务暂未开放", code: "compatibility_product_disabled" },
{ status: 503 },
);
}
const body = await request.json().catch(() => null) as { selfProfile?: Profile; partnerProfile?: Profile; relationshipType?: RelationshipType } | null;
if (!body?.selfProfile || !body.partnerProfile) {
return NextResponse.json({ error: "请提供双方星盘资料" }, { status: 400 });