0e4c2ebef4
Squash-merge the cloudflare-migration branch (7 commits by Kai ki) into staging with conflict resolution, feature integration, and bug fixes. Engine: - Paradigm D: single-stream Writer replacing dual-phase Plan/Beats - Delete Architect agent; story bible generated via Writer <plan> tag - Modular prompt architecture (segments/registry/builder) - StreamRouter for tagged stream splitting (<plan>/<story>/<choices>) Infrastructure: - Cloudflare Workers deployment (wrangler.jsonc, OpenNext adapter) - D1 database schema + Drizzle ORM (scaffolded, not yet active) - R2 storage helpers (scaffolded, not yet active) - Story persistence API routes + client-side persistence BYOK (Bring Your Own Key): - /api/llm/user-proxy with SSRF-protected LLM proxy (+ requireUser auth) - CORS-aware fetch in ai-client: auto-detect CORS failure, fallback to server proxy transparently via OpenAI SDK custom fetch - BYO config support added to classify-freeform and vision routes - SettingsModal CORS privacy notice (keys never logged/stored) SSE streaming: - engineClient.ts: fetchSSE helper for progressive scene events - startSession/requestScene accept optional emit callback - Fix SSE error event field name (error → message) in scene/start routes i18n integration: - Wire buildLanguageDirective into paradigm D's prompt builder - Update corsNotice i18n keys (zh-CN/en/ja) with CORS proxy privacy text - Preserve Session.language + LanguageSwitcher from i18n commit Co-authored-by: Kai ki <155355644+zbf1009@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
49 lines
1.5 KiB
TypeScript
49 lines
1.5 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { getDb } from "@/lib/db/client";
|
|
import { FeaturedRepository } from "@/lib/db/repositories/featuredRepo";
|
|
|
|
export const runtime = "nodejs";
|
|
|
|
/**
|
|
* GET /api/stories/featured?gender=male
|
|
*
|
|
* List active featured stories for homepage display.
|
|
* Fallback: D1 query fails → return empty array (homepage shows no cards, gracefully degrades).
|
|
*
|
|
* Query Params:
|
|
* gender: "male" | "female" (required)
|
|
*
|
|
* Response: { stories: FeaturedStory[] }
|
|
* Errors: 400 (invalid gender), 500 (should not reach user - caught and degraded)
|
|
*/
|
|
export async function GET(req: Request) {
|
|
const { searchParams } = new URL(req.url);
|
|
const genderParam = searchParams.get("gender");
|
|
|
|
// Validate gender
|
|
if (!genderParam || !["male", "female"].includes(genderParam)) {
|
|
return NextResponse.json(
|
|
{ error: "gender query parameter must be 'male' or 'female'" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
const gender = genderParam as "male" | "female";
|
|
|
|
try {
|
|
const db = getDb();
|
|
const repo = new FeaturedRepository(db);
|
|
|
|
const stories = await repo.listByGender(gender);
|
|
|
|
return NextResponse.json({ stories });
|
|
} catch (err) {
|
|
// D1 unavailable or query failed - degrade to empty array
|
|
// (homepage will show no cards but remain functional)
|
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
console.error("[stories/featured] D1 query failed, returning empty array:", message);
|
|
|
|
return NextResponse.json({ stories: [] });
|
|
}
|
|
}
|