fix(api): bind daily and synastry charts to stored profiles
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled

Stop accepting client-supplied birth data on those paths, and cap session writes plus location lookups so a logged-in caller cannot farm compute.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-19 19:39:01 +08:00
co-authored by Cursor
parent 40a595045e
commit c38f11dbd3
16 changed files with 548 additions and 39 deletions
@@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import { birthLocationSearchQuerySchema } from "@/lib/location-contract";
import { consumeUserRequestRateLimit } from "@/lib/request-rate-limit";
import { searchGlobalBirthLocations } from "@/lib/geoapify-location-service";
import { createServerSupabaseClient } from "@/lib/supabase/server";
@@ -10,6 +11,15 @@ export async function GET(request: Request) {
const { data: { user }, error: authError } = await supabase.auth.getUser();
if (authError || !user) return NextResponse.json({ error: "请先登录" }, { status: 401 });
const limited = consumeUserRequestRateLimit("locationSearch", user.id);
if (!limited.ok) {
return NextResponse.json({
error: "请求过于频繁",
code: "rate_limited",
retryAfterSeconds: limited.retryAfterSeconds,
}, { status: 429, headers: { "Retry-After": String(limited.retryAfterSeconds) } });
}
const url = new URL(request.url);
const parsed = birthLocationSearchQuerySchema.safeParse(Object.fromEntries(url.searchParams));
if (!parsed.success) return NextResponse.json({
@@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import { resolveBirthLocationTimezone } from "@/lib/birth-location-timezone-service";
import { consumeUserRequestRateLimit } from "@/lib/request-rate-limit";
import { birthLocationTimezoneQuerySchema } from "@/lib/location-contract";
import { createServerSupabaseClient } from "@/lib/supabase/server";
@@ -10,6 +11,15 @@ export async function POST(request: Request) {
const { data: { user }, error: authError } = await supabase.auth.getUser();
if (authError || !user) return NextResponse.json({ error: "请先登录" }, { status: 401 });
const limited = consumeUserRequestRateLimit("locationTimezone", user.id);
if (!limited.ok) {
return NextResponse.json({
error: "请求过于频繁",
code: "rate_limited",
retryAfterSeconds: limited.retryAfterSeconds,
}, { status: 429, headers: { "Retry-After": String(limited.retryAfterSeconds) } });
}
let body: unknown;
try {
body = await request.json();