feat: Implement a comprehensive reputation system - #67
0xdevcollins merged 5 commits into
Conversation
…s, service logic, data types, and React hooks.
📝 WalkthroughWalkthroughAdds a reputation subsystem: TypeScript types, an in-memory ReputationService, backend API routes for reputation operations (get by user/wallet/me, rate, link-wallet), a typed HTTP client, and React Query hooks for fetching, mutations, caching, and prefetching. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client Component
participant RQ as React Query
participant API as /api/reputation/*
participant Service as ReputationService
rect rgba(100,150,200,0.5)
Note over Client,Service: Fetch contributor reputation
Client->>RQ: useContributorReputation(userId)
RQ->>API: GET /api/reputation/{userId}
API->>Service: getReputation(userId)
Service-->>API: ContributorReputation or null
API-->>RQ: 200 JSON or 404
RQ-->>Client: data / loading state
end
rect rgba(150,100,200,0.5)
Note over Client,Service: Rate contributor mutation
Client->>RQ: mutate(rateData)
RQ->>API: POST /api/reputation/rate
API->>Service: rateContributor(maintainerId, contributorId, rating)
Service-->>API: { success: boolean }
API-->>RQ: { success }
RQ->>RQ: invalidateQueries(REPUTATION_KEYS.user(contributorId))
RQ-->>Client: mutation result
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@app/api/reputation/link-wallet/route.ts`:
- Around line 4-18: The POST handler accepts a userId from the body without
confirming the caller is that user; add an auth identity check before linking
the wallet: retrieve the authenticated caller id from your auth/session (the
same pattern used in rate/route.ts), compare it to the body userId and return
403 if they differ, then perform signature verification (verifyMessage(...) ===
address) and only after both checks call ReputationService.linkWallet(userId,
address); ensure error responses are returned for failed auth or signature
checks and that the final success response is sent only after the service call
completes.
In `@app/api/reputation/me/route.ts`:
- Line 5: The exported handler GET currently declares an unused parameter
request (function GET(request: NextRequest)); remove the unused parameter by
changing the function signature to GET() and update any related type imports if
they only existed for that parameter (e.g., remove NextRequest import if now
unused). Ensure no internal references to request remain in the GET function
body and run/adjust tests or callers expecting the old signature.
In `@app/api/reputation/rate/route.ts`:
- Around line 11-31: The code accepts a non-numeric rating because it only
checks range; change the handling in route.ts so you coerce/parse the incoming
rating and validate it's a finite number before the 1–5 range check: read the
raw rating from the request (e.g., ratingRaw or the existing rating variable),
convert to a Number (or parseFloat), then use Number.isFinite(converted) (or
isFinite) to reject non-numeric/NaN/Infinity values and return 400 if invalid;
only after that perform the existing rating < 1 || rating > 5 check and pass the
numeric value into ReputationService.rateContributor("maintainer-1",
contributorId, rating).
- Around line 7-10: Uncomment and re-enable the authentication guard in route.ts
by calling getCurrentUser() and returning a 403 when the caller is not
authorized; to do that, extend the User type in lib/server-auth.ts to include
either a boolean isMaintainer or a role field (e.g., role: 'maintainer' |
'user') and update getCurrentUser() to populate that field from your user store;
then update the guard in route.ts (the block containing getCurrentUser() and the
commented isMaintainer check) to verify the new field and reject unauthorized
callers before allowing rating logic to proceed.
In `@lib/services/reputation.ts`:
- Around line 44-52: The function calculateReputationScore currently skips
zero-valued multipliers because it uses truthy checks; update the three
conditional checks to explicitly test for presence (e.g., multipliers.complexity
!== undefined && multipliers.complexity !== null) or use Number.isFinite to
ensure zeros are applied and invalid values ignored, so replace the if
(multipliers.complexity) / if (multipliers.speed) / if (multipliers.quality)
lines with explicit existence/finite-number checks before multiplying.
🧹 Nitpick comments (8)
lib/services/reputation.ts (1)
83-87: Normalize wallet addresses to avoid case-mismatch misses.♻️ Suggested refactor
- const user = Object.values(MOCK_REPUTATION_DB).find(u => u.walletAddress === address); + const normalized = address.toLowerCase(); + const user = Object.values(MOCK_REPUTATION_DB).find( + u => u.walletAddress?.toLowerCase() === normalized + );lib/api/reputation.ts (2)
10-12: EncodeuserIdin the path to avoid malformed routes.♻️ Suggested refactor
- return get<ContributorReputation>(`${REPUTATION_ENDPOINT}/${userId}`); + return get<ContributorReputation>(`${REPUTATION_ENDPOINT}/${encodeURIComponent(userId)}`);
14-16: Encode wallet addresses in the path to avoid edge-case routing issues.♻️ Suggested refactor
- return get<ContributorReputation>(`${REPUTATION_ENDPOINT}/wallet/${address}`); + return get<ContributorReputation>(`${REPUTATION_ENDPOINT}/wallet/${encodeURIComponent(address)}`);app/api/reputation/link-wallet/route.ts (1)
12-18: Track incomplete implementation with a TODO or issue.The signature verification and service call are critical for production but are currently stubbed. Consider adding a more explicit TODO comment or opening an issue to track this work, ensuring it doesn't ship incomplete.
Would you like me to open a GitHub issue to track the implementation of signature verification and the service integration?
hooks/use-reputation.ts (4)
30-36: Consider addingenabledflag for unauthenticated users.Unlike
useContributorReputationanduseReputationByWallet, this hook always attempts to fetch. If used before authentication is confirmed, it will make unnecessary API calls that return errors.Suggested improvement
-export const useMyReputation = () => { +export const useMyReputation = (options?: { enabled?: boolean }) => { return useQuery({ queryKey: REPUTATION_KEYS.me(), queryFn: () => reputationApi.fetchMyReputation(), staleTime: 1000 * 60 * 5, + enabled: options?.enabled ?? true, }); };
50-59: Consider invalidating wallet queries after linking.Currently only the
mequery is invalidated. If the user's reputation was previously fetched by wallet address (viauseReputationByWallet), that cached data won't reflect the new wallet association. Consider also invalidating wallet-related queries or using a broader invalidation.Suggested improvement
export const useLinkWallet = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: reputationApi.linkWalletToReputation, - onSuccess: () => { + onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: REPUTATION_KEYS.me() }); + // Also invalidate the newly linked wallet's query + queryClient.invalidateQueries({ queryKey: REPUTATION_KEYS.wallet(variables.address) }); } }); };
12-36: Extract staleTime constant to reduce duplication.The 5-minute stale time is repeated across multiple hooks. Extracting it to a constant improves maintainability.
Suggested improvement
+const REPUTATION_STALE_TIME = 1000 * 60 * 5; // 5 minutes + export const useContributorReputation = (userId: string) => { return useQuery({ queryKey: REPUTATION_KEYS.user(userId), queryFn: () => reputationApi.fetchContributorReputation(userId), enabled: !!userId, - staleTime: 1000 * 60 * 5, // 5 minutes + staleTime: REPUTATION_STALE_TIME, }); };Apply similarly to
useReputationByWallet,useMyReputation, andusePrefetchReputation.
61-71: Consider memoizing the returned prefetch function.The hook returns a new function reference on every render. Wrapping it with
useCallbackwould provide a stable reference, which is helpful if the function is passed as a dependency or prop.Suggested improvement
+import { useCallback } from 'react'; + export const usePrefetchReputation = () => { const queryClient = useQueryClient(); - return (userId: string) => { + return useCallback((userId: string) => { queryClient.prefetchQuery({ queryKey: REPUTATION_KEYS.user(userId), queryFn: () => reputationApi.fetchContributorReputation(userId), staleTime: 1000 * 60 * 5, }); - }; + }, [queryClient]); };
…oints, and improve rating validation and score calculation.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@app/api/reputation/link-wallet/route.ts`:
- Around line 19-27: Replace the mocked isValidSignature in route.ts with real
cryptographic verification: use the incoming signature, address and userId to
reconstruct the signed message (e.g., `Link wallet ${address} to user
${userId}`), call ethers.utils.verifyMessage (or verifyMessage) to recover the
signer, compare the recovered address to the provided address and only proceed
if they match; if verification fails return NextResponse.json({ error: "Invalid
signature" }, { status: 403 }); also validate presence of signature/address
before verification and handle exceptions from verifyMessage to return a 403
error.
In `@lib/services/reputation.ts`:
- Around line 83-87: getReputationByWallet and the reputation storage allow
duplicate and case-sensitive wallet entries; normalize addresses (e.g.,
toLowerCase or use a checksum-normalizer consistently) when storing and
comparing so lookups are case-insensitive and canonical, and prevent duplicate
bindings by rejecting or unbinding any existing ContributorReputation whose
normalized walletAddress matches when linking a wallet. Update
getReputationByWallet to compare normalized addresses against MOCK_REPUTATION_DB
entries, ensure any methods that write walletAddress (e.g., the functions around
lines 99-109 that assign walletAddress) store the normalized form, and add a
uniqueness check in the wallet-binding flow to refuse or reassign existing
records referencing the same normalized address.
🧹 Nitpick comments (1)
app/api/reputation/rate/route.ts (1)
41-44: Use the authenticated user id instead of a hard-coded maintainer id.
This preserves auditability once the service persists ratings.♻️ Suggested change
- const success = await ReputationService.rateContributor("maintainer-1", contributorId, numericRating); + const success = await ReputationService.rateContributor(user.id, contributorId, numericRating);
…ormalization, duplicate detection, and detailed error responses.
This pull request introduces a comprehensive reputation system prototype, including backend API endpoints, a reputation service, type definitions, and frontend React hooks for fetching and mutating reputation data. The changes provide a foundation for contributor reputation profiles, wallet linking, rating contributors, and fetching reputation by user or wallet.
The most important changes are:
Backend API Endpoints:
[userId]), by wallet (wallet/[address]), for the current user (me), rating contributors (rate), and linking wallets (link-wallet). These endpoints handle validation, error responses, and call the service layer for data. (app/api/reputation/[userId]/route.tsR1-R27, app/api/reputation/wallet/[address]/route.tsR1-R27, [1] [2] [3]Reputation Service Logic:
ReputationServicewith methods for calculating scores, determining tiers and progress, fetching reputation by user or wallet, and rating contributors. Uses an in-memory mock database for prototyping.Type Definitions:
ContributorReputation,ReputationTier,BountyStats,ReputationBreakdown, and input/output types for rating and history.Frontend API Integration:
reputationApimodule for interacting with the backend reputation endpoints, supporting fetch, rate, and link wallet operations.React Query Hooks:
use-reputation.tshooks for fetching and mutating reputation data in the frontend, including caching, invalidation, and prefetching strategies for user, wallet, and self reputation.closes #64
Summary by CodeRabbit
New Features
New Integrations
Types
Other
✏️ Tip: You can customize this high-level summary in your review settings.