perf: creator Dashboard 2.0 – Unified Library, Analytics & Draft Management#446
perf: creator Dashboard 2.0 – Unified Library, Analytics & Draft Management#446bibhupradhanofficial wants to merge 1 commit into
Conversation
📝 WalkthroughWalkthroughAdds four new creator API routes for analytics, draft management, and NFT operations; reimplements the dashboard as a multi-tab interface with cloud data integration; introduces a versioned Draft data model with AI metadata; and refactors supporting utility functions for blockchain and local draft management. Changes
Sequence DiagramsequenceDiagram
participant Client as Dashboard Client
participant API as Analytics Route
participant DB as MongoDB
participant Service as Aggregation Logic
Client->>API: GET /api/creator/analytics?wallet=0x...
API->>API: Validate wallet address
API->>DB: Query stories by wallet<br/>(title, status, updatedAt, nftTokenId)
DB-->>API: Story records
API->>DB: Fetch UserInteraction records<br/>(VIEW, LIKE, BOOKMARK)
DB-->>API: Interaction data
API->>Service: Aggregate interactions by story
Service->>Service: Calculate per-story metrics<br/>(views, likes, bookmarks, mints)
Service-->>API: Aggregated results
API->>Service: Compute totals across all stories
Service-->>API: Overall totals
API-->>Client: JSON: { totals, rows[] }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
app/dashboard/page.tsx (1)
301-303: Consider lazy-loading tab data instead of fetching everything at connect time.Fetching analytics, NFTs, and cloud drafts together increases initial load and backend pressure. Deferring NFTs/cloud drafts until their tabs are opened would improve perceived performance.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/dashboard/page.tsx` around lines 301 - 303, Currently fetchAnalytics(), fetchNfts(), and fetchCloudDrafts() are all called on connect which causes heavy initial load; change this to lazy-load per-tab by invoking fetchNfts() and fetchCloudDrafts() only when their respective tab becomes active (e.g., on tab click or when activeTab state changes) while keeping fetchAnalytics() on connect if needed; add guards in the tab handlers or in useEffect tied to activeTab to ensure each fetch runs only once (use flags like hasFetchedNfts / hasFetchedCloudDrafts) and update the UI loading state accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/api/creator/analytics/route.ts`:
- Around line 18-21: The route currently calls Story.find(...) and loads all
matching stories into stories, causing unbounded memory/CPU for large wallets;
modify the handler that calls Story.find to enforce a hard cap (e.g., limit
parameter) and add pagination/cursor support by accepting limit and cursor/query
params, applying .limit(limit) and a cursor filter (e.g., updatedAt/_id >
cursor) to the query, and return the next-cursor along with the page of stories
so downstream aggregation uses only that page; update the code paths that
aggregate over the returned story IDs to operate on the paged slice rather than
the entire set (refer to the Story.find(...).select(...).sort(...) usage and any
aggregation code that consumes stories).
In `@app/api/creator/drafts/`[draftKey]/route.ts:
- Around line 29-31: The code uses Date.now() as a fallback when
draft.current?.updatedAt is missing (variable currentUpdatedAt) — this makes
reads non-deterministic; replace the Date.now() fallback with a stable
alternative such as null or the top-level draft.updatedAt value instead, and
apply the same change to the other occurrence (the similar fallback at the
second site handling previous/other updatedAt around lines where
draft.current?.updatedAt is read). Ensure consumers of currentUpdatedAt (and the
other updatedAt variable) can accept null or the top-level draft.updatedAt and
adjust any downstream comparisons accordingly.
In `@app/api/creator/drafts/route.ts`:
- Around line 81-86: The pagination object in the drafts route can yield pages =
0 when total is 0; update the pages computation in the pagination return (where
pagination: { page, limit, total, pages: ... } is built in
app/api/creator/drafts/route.ts) to normalize to at least 1, e.g. replace
Math.ceil(total / limit) with Math.max(1, Math.ceil(total / limit)) so
paginators never receive 0 pages.
- Around line 18-20: The GET handlers in app/api/creator/drafts/route.ts and
app/api/creator/drafts/[draftKey]/route.ts currently trust a client-supplied
wallet query and filter by ownerWallet; change them to authenticate the caller
(via your existing session/JWT middleware or auth util) inside the GET handler,
derive the caller's wallet from the authenticated session, and reject the
request if no authenticated wallet is present; then use that authenticated
wallet to query drafts (instead of the query param) and for single-draft access
verify the draft.ownerWallet === authenticatedWallet (or return 403) when
resolving the draft by draftKey; remove or ignore the client-supplied wallet
query and add clear 401/403 responses for unauthenticated/unauthorized callers.
In `@app/api/creator/nfts/route.ts`:
- Around line 36-60: The on-chain check loop inside the candidates.map (where
txHash, tokenId, syncStatus are computed and checkTxStatus is called) must be
made bounded and fail-fast: wrap each checkTxStatus(txHash) call with a timeout
(e.g., reject after a few seconds) and run the checks with a concurrency limiter
or in small batches (instead of firing unbounded Promise.all across all
stories), and replace the empty catch with logging/propagating the error so
operational failures are visible; adjust logic to treat timed-out/rejected
checks as 'pending' or 'missing' as appropriate and still allow tokenId to be
updated when a successful response returns.
In `@app/dashboard/page.tsx`:
- Around line 111-133: The memoized localDrafts (useMemo block computing
listDraftRecords) can become stale because external mutations via
clearDraftRecord() and upsertDraftRecord() don't trigger the memo to recompute;
update the component to depend on a reactive signal that changes when drafts
storage mutates (e.g., introduce a draftsVersion state or useDraftsSubscription
hook) and include that signal in the useMemo dependency array along with
draftPipelineFilter and draftQuery so listDraftRecords(...) is re-run after
clearDraftRecord/upsertDraftRecord; reference the localDrafts useMemo,
listDraftRecords, clearDraftRecord, and upsertDraftRecord when adding the
reactive trigger.
---
Nitpick comments:
In `@app/dashboard/page.tsx`:
- Around line 301-303: Currently fetchAnalytics(), fetchNfts(), and
fetchCloudDrafts() are all called on connect which causes heavy initial load;
change this to lazy-load per-tab by invoking fetchNfts() and fetchCloudDrafts()
only when their respective tab becomes active (e.g., on tab click or when
activeTab state changes) while keeping fetchAnalytics() on connect if needed;
add guards in the tab handlers or in useEffect tied to activeTab to ensure each
fetch runs only once (use flags like hasFetchedNfts / hasFetchedCloudDrafts) and
update the UI loading state accordingly.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (13)
app/api/creator/analytics/route.tsapp/api/creator/drafts/[draftKey]/route.tsapp/api/creator/drafts/route.tsapp/api/creator/nfts/route.tsapp/dashboard/page.tsxcomponents/dashboard/analytics/page.tsxcomponents/royalty/revenue-chart.tsxcomponents/royalty/royalty-config-form.tsxlib/blockchain.tslib/royalty-service.tslib/story-draft-manager.tsmodels/Draft.tstests/ai-security.test.ts
| const stories = await Story.find({ authorWallet: wallet }) | ||
| .select({ _id: 1, title: 1, status: 1, updatedAt: 1, nftTokenId: 1, nftTxHash: 1 }) | ||
| .sort({ updatedAt: -1 }) | ||
| .lean(); |
There was a problem hiding this comment.
Bound this route to avoid unbounded query and aggregation load.
The current flow fetches all stories and aggregates over all IDs in one request. For large wallets, this can degrade latency and memory usage significantly. Add an enforced limit (and ideally pagination/cursor support).
💡 Proposed hard-cap fix
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const wallet = (searchParams.get('wallet') || '').trim().toLowerCase();
+ const limit = Math.min(
+ Math.max(Number(searchParams.get('limit') || 200), 1),
+ 500
+ );
@@
const stories = await Story.find({ authorWallet: wallet })
.select({ _id: 1, title: 1, status: 1, updatedAt: 1, nftTokenId: 1, nftTxHash: 1 })
.sort({ updatedAt: -1 })
+ .limit(limit)
.lean();Also applies to: 30-45
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/api/creator/analytics/route.ts` around lines 18 - 21, The route currently
calls Story.find(...) and loads all matching stories into stories, causing
unbounded memory/CPU for large wallets; modify the handler that calls Story.find
to enforce a hard cap (e.g., limit parameter) and add pagination/cursor support
by accepting limit and cursor/query params, applying .limit(limit) and a cursor
filter (e.g., updatedAt/_id > cursor) to the query, and return the next-cursor
along with the page of stories so downstream aggregation uses only that page;
update the code paths that aggregate over the returned story IDs to operate on
the paged slice rather than the entire set (refer to the
Story.find(...).select(...).sort(...) usage and any aggregation code that
consumes stories).
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
| if (!wallet || !VALIDATION_PATTERNS.walletAddress.test(wallet)) { | ||
| return NextResponse.json({ error: 'Invalid wallet address' }, { status: 400 }); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Checking for middleware files ==="
fd 'middleware.ts' --type f
echo
echo "=== Listing creator API route files ==="
fd 'route.ts' 'app/api/creator' --type f
echo
echo "=== Searching for auth/session verification in creator APIs ==="
rg -n --type=ts -C3 'getServerSession|auth\(|verify|jwt|clerk|siwe|signature|session' 'app/api/creator' 2>/dev/null || echo "No auth patterns found"
echo
echo "=== Checking wallet-param based access in draft routes ==="
rg -n --type=ts -C3 'searchParams\.get.*wallet|ownerWallet|walletAddress' 'app/api/creator/drafts' 2>/dev/null || echo "Pattern not found, checking entire file"
echo
echo "=== Full content of drafts/route.ts to understand auth context ==="
wc -l app/api/creator/drafts/route.ts && head -50 app/api/creator/drafts/route.tsRepository: IndieHub25/GroqTales
Length of output: 6903
Implement authentication and caller-ownership verification for draft access.
The GET endpoints at app/api/creator/drafts/route.ts (lines 18-20, 24-25) and app/api/creator/drafts/[draftKey]/route.ts (lines 12, 15-17, 24) accept a wallet address as a query parameter and retrieve drafts with matching ownerWallet, but provide no authentication or ownership verification. This allows any caller to access another creator's drafts by passing a valid wallet address.
Instead of filtering by client-supplied wallet, these endpoints must:
- Authenticate the caller (via session, JWT, or other mechanism)
- Verify the caller owns the wallet they're querying, or reject access
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/api/creator/drafts/route.ts` around lines 18 - 20, The GET handlers in
app/api/creator/drafts/route.ts and app/api/creator/drafts/[draftKey]/route.ts
currently trust a client-supplied wallet query and filter by ownerWallet; change
them to authenticate the caller (via your existing session/JWT middleware or
auth util) inside the GET handler, derive the caller's wallet from the
authenticated session, and reject the request if no authenticated wallet is
present; then use that authenticated wallet to query drafts (instead of the
query param) and for single-draft access verify the draft.ownerWallet ===
authenticatedWallet (or return 403) when resolving the draft by draftKey; remove
or ignore the client-supplied wallet query and add clear 401/403 responses for
unauthenticated/unauthorized callers.
There was a problem hiding this comment.
@bibhupradhanofficial implement authentication with already set up supabase setup
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
| const payload = await Promise.all(candidates.map(async (story: any) => { | ||
| const txHash = story.nftTxHash || undefined; | ||
| let tokenId = story.nftTokenId || undefined; | ||
| const status = story.status || 'draft'; | ||
| let syncStatus: 'missing' | 'pending' | 'confirmed' | 'failed' = | ||
| status === 'failed' | ||
| ? 'failed' | ||
| : tokenId | ||
| ? 'confirmed' | ||
| : txHash || status === 'publishing' | ||
| ? 'pending' | ||
| : 'missing'; | ||
|
|
||
| if (txHash && syncStatus === 'pending') { | ||
| try { | ||
| const chainStatus = await checkTxStatus(txHash); | ||
| if (chainStatus.status === 'confirmed') { | ||
| syncStatus = 'confirmed'; | ||
| tokenId = tokenId || (chainStatus as any).tokenId || undefined; | ||
| } else if (chainStatus.status === 'reverted') { | ||
| syncStatus = 'failed'; | ||
| } | ||
| } catch { | ||
| } | ||
| } |
There was a problem hiding this comment.
Guard on-chain checks with bounded execution and timeout.
This endpoint can fire one blockchain status request per story in parallel, with no timeout. Under larger wallets, this can overload RPC and tie up request handling. Also, silent catch hides operational failures.
🛡️ Safer pattern (timeout + controlled execution)
- const payload = await Promise.all(candidates.map(async (story: any) => {
+ const withTimeout = <T,>(p: Promise<T>, ms: number) =>
+ Promise.race([
+ p,
+ new Promise<never>((_, reject) =>
+ setTimeout(() => reject(new Error('tx-status-timeout')), ms)
+ ),
+ ]);
+
+ const payload = [];
+ for (const story of candidates) {
@@
- if (txHash && syncStatus === 'pending') {
+ if (txHash && syncStatus === 'pending') {
try {
- const chainStatus = await checkTxStatus(txHash);
+ const chainStatus = await withTimeout(checkTxStatus(txHash), 5000);
if (chainStatus.status === 'confirmed') {
syncStatus = 'confirmed';
tokenId = tokenId || (chainStatus as any).tokenId || undefined;
} else if (chainStatus.status === 'reverted') {
syncStatus = 'failed';
}
- } catch {
+ } catch (error) {
+ console.warn(`[creator/nfts] tx status check failed for ${txHash}:`, error);
}
}
@@
- }));
+ payload.push({
+ storyId: story._id.toString(),
+ title: story.title || 'Untitled',
+ status,
+ ipfsHash: story.ipfsHash || undefined,
+ nftTxHash: txHash,
+ nftTokenId: tokenId,
+ syncStatus,
+ explorer,
+ updatedAt: story.updatedAt ? new Date(story.updatedAt).getTime() : Date.now(),
+ });
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/api/creator/nfts/route.ts` around lines 36 - 60, The on-chain check loop
inside the candidates.map (where txHash, tokenId, syncStatus are computed and
checkTxStatus is called) must be made bounded and fail-fast: wrap each
checkTxStatus(txHash) call with a timeout (e.g., reject after a few seconds) and
run the checks with a concurrency limiter or in small batches (instead of firing
unbounded Promise.all across all stories), and replace the empty catch with
logging/propagating the error so operational failures are visible; adjust logic
to treat timed-out/rejected checks as 'pending' or 'missing' as appropriate and
still allow tokenId to be updated when a successful response returns.
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
| const localDrafts = useMemo(() => { | ||
| const normalizedQuery = draftQuery.trim().toLowerCase(); | ||
| return listDraftRecords((record) => { | ||
| if (draftPipelineFilter !== "all" && record.aiMetadata?.pipelineState !== draftPipelineFilter) { | ||
| return false; | ||
| } | ||
| if (!normalizedQuery) { | ||
| return true; | ||
| } | ||
| const haystack = [ | ||
| record.current?.title, | ||
| record.current?.genre, | ||
| record.storyType, | ||
| record.storyFormat, | ||
| record.draftKey, | ||
| ] | ||
| .filter(Boolean) | ||
| .join(" ") | ||
| .toLowerCase(); | ||
| return haystack.includes(normalizedQuery); | ||
| }); | ||
| }, [draftPipelineFilter, draftQuery]); | ||
|
|
There was a problem hiding this comment.
Local draft mutations won’t reliably refresh the rendered list.
listDraftRecords() is memoized only by query/filter, but clearDraftRecord() and upsertDraftRecord() mutate external storage without updating component state. This can leave stale draft cards visible until an unrelated re-render.
✅ Minimal reactive fix
export default function DashboardPage() {
@@
+ const [draftStoreVersion, setDraftStoreVersion] = useState(0);
@@
const localDrafts = useMemo(() => {
@@
- }, [draftPipelineFilter, draftQuery]);
+ }, [draftPipelineFilter, draftQuery, draftStoreVersion]);
@@
const deleteDraft = (draft: StoryDraftRecord) => {
clearDraftRecord(draft.draftKey);
+ setDraftStoreVersion((v) => v + 1);
toast({
title: "Draft deleted",
description: draft.current?.title?.trim() ? draft.current.title : "Untitled draft",
});
};
@@
const payload = (await response.json()) as StoryDraftRecord;
const imported = upsertDraftRecord(payload);
+ setDraftStoreVersion((v) => v + 1);
openDraft(imported);Also applies to: 330-336, 352-353
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/dashboard/page.tsx` around lines 111 - 133, The memoized localDrafts
(useMemo block computing listDraftRecords) can become stale because external
mutations via clearDraftRecord() and upsertDraftRecord() don't trigger the memo
to recompute; update the component to depend on a reactive signal that changes
when drafts storage mutates (e.g., introduce a draftsVersion state or
useDraftsSubscription hook) and include that signal in the useMemo dependency
array along with draftPipelineFilter and draftQuery so listDraftRecords(...) is
re-run after clearDraftRecord/upsertDraftRecord; reference the localDrafts
useMemo, listDraftRecords, clearDraftRecord, and upsertDraftRecord when adding
the reactive trigger.
There was a problem hiding this comment.
@bibhupradhanofficial update the component state so the list gets refreshed
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
Drago-03
left a comment
There was a problem hiding this comment.
Update with the requested changes and resolve conflicts
Description
Issue Reference (e.g. Fixes #123): ---
Summary of Changes (high-level):
Context / Motivation:
Type of Change
Select the relevant categories:
Technical Checklist
Tick everything that applies. Leave non‑applicable items unchecked.
AI / Application Logic
GROQ_API_KEY.Web3 / Smart Contracts
smart_contractsand they passed.Frontend / UX / Accessibility
aria-*, alt text) and semantic HTML for new UI.Backend / Database
Security & Privacy
.envfiles are committed.Code Quality
npm run lint(or equivalent) and resolved reported issues.anyby default.Testing Evidence
Describe how you tested these changes. Include commands and logs where possible.
If this PR affects UI flows, also describe the manual test steps you followed.
Visual Proof (for UI / UX changes)
Contributor Status
Tick all that apply to you for this PR:
Review & Impact
Breaking Changes
Dependencies
Backward Compatibility / Migrations
Final Acknowledgements (Mandatory or will be marked invalid)
You must check all of the following before requesting review. These are required:
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes