196 lines
6.7 KiB
TypeScript
196 lines
6.7 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { db, posts, users, media, remotePosts } from '@/db';
|
|
import { eq, desc, and } from 'drizzle-orm';
|
|
import { fetchRemotePost } from '@/lib/activitypub/fetchRemotePost';
|
|
|
|
export async function GET(
|
|
request: Request,
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
) {
|
|
try {
|
|
const { id } = await params;
|
|
|
|
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
|
|
|
let mainPost: any = null;
|
|
let replyPosts: any[] = [];
|
|
|
|
const post = await db.query.posts.findFirst({
|
|
where: eq(posts.id, id),
|
|
with: {
|
|
author: true,
|
|
media: true,
|
|
replyTo: {
|
|
with: { author: true },
|
|
},
|
|
},
|
|
});
|
|
|
|
if (post) {
|
|
mainPost = 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 allPostIds = [post.id, ...replies.map(r => r.id)];
|
|
|
|
try {
|
|
const { requireAuth } = await import('@/lib/auth');
|
|
const { likes } = await import('@/db');
|
|
const { inArray } = await import('drizzle-orm');
|
|
|
|
const viewer = await requireAuth();
|
|
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),
|
|
} as any;
|
|
|
|
replyPosts = replies.map(r => ({
|
|
...r,
|
|
isLiked: likedPostIds.has(r.id),
|
|
isReposted: repostedPostIds.has(r.id),
|
|
})) as any[];
|
|
}
|
|
} catch {
|
|
}
|
|
} else {
|
|
const cached = await db.query.remotePosts.findFirst({
|
|
where: eq(remotePosts.apId, id),
|
|
});
|
|
|
|
if (cached) {
|
|
mainPost = {
|
|
id: cached.id,
|
|
content: cached.content,
|
|
createdAt: cached.publishedAt.toISOString(),
|
|
likesCount: 0,
|
|
repostsCount: 0,
|
|
repliesCount: 0,
|
|
author: {
|
|
id: cached.authorHandle,
|
|
handle: cached.authorHandle,
|
|
displayName: cached.authorDisplayName || cached.authorHandle,
|
|
avatarUrl: cached.authorAvatarUrl,
|
|
bio: null,
|
|
isRemote: true,
|
|
},
|
|
media: cached.mediaJson ? JSON.parse(cached.mediaJson) : null,
|
|
linkPreviewUrl: cached.linkPreviewUrl,
|
|
linkPreviewTitle: cached.linkPreviewTitle,
|
|
linkPreviewDescription: cached.linkPreviewDescription,
|
|
linkPreviewImage: cached.linkPreviewImage,
|
|
isLiked: false,
|
|
isReposted: false,
|
|
};
|
|
} else {
|
|
const postUrl = `https://${nodeDomain}/posts/${id}`;
|
|
const result = await fetchRemotePost(postUrl, nodeDomain);
|
|
|
|
if (result.post) {
|
|
mainPost = result.post;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!mainPost) {
|
|
return NextResponse.json({ error: 'Post not found' }, { status: 404 });
|
|
}
|
|
|
|
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 }
|
|
);
|
|
}
|
|
}
|
|
|
|
export async function DELETE(
|
|
request: Request,
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
) {
|
|
try {
|
|
const { requireAuth } = await import('@/lib/auth');
|
|
const user = await requireAuth();
|
|
const { id } = await params;
|
|
|
|
const post = await db.query.posts.findFirst({
|
|
where: eq(posts.id, id),
|
|
});
|
|
|
|
if (!post) {
|
|
return NextResponse.json({ error: 'Post not found' }, { status: 404 });
|
|
}
|
|
|
|
if (post.userId !== user.id) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 });
|
|
}
|
|
|
|
// 1. If it's a reply, decrement parent's repliesCount
|
|
if (post.replyToId) {
|
|
const parentPost = await db.query.posts.findFirst({
|
|
where: eq(posts.id, post.replyToId),
|
|
});
|
|
if (parentPost && parentPost.repliesCount > 0) {
|
|
await db.update(posts)
|
|
.set({ repliesCount: parentPost.repliesCount - 1 })
|
|
.where(eq(posts.id, post.replyToId));
|
|
}
|
|
}
|
|
|
|
// 2. Delete the post (cascades to media, likes, notifications)
|
|
await db.delete(posts).where(eq(posts.id, id));
|
|
|
|
// 3. Decrement user's postsCount
|
|
if (user.postsCount > 0) {
|
|
await db.update(users)
|
|
.set({ postsCount: user.postsCount - 1 })
|
|
.where(eq(users.id, user.id));
|
|
}
|
|
|
|
return NextResponse.json({ success: true });
|
|
} catch (error) {
|
|
console.error('Delete post error:', error);
|
|
if (error instanceof Error && error.message === 'Authentication required') {
|
|
return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
|
|
}
|
|
return NextResponse.json(
|
|
{ error: 'Failed to delete post' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|