diff --git a/crates/adapters/activitypub-base/src/service.rs b/crates/adapters/activitypub-base/src/service.rs index 4bbe7e7..4a92c57 100644 --- a/crates/adapters/activitypub-base/src/service.rs +++ b/crates/adapters/activitypub-base/src/service.rs @@ -422,9 +422,11 @@ impl ActivityPubService { ); } + let domain = remote_actor.ap_id.host_str().unwrap_or(""); + let full_handle = format!("{}@{}", remote_actor.username, domain); let remote = RemoteActor { url: remote_actor.ap_id.to_string(), - handle: remote_actor.username.clone(), + handle: full_handle, inbox_url: remote_actor.inbox_url.to_string(), shared_inbox_url: None, display_name: Some(remote_actor.username.clone()), diff --git a/thoughts-frontend/app/users/[username]/page.tsx b/thoughts-frontend/app/users/[username]/page.tsx index 25474f8..ab172d8 100644 --- a/thoughts-frontend/app/users/[username]/page.tsx +++ b/thoughts-frontend/app/users/[username]/page.tsx @@ -3,6 +3,8 @@ import { getFollowersList, getFollowingList, getMe, + getRemoteFollowers, + getRemoteFollowing, getTopFriends, getUserProfile, getUserThoughts, @@ -95,16 +97,27 @@ export default async function ProfilePage({ params }: ProfilePageProps) { thoughtsResult.status === "fulfilled" ? thoughtsResult.value.items : []; const thoughtThreads = buildThoughtThreads(thoughts); - const followersCount = + const localFollowersCount = followersResult.status === "fulfilled" ? followersResult.value.total : 0; - const followingCount = + const localFollowingCount = followingResult.status === "fulfilled" ? followingResult.value.total : 0; const isOwnProfile = me?.username === user.username; + + const [remoteFollowersCount, remoteFollowingCount] = + isOwnProfile && token + ? await Promise.all([ + getRemoteFollowers(token).then((r) => r.length).catch(() => 0), + getRemoteFollowing(token).then((r) => r.length).catch(() => 0), + ]) + : [0, 0]; + + const followersCount = localFollowersCount + remoteFollowersCount; + const followingCount = localFollowingCount + remoteFollowingCount; const isFollowing = user.isFollowedByViewer; const apiDomain = process.env.NEXT_PUBLIC_API_URL diff --git a/thoughts-frontend/components/federation/pending-requests.tsx b/thoughts-frontend/components/federation/pending-requests.tsx index 6f995b8..436517b 100644 --- a/thoughts-frontend/components/federation/pending-requests.tsx +++ b/thoughts-frontend/components/federation/pending-requests.tsx @@ -12,6 +12,7 @@ import { UserAvatar } from "@/components/user-avatar"; import { Button } from "@/components/ui/button"; import { toast } from "sonner"; import Link from "next/link"; +import { fullFediverseHandle } from "@/lib/utils"; interface Props { compact?: boolean; @@ -71,7 +72,7 @@ export function PendingRequests({ compact = false }: Props) { {actor.displayName || actor.handle}

- @{actor.handle} + @{fullFediverseHandle(actor.handle, actor.url)}

diff --git a/thoughts-frontend/components/federation/remote-followers.tsx b/thoughts-frontend/components/federation/remote-followers.tsx index 7414c76..0ec49d3 100644 --- a/thoughts-frontend/components/federation/remote-followers.tsx +++ b/thoughts-frontend/components/federation/remote-followers.tsx @@ -7,6 +7,7 @@ import { UserAvatar } from "@/components/user-avatar"; import { Button } from "@/components/ui/button"; import { toast } from "sonner"; import Link from "next/link"; +import { fullFediverseHandle } from "@/lib/utils"; export function RemoteFollowers() { const { token } = useAuth(); @@ -51,7 +52,7 @@ export function RemoteFollowers() { {actor.displayName || actor.handle}

- @{actor.handle} + @{fullFediverseHandle(actor.handle, actor.url)}

diff --git a/thoughts-frontend/components/federation/remote-following.tsx b/thoughts-frontend/components/federation/remote-following.tsx index 33e1a67..b27062b 100644 --- a/thoughts-frontend/components/federation/remote-following.tsx +++ b/thoughts-frontend/components/federation/remote-following.tsx @@ -7,6 +7,7 @@ import { UserAvatar } from "@/components/user-avatar"; import { Button } from "@/components/ui/button"; import { toast } from "sonner"; import Link from "next/link"; +import { fullFediverseHandle } from "@/lib/utils"; export function RemoteFollowing() { const { token } = useAuth(); @@ -51,7 +52,7 @@ export function RemoteFollowing() { {actor.displayName || actor.handle}

- @{actor.handle} + @{fullFediverseHandle(actor.handle, actor.url)}

diff --git a/thoughts-frontend/lib/utils.ts b/thoughts-frontend/lib/utils.ts index f4a2c83..539a602 100644 --- a/thoughts-frontend/lib/utils.ts +++ b/thoughts-frontend/lib/utils.ts @@ -6,6 +6,17 @@ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } +/** Construct a full fediverse handle like `user@instance.social`. + * Falls back gracefully for existing DB rows that only stored the username. */ +export function fullFediverseHandle(handle: string, actorUrl: string): string { + if (handle.includes("@")) return handle; + try { + return `${handle}@${new URL(actorUrl).hostname}`; + } catch { + return handle; + } +} + export function buildThoughtThreads(thoughts: Thought[]): ThoughtThreadType[] { const thoughtMap = new Map(); thoughts.forEach((t) => thoughtMap.set(t.id, t));