From 2627c9fb0f1e1eb3a402a231b8b37079f3244429 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Mon, 30 Mar 2026 20:33:31 +0100 Subject: [PATCH 1/2] [#635] Dedupe profile page DB queries into single lookup - Added getFullUserProfile() that fetches getUserFromDB() once and passes the result to getFarcasterProfile() and fetchAgentMetadata() - Both functions now accept optional pre-fetched dbUser parameter - Profile page uses single useQuery instead of 3 parallel queries - Reduces 3+ DB round-trips to 1 on every profile page load Fixes realproject7/plotlink#635 Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/actions.ts | 42 +++++++++++++++++++++++------- src/app/profile/[address]/page.tsx | 27 ++++++++----------- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/lib/actions.ts b/lib/actions.ts index 9f77fd8b..02dad879 100644 --- a/lib/actions.ts +++ b/lib/actions.ts @@ -12,19 +12,21 @@ import type { Address } from "viem"; /** * Server action that resolves an Ethereum address to a Farcaster profile. * Prefers cached DB data, falls back to live API. + * Accepts an optional pre-fetched dbUser to avoid redundant DB lookups. */ export async function getFarcasterProfile( address: string, + dbUser?: User | null, ): Promise { // Try DB first (only if user has a FID — wallet-only users have no Farcaster profile) - const dbUser = await getUserFromDB(address); - if (dbUser && dbUser.fid != null) { + const user = dbUser !== undefined ? dbUser : await getUserFromDB(address); + if (user && user.fid != null) { return { - fid: dbUser.fid, - username: dbUser.username ?? "", - displayName: dbUser.display_name ?? dbUser.username ?? "", - pfpUrl: dbUser.pfp_url ?? null, - bio: dbUser.bio ?? null, + fid: user.fid, + username: user.username ?? "", + displayName: user.display_name ?? user.username ?? "", + pfpUrl: user.pfp_url ?? null, + bio: user.bio ?? null, }; } // Fallback to live API @@ -34,12 +36,14 @@ export async function getFarcasterProfile( /** * Server action that resolves ERC-8004 agent metadata from a wallet address. * Checks DB cache first, falls back to RPC. Caches externally registered agents. + * Accepts an optional pre-fetched dbUser to avoid redundant DB lookups. */ export async function fetchAgentMetadata( address: string, + preloadedUser?: User | null, ): Promise { - // DB-first: check cached agent data (agent-specific lookup) - const dbUser = await getAgentUserFromDB(address); + // DB-first: check cached agent data + const dbUser = preloadedUser !== undefined ? preloadedUser : await getAgentUserFromDB(address); if (dbUser?.agent_id != null) { return { agentId: String(dbUser.agent_id), @@ -83,6 +87,26 @@ export async function fetchAgentMetadata( return meta; } +/** + * Fetch the full user profile in a single DB lookup. + * Returns dbUser, fcProfile, and agentMeta derived from one shared row. + * External API fallbacks still fire when DB data is missing. + */ +export async function getFullUserProfile( + address: string, +): Promise<{ + dbUser: User | null; + fcProfile: FarcasterProfile | null; + agentMeta: AgentMetadata | null; +}> { + const dbUser = await getUserFromDB(address); + const [fcProfile, agentMeta] = await Promise.all([ + getFarcasterProfile(address, dbUser), + fetchAgentMetadata(address, dbUser), + ]); + return { dbUser, fcProfile, agentMeta }; +} + /** * Check if a user exists in the DB (any row, with or without agent_id). * Returns true if a known user, false if completely unknown wallet. diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx index f3423298..a7ce53cc 100644 --- a/src/app/profile/[address]/page.tsx +++ b/src/app/profile/[address]/page.tsx @@ -8,7 +8,7 @@ import { formatUnits, type Address } from "viem"; import Link from "next/link"; import { supabase, type Storyline, type Donation, type TradeHistory, type User } from "../../../../lib/supabase"; import { STORY_FACTORY, RESERVE_LABEL, EXPLORER_URL, MCV2_BOND, PLOT_TOKEN } from "../../../../lib/contracts/constants"; -import { getFarcasterProfile, fetchAgentMetadata, getUserFromDB } from "../../../../lib/actions"; +import { getFullUserProfile } from "../../../../lib/actions"; import { truncateAddress } from "../../../../lib/utils"; import { formatPrice } from "../../../../lib/format"; import { getTokenPrice, mcv2BondAbi, erc20Abi, type TokenPriceInfo } from "../../../../lib/price"; @@ -30,23 +30,18 @@ export default function ProfilePage() { const [tab, setTab] = useState("stories"); - // DB user data (cached profiles) - const { data: dbUser } = useQuery({ - queryKey: ["db-user", address], - queryFn: () => getUserFromDB(address), + // Unified profile fetch: single DB lookup, derives FC + agent from shared result + const { data: fullProfile, isLoading: profileLoading } = useQuery({ + queryKey: ["full-profile", address], + queryFn: () => getFullUserProfile(address), }); - const { data: fcProfile, isLoading: fcLoading } = useQuery({ - queryKey: ["fc-profile", address], - queryFn: () => getFarcasterProfile(address), - }); - - const { data: agentMeta, isLoading: agentLoading } = useQuery({ - queryKey: ["agent-meta", address], - queryFn: () => fetchAgentMetadata(address), - }); - - const isAgent = !agentLoading && agentMeta !== null && agentMeta !== undefined; + const dbUser = fullProfile?.dbUser ?? null; + const fcProfile = fullProfile?.fcProfile ?? null; + const fcLoading = profileLoading; + const agentMeta = fullProfile?.agentMeta ?? null; + const agentLoading = profileLoading; + const isAgent = !profileLoading && agentMeta !== null && agentMeta !== undefined; // Cumulative claimed royalties (on-chain) const { data: claimedRoyalties } = useQuery({ From aafc753edebfa041c3b0543896294bf0c3e4bbd3 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Mon, 30 Mar 2026 20:35:25 +0100 Subject: [PATCH 2/2] [#635] Fall back to getAgentUserFromDB when preloaded user lacks agent_id Only reuses the preloaded user for agent metadata when it has agent_id. Otherwise falls back to the agent-specific lookup (getAgentUserFromDB) which searches agent_wallet and agent_owner columns. Addresses T2a review feedback on PR #652. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/actions.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/actions.ts b/lib/actions.ts index 02dad879..e2085473 100644 --- a/lib/actions.ts +++ b/lib/actions.ts @@ -42,8 +42,11 @@ export async function fetchAgentMetadata( address: string, preloadedUser?: User | null, ): Promise { - // DB-first: check cached agent data - const dbUser = preloadedUser !== undefined ? preloadedUser : await getAgentUserFromDB(address); + // DB-first: check cached agent data. + // Use preloaded user if it has agent_id; otherwise fall back to agent-specific lookup. + const dbUser = preloadedUser?.agent_id != null + ? preloadedUser + : await getAgentUserFromDB(address); if (dbUser?.agent_id != null) { return { agentId: String(dbUser.agent_id),