Skip to content

Commit 6aa5942

Browse files
authored
merge pull request #72 from r4topunk/fix/tv-feed-build
fix(tv): skip /api/tv/feed prerender; drop debug logs
2 parents afb9577 + 799cd10 commit 6aa5942

2 files changed

Lines changed: 39 additions & 169 deletions

File tree

src/app/api/tv/feed/route.ts

Lines changed: 36 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@ import {
1010
} from "@/services/farcaster-tv-aggregator";
1111
import { GNARS_CREATOR_COIN, GNARS_ZORA_HANDLE } from "@/lib/config";
1212

13-
// Let Next.js handle caching via ISR — serves stale response instantly while
14-
// revalidating in the background. No more synchronous cache rebuilds blocking requests.
15-
export const revalidate = 3600; // 1 hour
13+
// Dynamic route — never prerendered at build time. CDN handles caching via
14+
// the Cache-Control header on the response (see GET below). Keeps the build
15+
// free of external API calls (Zora, Neynar, subgraph) that can rate-limit.
16+
export const dynamic = "force-dynamic";
17+
export const runtime = "nodejs";
18+
19+
const CACHE_CONTROL_HEADER = "public, s-maxage=3600, stale-while-revalidate=86400";
1620

1721
// Gnars addresses (use centralized config)
1822
const GNARS_COIN_ADDRESS = GNARS_CREATOR_COIN;
@@ -246,8 +250,6 @@ async function fetchCreatorContent(
246250
creators: QualifiedCreator[],
247251
loadedAddresses: Set<string>,
248252
): Promise<TVItemData[]> {
249-
console.log(`[api/tv] Fetching content from ${creators.length} creators...`);
250-
251253
const allItems: TVItemData[] = [];
252254

253255
await runWithConcurrency(
@@ -278,32 +280,24 @@ async function fetchCreatorContent(
278280
allItems.push(item);
279281
}
280282
}
281-
} catch (err) {
282-
console.warn(`[api/tv] Failed to fetch content for ${creator.handle}:`, err);
283+
} catch {
284+
// Skip on error — single creator failure shouldn't break the feed
283285
}
284286
},
285287
MAX_CONCURRENT_COIN_FETCHES,
286288
);
287289

288-
console.log(`[api/tv] Fetched ${allItems.length} items from creators`);
289290
return allItems;
290291
}
291292

292293
/**
293294
* Fetch GNARS-paired coins from subgraph with concurrency limit
294295
*/
295296
async function fetchPairedCoins(loadedAddresses: Set<string>): Promise<TVItemData[]> {
296-
console.log("[api/tv] Fetching GNARS-paired coins...");
297-
298297
try {
299298
const pairedCoins = await fetchGnarsPairedCoins({ first: 100 });
300299

301-
if (!pairedCoins.length) {
302-
console.log("[api/tv] No paired coins in subgraph");
303-
return [];
304-
}
305-
306-
console.log(`[api/tv] Found ${pairedCoins.length} paired coins, fetching details...`);
300+
if (!pairedCoins.length) return [];
307301

308302
const items: TVItemData[] = [];
309303

@@ -338,7 +332,6 @@ async function fetchPairedCoins(loadedAddresses: Set<string>): Promise<TVItemDat
338332
MAX_CONCURRENT_COIN_FETCHES,
339333
);
340334

341-
console.log(`[api/tv] Loaded ${items.length} GNARS-paired coins with media`);
342335
return items;
343336
} catch (err) {
344337
const msg = err instanceof Error ? err.message : String(err);
@@ -351,8 +344,6 @@ async function fetchPairedCoins(loadedAddresses: Set<string>): Promise<TVItemDat
351344
* Fetch Gnars profile content
352345
*/
353346
async function fetchGnarsProfileContent(loadedAddresses: Set<string>): Promise<TVItemData[]> {
354-
console.log("[api/tv] Fetching Gnars profile content...");
355-
356347
try {
357348
const response = await getProfileCoins({
358349
identifier: GNARS_PROFILE_HANDLE,
@@ -389,7 +380,6 @@ async function fetchGnarsProfileContent(loadedAddresses: Set<string>): Promise<T
389380
MAX_CONCURRENT_COIN_FETCHES,
390381
);
391382

392-
console.log(`[api/tv] Loaded ${items.length} from Gnars profile`);
393383
return items;
394384
} catch (err) {
395385
console.warn("[api/tv] Failed to fetch Gnars profile:", err);
@@ -435,8 +425,6 @@ async function mapDroposalToTVItem(droposal: {
435425

436426
export async function GET() {
437427
const startTime = Date.now();
438-
console.log("[api/tv] Starting feed fetch...");
439-
440428
const loadedAddresses = new Set<string>();
441429

442430
try {
@@ -505,35 +493,33 @@ export async function GET() {
505493
});
506494

507495
const elapsed = Date.now() - startTime;
508-
console.log(`[api/tv] Feed ready: ${allItems.length} items in ${elapsed}ms`);
509-
console.log(
510-
`[api/tv] Sources: ${pairedCoins.length} paired, ${creatorContent.length} creators, ${gnarsContent.length} gnars, ${farcasterItems.length} farcaster, ${droposalItems.length} droposals`,
511-
);
512-
console.log(`[api/tv] Farcaster cache: ${farcasterData.cache.source}`);
513-
514-
return NextResponse.json({
515-
items: allItems,
516-
creators: qualifiedCreators.map((c) => ({
517-
handle: c.handle,
518-
avatarUrl: c.avatarUrl,
519-
coinBalance: c.coinBalance,
520-
nftBalance: c.nftBalance,
521-
})),
522-
stats: {
523-
total: allItems.length,
524-
withVideo: allItems.filter((i) => i.videoUrl).length,
525-
withImage: allItems.filter((i) => !i.videoUrl && i.imageUrl).length,
526-
gnarsPaired: pairedCoins.length,
527-
droposals: droposalItems.length,
528-
creatorsCount: qualifiedCreators.length,
529-
farcasterItems: farcasterItems.length,
530-
farcasterCreators: farcasterData.stats.creators,
531-
farcasterCoins: farcasterData.stats.coins,
532-
farcasterNfts: farcasterData.stats.nfts,
496+
497+
return NextResponse.json(
498+
{
499+
items: allItems,
500+
creators: qualifiedCreators.map((c) => ({
501+
handle: c.handle,
502+
avatarUrl: c.avatarUrl,
503+
coinBalance: c.coinBalance,
504+
nftBalance: c.nftBalance,
505+
})),
506+
stats: {
507+
total: allItems.length,
508+
withVideo: allItems.filter((i) => i.videoUrl).length,
509+
withImage: allItems.filter((i) => !i.videoUrl && i.imageUrl).length,
510+
gnarsPaired: pairedCoins.length,
511+
droposals: droposalItems.length,
512+
creatorsCount: qualifiedCreators.length,
513+
farcasterItems: farcasterItems.length,
514+
farcasterCreators: farcasterData.stats.creators,
515+
farcasterCoins: farcasterData.stats.coins,
516+
farcasterNfts: farcasterData.stats.nfts,
517+
},
518+
fetchedAt: new Date().toISOString(),
519+
durationMs: elapsed,
533520
},
534-
fetchedAt: new Date().toISOString(),
535-
durationMs: elapsed,
536-
});
521+
{ headers: { "Cache-Control": CACHE_CONTROL_HEADER } },
522+
);
537523
} catch (error) {
538524
console.error("[api/tv] Feed fetch error:", error);
539525
return NextResponse.json({ error: "Failed to fetch TV feed" }, { status: 500 });

src/services/farcaster-tv-aggregator.ts

Lines changed: 3 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -371,8 +371,6 @@ async function fetchNftHolderCandidates(
371371
return [];
372372
}
373373

374-
console.log(`[farcaster-tv] NFT holder candidates from subgraph: ${holders.length}`);
375-
376374
// Resolve wallets to Zora profiles
377375
const candidates: CandidateCreator[] = [];
378376

@@ -408,10 +406,6 @@ async function fetchNftHolderCandidates(
408406
MAX_CONCURRENT_PROFILE_FETCHES,
409407
);
410408

411-
console.log(
412-
`[farcaster-tv] NFT holder candidates with Zora profiles: ${candidates.length}`,
413-
);
414-
415409
return candidates;
416410
}
417411

@@ -462,10 +456,6 @@ async function fetchQualifiedCreators(): Promise<QualifiedCreator[]> {
462456

463457
const allCandidates = [...coinCandidates, ...nftCandidates];
464458

465-
console.log(
466-
`[farcaster-tv] Candidates: ${coinCandidates.length} from coin holders, ${nftCandidates.length} from NFT holders`,
467-
);
468-
469459
// Resolve wallets for all candidates (coin candidates may already have wallets from prior step)
470460
const candidatesWithWallets = await fetchProfileWallets(allCandidates);
471461

@@ -504,7 +494,6 @@ async function fetchQualifiedCreators(): Promise<QualifiedCreator[]> {
504494
const allowlistMissing = GNARS_CREATOR_ALLOWLIST.filter((h) => !qualifiedHandles.has(h));
505495

506496
if (allowlistMissing.length > 0) {
507-
console.log(`[farcaster-tv] Adding ${allowlistMissing.length} allowlisted creators:`, allowlistMissing);
508497
for (const handle of allowlistMissing) {
509498
qualifiedCreators.push({
510499
handle,
@@ -516,16 +505,6 @@ async function fetchQualifiedCreators(): Promise<QualifiedCreator[]> {
516505
}
517506
}
518507

519-
console.log("[farcaster-tv][qualified-creators] Total:", qualifiedCreators.length);
520-
for (const creator of qualifiedCreators) {
521-
console.log("[farcaster-tv][qualified-creators] Creator:", {
522-
handle: creator.handle,
523-
wallets: creator.wallets,
524-
coinBalance: creator.coinBalance,
525-
nftBalance: creator.nftBalance,
526-
});
527-
}
528-
529508
return qualifiedCreators;
530509
}
531510

@@ -540,55 +519,23 @@ async function fetchFarcasterMatches(
540519
if (creators.length === 0) return [];
541520

542521
const wallets = creators.flatMap((creator) => creator.wallets);
543-
console.log("[farcaster-tv][farcaster-match-start] Qualified creators:", creators.length);
544-
console.log("[farcaster-tv][farcaster-match-start] Wallets to Neynar:", wallets);
545522
const profilesByAddress = useCache
546523
? await fetchFarcasterProfilesByAddress(wallets)
547524
: await fetchFarcasterProfilesByAddressUncached(wallets);
548525

549-
console.log("[farcaster-tv][farcaster-match-start] Neynar wallet->profile:", profilesByAddress);
550-
console.log(
551-
"[farcaster-tv] Farcaster wallet matches:",
552-
wallets.map((wallet) => {
553-
const profile = profilesByAddress[wallet.toLowerCase()];
554-
return {
555-
wallet,
556-
fid: profile?.fid ?? null,
557-
username: profile?.username ?? null,
558-
followerCount: profile?.followerCount ?? null,
559-
};
560-
}),
561-
);
562-
563526
const matches: FarcasterCreatorMatch[] = [];
564527

565528
for (const creator of creators) {
566529
const profiles = creator.wallets
567530
.map((wallet) => profilesByAddress[wallet.toLowerCase()])
568531
.filter((profile): profile is FarcasterProfile => Boolean(profile));
569532

570-
if (profiles.length === 0) {
571-
console.log("[farcaster-tv] Skipping creator (no Farcaster match):", {
572-
handle: creator.handle,
573-
wallets: creator.wallets,
574-
});
575-
continue;
576-
}
533+
if (profiles.length === 0) continue;
577534

578535
const bestProfile = profiles.sort((a, b) => b.followerCount - a.followerCount)[0];
579-
matches.push({
580-
profile: bestProfile,
581-
});
536+
matches.push({ profile: bestProfile });
582537
}
583538

584-
console.log(
585-
"[farcaster-tv][farcaster-match-start] Final matches (fids):",
586-
matches.map((match) => ({
587-
fid: match.profile.fid,
588-
username: match.profile.username,
589-
})),
590-
);
591-
592539
return matches;
593540
}
594541

@@ -753,23 +700,7 @@ async function fetchFarcasterHoldings(
753700
if (matches.length === 0) return { items: [], stats: { creators: 0, coins: 0, nfts: 0 } };
754701

755702
const rankedAll = rankByFollowerCount(matches);
756-
console.log(
757-
"[farcaster-tv] Farcaster ranking (pre-limit):",
758-
rankedAll.map((match, index) => ({
759-
rank: index + 1,
760-
fid: match.profile.fid,
761-
username: match.profile.username,
762-
followerCount: match.profile.followerCount,
763-
})),
764-
);
765-
766703
const ranked = rankedAll.slice(0, MAX_FARCASTER_USERS);
767-
if (ranked.length !== rankedAll.length) {
768-
console.log("[farcaster-tv] Farcaster ranking truncated:", {
769-
total: rankedAll.length,
770-
limit: MAX_FARCASTER_USERS,
771-
});
772-
}
773704
const farcasterLoadedKeys = new Set<string>();
774705

775706
const items: TVItemData[] = [];
@@ -840,47 +771,10 @@ async function fetchFarcasterHoldings(
840771
nftCount++;
841772
}
842773

843-
const creatorCoinItems = coinItems.filter(Boolean).length;
844-
const creatorNftItems = nftItems.length;
845-
if (creatorCoinItems === 0 && creatorNftItems === 0) {
846-
console.log("[farcaster-tv] Skipping creator (no eligible items):", {
847-
fid: match.profile.fid,
848-
username: match.profile.username,
849-
followerCount: match.profile.followerCount,
850-
coinsFetched: coins.length,
851-
nftsFetched: nfts.length,
852-
coinsAfterFilter: topCoins.length,
853-
nftsAfterFilter: topNfts.length,
854-
});
855-
} else {
856-
console.log("[farcaster-tv] Creator included:", {
857-
fid: match.profile.fid,
858-
username: match.profile.username,
859-
followerCount: match.profile.followerCount,
860-
coinsFetched: coins.length,
861-
nftsFetched: nfts.length,
862-
coinsAfterFilter: topCoins.length,
863-
nftsAfterFilter: topNfts.length,
864-
coinItems: creatorCoinItems,
865-
nftItems: creatorNftItems,
866-
});
867-
}
868774
},
869775
MAX_CONCURRENT_FARCASTER_FETCHES,
870776
);
871777

872-
console.log(
873-
"[farcaster-tv] Final items:",
874-
items.map((item) => ({
875-
id: item.id,
876-
farcasterFid: item.farcasterFid ?? null,
877-
farcasterUsername: item.farcasterUsername ?? null,
878-
farcasterType: item.farcasterType ?? null,
879-
creator: item.creator,
880-
coinAddress: item.coinAddress ?? null,
881-
})),
882-
);
883-
884778
return { items, stats: { creators: ranked.length, coins: coinCount, nfts: nftCount } };
885779
}
886780

@@ -930,20 +824,10 @@ const getCachedFarcasterTVPayload = unstable_cache(
930824
* - unstable_cache provides cross-request caching with a 15-minute revalidation window.
931825
*/
932826
export const getFarcasterTVData = reactCache(async (): Promise<FarcasterTVData> => {
933-
const callStart = Date.now();
934827
const lruHit = farcasterTvLru.get(FARCASTER_TV_CACHE_KEY);
935-
936-
if (lruHit) {
937-
const elapsed = Date.now() - callStart;
938-
console.log(`[farcaster-tv] LRU hit in ${elapsed}ms`);
939-
return { ...lruHit, cache: { source: "lru" } };
940-
}
828+
if (lruHit) return { ...lruHit, cache: { source: "lru" } };
941829

942830
const payload = await getCachedFarcasterTVPayload();
943831
farcasterTvLru.set(FARCASTER_TV_CACHE_KEY, payload);
944-
945-
const elapsed = Date.now() - callStart;
946-
console.log(`[farcaster-tv] Cached fetch in ${elapsed}ms (build ${payload.durationMs}ms)`);
947-
948832
return { ...payload, cache: { source: "next" } };
949833
});

0 commit comments

Comments
 (0)