Skip to content

[#563] Add users table with SteemHunt API + cached profiles#565

Merged
realproject7 merged 5 commits intomainfrom
task/563-users-table
Mar 26, 2026
Merged

[#563] Add users table with SteemHunt API + cached profiles#565
realproject7 merged 5 commits intomainfrom
task/563-users-table

Conversation

@realproject7
Copy link
Copy Markdown
Owner

Summary

  • Users table (migration 00027) with all Farcaster identity, X/Twitter stats, Quotient score, and freshness-tracking columns
  • SteemHunt API client (lib/farcaster-indexer.ts) as primary data source (free) with circuit breaker (5 failures → open 1 min), retry (3 attempts, exponential backoff), 1-hour in-memory cache, and in-flight deduplication
  • Neynar fallback — existing lib/farcaster.ts used when SteemHunt is unavailable
  • Quotient client (lib/quotient.ts) — fetches engagement/reputation scores with 7-day TTL
  • X stats client (lib/x-stats.ts) — fetches Twitter profile stats via twitterapi.io
  • 3 new API routes:
    • POST /api/user/register-by-wallet — called on wallet connect, upserts all fields, 5-min freshness check
    • POST /api/user/onboard — manual refresh with server-enforced 5-min cooldown
    • POST /api/user/x-stats — fetch X stats with 5-min cooldown
  • Profile page reads from DB first (via getUserFromDB server action), falls back to live API
  • Profile header now displays X handle link, Quotient score, Farcaster follower counts
  • Refresh Profile button on own profile with cooldown enforcement and error display
  • useConnectedIdentity hook now fires register-by-wallet on wallet connect (fire-and-forget)

Reference

  • Schema pattern from ~/Projects/dropcast (supabase-schema.sql, add-steemhunt-columns.sql, BE-148-add-x-stats-columns.sql, add-quotient-score.sql)
  • Client patterns from ~/Projects/dropcast/lib/farcaster-indexer.ts and lib/twitterapi.ts

Fixes #563

Test plan

  • Run migration 00027 against Supabase
  • Connect wallet → verify user row created in users table
  • Visit own profile → verify data displayed from DB
  • Click "Refresh Profile" → verify data refreshed (and cooldown enforced)
  • Visit profile of user with X handle → verify X link shown
  • Verify build passes (npm run build)

…utton

- Create users table (Supabase migration) with all Farcaster, X/Twitter,
  and Quotient score columns
- Add farcaster-indexer.ts with circuit breaker, retry, in-memory cache
- Add quotient.ts client for reputation scores
- Add x-stats.ts for X/Twitter profile stats via twitterapi.io
- POST /api/user/register-by-wallet — upserts on wallet connect
- POST /api/user/onboard — manual refresh with 5-min cooldown
- POST /api/user/x-stats — fetch X stats with 5-min cooldown
- Profile page reads from DB first, falls back to live API
- Display X handle, Quotient score, follower counts on profile
- Refresh Profile button on own profile with cooldown enforcement

Fixes #563

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
plotlink Ignored Ignored Mar 26, 2026 3:25pm

Request Review

Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

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

Verdict: REQUEST CHANGES

Summary

The SteemHunt/Neynar caching pipeline is mostly in place, but two required behaviors are still missing from the end-to-end user-profile flow.

Findings

  • [medium] The main registration/refresh pipeline never populates the new x_* columns, so ordinary wallet-connect and "Refresh Profile" flows leave the new X-stats UI blank.
    • File: src/app/api/user/register-by-wallet/route.ts:96
    • File: src/app/api/user/onboard/route.ts:103
    • File: src/app/profile/[address]/page.tsx:294
    • Suggestion: fetch and persist X stats during register-by-wallet / onboard, or explicitly invoke /api/user/x-stats from those flows before rendering dbUser.x_followers_count.
  • [medium] The cooldown window is only surfaced after a rejected click, so the own-profile page still does not display the current 5-minute cooldown state required by the ticket.
    • File: src/app/profile/[address]/page.tsx:69
    • File: src/app/profile/[address]/page.tsx:307
    • Suggestion: derive the remaining cooldown from dbUser.steemhunt_fetched_at, disable the button while cooling down, and render the countdown immediately on page load.

Decision

Requesting changes because the PR does not yet satisfy the required cached X-stats population and proactive cooldown display on the own-profile UI.

…tract helper

- Cooldown now gates ALL refreshes in /api/user/onboard regardless of
  forceRefresh flag
- Widen quotient_score from DECIMAL(5,4) to DECIMAL(10,4) to handle
  any score scale
- Extract shared buildUserData() helper to lib/user-data.ts to reduce
  duplication between register-by-wallet and onboard routes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

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

T2b approves. All three prior findings addressed: cooldown now gates all refreshes unconditionally, quotient_score widened to DECIMAL(10,4), and shared buildUserData() helper extracted. T2a has separate open findings — deferring to T2a on those.

… cooldown timer

- buildUserData() now fetches X stats via twitterapi.io when twitter
  handle is available (non-blocking on failure)
- Profile page shows proactive cooldown timer (mm:ss) on Refresh button
  computed from steemhunt_fetched_at, updates every second
- Button disabled during cooldown period

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

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

Verdict: REQUEST CHANGES

Summary

The new X-stats population path is in place, but the proactive cooldown label still reports the wrong remaining time.

Findings

  • [medium] The countdown formatter rounds both the minute and second components up independently, so the button text overstates the remaining cooldown for almost the entire window. For example, with about 4m 32s left it renders Refresh (5m 32s).
    • File: src/app/profile/[address]/page.tsx:348
    • Suggestion: derive a single rounded total-seconds value first, then split it with Math.floor(totalSeconds / 60) and totalSeconds % 60 so the displayed countdown matches the enforced cooldown.

Decision

Requesting changes because the own-profile cooldown display is still inaccurate, and the ticket explicitly requires that cooldown state to be shown clearly.

Math.ceil on minutes overstated remaining time (e.g. 4m32s showed as
5m32s). Now uses Math.floor for minutes and Math.ceil for seconds so
the display matches the actual lockout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

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

Verdict: REQUEST CHANGES

Summary

The cooldown label changed, but the formatter is still incorrect near minute boundaries.

Findings

  • [medium] src/app/profile/[address]/page.tsx:348 now floors minutes and ceils seconds independently. That still produces invalid countdown text such as Refresh (4m 60s) when about 4m 59.1s remain. The display needs to derive one rounded total-seconds value first and then split it into minutes/seconds.
    • File: src/app/profile/[address]/page.tsx:348
    • Suggestion: compute const totalSeconds = Math.ceil(cooldownRemaining / 1000) (or Math.floor, if you prefer), then render Math.floor(totalSeconds / 60) and totalSeconds % 60.

Decision

Requesting changes because the own-profile cooldown display is still not reliably accurate.

Ceil total milliseconds to seconds once, then floor-divide for minutes
and modulo for seconds. Prevents invalid "4m 60s" at minute boundaries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

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

Verdict: APPROVE

Summary

The remaining cooldown-display issue is fixed. The countdown now derives one rounded total-seconds value before splitting into minutes and seconds, so the own-profile cooldown state matches the actual lockout window.

Findings

  • None.

Decision

Approving because the X-stats population path and proactive cooldown display now satisfy the Batch 24 / #563 requirements.

@realproject7 realproject7 merged commit 8fbc9cb into main Mar 26, 2026
5 checks passed
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.

Add users table with SteemHunt API — cache profile data, X account, refresh button

2 participants