From 90f0e2417e04c198acd572f303d5233e3147297e Mon Sep 17 00:00:00 2001 From: ld3zian <42660576+ld3z@users.noreply.github.com> Date: Tue, 11 Mar 2025 14:09:52 -0700 Subject: [PATCH] huh --- src/lib/api.ts | 39 ++++++++++++----------------- src/lib/db.ts | 67 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 24 deletions(-) diff --git a/src/lib/api.ts b/src/lib/api.ts index 0c409de..c22762a 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,5 +1,5 @@ -import type { Comic, Genre, ComicType } from './types'; -import { getRedisClient } from './db'; +import type { Comic, Genre, ComicType, ChapterDetail } from './types'; +import { getRedisClient, CHAPTER_CACHE_TTL } from './db'; const languageMap = { "en": "English", @@ -139,20 +139,6 @@ interface ComicDetail { }; } -interface ChapterDetail { - id: string; - chap: string; - title: string; - updated_at: string; - md_comics: { - title: string; - slug: string; - md_covers?: { - b2key: string; - }[]; - }; -} - interface ChapterParams { limit?: number; lang?: string; @@ -270,7 +256,6 @@ export async function getChaptersForSlugs( ): Promise { console.log(`Fetching chapters for ${slugs.length} comics`); const redis = await getRedisClient(); - const CACHE_TTL = 60 * 60; const uniqueSlugs = [...new Set(slugs)]; const pipeline = redis.pipeline(); @@ -293,7 +278,7 @@ export async function getChaptersForSlugs( cacheResults.forEach(([err, result], index) => { if (!err && result) { try { - const parsed = JSON.parse(result); + const parsed = JSON.parse(result as string); if (Array.isArray(parsed)) { cachedChapters.push(...parsed); } @@ -351,7 +336,7 @@ export async function getChaptersForSlugs( cacheKey, JSON.stringify(chaps), 'EX', - CACHE_TTL + CHAPTER_CACHE_TTL ); }); @@ -360,10 +345,18 @@ export async function getChaptersForSlugs( // Combine and sort all chapters const allChapters = [...cachedChapters, ...liveChapters]; const deduplicatedChapters = deduplicateChapters(allChapters); - return deduplicatedChapters.sort((a, b) => - parseFloat(b.chap) - parseFloat(a.chap) || - new Date(b.created_at).getTime() - new Date(a.created_at).getTime() - ); + return deduplicatedChapters.sort((a, b) => { + // Ensure the created_at fields are valid dates + const dateA = new Date(a.created_at).getTime(); + const dateB = new Date(b.created_at).getTime(); + + // First try to sort by chapter number + const chapDiff = parseFloat(b.chap) - parseFloat(a.chap); + if (!isNaN(chapDiff)) return chapDiff; + + // Fall back to date if chapter numbers are equal or invalid + return dateB - dateA; + }); } // Add export to the validation function diff --git a/src/lib/db.ts b/src/lib/db.ts index 4cf39ae..e28ea3c 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -1,9 +1,9 @@ import Redis from "ioredis"; import type { FeedMapping } from "./types"; -import { getChaptersForSlugs } from "./api"; // Configuration constants const DEFAULT_CACHE_TTL = 60 * 60 * 24 * 30; // 30 days +export const CHAPTER_CACHE_TTL = 60 * 60; // 1 hour const MAX_RETRIES = 3; const BASE_DELAY_MS = 50; const IS_VERCEL = process.env.VERCEL || false; @@ -216,5 +216,70 @@ export async function getFeedMapping( } } +/** + * Pre-warm the chapter cache for better performance + * This function checks cache status and uses the getChaptersForSlugs function from api.ts + * to fetch and cache data in the background + */ +export async function warmChapterCache( + slugs: string[], + lang: string +): Promise { + try { + console.log(`Warming chapter cache for ${slugs.length} comics in ${lang}`); + const redis = await getRedisClient(); + + // Validate slugs + const validSlugs = slugs.filter(slug => + slug && typeof slug === 'string' && slug.match(/^[a-z0-9-]+$/)); + + if (validSlugs.length !== slugs.length) { + console.warn(`Filtered out ${slugs.length - validSlugs.length} invalid slugs`); + } + + // Check which slugs are already in cache + const pipeline = redis.pipeline(); + const cacheKeys = validSlugs.map(slug => `chapters:${slug}:${lang}`); + + cacheKeys.forEach(key => { + pipeline.exists(key); + }); + + const results = await pipeline.exec(); + const slugsToFetch: string[] = []; + + results?.forEach(([err, exists], index) => { + if (err) { + console.error(`Error checking cache for ${cacheKeys[index]}:`, err); + slugsToFetch.push(validSlugs[index]); + } else if (!exists) { + slugsToFetch.push(validSlugs[index]); + } + }); + + if (slugsToFetch.length === 0) { + console.log('All chapters already in cache, no warming needed'); + return; + } + + console.log(`Cache miss for ${slugsToFetch.length} comics - starting background fetch`); + + // To avoid circular dependencies, we'll import getChaptersForSlugs dynamically + const { getChaptersForSlugs } = await import('./api'); + + // Start fetching in the background without waiting for the result + getChaptersForSlugs(slugsToFetch, lang) + .then(chapters => { + console.log(`Successfully warmed cache with ${chapters.length} chapters`); + }) + .catch(error => { + console.error('Error warming chapter cache:', error); + }); + + } catch (error) { + console.error('Error in warmChapterCache:', error); + } +} + // Initialize connections lazily console.log("Redis will connect on first use");