Various fixes and improvments
This commit is contained in:
@@ -28,6 +28,19 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
return NextResponse.json({ error: 'Post not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Check if already reposted by this user
|
||||
const existingRepost = await db.query.posts.findFirst({
|
||||
where: and(
|
||||
eq(posts.userId, user.id),
|
||||
eq(posts.repostOfId, postId),
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
});
|
||||
|
||||
if (existingRepost) {
|
||||
return NextResponse.json({ error: 'Already reposted' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Create repost
|
||||
const [repost] = await db.insert(posts).values({
|
||||
userId: user.id,
|
||||
@@ -58,7 +71,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
|
||||
// TODO: Federate the repost (Announce activity)
|
||||
|
||||
return NextResponse.json({ success: true, repost });
|
||||
return NextResponse.json({ success: true, repost, reposted: true });
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === 'Authentication required') {
|
||||
return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
|
||||
@@ -66,3 +79,57 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
return NextResponse.json({ error: 'Failed to repost' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// Unrepost a post
|
||||
export async function DELETE(request: Request, context: RouteContext) {
|
||||
try {
|
||||
const user = await requireAuth();
|
||||
const { id: postId } = await context.params;
|
||||
|
||||
if (user.isSuspended || user.isSilenced) {
|
||||
return NextResponse.json({ error: 'Account restricted' }, { status: 403 });
|
||||
}
|
||||
|
||||
// Check if original post exists
|
||||
const originalPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, postId),
|
||||
});
|
||||
|
||||
// Find the repost by this user
|
||||
const repost = await db.query.posts.findFirst({
|
||||
where: and(
|
||||
eq(posts.userId, user.id),
|
||||
eq(posts.repostOfId, postId),
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
});
|
||||
|
||||
if (!repost) {
|
||||
return NextResponse.json({ error: 'Not reposted' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Mark repost as removed
|
||||
await db.update(posts)
|
||||
.set({ isRemoved: true })
|
||||
.where(eq(posts.id, repost.id));
|
||||
|
||||
// Update original post's repost count
|
||||
if (originalPost) {
|
||||
await db.update(posts)
|
||||
.set({ repostsCount: Math.max(0, originalPost.repostsCount - 1) })
|
||||
.where(eq(posts.id, postId));
|
||||
}
|
||||
|
||||
// Update user's post count
|
||||
await db.update(users)
|
||||
.set({ postsCount: Math.max(0, user.postsCount - 1) })
|
||||
.where(eq(users.id, user.id));
|
||||
|
||||
return NextResponse.json({ success: true, reposted: false });
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === 'Authentication required') {
|
||||
return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
|
||||
}
|
||||
return NextResponse.json({ error: 'Failed to unrepost' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { db, posts, users, media } from '@/db';
|
||||
import { eq, desc, and } from 'drizzle-orm';
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const { id } = params;
|
||||
|
||||
// Fetch the main post
|
||||
const post = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, id),
|
||||
with: {
|
||||
author: true,
|
||||
media: true,
|
||||
replyTo: {
|
||||
with: { author: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!post) {
|
||||
return NextResponse.json({ error: 'Post not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Fetch replies to this post
|
||||
const replies = await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.replyToId, id),
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
with: {
|
||||
author: true,
|
||||
media: true,
|
||||
},
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
});
|
||||
|
||||
let mainPost = post;
|
||||
let replyPosts = replies;
|
||||
|
||||
try {
|
||||
const { requireAuth } = await import('@/lib/auth');
|
||||
const { likes } = await import('@/db');
|
||||
const { inArray } = await import('drizzle-orm');
|
||||
|
||||
const viewer = await requireAuth();
|
||||
const allPostIds = [post.id, ...replies.map(r => r.id)];
|
||||
|
||||
if (allPostIds.length > 0) {
|
||||
const viewerLikes = await db.query.likes.findMany({
|
||||
where: and(
|
||||
eq(likes.userId, viewer.id),
|
||||
inArray(likes.postId, allPostIds)
|
||||
),
|
||||
});
|
||||
const likedPostIds = new Set(viewerLikes.map(l => l.postId));
|
||||
|
||||
const viewerReposts = await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.userId, viewer.id),
|
||||
inArray(posts.repostOfId, allPostIds)
|
||||
),
|
||||
});
|
||||
const repostedPostIds = new Set(viewerReposts.map(r => r.repostOfId));
|
||||
|
||||
mainPost = {
|
||||
...post,
|
||||
isLiked: likedPostIds.has(post.id),
|
||||
isReposted: repostedPostIds.has(post.id),
|
||||
};
|
||||
|
||||
replyPosts = replies.map(r => ({
|
||||
...r,
|
||||
isLiked: likedPostIds.has(r.id),
|
||||
isReposted: repostedPostIds.has(r.id),
|
||||
}));
|
||||
}
|
||||
} catch {
|
||||
// Not authenticated or other error, skip flags
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
post: mainPost,
|
||||
replies: replyPosts,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Get post detail error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to get post detail' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
+58
-27
@@ -1,5 +1,5 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { db, posts, users, media, follows, mutes, blocks } from '@/db';
|
||||
import { db, posts, users, media, follows, mutes, blocks, likes } from '@/db';
|
||||
import { requireAuth } from '@/lib/auth';
|
||||
import { eq, desc, and, inArray, isNull, notInArray, or } from 'drizzle-orm';
|
||||
import type { SQL } from 'drizzle-orm';
|
||||
@@ -128,13 +128,8 @@ export async function GET(request: Request) {
|
||||
const limit = Math.min(parseInt(searchParams.get('limit') || '20'), 50);
|
||||
|
||||
let feedPosts;
|
||||
const moderatedUsers = await db.select({ id: users.id })
|
||||
.from(users)
|
||||
.where(or(eq(users.isSuspended, true), eq(users.isSilenced, true)));
|
||||
const moderatedIds = moderatedUsers.map((item) => item.id);
|
||||
const baseFilter = buildWhere(
|
||||
eq(posts.isRemoved, false),
|
||||
moderatedIds.length ? notInArray(posts.userId, moderatedIds) : undefined
|
||||
eq(posts.isRemoved, false)
|
||||
);
|
||||
|
||||
if (type === 'public') {
|
||||
@@ -168,7 +163,9 @@ export async function GET(request: Request) {
|
||||
} else if (type === 'curated') {
|
||||
let viewer = null;
|
||||
try {
|
||||
viewer = await requireAuth();
|
||||
const { getSession } = await import('@/lib/auth');
|
||||
const session = await getSession();
|
||||
viewer = session?.user || null;
|
||||
} catch {
|
||||
viewer = null;
|
||||
}
|
||||
@@ -229,7 +226,7 @@ export async function GET(request: Request) {
|
||||
}
|
||||
if (engagement >= 5) {
|
||||
reasons.push(`Popular: ${post.likesCount} likes, ${post.repostsCount} reposts`);
|
||||
} else if (engagement > 0) {
|
||||
} else if (post.repliesCount > 0) {
|
||||
reasons.push(`Active conversation: ${post.repliesCount} replies`);
|
||||
}
|
||||
if (ageHours <= 6) {
|
||||
@@ -264,21 +261,7 @@ export async function GET(request: Request) {
|
||||
})
|
||||
.slice(0, limit);
|
||||
|
||||
return NextResponse.json({
|
||||
posts: rankedPosts,
|
||||
meta: {
|
||||
algorithm: 'curated-v1',
|
||||
windowHours: CURATION_WINDOW_HOURS,
|
||||
seedLimit,
|
||||
weights: {
|
||||
engagement: 1.4,
|
||||
recency: 1.1,
|
||||
followBoost: 0.9,
|
||||
selfBoost: 0.5,
|
||||
},
|
||||
},
|
||||
nextCursor: rankedPosts.length === limit ? rankedPosts[rankedPosts.length - 1]?.id : null,
|
||||
});
|
||||
feedPosts = rankedPosts;
|
||||
} else {
|
||||
// Home timeline - need auth
|
||||
try {
|
||||
@@ -315,12 +298,60 @@ export async function GET(request: Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Populate isLiked and isReposted for authenticated users
|
||||
try {
|
||||
const { getSession } = await import('@/lib/auth');
|
||||
const session = await getSession();
|
||||
|
||||
if (session?.user && feedPosts && feedPosts.length > 0) {
|
||||
const viewer = session.user;
|
||||
const postIds = feedPosts.map(p => p.id).filter(Boolean);
|
||||
|
||||
if (postIds.length > 0) {
|
||||
const viewerLikes = await db.query.likes.findMany({
|
||||
where: and(
|
||||
eq(likes.userId, viewer.id),
|
||||
inArray(likes.postId, postIds)
|
||||
),
|
||||
});
|
||||
const likedPostIds = new Set(viewerLikes.map(l => l.postId));
|
||||
|
||||
const viewerReposts = await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.userId, viewer.id),
|
||||
inArray(posts.repostOfId, postIds)
|
||||
),
|
||||
});
|
||||
const repostedPostIds = new Set(viewerReposts.map(r => r.repostOfId));
|
||||
|
||||
feedPosts = feedPosts.map(p => ({
|
||||
...p,
|
||||
isLiked: likedPostIds.has(p.id),
|
||||
isReposted: repostedPostIds.has(p.id),
|
||||
}));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error populating interaction flags:', error);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
posts: feedPosts,
|
||||
nextCursor: feedPosts.length === limit ? feedPosts[feedPosts.length - 1]?.id : null,
|
||||
posts: feedPosts || [],
|
||||
meta: type === 'curated' ? {
|
||||
algorithm: 'curated-v1',
|
||||
windowHours: CURATION_WINDOW_HOURS,
|
||||
seedLimit: Math.min(limit * CURATION_SEED_MULTIPLIER, CURATION_SEED_CAP),
|
||||
weights: {
|
||||
engagement: 1.4,
|
||||
recency: 1.1,
|
||||
followBoost: 0.9,
|
||||
selfBoost: 0.5,
|
||||
},
|
||||
} : undefined,
|
||||
nextCursor: (feedPosts?.length === limit) ? feedPosts[feedPosts.length - 1]?.id : null,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Get feed error:', error);
|
||||
console.error('Get feed error details:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to get feed' },
|
||||
{ status: 500 }
|
||||
|
||||
Reference in New Issue
Block a user