121 lines
3.6 KiB
TypeScript
121 lines
3.6 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { createClient } from "@supabase/supabase-js";
|
|
|
|
export const runtime = "nodejs";
|
|
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 || "av";
|
|
|
|
function client() {
|
|
return createClient(SUPABASE_URL, SUPABASE_ANON, {
|
|
db: { schema: SCHEMA as "public" },
|
|
auth: { persistSession: false },
|
|
});
|
|
}
|
|
|
|
export async function GET(
|
|
_req: Request,
|
|
{ params }: { params: Promise<{ username: string }> },
|
|
) {
|
|
const { username } = await params;
|
|
const sb = client();
|
|
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")
|
|
.select("*")
|
|
.eq("display_name", decoded)
|
|
.order("created_at", { ascending: true })
|
|
.limit(1);
|
|
profile = (r2.data && r2.data[0]) || null;
|
|
}
|
|
|
|
if (!profile) {
|
|
return NextResponse.json({ error: "not found" }, { status: 404 });
|
|
}
|
|
|
|
const displayName = profile.display_name as string;
|
|
|
|
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),
|
|
]);
|
|
|
|
const threadIds = [...new Set((replies || []).map((r) => r.thread_id).filter(Boolean))];
|
|
let titleByThread: Record<string, string> = {};
|
|
if (threadIds.length > 0) {
|
|
const { data: threadTitles } = await sb
|
|
.from("forum_threads")
|
|
.select("id,title")
|
|
.in("id", threadIds);
|
|
titleByThread = Object.fromEntries(
|
|
(threadTitles || []).map((t) => [t.id, t.title]),
|
|
);
|
|
}
|
|
|
|
const repliesWithTitles = (replies || []).map((r) => ({
|
|
...r,
|
|
forum_threads: { title: titleByThread[r.thread_id] || "(thread)" },
|
|
}));
|
|
|
|
const totalThreads = threadCount ?? 0;
|
|
const totalReplies = replyCount ?? 0;
|
|
|
|
return NextResponse.json({
|
|
profile: {
|
|
username: profile.username,
|
|
display_name: profile.display_name,
|
|
bio: profile.bio || null,
|
|
avatar_url: profile.avatar_url || null,
|
|
role_title: profile.role_title || null,
|
|
company: profile.company || null,
|
|
location_city: profile.location_city || null,
|
|
location_country: profile.location_country || null,
|
|
years_experience: profile.years_experience || null,
|
|
expertise_level: profile.expertise_level || null,
|
|
expertise_tags: profile.expertise_tags || [],
|
|
signature: profile.signature || null,
|
|
join_date: profile.join_date || null,
|
|
last_seen_at: profile.last_seen_at || null,
|
|
thread_count: totalThreads,
|
|
reply_count: totalReplies,
|
|
post_count: totalThreads + totalReplies,
|
|
},
|
|
threads: threads || [],
|
|
replies: repliesWithTitles,
|
|
});
|
|
} |