Skip to content

feat: Implement a comprehensive reputation system - #67

Merged
0xdevcollins merged 5 commits into
boundlessfi:mainfrom
Dprof-in-tech:feat-implement-contributor-reputaion-system
Jan 30, 2026
Merged

0xdevcollins merged 5 commits into
boundlessfi:mainfrom
Dprof-in-tech:feat-implement-contributor-reputaion-system

Conversation

@Dprof-in-tech

@Dprof-in-tech Dprof-in-tech commented Jan 30, 2026

Copy link
Copy Markdown
Contributor

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:

Reputation Service Logic:

  • Implemented ReputationService with 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:

  • Defined detailed TypeScript types for reputation data, including ContributorReputation, ReputationTier, BountyStats, ReputationBreakdown, and input/output types for rating and history.

Frontend API Integration:

  • Created reputationApi module for interacting with the backend reputation endpoints, supporting fetch, rate, and link wallet operations.

React Query Hooks:

  • Added use-reputation.ts hooks 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

    • Contributor reputation system with scores, tiers, progress, breakdowns and bounty metrics
    • Endpoints to fetch reputation by user, by wallet, or for the current authenticated user
    • Ability to link a wallet to a reputation profile
    • Ability to rate contributors on completed bounties
  • New Integrations

    • Client-side hooks and API helpers for fetching, prefetching, rating and linking wallets
  • Types

    • New reputation data models and types for profiles, tiers, bounties and rating inputs
  • Other

    • Mock in-memory reputation service for demo/testing (non-persistent)

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 30, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
API Routes
app/api/reputation/[userId]/route.ts, app/api/reputation/me/route.ts, app/api/reputation/wallet/[address]/route.ts, app/api/reputation/rate/route.ts, app/api/reputation/link-wallet/route.ts
Added Next.js App Router handlers: GET reputation by user, GET my reputation (auth), GET by wallet, POST rate contributor (validation/auth), POST link wallet (auth + signature check). Return JSON responses, 400/401/403/404/500 handling, and console logging.
Service Layer
lib/services/reputation.ts
New in-memory ReputationService with score calculation, tier mapping, tier progress, simulated async fetches by user/wallet, simulated rating and wallet-linking methods, and mock data.
Type Definitions
types/reputation.ts
New types: ReputationTier, ReputationBreakdown, BountyStats, BountyCompletionRecord, ContributorReputation, RateContributorInput, ReputationHistoryParams, ReputationHistoryResponse.
API Client
lib/api/reputation.ts
New exported reputationApi with typed wrappers: fetchContributorReputation, fetchContributorByWallet, fetchMyReputation, rateContributor, linkWalletToReputation (uses existing get/post helpers).
React Query Hooks
hooks/use-reputation.ts
Added REPUTATION_KEYS and hooks: useContributorReputation, useReputationByWallet, useMyReputation, useRateContributor, useLinkWallet, usePrefetchReputation. Configures 5-minute stale times and invalidates relevant queries on mutations.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I hopped through types and routes today,

wallets tied and scores in play,
hooks that cache and services hum,
ratings counted, tiers become,
a cheerful hop — reputation’s way.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and specifically describes the main change: implementing a comprehensive reputation system with all the related features.
Linked Issues check ✅ Passed All acceptance criteria from issue #64 are comprehensively implemented: TypeScript types, API service layer, React Query hooks, backend API endpoints, and reputation calculation service.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to implementing the reputation system as defined in issue #64; no extraneous modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: Encode userId in 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 adding enabled flag for unauthenticated users.

Unlike useContributorReputation and useReputationByWallet, 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 me query is invalidated. If the user's reputation was previously fetched by wallet address (via useReputationByWallet), 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, and usePrefetchReputation.


61-71: Consider memoizing the returned prefetch function.

The hook returns a new function reference on every render. Wrapping it with useCallback would 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]);
 };

Comment thread app/api/reputation/link-wallet/route.ts Outdated
Comment thread app/api/reputation/me/route.ts Outdated
Comment thread app/api/reputation/rate/route.ts Outdated
Comment thread app/api/reputation/rate/route.ts
Comment thread lib/services/reputation.ts
…oints, and improve rating validation and score calculation.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Comment thread app/api/reputation/link-wallet/route.ts
Comment thread lib/services/reputation.ts
…ormalization, duplicate detection, and detailed error responses.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Reputation Data Types, API Layer, and React Query Hooks

2 participants