import { NextResponse } from 'next/server'; import { db, posts, likes, users, notifications } from '@/db'; import { requireAuth } from '@/lib/auth'; import { eq, and } from 'drizzle-orm'; type RouteContext = { params: Promise<{ id: string }> }; // Like a post export async function POST(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 post exists const post = await db.query.posts.findFirst({ where: eq(posts.id, postId), }); if (!post) { return NextResponse.json({ error: 'Post not found' }, { status: 404 }); } if (post.isRemoved) { return NextResponse.json({ error: 'Post not found' }, { status: 404 }); } // Check if already liked const existingLike = await db.query.likes.findFirst({ where: and( eq(likes.userId, user.id), eq(likes.postId, postId) ), }); if (existingLike) { return NextResponse.json({ error: 'Already liked' }, { status: 400 }); } // Create like await db.insert(likes).values({ userId: user.id, postId, }); // Update post's like count await db.update(posts) .set({ likesCount: post.likesCount + 1 }) .where(eq(posts.id, postId)); if (post.userId !== user.id) { await db.insert(notifications).values({ userId: post.userId, actorId: user.id, postId, type: 'like', }); } // Federate the like if the post has an ActivityPub ID if (post.apId) { (async () => { try { const { createLikeActivity } = await import('@/lib/activitypub/activities'); const { deliverActivity } = await import('@/lib/activitypub/outbox'); const { fetchRemoteActor } = await import('@/lib/activitypub/fetch'); const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000'; // Get the post author's actor URL const postWithAuthor = await db.query.posts.findFirst({ where: eq(posts.id, postId), with: { author: true }, }); if (!postWithAuthor?.author) return; const author = postWithAuthor.author as { handle: string }; // Construct the author's actor URL const authorActorUrl = `https://${nodeDomain}/users/${author.handle}`; // For remote posts, we'd need to fetch the remote actor's inbox // For local posts, just log it since local delivery doesn't need federation console.log(`[Federation] Like activity for post ${post.apId} from @${user.handle}`); } catch (err) { console.error('[Federation] Error federating like:', err); } })(); } return NextResponse.json({ success: true, liked: true }); } catch (error) { if (error instanceof Error && error.message === 'Authentication required') { return NextResponse.json({ error: 'Authentication required' }, { status: 401 }); } return NextResponse.json({ error: 'Failed to like post' }, { status: 500 }); } } // Unlike 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 post exists const post = await db.query.posts.findFirst({ where: eq(posts.id, postId), }); if (!post) { return NextResponse.json({ error: 'Post not found' }, { status: 404 }); } if (post.isRemoved) { return NextResponse.json({ error: 'Post not found' }, { status: 404 }); } // Find the like const existingLike = await db.query.likes.findFirst({ where: and( eq(likes.userId, user.id), eq(likes.postId, postId) ), }); if (!existingLike) { return NextResponse.json({ error: 'Not liked' }, { status: 400 }); } // Remove like await db.delete(likes).where(eq(likes.id, existingLike.id)); // Update post's like count await db.update(posts) .set({ likesCount: Math.max(0, post.likesCount - 1) }) .where(eq(posts.id, postId)); return NextResponse.json({ success: true, liked: 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 unlike post' }, { status: 500 }); } }