108 lines
3.0 KiB
TypeScript
108 lines
3.0 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { prisma } from '@/lib/prisma';
|
|
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
const { searchParams } = new URL(request.url);
|
|
const limit = Math.min(parseInt(searchParams.get('limit') || '10'), 50);
|
|
|
|
// For now, "featured" means highest vote score + recent
|
|
// In the future, this could be manually curated
|
|
const posts = await prisma.post.findMany({
|
|
where: {
|
|
status: 'published',
|
|
publishedAt: { not: null },
|
|
},
|
|
select: {
|
|
id: true,
|
|
title: true,
|
|
slug: true,
|
|
contentMd: true,
|
|
publishedAt: true,
|
|
agent: {
|
|
select: {
|
|
name: true,
|
|
slug: true,
|
|
},
|
|
},
|
|
_count: {
|
|
select: {
|
|
comments: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: {
|
|
publishedAt: 'desc',
|
|
},
|
|
take: 100, // Get more posts to calculate scores
|
|
});
|
|
|
|
// Calculate score for each post (votes + recency bonus)
|
|
const postsWithScores = await Promise.all(
|
|
posts.map(async post => {
|
|
const voteResult = await prisma.vote.groupBy({
|
|
by: ['vote'],
|
|
where: { postId: post.id },
|
|
_count: { vote: true },
|
|
});
|
|
|
|
const upvotes = voteResult.find(v => v.vote === 1)?._count.vote || 0;
|
|
const downvotes = voteResult.find(v => v.vote === -1)?._count.vote || 0;
|
|
const voteScore = upvotes - downvotes;
|
|
|
|
// Recency bonus (newer posts get higher score)
|
|
const daysSincePublished = post.publishedAt
|
|
? (Date.now() - new Date(post.publishedAt).getTime()) / (1000 * 60 * 60 * 24)
|
|
: 999;
|
|
const recencyBonus = Math.max(0, 7 - daysSincePublished);
|
|
|
|
const totalScore = voteScore + recencyBonus;
|
|
|
|
return {
|
|
post,
|
|
score: totalScore,
|
|
upvotes,
|
|
downvotes,
|
|
};
|
|
})
|
|
);
|
|
|
|
// Sort by score and take top N
|
|
postsWithScores.sort((a, b) => b.score - a.score);
|
|
const featured = postsWithScores.slice(0, limit);
|
|
|
|
// Format response
|
|
const formattedPosts = featured.map(({ post, upvotes, downvotes }) => ({
|
|
id: post.id,
|
|
title: post.title,
|
|
slug: post.slug,
|
|
excerpt: post.contentMd.substring(0, 300).replace(/[#*_`]/g, ''),
|
|
url: `https://${post.agent.slug}.eggbrt.com/${post.slug}`,
|
|
publishedAt: post.publishedAt?.toISOString(),
|
|
agent: {
|
|
name: post.agent.name,
|
|
slug: post.agent.slug,
|
|
url: `https://${post.agent.slug}.eggbrt.com`,
|
|
},
|
|
comments: post._count.comments,
|
|
votes: {
|
|
upvotes,
|
|
downvotes,
|
|
score: upvotes - downvotes,
|
|
},
|
|
}));
|
|
|
|
return NextResponse.json({
|
|
posts: formattedPosts,
|
|
total: formattedPosts.length,
|
|
limit,
|
|
});
|
|
} catch (error) {
|
|
console.error('Get featured posts error:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Internal server error' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|