better error handling and performance improvements

This commit is contained in:
ld3zian 2025-01-28 18:49:06 -07:00
parent 6ca77cf6af
commit 17ceea25d9
6 changed files with 87 additions and 23 deletions

View file

@ -20,6 +20,8 @@ const isNSFW = comic.md_comics.content_rating === 'erotica';
<div <div
class="bg-secondary rounded-xl overflow-hidden shadow-lg h-full flex flex-col max-w-[280px] mx-auto border border-hover/30 hover:border-accent/50 transition-all duration-300" class="bg-secondary rounded-xl overflow-hidden shadow-lg h-full flex flex-col max-w-[280px] mx-auto border border-hover/30 hover:border-accent/50 transition-all duration-300"
data-genres={JSON.stringify(comic.md_comics.genres)} data-genres={JSON.stringify(comic.md_comics.genres)}
role="article"
aria-labelledby={`comic-title-${comic.id}`}
> >
<a href={`https://comick.io/comic/${comic.md_comics.slug}`} class="flex flex-col h-full group relative"> <a href={`https://comick.io/comic/${comic.md_comics.slug}`} class="flex flex-col h-full group relative">
<div class="relative overflow-hidden aspect-[2/3] rounded-t-xl"> <div class="relative overflow-hidden aspect-[2/3] rounded-t-xl">
@ -50,7 +52,7 @@ const isNSFW = comic.md_comics.content_rating === 'erotica';
<div class="p-3 sm:p-4 md:p-5 flex-grow flex flex-col bg-secondary relative rounded-b-xl"> <div class="p-3 sm:p-4 md:p-5 flex-grow flex flex-col bg-secondary relative rounded-b-xl">
<div class="absolute -top-6 sm:-top-8 left-2 right-2 sm:left-3 sm:right-3 bg-secondary/95 backdrop-blur-[12px] p-2 sm:p-3 rounded-lg shadow-lg border border-hover/30"> <div class="absolute -top-6 sm:-top-8 left-2 right-2 sm:left-3 sm:right-3 bg-secondary/95 backdrop-blur-[12px] p-2 sm:p-3 rounded-lg shadow-lg border border-hover/30">
<h3 class="text-base sm:text-lg font-bold line-clamp-1 text-center text-primary drop-shadow-[0_1px_1px_rgba(0,0,0,0.5)]"> <h3 id={`comic-title-${comic.id}`} class="text-base sm:text-lg font-bold line-clamp-1 text-center text-primary drop-shadow-[0_1px_1px_rgba(0,0,0,0.5)]">
{comic.md_comics.title} {comic.md_comics.title}
</h3> </h3>
</div> </div>

View file

@ -75,3 +75,10 @@ export function getRandomBackground(): BackgroundImage {
} }
return backgrounds[Math.floor(Math.random() * backgrounds.length)]; return backgrounds[Math.floor(Math.random() * backgrounds.length)];
} }
export function preloadBackgrounds() {
backgrounds.forEach(bg => {
const img = new Image();
img.src = bg.url;
});
}

View file

@ -147,11 +147,12 @@ const isCreditsPage = Astro.url.pathname === '/credits';
} }
}; };
// Initialize theme // Add theme initialization to prevent flash of wrong theme
const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null; const themeScript = `
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; const theme = localStorage.getItem('theme') ??
const initialTheme = savedTheme || (prefersDark ? 'dark' : 'light'); (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
updateThemeToggle(initialTheme); document.documentElement.setAttribute('data-theme', theme);
`;
// Event listeners // Event listeners
settingsButton?.addEventListener('click', () => { settingsButton?.addEventListener('click', () => {

View file

@ -69,30 +69,43 @@ export function getGenreNames(genreIds: number[], genres: Genre[]): string[] {
.filter(name => name !== ''); .filter(name => name !== '');
} }
export class APIError extends Error {
constructor(
message: string,
public statusCode?: number,
public endpoint?: string
) {
super(message);
this.name = 'APIError';
}
}
export async function fetchComics( export async function fetchComics(
language: string = 'en', language: string = 'en',
contentType: string = 'sfw', contentType: string = 'sfw',
comicType: ComicType = 'all', comicType: ComicType = 'all',
page: number = 1 page: number = 1
): Promise<Comic[]> { ): Promise<Comic[]> {
const apiUrl = `${urlBase}?lang=${language}&page=${page}&order=new&accept_erotic_content=${contentType === 'nsfw'}${comicType === 'all' ? '' : `&type=${comicType}`}`;
try { try {
const typeParam = comicType === 'all' ? '' : `&type=${comicType}`;
const apiUrl = `${urlBase}?lang=${language}&page=${page}&order=new&accept_erotic_content=${contentType === 'nsfw'}${typeParam}`;
console.log('Fetching comics from:', apiUrl); console.log('Fetching comics from:', apiUrl);
const response = await fetchWithRetry(apiUrl); const response = await fetchWithRetry(apiUrl);
const data = await response.json(); const data = await response.json();
if (!Array.isArray(data)) { if (!Array.isArray(data)) {
console.error('Unexpected API response format:', data); throw new APIError('Invalid API response format', undefined, apiUrl);
return [];
} }
return data; return data;
} catch (error) { } catch (error) {
console.error('Error fetching comics:', error); if (error instanceof APIError) throw error;
return []; throw new APIError(
'Failed to fetch comics',
error instanceof Response ? error.status : undefined,
apiUrl
);
} }
} }

View file

@ -3,6 +3,8 @@ import type { FeedMapping } from './types';
const REDIS_URL = import.meta.env.REDIS_URL || process.env.REDIS_URL; const REDIS_URL = import.meta.env.REDIS_URL || process.env.REDIS_URL;
let redis: Redis | null = null; let redis: Redis | null = null;
let redisRetryCount = 0;
const MAX_RETRIES = 3;
try { try {
console.log('Environment check:', { console.log('Environment check:', {
@ -23,11 +25,14 @@ try {
console.log(`Retry attempt ${times} with delay ${delay}ms`); console.log(`Retry attempt ${times} with delay ${delay}ms`);
return delay; return delay;
}, },
maxRetriesPerRequest: 3, maxRetriesPerRequest: 2,
enableReadyCheck: true, enableReadyCheck: true,
reconnectOnError(err) { reconnectOnError: (err) => {
console.log('Reconnect on error:', err.message); const targetError = 'READONLY';
return true; if (err.message.includes(targetError)) {
return true;
}
return false;
} }
}); });
@ -55,6 +60,38 @@ try {
} }
} }
async function getRedisClient(): Promise<Redis> {
if (redis?.status === 'ready') return redis;
if (redisRetryCount >= MAX_RETRIES) {
throw new Error('Failed to connect to Redis after multiple attempts');
}
try {
redis = new Redis(REDIS_URL, {
retryStrategy(times) {
const delay = Math.min(times * 50, 2000);
console.log(`Retry attempt ${times} with delay ${delay}ms`);
return delay;
},
maxRetriesPerRequest: 2,
enableReadyCheck: true,
reconnectOnError: (err) => {
const targetError = 'READONLY';
if (err.message.includes(targetError)) {
return true;
}
return false;
}
});
redisRetryCount = 0;
return redis;
} catch (error) {
redisRetryCount++;
throw error;
}
}
export async function storeFeedMapping(feedId: string, slugs: string[], lang: string): Promise<void> { export async function storeFeedMapping(feedId: string, slugs: string[], lang: string): Promise<void> {
if (!redis) { if (!redis) {
console.error('Redis client not initialized'); console.error('Redis client not initialized');

View file

@ -6,11 +6,9 @@ export interface Comic {
title: string; title: string;
slug: string; slug: string;
genres: number[]; genres: number[];
md_covers: { md_covers: ComicCover[];
b2key: string; content_rating: 'safe' | 'suggestive' | 'erotica';
}[]; comic_type: ComicType;
content_rating: string;
comic_type: string;
}; };
} }
@ -24,4 +22,10 @@ export interface FeedMapping {
lang: string; lang: string;
} }
export type ComicType = 'manga' | 'manhwa' | 'manhua' | 'all'; export type ComicType = 'manga' | 'manhwa' | 'manhua' | 'all';
export interface ComicCover {
b2key: string;
width?: number;
height?: number;
}