forum: fix API + UI column-name mismatch against bb.forum_threads
The schema has upvotes / downvotes / vote_score; API + UI were
querying/reading upvote_count / downvote_count (non-existent
columns). Threads existed in the DB but didn't render.
- /api/forum/threads route.ts:
- 'top-voted' sort: order by vote_score (was upvote_count)
- 'newest' default sort: forum_threads has no 'status' column,
so order pinned-first via is_pinned, then last_reply_at desc
- UI ForumThread interface + components: rename
upvote_count → upvotes, add downvotes + vote_score so the
vote display in the thread cards and the admin moderation
list pulls live data from the existing columns.
After this lands and the deploy completes, the 50 seeded
threads + the 9 ai-seeded new threads + 253 backfilled replies
already in the DB will all render at /forum.
This commit is contained in:
@@ -14,7 +14,7 @@ interface ForumThread {
|
||||
body: string;
|
||||
reply_count: number;
|
||||
view_count: number;
|
||||
upvote_count: number;
|
||||
upvotes: number;
|
||||
is_pinned: boolean;
|
||||
is_featured: boolean;
|
||||
is_locked: boolean;
|
||||
|
||||
@@ -31,7 +31,9 @@ export async function GET(req: NextRequest) {
|
||||
}
|
||||
|
||||
if (sort === 'top-voted') {
|
||||
query = query.order('upvote_count', { ascending: false }).order('created_at', { ascending: false });
|
||||
// The schema has upvotes / downvotes / vote_score (no upvote_count).
|
||||
// Use vote_score so downvotes properly demote a thread.
|
||||
query = query.order('vote_score', { ascending: false }).order('created_at', { ascending: false });
|
||||
} else if (sort === 'most-viewed') {
|
||||
query = query.order('view_count', { ascending: false }).order('created_at', { ascending: false });
|
||||
} else if (sort === 'most-replied') {
|
||||
@@ -39,8 +41,9 @@ export async function GET(req: NextRequest) {
|
||||
} else if (sort === 'unanswered') {
|
||||
query = query.eq('reply_count', 0).order('created_at', { ascending: false });
|
||||
} else {
|
||||
// newest — default
|
||||
query = query.order('status', { ascending: false }).order('last_reply_at', { ascending: false });
|
||||
// newest — default. forum_threads has no 'status' column; sort
|
||||
// pinned-first then by recent activity.
|
||||
query = query.order('is_pinned', { ascending: false }).order('last_reply_at', { ascending: false, nullsFirst: false });
|
||||
}
|
||||
|
||||
const { data, error, count } = await query;
|
||||
|
||||
@@ -22,7 +22,7 @@ interface ForumThread {
|
||||
status: string;
|
||||
reply_count: number;
|
||||
view_count: number;
|
||||
upvote_count?: number;
|
||||
upvotes?: number;
|
||||
last_reply_at: string;
|
||||
created_at: string;
|
||||
is_ai_seeded: boolean;
|
||||
@@ -71,7 +71,7 @@ function TopThreadsWidget({ categoryId }: { categoryId: string }) {
|
||||
];
|
||||
|
||||
const getMetricValue = (thread: ForumThread) => {
|
||||
if (activeMetric === 'upvotes') return { value: thread.upvote_count ?? 0, label: 'votes', color: 'text-[#22c55e]' };
|
||||
if (activeMetric === 'upvotes') return { value: thread.upvotes ?? 0, label: 'votes', color: 'text-[#22c55e]' };
|
||||
if (activeMetric === 'replies') return { value: thread.reply_count, label: 'replies', color: 'text-[#3b82f6]' };
|
||||
return { value: thread.view_count, label: 'views', color: 'text-[#f59e0b]' };
|
||||
};
|
||||
@@ -451,7 +451,7 @@ export default function ForumCategoryPage() {
|
||||
</div>
|
||||
{sort === 'top-voted' && (
|
||||
<div className="flex-shrink-0 text-right hidden md:block">
|
||||
<div className="text-[#22c55e] font-body text-sm font-semibold">+{thread.upvote_count ?? 0}</div>
|
||||
<div className="text-[#22c55e] font-body text-sm font-semibold">+{thread.upvotes ?? 0}</div>
|
||||
<div className="text-[#555] font-body text-xs">votes</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -22,12 +22,15 @@ interface ForumCategory {
|
||||
interface ForumThread {
|
||||
id: string;
|
||||
title: string;
|
||||
author_id?: string | null;
|
||||
author_name: string;
|
||||
reply_count: number;
|
||||
view_count: number;
|
||||
last_reply_at: string;
|
||||
created_at: string;
|
||||
upvote_count?: number;
|
||||
upvotes?: number;
|
||||
downvotes?: number;
|
||||
vote_score?: number;
|
||||
forum_categories?: { name: string; slug: string };
|
||||
}
|
||||
|
||||
@@ -77,7 +80,7 @@ function TopThreadsWidget({ categoryId }: { categoryId?: string }) {
|
||||
];
|
||||
|
||||
const getMetricValue = (thread: ForumThread) => {
|
||||
if (activeMetric === 'upvotes') return { value: thread.upvote_count ?? 0, label: 'votes', color: 'text-[#22c55e]' };
|
||||
if (activeMetric === 'upvotes') return { value: thread.upvotes ?? 0, label: 'votes', color: 'text-[#22c55e]' };
|
||||
if (activeMetric === 'replies') return { value: thread.reply_count, label: 'replies', color: 'text-[#3b82f6]' };
|
||||
return { value: thread.view_count, label: 'views', color: 'text-[#f59e0b]' };
|
||||
};
|
||||
@@ -493,7 +496,7 @@ export default function ForumIndexPage() {
|
||||
</div>
|
||||
{sort === 'top-voted' && (
|
||||
<div className="flex-shrink-0 text-right hidden md:block">
|
||||
<div className="text-[#22c55e] font-body text-sm font-semibold">+{thread.upvote_count ?? 0}</div>
|
||||
<div className="text-[#22c55e] font-body text-sm font-semibold">+{thread.upvotes ?? 0}</div>
|
||||
<div className="text-[#555] font-body text-xs">votes</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user