Phase 6C featured carousel + Phase 2 event tag chips
FeaturedCarouselServer (RSC) pulls 5 most recent featured=true rows from
bb.ai_rewritten_articles UNION bb.wp_imported_posts, renders into the
client FeaturedCarousel component: 4s auto-advance (pause on hover),
chevron + dot nav + NN/NN counter, [ID xxxx] / [CATEGORY] badges,
serif headline, lede, optional neural-summary box with corner brackets +
confidence + entity chips, byline with initials avatar. Replaces the
FeaturedBento on the homepage.
Seeded bb.wp_imported_posts.featured = TRUE on 5 recent posts so the
carousel renders out-of-the-box (Amagi, Riedel, LiveU BroadcastAsia,
Cobalt Best-of-Show, Leader MPTS).
Phase 2 UI:
EventTagPicker (client) — chip multi-select reading bb.events,
writing to bb.article_events via
/api/admin/review-queue/[id]/events.
article_table=ai_rewritten for now;
future submission-form integration will
cover other tables.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
63
src/app/api/admin/review-queue/[id]/events/route.ts
Normal file
63
src/app/api/admin/review-queue/[id]/events/route.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
import { createAdminClient } from "@/lib/supabase/admin";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
async function requireAdmin() {
|
||||
const sb = await createClient();
|
||||
const { data: { user } } = await sb.auth.getUser();
|
||||
if (!user) return { ok: false as const, status: 401 };
|
||||
const { data: profile } = await sb
|
||||
.from("user_profiles")
|
||||
.select("role, is_active")
|
||||
.eq("id", user.id)
|
||||
.maybeSingle();
|
||||
if (!profile?.is_active) return { ok: false as const, status: 403 };
|
||||
if (!["administrator", "admin", "editor"].includes((profile as any).role)) {
|
||||
return { ok: false as const, status: 403 };
|
||||
}
|
||||
return { ok: true as const };
|
||||
}
|
||||
|
||||
export async function GET(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const guard = await requireAdmin();
|
||||
if (!guard.ok) return NextResponse.json({ error: "Unauthorized" }, { status: guard.status });
|
||||
const { id } = await params;
|
||||
const admin = createAdminClient();
|
||||
const [{ data: events }, { data: tagged }] = await Promise.all([
|
||||
admin.from("events").select("id, slug, name, start_date, end_date, status").order("start_date"),
|
||||
admin.from("article_events").select("event_id").eq("article_id", id).eq("article_table", "ai_rewritten"),
|
||||
]);
|
||||
const taggedIds = new Set((tagged || []).map((r: any) => r.event_id));
|
||||
return NextResponse.json({
|
||||
events: (events || []) as any[],
|
||||
tagged_event_ids: [...taggedIds],
|
||||
});
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const guard = await requireAdmin();
|
||||
if (!guard.ok) return NextResponse.json({ error: "Unauthorized" }, { status: guard.status });
|
||||
const { id } = await params;
|
||||
const { event_id, action } = (await req.json()) as { event_id?: string; action?: string };
|
||||
if (!event_id || !["add", "remove"].includes(action || "")) {
|
||||
return NextResponse.json({ error: "event_id + action(add|remove) required" }, { status: 400 });
|
||||
}
|
||||
const admin = createAdminClient();
|
||||
if (action === "add") {
|
||||
const { error } = await admin
|
||||
.from("article_events")
|
||||
.upsert({ article_id: id, article_table: "ai_rewritten", event_id }, { onConflict: "article_id,article_table,event_id" });
|
||||
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
} else {
|
||||
const { error } = await admin
|
||||
.from("article_events")
|
||||
.delete()
|
||||
.eq("article_id", id)
|
||||
.eq("article_table", "ai_rewritten")
|
||||
.eq("event_id", event_id);
|
||||
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
Reference in New Issue
Block a user