diff --git a/src/app/api/forum/threads/[id]/route.ts b/src/app/api/forum/threads/[id]/route.ts
index 1b7a6d8..d4bc2d8 100644
--- a/src/app/api/forum/threads/[id]/route.ts
+++ b/src/app/api/forum/threads/[id]/route.ts
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
+import { attachAuthorUsernames, buildAuthorUsernameMap } from '@/lib/forum/authorUsernames';
export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
try {
@@ -30,7 +31,13 @@ export async function GET(req: NextRequest, { params }: { params: Promise<{ id:
if (repliesError) throw repliesError;
- return NextResponse.json({ thread, replies: replies || [] });
+ const usernameMap = await buildAuthorUsernameMap(supabase);
+ const [threadWithUsername] = attachAuthorUsernames([thread], usernameMap);
+
+ return NextResponse.json({
+ thread: threadWithUsername,
+ replies: attachAuthorUsernames(replies, usernameMap),
+ });
} catch (err: any) {
return NextResponse.json({ error: err.message }, { status: 500 });
}
diff --git a/src/app/api/forum/threads/route.ts b/src/app/api/forum/threads/route.ts
index d30d901..c54799c 100644
--- a/src/app/api/forum/threads/route.ts
+++ b/src/app/api/forum/threads/route.ts
@@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { ensureForumProfile } from '@/lib/forum/profile';
+import { attachAuthorUsernames, buildAuthorUsernameMap } from '@/lib/forum/authorUsernames';
export async function GET(req: NextRequest) {
try {
@@ -50,7 +51,13 @@ export async function GET(req: NextRequest) {
const { data, error, count } = await query;
if (error) throw error;
- return NextResponse.json({ threads: data, total: count, page, limit });
+ const usernameMap = await buildAuthorUsernameMap(supabase);
+ return NextResponse.json({
+ threads: attachAuthorUsernames(data, usernameMap),
+ total: count,
+ page,
+ limit,
+ });
} catch (err: any) {
return NextResponse.json({ error: err.message }, { status: 500 });
}
diff --git a/src/app/api/forum/user-by-username/[username]/route.ts b/src/app/api/forum/user-by-username/[username]/route.ts
index 61ac981..9b539ad 100644
--- a/src/app/api/forum/user-by-username/[username]/route.ts
+++ b/src/app/api/forum/user-by-username/[username]/route.ts
@@ -6,7 +6,7 @@ export const revalidate = 120;
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const SUPABASE_ANON = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
-const SCHEMA = process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "bb";
+const SCHEMA = process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "av";
function client() {
return createClient(SUPABASE_URL, SUPABASE_ANON, {
@@ -16,24 +16,19 @@ function client() {
}
export async function GET(
- req: Request,
+ _req: Request,
{ params }: { params: Promise<{ username: string }> },
) {
const { username } = await params;
const sb = client();
-
- // 1) Profile — accept EITHER username OR display_name (URL-encoded).
- // forum_threads/forum_replies store author_name = display_name,
- // so the link in the UI uses the display name; we resolve it here.
- // Multiple profiles can share a display_name (Michael Carter, Alex Rivera,
- // etc.) — pick the earliest-created profile deterministically rather than
- // erroring out with maybeSingle().
const decoded = decodeURIComponent(username);
+
let { data: profile } = await sb
.from("forum_user_profiles")
.select("*")
.eq("username", decoded)
.maybeSingle();
+
if (!profile) {
const r2 = await sb
.from("forum_user_profiles")
@@ -48,51 +43,79 @@ export async function GET(
return NextResponse.json({ error: "not found" }, { status: 404 });
}
- // 2) Their threads (use display_name match since virtual users live
- // outside the auth.users / bb.user_profiles graph and the
- // forum_threads.author_id is NULL for them).
- const displayName = (profile as any).display_name as string;
- const { data: threads } = await sb
- .from("forum_threads")
- .select("id,title,reply_count,view_count,vote_score,created_at,category_id,forum_categories(name,slug)")
- .eq("author_name", displayName)
- .order("created_at", { ascending: false })
- .limit(50);
+ const displayName = profile.display_name as string;
- // 3) Recent replies
- const { data: replies } = await sb
- .from("forum_replies")
- .select("id,thread_id,body,vote_score,created_at,forum_threads(title)")
- .eq("author_name", displayName)
- .order("created_at", { ascending: false })
- .limit(20);
+ const [
+ { count: threadCount },
+ { count: replyCount },
+ { data: threads },
+ { data: replies },
+ ] = await Promise.all([
+ sb
+ .from("forum_threads")
+ .select("*", { count: "exact", head: true })
+ .eq("author_name", displayName),
+ sb
+ .from("forum_replies")
+ .select("*", { count: "exact", head: true })
+ .eq("author_name", displayName),
+ sb
+ .from("forum_threads")
+ .select(
+ "id,title,reply_count,view_count,vote_score,created_at,category_id,forum_categories(name,slug)",
+ )
+ .eq("author_name", displayName)
+ .order("created_at", { ascending: false })
+ .limit(50),
+ sb
+ .from("forum_replies")
+ .select("id,thread_id,body,vote_score,created_at")
+ .eq("author_name", displayName)
+ .order("created_at", { ascending: false })
+ .limit(20),
+ ]);
- // 4) Computed counts
- const threadCount = (threads || []).length;
- const replyCount = (replies || []).length;
+ const threadIds = [...new Set((replies || []).map((r) => r.thread_id).filter(Boolean))];
+ let titleByThread: Record
- e.stopPropagation()}>{thread.author_name} + e.stopPropagation()}>{thread.author_name} {thread.forum_categories && ( <> · {thread.forum_categories.name}> )} @@ -407,7 +413,7 @@ export default function ForumIndexPage() {
by{' '} e.stopPropagation()} className="text-[#1D4ED8] hover:underline" > @@ -530,7 +536,7 @@ export default function ForumIndexPage() { {thread.title}
- by e.stopPropagation()}>{thread.author_name}
+ by e.stopPropagation()}>{thread.author_name}
{thread.forum_categories && (
<> · {thread.forum_categories.name}>
)}
diff --git a/src/app/forum/thread/[id]/page.tsx b/src/app/forum/thread/[id]/page.tsx
index 64df2cc..282aee7 100644
--- a/src/app/forum/thread/[id]/page.tsx
+++ b/src/app/forum/thread/[id]/page.tsx
@@ -26,11 +26,17 @@ function InThreadAd({ adIndex }: { adIndex: number }) {
);
}
+function profileHref(author: { author_username?: string | null; author_name: string }): string {
+ const slug = author.author_username || author.author_name;
+ return `/forum/user/${encodeURIComponent(slug)}`;
+}
+
interface ForumThread {
id: string;
title: string;
body: string;
author_name: string;
+ author_username?: string | null;
status: string;
reply_count: number;
view_count: number;
@@ -45,7 +51,7 @@ interface ForumReply {
id: string;
body: string;
author_name: string;
- is_ai_response: boolean;
+ author_username?: string | null;
upvotes: number;
downvotes: number;
vote_score: number;
@@ -322,7 +328,7 @@ export default function ForumThreadPage() {
@{profile.username}