diff --git a/src/app/api/chat/receive/route.ts b/src/app/api/chat/receive/route.ts index 3d62c13..f3d16ca 100644 --- a/src/app/api/chat/receive/route.ts +++ b/src/app/api/chat/receive/route.ts @@ -64,6 +64,16 @@ export async function POST(request: NextRequest) { publicKey = remoteProfile.publicKey; senderDisplayName = remoteProfile.displayName || handle; senderAvatarUrl = remoteProfile.avatarUrl; + + // CACHE: Upsert the remote user into our local database + const { upsertRemoteUser } = await import('@/lib/swarm/user-cache'); + await upsertRemoteUser({ + handle: handle, // already full handle if remote + displayName: senderDisplayName, + avatarUrl: senderAvatarUrl || null, + did: did || '', + isBot: remoteProfile.isBot || false + }); } } } else { diff --git a/src/app/api/swarm/chat/conversations/route.ts b/src/app/api/swarm/chat/conversations/route.ts index 0f655ed..901675d 100644 --- a/src/app/api/swarm/chat/conversations/route.ts +++ b/src/app/api/swarm/chat/conversations/route.ts @@ -57,15 +57,42 @@ export async function GET(request: NextRequest) { }; // Try to get cached user info - const cachedUser = await db.query.users.findFirst({ + let cachedUser = await db.query.users.findFirst({ where: eq(users.handle, participant2Handle), }); + // LAZY LOAD: If remote and not cached, try to fetch it now + if (!cachedUser && isRemote) { + try { + const [rHandle, rDomain] = participant2Handle.split('@'); + const { fetchSwarmUserProfile } = await import('@/lib/swarm/interactions'); + const profileData = await fetchSwarmUserProfile(rHandle, rDomain, 0); + + if (profileData?.profile) { + const { upsertRemoteUser } = await import('@/lib/swarm/user-cache'); + await upsertRemoteUser({ + handle: participant2Handle, + displayName: profileData.profile.displayName, + avatarUrl: profileData.profile.avatarUrl || null, + did: profileData.profile.did || '', + isBot: profileData.profile.isBot || false, + }); + + // Re-query to get the new cached user + cachedUser = await db.query.users.findFirst({ + where: eq(users.handle, participant2Handle), + }) as any; + } + } catch (e) { + console.error(`[Lazy Load] Failed for ${participant2Handle}:`, e); + } + } + if (cachedUser) { participant2Info = { handle: cachedUser.handle, - displayName: cachedUser.displayName || cachedUser.handle, - avatarUrl: cachedUser.avatarUrl, + displayName: (cachedUser as any).displayName || cachedUser.handle, + avatarUrl: (cachedUser as any).avatarUrl || null, }; } @@ -73,7 +100,7 @@ export async function GET(request: NextRequest) { ...conv, participant2: { ...participant2Info, - isBot: cachedUser?.isBot || false, + isBot: (cachedUser as any)?.isBot || false, }, unreadCount: Number(unreadCount[0]?.count || 0), }; diff --git a/src/app/api/users/[handle]/followers/route.ts b/src/app/api/users/[handle]/followers/route.ts index d125592..d35fc80 100644 --- a/src/app/api/users/[handle]/followers/route.ts +++ b/src/app/api/users/[handle]/followers/route.ts @@ -1,6 +1,6 @@ -import { NextResponse } from 'next/server'; -import { db, follows, users } from '@/db'; +import { db, follows, users, remoteFollowers } from '@/db'; import { eq } from 'drizzle-orm'; +import { hydrateSwarmUsers } from '@/lib/swarm/user-hydration'; type RouteContext = { params: Promise<{ handle: string }> }; @@ -46,7 +46,8 @@ export async function GET(request: Request, context: RouteContext) { isRemote: true, isBot: f.isBot, })); - return NextResponse.json({ followers, nextCursor: null }); + const hydratedFollowers = await hydrateSwarmUsers(followers); + return NextResponse.json({ followers: hydratedFollowers, nextCursor: null }); } // If swarm fetch fails, return empty return NextResponse.json({ followers: [], nextCursor: null }); @@ -80,7 +81,7 @@ export async function GET(request: Request, context: RouteContext) { .where(eq(follows.followingId, user.id)) .limit(limit); - const allFollowers = userFollowers.map(f => ({ + const localFollowers = userFollowers.map(f => ({ id: f.follower.id, handle: f.follower.handle, displayName: f.follower.displayName, @@ -90,13 +91,30 @@ export async function GET(request: Request, context: RouteContext) { isRemote: false, })); - // Hydrate remote users with fresh data from swarm (if we had local storage for remote followers, we'd merge them here) - // Since we don't store remote followers locally for local users (only incoming follows), - // we mainly need this if we were merging remote lists which we only do in the swarm endpoint. - // However, let's keep it consistent in case we add remote followers storage. + // Get remote followers + const userRemoteFollowers = await db.query.remoteFollowers.findMany({ + where: eq(remoteFollowers.userId, user.id), + limit, + }); + + const remoteFollowersList = userRemoteFollowers.map(f => ({ + id: f.actorUrl, + handle: f.handle || 'unknown', + displayName: f.handle?.split('@')[0] || 'Unknown', + avatarUrl: null, + bio: null, + isBot: false, + isRemote: true, + })); + + // Merge and return + const allFollowers = [...localFollowers, ...remoteFollowersList].slice(0, limit); + + // Hydrate users with fresh data from swarm + const hydratedFollowers = await hydrateSwarmUsers(allFollowers); return NextResponse.json({ - followers: allFollowers, + followers: hydratedFollowers, nextCursor: userFollowers.length === limit ? userFollowers[userFollowers.length - 1]?.id : null, }); } catch (error) { diff --git a/src/app/api/users/[handle]/following/route.ts b/src/app/api/users/[handle]/following/route.ts index 7896b94..9dc0f04 100644 --- a/src/app/api/users/[handle]/following/route.ts +++ b/src/app/api/users/[handle]/following/route.ts @@ -47,7 +47,8 @@ export async function GET(request: Request, context: RouteContext) { isRemote: true, isBot: f.isBot, })); - return NextResponse.json({ following, nextCursor: null }); + const hydratedFollowing = await hydrateSwarmUsers(following); + return NextResponse.json({ following: hydratedFollowing, nextCursor: null }); } // If swarm fetch fails, return empty return NextResponse.json({ following: [], nextCursor: null }); diff --git a/src/app/api/users/[handle]/route.ts b/src/app/api/users/[handle]/route.ts index d7129c0..65ffba8 100644 --- a/src/app/api/users/[handle]/route.ts +++ b/src/app/api/users/[handle]/route.ts @@ -46,6 +46,16 @@ export async function GET(request: Request, context: RouteContext) { if (profileData?.profile) { const profile = profileData.profile; + // CACHE: Upsert the remote user into our local database + const { upsertRemoteUser } = await import('@/lib/swarm/user-cache'); + await upsertRemoteUser({ + handle: `${profile.handle}@${remoteDomain}`, + displayName: profile.displayName, + avatarUrl: profile.avatarUrl || null, + did: profile.did || '', + isBot: profile.isBot || false + }); + return NextResponse.json({ user: { id: `swarm:${remoteDomain}:${profile.handle}`, diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx index cae2739..4e9d15b 100644 --- a/src/app/chat/page.tsx +++ b/src/app/chat/page.tsx @@ -549,7 +549,7 @@ export default function ChatPage() { // LIST VIEW return ( - <> +
No conversations yet
-No conversations yet
+