feat: Enable fetching and caching of remote ActivityPub posts in the post details API.

This commit is contained in:
Christopher
2026-01-22 18:32:37 -08:00
parent fdb6cfbd35
commit 72d148066e
2 changed files with 271 additions and 59 deletions
+102 -59
View File
@@ -1,6 +1,7 @@
import { NextResponse } from 'next/server';
import { db, posts, users, media } from '@/db';
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,
@@ -9,7 +10,11 @@ export async function GET(
try {
const { id } = await params;
// Fetch the main post
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: {
@@ -21,65 +26,103 @@ export async function GET(
},
});
if (!post) {
return NextResponse.json({ error: 'Post not found' }, { status: 404 });
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;
}
}
}
// 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),
} as any;
replyPosts = replies.map(r => ({
...r,
isLiked: likedPostIds.has(r.id),
isReposted: repostedPostIds.has(r.id),
})) as any;
}
} catch {
// Not authenticated or other error, skip flags
if (!mainPost) {
return NextResponse.json({ error: 'Post not found' }, { status: 404 });
}
return NextResponse.json({