Replace fetchReputationByUsername's full /users table scan with a scoped username lookup
I maintainer di solito rispondono entro 1 giorno
@Ayom04 ci sta già lavorando.
Dal 16/8/2026.
Valutazione
Questa issue non è ancora stata valutata.
Descrizione
Overview
fetchReputationByUsername, the function powering every single visit to /reputation/[handle] (every public contributor profile page in the app), fetches the entire user table from the backend just to find one row by username:
export async function fetchReputationByUsername(
username: string,
fallback: ReputationProfile | null,
): Promise<ReputationProfile | null> {
try {
const users = await request<(RawUserProfile & { id: string })[]>("/users");
const user = users.find((u) => u.username === username);
if (!user) return fallback;
const snapshot = await request<RawReputationSnapshot | null>(
`/reputation/${user.id}`,
);
return adaptReputation(user, snapshot);
} catch {
return fallback;
}
}
GET /users with no query parameters, no pagination, no filter — the entire platform's user list, downloaded and linear-scanned client-side (well, Server-Component-side) via .find(), purely to translate a username string into an id so a second request can fetch that user's reputation snapshot. This function is also called from a notFound()-triggering path (src/app/reputation/[handle]/page.tsx:14-20), meaning even a mistyped/nonexistent handle — the kind of request a crawler, a broken link, or a typo generates constantly — still pays the cost of downloading the full user table before concluding there's no match.
This doesn't scale. Per the landing page's own marketing copy, the platform already claims "341 contributors" — at that count this is a real but modest overhead; it becomes a serious, compounding cost as the user base grows, given /reputation/[handle] is: (a) a public, unauthenticated, presumably crawlable/shareable page (every contributor has an incentive to link their own profile), (b) rendered fresh on every request (cache: "no-store" is set in the underlying request() helper — see src/lib/api.ts:27-37 — so there's no HTTP-level caching softening this at all), and (c) architecturally guaranteed to keep getting slower over time as the user table grows, independent of how many people are actually viewing any given profile.
Requirements
- Replace the "fetch everyone, filter client-side" pattern with a scoped lookup — either a query-parameterized
/users?username=Xrequest, a dedicated/users/by-username/:usernameendpoint, or whatever shapemergefi-backendalready exposes (or would need to add) for looking up exactly one user by username. This will likely require backend coordination/a corresponding change inmergefi-backend— document that dependency explicitly in the PR rather than silently blocking on it, and consider whether a frontend-side interim mitigation (e.g. caching the full user list in-memory across requests within some short TTL, though this raises its own staleness concerns for a page meant to reflect a contributor's live reputation) is worth doing in the meantime. - Once the lookup is scoped, evaluate whether the subsequent
/reputation/${user.id}fetch can be combined into a single request (e.g. a backend endpoint that resolves username → reputation snapshot directly, avoiding two sequential round-trips for one page render) — this is a secondary, smaller optimization on top of the primary fix, worth doing in the same pass if backend coordination is already happening. - Confirm the
notFound()path for a nonexistent handle no longer requires downloading the full user table before concluding there's no match.
Acceptance Criteria
- Loading
/reputation/[handle]no longer issues a request for the entire, unfiltered/userscollection. - A nonexistent handle still correctly triggers
notFound(), without needing to have fetched more than the minimum data required to determine the user doesn't exist. - The existing behavior — successful profile load, live-vs-fallback mock behavior on fetch failure — is unchanged from the caller's perspective (
ReputationPageshouldn't need to change beyond whatever prop/shape adjustments the new lookup requires). - The PR documents whether this required a corresponding
mergefi-backendchange, and links to or describes that coordination.
Additional Notes
Precise references:
src/lib/api.ts:140-155— the fullfetchReputationByUsernamefunction; the unscopedrequest<...>("/users")call at line 145, the linear.find()at line 146.src/lib/api.ts:27-37— the underlyingrequest()helper, confirmingcache: "no-store"is always set (no HTTP caching layer softens the repeated full-table fetch across requests).src/app/reputation/[handle]/page.tsx:9-20— the only call site, anasyncServer Component that callsfetchReputationByUsername(handle, mockReputationProfiles[handle] ?? null)and immediatelynotFound()s on anullresult — confirming the full-table fetch happens on every single page view, including ones that end in a 404.- Landing page context:
src/lib/mock-data.ts:161-166(platformStats.activeContributors: 341) — the platform's own stated current scale, for context on how large "the entire user table" already claims to be even before further growth.
Edge cases: if the eventual scoped-lookup endpoint is case-sensitive or has different normalization behavior than the current .find((u) => u.username === username) exact-match comparison, verify handle casing/URL-decoding behavior is preserved (GitHub usernames are case-insensitive for uniqueness purposes but are typically stored/displayed with their original casing — confirm the new endpoint's matching semantics don't silently 404 a handle that currently resolves via the exact-match .find()).
Test/reproduction plan: with a mocked request(), assert that resolving /reputation/somehandle issues a request scoped to somehandle (e.g. asserting the mocked fetch was called with a URL containing the username as a query/path parameter) rather than a bare /users call with no parameters; assert a mocked "not found" response from the new scoped endpoint still results in notFound() being triggered by the page.
- Lingua principale
- TypeScript
- Stelle
- 1
- Fork
- 94
- Merge medio
- 1h 2m
- PR unite (30g)
- 25
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di MergeFi/frontend
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
bug testing
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
I maintainer di solito rispondono entro 1 giorno
-
accessibility bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
MergeFi/frontend#835 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di MergeFi/frontend
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
zapcooking/frontend#946 ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
WiliamRosa/cosmos-semantic-platform#40 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
liberation-data/drivine#733 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 64/100
makecindy/cindy#10062 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno