5efd3adbfd
- Create new swarm endpoints for fetching user followers list at /api/swarm/users/[handle]/followers - Create new swarm endpoints for fetching user following list at /api/swarm/users/[handle]/following - Add SwarmFollowerUser and SwarmFollowingUser interfaces for federated user data - Implement local and remote follower/following aggregation in swarm endpoints - Update local followers endpoint to fetch data from remote swarm nodes - Update local following endpoint to fetch data from remote swarm nodes - Add error handling and database availability checks for all endpoints - Include node domain and timestamp metadata in swarm responses - Support pagination with configurable limit parameter (max 100) - Enable cross-node follower/following visibility for federated social graph
104 lines
3.0 KiB
TypeScript
104 lines
3.0 KiB
TypeScript
/**
|
|
* Swarm User Followers Endpoint
|
|
*
|
|
* GET: Returns a user's followers list for swarm requests
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|
import { db, follows, users, remoteFollowers } from '@/db';
|
|
import { eq } from 'drizzle-orm';
|
|
|
|
export interface SwarmFollowerUser {
|
|
handle: string;
|
|
displayName: string;
|
|
avatarUrl?: string;
|
|
bio?: string;
|
|
isBot?: boolean;
|
|
isRemote?: boolean;
|
|
}
|
|
|
|
type RouteContext = { params: Promise<{ handle: string }> };
|
|
|
|
/**
|
|
* GET /api/swarm/users/[handle]/followers
|
|
*
|
|
* Returns a user's followers list.
|
|
* Used by other nodes to display who follows a remote user.
|
|
*/
|
|
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') || '50'), 100);
|
|
|
|
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),
|
|
});
|
|
|
|
if (!user) {
|
|
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
|
}
|
|
|
|
if (user.isSuspended) {
|
|
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
|
}
|
|
|
|
// Get local followers
|
|
const userFollowers = await db
|
|
.select({
|
|
id: follows.id,
|
|
follower: users,
|
|
})
|
|
.from(follows)
|
|
.innerJoin(users, eq(follows.followerId, users.id))
|
|
.where(eq(follows.followingId, user.id))
|
|
.limit(limit);
|
|
|
|
const localFollowers: SwarmFollowerUser[] = userFollowers.map(f => ({
|
|
handle: f.follower.handle, // Local handle without domain
|
|
displayName: f.follower.displayName || f.follower.handle,
|
|
avatarUrl: f.follower.avatarUrl || undefined,
|
|
bio: f.follower.bio || undefined,
|
|
isBot: f.follower.isBot || undefined,
|
|
isRemote: false,
|
|
}));
|
|
|
|
// Get remote followers
|
|
const userRemoteFollowers = await db.query.remoteFollowers.findMany({
|
|
where: eq(remoteFollowers.userId, user.id),
|
|
limit,
|
|
});
|
|
|
|
const remoteFollowersList: SwarmFollowerUser[] = userRemoteFollowers.map(f => ({
|
|
handle: f.handle || 'unknown', // Remote handle with @domain
|
|
displayName: f.handle?.split('@')[0] || 'Unknown',
|
|
avatarUrl: undefined,
|
|
bio: undefined,
|
|
isRemote: true,
|
|
}));
|
|
|
|
// Merge all followers
|
|
const allFollowers = [...localFollowers, ...remoteFollowersList].slice(0, limit);
|
|
|
|
return NextResponse.json({
|
|
followers: allFollowers,
|
|
nodeDomain,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
} catch (error) {
|
|
console.error('Swarm user followers error:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to fetch followers list' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|