Files
Synapsis/src/app/api/swarm/users/[handle]/route.ts
T

166 lines
4.9 KiB
TypeScript

/**
* Swarm User Profile Endpoint
*
* GET: Returns a user's profile and posts for swarm requests
*/
import { NextRequest, NextResponse } from 'next/server';
import { db, posts, users, media, nodes } from '@/db';
import { eq, desc, and } from 'drizzle-orm';
export interface SwarmUserProfile {
handle: string;
displayName: string;
bio?: string;
avatarUrl?: string;
headerUrl?: string;
website?: string;
followersCount: number;
followingCount: number;
postsCount: number;
createdAt: string;
isBot?: boolean;
botOwnerHandle?: string; // Handle of the bot's owner (e.g., "user" or "user@domain")
nodeDomain: string;
chatPublicKey?: string;
}
export interface SwarmUserPost {
id: string;
content: string;
createdAt: string;
isNsfw: boolean;
likesCount: number;
repostsCount: number;
repliesCount: number;
media?: { url: string; mimeType?: string; altText?: string }[];
linkPreviewUrl?: string;
linkPreviewTitle?: string;
linkPreviewDescription?: string;
linkPreviewImage?: string;
}
type RouteContext = { params: Promise<{ handle: string }> };
/**
* GET /api/swarm/users/[handle]
*
* Returns a user's profile and recent posts.
* Used by other nodes to display remote user profiles.
*/
export async function GET(request: NextRequest, context: RouteContext) {
try {
const { handle } = await context.params;
const cleanHandle = handle.toLowerCase().replace(/^@/, '');
const { searchParams } = new URL(request.url);
const limit = Math.min(parseInt(searchParams.get('limit') || '25'), 50);
if (!db) {
return NextResponse.json({ error: 'Database not available' }, { status: 503 });
}
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost';
// Find the user
const user = await db.query.users.findFirst({
where: eq(users.handle, cleanHandle),
with: {
botOwner: true, // Include bot owner if this is a bot
},
});
if (!user) {
return NextResponse.json({ error: 'User not found' }, { status: 404 });
}
if (user.isSuspended) {
return NextResponse.json({ error: 'User not found' }, { status: 404 });
}
// Build profile response
const profile: SwarmUserProfile = {
handle: user.handle,
displayName: user.displayName || user.handle,
bio: user.bio || undefined,
avatarUrl: user.avatarUrl || undefined,
headerUrl: user.headerUrl || undefined,
website: user.website || undefined,
followersCount: user.followersCount,
followingCount: user.followingCount,
postsCount: user.postsCount,
createdAt: user.createdAt.toISOString(),
isBot: user.isBot || undefined,
botOwnerHandle: user.isBot && user.botOwner ? user.botOwner.handle : undefined,
nodeDomain,
chatPublicKey: user.chatPublicKey || undefined,
};
// Get user's recent posts
const userPosts = await db
.select({
id: posts.id,
content: posts.content,
createdAt: posts.createdAt,
isNsfw: posts.isNsfw,
likesCount: posts.likesCount,
repostsCount: posts.repostsCount,
repliesCount: posts.repliesCount,
linkPreviewUrl: posts.linkPreviewUrl,
linkPreviewTitle: posts.linkPreviewTitle,
linkPreviewDescription: posts.linkPreviewDescription,
linkPreviewImage: posts.linkPreviewImage,
})
.from(posts)
.where(
and(
eq(posts.userId, user.id),
eq(posts.isRemoved, false)
)
)
.orderBy(desc(posts.createdAt))
.limit(limit);
// Fetch media for each post
const swarmPosts: SwarmUserPost[] = [];
for (const post of userPosts) {
const postMedia = await db
.select({ url: media.url, mimeType: media.mimeType, altText: media.altText })
.from(media)
.where(eq(media.postId, post.id));
swarmPosts.push({
id: post.id,
content: post.content,
createdAt: post.createdAt.toISOString(),
isNsfw: post.isNsfw,
likesCount: post.likesCount,
repostsCount: post.repostsCount,
repliesCount: post.repliesCount,
media: postMedia.length > 0 ? postMedia.map(m => ({
url: m.url,
mimeType: m.mimeType || undefined,
altText: m.altText || undefined,
})) : undefined,
linkPreviewUrl: post.linkPreviewUrl || undefined,
linkPreviewTitle: post.linkPreviewTitle || undefined,
linkPreviewDescription: post.linkPreviewDescription || undefined,
linkPreviewImage: post.linkPreviewImage || undefined,
});
}
return NextResponse.json({
profile,
posts: swarmPosts,
nodeDomain,
timestamp: new Date().toISOString(),
});
} catch (error) {
console.error('Swarm user profile error:', error);
return NextResponse.json(
{ error: 'Failed to fetch user profile' },
{ status: 500 }
);
}
}