Compare commits

..

5 commits

Author SHA1 Message Date
ld3zian
f84046c66c add moar bgs 2025-03-03 19:43:20 -07:00
ld3zian
40da736b63 fix imports 2025-03-03 19:11:20 -07:00
ld3zian
33db20e16b replicating fix 2025-03-03 16:12:25 -07:00
ld3zian
8e6ea73460 fix fallback servers 2025-03-03 16:04:16 -07:00
ld3zian
8e7a3af051 update node 2025-03-03 15:59:41 -07:00
9 changed files with 189 additions and 30 deletions

8
package-lock.json generated
View file

@ -12,7 +12,7 @@
"@astrojs/tailwind": "^6.0.0",
"@astrojs/vercel": "^8.1.0",
"@floating-ui/dom": "^1.6.13",
"@types/node": "^22.13.7",
"@types/node": "^22.13.9",
"@vercel/analytics": "^1.5.0",
"@vercel/kv": "^3.0.0",
"astro": "^5.4.1",
@ -1465,9 +1465,9 @@
}
},
"node_modules/@types/node": {
"version": "22.13.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.7.tgz",
"integrity": "sha512-oU2q+BsQldB9lYxHNp/5aZO+/Bs0Usa74Abo9mAKulz4ahQyXRHK6UVKYIN8KSC8HXwhWSi7b49JnX+txuac0w==",
"version": "22.13.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz",
"integrity": "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==",
"dependencies": {
"undici-types": "~6.20.0"
}

View file

@ -15,7 +15,7 @@
"@astrojs/tailwind": "^6.0.0",
"@astrojs/vercel": "^8.1.0",
"@floating-ui/dom": "^1.6.13",
"@types/node": "^22.13.7",
"@types/node": "^22.13.9",
"@vercel/analytics": "^1.5.0",
"@vercel/kv": "^3.0.0",
"astro": "^5.4.1",

BIN
public/genshin.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

BIN
public/meata.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
public/sleepy.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

View file

@ -476,17 +476,51 @@ const languages = [
`;
const response = await fetch(`/api/feed/${feedId}`);
if (!response.ok) throw new Error('Failed to fetch feed');
if (!response.ok) {
const errorText = await response.text();
console.error(`Feed import failed with status ${response.status}: ${errorText}`);
throw new Error(`Failed to fetch feed: ${response.status} ${response.statusText}`);
}
const { slugs: feedSlugs, lang } = await response.json();
const data = await response.json();
console.log('Feed import data:', data); // Diagnostic log
// Validate response structure
if (!data || typeof data !== 'object') {
throw new Error('Invalid feed data format: not an object');
}
// Update language if provided
if (lang && langSelect) {
langSelect.value = lang;
const feedSlugs = data.slugs;
const lang = data.lang;
// Validate slugs array
if (!feedSlugs || !Array.isArray(feedSlugs)) {
throw new Error('Invalid feed data: missing or invalid slugs array');
}
// Validate language
if (!lang || typeof lang !== 'string') {
console.warn('Feed has missing or invalid language, using default');
} else if (langSelect) {
// Only update language if it's valid
const validLanguages = Array.from(langSelect.options).map(opt => opt.value);
if (validLanguages.includes(lang)) {
langSelect.value = lang;
} else {
console.warn(`Feed has unsupported language: ${lang}`);
}
}
// Add new slugs to existing collection
feedSlugs.forEach((slug: string) => slugs.add(slug));
let importCount = 0;
feedSlugs.forEach((slug: string) => {
if (typeof slug === 'string' && slug.trim()) {
slugs.add(slug.trim());
importCount++;
} else {
console.warn('Skipping invalid slug in feed:', slug);
}
});
// Update UI
updateSlugs();
@ -495,7 +529,7 @@ const languages = [
// Show success message
const successDiv = document.createElement('div');
successDiv.className = 'text-sm text-green-500 mt-1';
successDiv.textContent = 'Feed imported successfully!';
successDiv.textContent = `Feed imported successfully! Added ${importCount} comics.`;
feedIdInput.parentNode?.appendChild(successDiv);
setTimeout(() => successDiv.remove(), 3000);
@ -503,9 +537,11 @@ const languages = [
console.error('Failed to import feed:', error);
const errorDiv = document.createElement('div');
errorDiv.className = 'text-sm text-red-500 mt-1';
errorDiv.textContent = 'Failed to import feed. Please check the feed ID and try again.';
errorDiv.textContent = error instanceof Error
? `Import failed: ${error.message}`
: 'Failed to import feed. Please check the feed ID and try again.';
feedIdInput.parentNode?.appendChild(errorDiv);
setTimeout(() => errorDiv.remove(), 3000);
setTimeout(() => errorDiv.remove(), 5000);
} finally {
importFeedButton.disabled = false;
importFeedButton.innerHTML = `

View file

@ -74,6 +74,43 @@ export const backgrounds: BackgroundImage[] = [
},
],
},
{
url: "/sleepy.webp",
title: "???",
artist: "soaphisoap",
source: "https://safebooru.org/index.php?page=post&s=view&id=5418793",
socials: [
{
name: "Twitter",
link: "https://x.com/soaphisoap",
},
],
},
{
url: "/genshin.webp",
title: "???",
artist: "charlieinoni",
source: "https://safebooru.org/index.php?page=post&s=view&id=5531630",
socials: [
{
name: "Twitter",
link: "https://x.com/charlieinoni",
},
],
},
{
url: "/meata.webp",
title: "???",
artist: "???",
source:
"https://i.pximg.net/img-original/img/2025/03/03/15/13/24/127822472_p0.png",
socials: [
{
name: "Twitter",
link: "https://x.com/",
},
],
},
// Add more backgrounds here!
// {
// url: 'your-image-url',
@ -105,4 +142,4 @@ export function preloadBackgrounds() {
const img = new Image();
img.src = bg.url;
});
}
}

View file

@ -284,19 +284,82 @@ export async function checkCacheStatus(slugs: string[], lang: string): Promise<s
}
export async function getFeedMapping(feedId: string): Promise<FeedMapping | null> {
const redis = await getRedisClient();
let foundMapping: string | null = null;
let sourceRedisName: string = "";
// First try primary
try {
const mapping = await redis.get(feedId);
if (!mapping) return null;
await redis.expire(feedId, DEFAULT_CACHE_TTL);
const data = JSON.parse(mapping);
return {
slugs: data.slugs,
lang: data.lang
};
// Check primary database if available
if (primaryClient?.status === 'ready') {
try {
await primaryClient.ping();
const mapping = await primaryClient.get(feedId);
if (mapping) {
await primaryClient.expire(feedId, DEFAULT_CACHE_TTL);
foundMapping = mapping;
sourceRedisName = "Primary";
}
} catch (error) {
console.warn('Primary Redis connection failed while retrieving mapping:', error);
// Fall through to try replicas
}
}
// If not found in primary, check replicas
if (!foundMapping) {
for (const [url, client] of replicaClients.entries()) {
if (client.status === 'ready') {
try {
await client.ping();
console.log(`Trying to get key from ${getRedisName(url)}`);
const mapping = await client.get(feedId);
if (mapping) {
await client.expire(feedId, DEFAULT_CACHE_TTL);
foundMapping = mapping;
sourceRedisName = getRedisName(url);
break; // Exit the loop once we find the mapping
}
} catch (error) {
console.warn(`${getRedisName(url)} connection failed while retrieving mapping:`, error);
// Continue to next replica
}
}
}
}
// If we found the mapping in any database, re-synchronize to all other databases
if (foundMapping) {
console.log(`Found mapping in ${sourceRedisName}. Re-synchronizing to all Redis instances...`);
// Parse data for returning
const data = JSON.parse(foundMapping);
const result = {
slugs: data.slugs,
lang: data.lang
};
// Fire-and-forget re-sync to all databases
(async () => {
try {
await writeToAllRedis(async (redis) => {
await redis.set(
feedId,
foundMapping!,
'EX',
DEFAULT_CACHE_TTL
);
});
console.log(`Successfully re-synchronized mapping for ${feedId} to all Redis instances`);
} catch (syncError) {
console.error('Error during Redis re-synchronization:', syncError);
}
})();
return result;
}
// If we get here, the key wasn't found in any database
return null;
} catch (error) {
console.error('Error retrieving feed mapping:', error);
throw new Error('Failed to retrieve feed mapping');

View file

@ -9,20 +9,43 @@ export const GET: APIRoute = async ({ params }) => {
}
try {
console.log(`Fetching feed mapping for ID: ${feedId}`);
const mapping = await getFeedMapping(feedId);
if (!mapping) {
return new Response("Feed not found", { status: 404 });
console.error(`Feed not found for ID: ${feedId}`);
return new Response(`Feed with ID "${feedId}" not found`, { status: 404 });
}
return new Response(JSON.stringify(mapping), {
// Validate mapping structure before returning
if (!Array.isArray(mapping.slugs)) {
console.error(`Invalid slugs in feed mapping for ID: ${feedId}`, mapping);
return new Response(`Feed data corruption: invalid slugs format`, {
status: 500
});
}
// Ensure consistent response format
const response = {
slugs: mapping.slugs.filter(slug => typeof slug === 'string' && slug.trim()),
lang: typeof mapping.lang === 'string' ? mapping.lang : 'en'
};
console.log(`Successfully retrieved feed mapping for ID: ${feedId}`, response);
return new Response(JSON.stringify(response), {
status: 200,
headers: {
'Content-Type': 'application/json'
}
});
} catch (error) {
console.error('Error fetching feed:', error);
return new Response("Internal server error", { status: 500 });
console.error(`Error fetching feed ${feedId}:`, error);
const errorMessage = error instanceof Error
? `Internal server error: ${error.message}`
: "Internal server error";
return new Response(errorMessage, { status: 500 });
}
};