mirror of
https://github.com/ld3z/new-manga.git
synced 2026-11-09 20:53:45 -07:00
does this work?
This commit is contained in:
parent
8271bc4957
commit
24eeb93f68
5 changed files with 104 additions and 84 deletions
|
|
@ -4,7 +4,11 @@ import tailwind from "@astrojs/tailwind";
|
|||
|
||||
export default defineConfig({
|
||||
output: "server",
|
||||
adapter: vercel(),
|
||||
adapter: vercel({
|
||||
analytics: true,
|
||||
maxDuration: 60,
|
||||
includeFiles: ['.env'],
|
||||
}),
|
||||
image: {
|
||||
remotePatterns: [{ protocol: "https" }],
|
||||
},
|
||||
|
|
@ -14,7 +18,13 @@ export default defineConfig({
|
|||
'process.env.PRIMARY_REDIS_URL': JSON.stringify(process.env.PRIMARY_REDIS_URL || process.env.REDIS_URL),
|
||||
'process.env.REPLICA_REDIS_URL_1': JSON.stringify(process.env.REPLICA_REDIS_URL_1 || process.env.REPLICA_REDIS_URL),
|
||||
'process.env.REPLICA_REDIS_URL_2': JSON.stringify(process.env.REPLICA_REDIS_URL_2),
|
||||
'process.env.REPLICA_REDIS_URL_3': JSON.stringify(process.env.REPLICA_REDIS_URL_3)
|
||||
}
|
||||
'process.env.REPLICA_REDIS_URL_3': JSON.stringify(process.env.REPLICA_REDIS_URL_3),
|
||||
'process.env.VERCEL': JSON.stringify(process.env.VERCEL || false)
|
||||
},
|
||||
server: {
|
||||
watch: {
|
||||
usePolling: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
});
|
||||
18
package-lock.json
generated
18
package-lock.json
generated
|
|
@ -14,7 +14,6 @@
|
|||
"@floating-ui/dom": "^1.6.13",
|
||||
"@types/node": "^22.13.9",
|
||||
"@vercel/analytics": "^1.5.0",
|
||||
"@vercel/kv": "^3.0.0",
|
||||
"astro": "^5.4.1",
|
||||
"feed": "^4.2.2",
|
||||
"ioredis": "^5.5.0",
|
||||
|
|
@ -1486,6 +1485,8 @@
|
|||
"version": "1.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.34.3.tgz",
|
||||
"integrity": "sha512-VT25TyODGy/8ljl7GADnJoMmtmJ1F8d84UXfGonRRF8fWYJz7+2J6GzW+a6ETGtk4OyuRTt7FRSvFG5GvrfSdQ==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"crypto-js": "^4.2.0"
|
||||
}
|
||||
|
|
@ -1532,17 +1533,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@vercel/edge/-/edge-1.2.1.tgz",
|
||||
"integrity": "sha512-1++yncEyIAi68D3UEOlytYb1IUcIulMWdoSzX2h9LuSeeyR7JtaIgR8DcTQ6+DmYOQn+5MCh6LY+UmK6QBByNA=="
|
||||
},
|
||||
"node_modules/@vercel/kv": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/kv/-/kv-3.0.0.tgz",
|
||||
"integrity": "sha512-pKT8fRnfyYk2MgvyB6fn6ipJPCdfZwiKDdw7vB+HL50rjboEBHDVBEcnwfkEpVSp2AjNtoaOUH7zG+bVC/rvSg==",
|
||||
"dependencies": {
|
||||
"@upstash/redis": "^1.34.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/nft": {
|
||||
"version": "0.29.1",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.29.1.tgz",
|
||||
|
|
@ -2383,7 +2373,9 @@
|
|||
"node_modules/crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
"@floating-ui/dom": "^1.6.13",
|
||||
"@types/node": "^22.13.9",
|
||||
"@vercel/analytics": "^1.5.0",
|
||||
"@vercel/kv": "^3.0.0",
|
||||
"astro": "^5.4.1",
|
||||
"feed": "^4.2.2",
|
||||
"ioredis": "^5.5.0",
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ let replicaClients: Map<string, Redis> = new Map();
|
|||
const DEFAULT_CACHE_TTL = 60 * 60 * 24 * 30; // 30 days
|
||||
const MAX_RETRIES = 5;
|
||||
const BASE_DELAY_MS = 100;
|
||||
const IS_VERCEL = process.env.VERCEL || false;
|
||||
const VERCEL_CONNECTION_TIMEOUT = 15000; // 15 seconds for Vercel
|
||||
const IS_VERCEL = process.env.VERCEL || import.meta.env.VERCEL || false;
|
||||
const VERCEL_CONNECTION_TIMEOUT = 25000; // 25 seconds for Vercel
|
||||
const LOCAL_CONNECTION_TIMEOUT = 5000; // 5 seconds for local
|
||||
|
||||
// Improved connection handling with exponential backoff
|
||||
|
|
@ -53,26 +53,34 @@ async function createRedisClient(url: string): Promise<Redis> {
|
|||
if (!url) throw new Error('Redis URL is not set');
|
||||
|
||||
const redisName = getRedisName(url);
|
||||
console.log(`Connecting to ${redisName} Redis instance in ${IS_VERCEL ? 'Vercel' : 'local'} environment`);
|
||||
|
||||
// Parse URL to extract authentication info
|
||||
const options = {
|
||||
retryStrategy: (times) => {
|
||||
if (times >= MAX_RETRIES) return null;
|
||||
if (times >= MAX_RETRIES) {
|
||||
console.error(`${redisName} max retries (${MAX_RETRIES}) reached, giving up`);
|
||||
return null;
|
||||
}
|
||||
const delay = Math.min(BASE_DELAY_MS * Math.pow(2, times), 5000);
|
||||
console.log(`${redisName} retry attempt ${times}, next try in ${delay}ms`);
|
||||
return delay + Math.random() * 200; // Add jitter
|
||||
},
|
||||
maxRetriesPerRequest: null, // Allow retries for queued commands
|
||||
maxRetriesPerRequest: 3, // Limit retries for queued commands
|
||||
enableOfflineQueue: true, // Enable command queuing
|
||||
enableReadyCheck: true,
|
||||
connectTimeout: 15000, // Increase connection timeout to 15 seconds for Vercel's serverless environment
|
||||
connectTimeout: IS_VERCEL ? VERCEL_CONNECTION_TIMEOUT : LOCAL_CONNECTION_TIMEOUT,
|
||||
disconnectTimeout: 10000, // Increase disconnect timeout to 10 seconds
|
||||
keepAlive: 10000, // Send keepalive every 10 seconds
|
||||
commandTimeout: 10000, // Command timeout
|
||||
commandTimeout: 15000, // Command timeout
|
||||
retryMaxDelay: 5000, // Maximum delay between retries
|
||||
reconnectOnError: (err) => {
|
||||
const targetErrors = ['READONLY', 'ETIMEDOUT', 'ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND'];
|
||||
return targetErrors.some(e => err.message.includes(e));
|
||||
const shouldReconnect = targetErrors.some(e => err.message.includes(e));
|
||||
if (shouldReconnect) {
|
||||
console.log(`Reconnecting due to error: ${err.message}`);
|
||||
}
|
||||
return shouldReconnect;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -86,71 +94,39 @@ export async function getRedisClient(): Promise<Redis> {
|
|||
console.log('Running in Vercel environment - using optimized Redis connection strategy');
|
||||
}
|
||||
|
||||
try {
|
||||
// Try primary first
|
||||
if (primaryClient?.status === 'ready') {
|
||||
if (!primaryClient) {
|
||||
console.log('Creating new primary Redis client');
|
||||
primaryClient = await createRedisClient(PRIMARY_REDIS_URL!);
|
||||
} else if (primaryClient.status !== 'ready') {
|
||||
console.log('Primary Redis client not ready, reconnecting');
|
||||
try {
|
||||
await primaryClient.disconnect();
|
||||
} catch (e) {
|
||||
console.error('Error disconnecting primary client:', e);
|
||||
}
|
||||
primaryClient = await createRedisClient(PRIMARY_REDIS_URL!);
|
||||
}
|
||||
|
||||
try {
|
||||
// Add timeout to ping
|
||||
const pingPromise = primaryClient.ping();
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Ping timeout')), 3000)
|
||||
setTimeout(() => reject(new Error('Ping timeout')), 5000)
|
||||
);
|
||||
|
||||
await Promise.race([pingPromise, timeoutPromise]);
|
||||
return primaryClient;
|
||||
} catch (error) {
|
||||
console.warn('Primary Redis connection failed, will try replicas:', error);
|
||||
// Fall through to try replicas
|
||||
console.error('Primary Redis client ping failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Try replicas in sequence
|
||||
for (const [url, client] of replicaClients.entries()) {
|
||||
if (client.status === 'ready') {
|
||||
try {
|
||||
// Add timeout to ping
|
||||
const pingPromise = client.ping();
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Ping timeout')), 3000)
|
||||
);
|
||||
|
||||
await Promise.race([pingPromise, timeoutPromise]);
|
||||
console.log(`Using ${getRedisName(url)}`);
|
||||
return client;
|
||||
} catch (error) {
|
||||
console.warn(`${getRedisName(url)} connection failed:`, error);
|
||||
// Continue to next replica
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reconnect to primary
|
||||
try {
|
||||
console.log('Initializing new primary Redis connection...');
|
||||
primaryClient = await createRedisClient(PRIMARY_REDIS_URL!);
|
||||
|
||||
primaryClient
|
||||
.on('ready', () => console.log('Primary Redis connection ready'))
|
||||
.on('error', (err: RedisNetworkError) => {
|
||||
// Log more details for connection errors
|
||||
if (err.code === 'ETIMEDOUT') {
|
||||
console.error('Primary Redis connection timeout. This is common in serverless environments like Vercel. Check your network connectivity and Redis server availability.');
|
||||
} else if (err.code === 'ECONNREFUSED') {
|
||||
console.error('Primary Redis connection refused. Ensure the Redis server is running and the URL is correct.');
|
||||
} else if (err.code === 'ENOTFOUND') {
|
||||
console.error('Primary Redis host not found. Check your PRIMARY_REDIS_URL environment variable.');
|
||||
} else {
|
||||
console.error('Primary Redis error:', err);
|
||||
}
|
||||
})
|
||||
.on('reconnecting', () => console.log('Primary Redis reconnecting'))
|
||||
.on('end', () => console.log('Primary Redis connection closed'));
|
||||
|
||||
return primaryClient;
|
||||
} catch (error) {
|
||||
console.error('Failed to connect to primary Redis:', error);
|
||||
} catch (primaryError) {
|
||||
console.error('Error connecting to primary Redis:', primaryError);
|
||||
|
||||
// Special handling for Vercel ETIMEDOUT errors
|
||||
if (IS_VERCEL && error instanceof Error && error.message.includes('ETIMEDOUT')) {
|
||||
if (IS_VERCEL && primaryError instanceof Error && primaryError.message.includes('ETIMEDOUT')) {
|
||||
console.log('Encountered ETIMEDOUT in Vercel environment. Retrying with longer timeout...');
|
||||
try {
|
||||
// Try one more time with increased timeout
|
||||
|
|
|
|||
|
|
@ -7,18 +7,52 @@ const feedMappings = new Map<string, { slugs: string[], lang: string }>();
|
|||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const { slugs, lang } = await request.json();
|
||||
console.log("Handling create-feed API request");
|
||||
|
||||
if (!request.body) {
|
||||
console.error("Request has no body");
|
||||
return new Response("Request body is required", { status: 400 });
|
||||
}
|
||||
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch (parseError) {
|
||||
console.error("Failed to parse request JSON:", parseError);
|
||||
return new Response("Invalid JSON in request body", { status: 400 });
|
||||
}
|
||||
|
||||
const { slugs, lang } = body;
|
||||
|
||||
if (!Array.isArray(slugs) || slugs.length === 0) {
|
||||
console.error("Invalid slugs array:", slugs);
|
||||
return new Response("Invalid slugs array", { status: 400 });
|
||||
}
|
||||
|
||||
if (!lang) {
|
||||
console.error("Language not specified");
|
||||
return new Response("Language is required", { status: 400 });
|
||||
}
|
||||
|
||||
console.log(`Creating feed for ${slugs.length} slugs in language: ${lang}`);
|
||||
|
||||
// Create a unique hash based on slugs and language
|
||||
const dataString = `${slugs.sort().join(',')}:${lang}`;
|
||||
const hash = createHash('md5').update(dataString).digest('hex').slice(0, 8);
|
||||
|
||||
// Store the mapping in Vercel KV
|
||||
try {
|
||||
// Store the mapping in Redis
|
||||
await storeFeedMapping(hash, slugs, lang);
|
||||
console.log(`Successfully created feed with ID: ${hash}`);
|
||||
} catch (dbError: any) {
|
||||
console.error("Database error while storing feed mapping:", dbError);
|
||||
return new Response(`Database error: ${dbError.message || "Unknown error"}`, {
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ feedId: hash }), {
|
||||
status: 200,
|
||||
|
|
@ -26,9 +60,18 @@ export const POST: APIRoute = async ({ request }) => {
|
|||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating feed:', error);
|
||||
return new Response("Internal server error", { status: 500 });
|
||||
} catch (error: any) {
|
||||
console.error('Unexpected error creating feed:', error);
|
||||
return new Response(JSON.stringify({
|
||||
error: "Internal server error",
|
||||
message: error.message || "Unknown error",
|
||||
stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
|
||||
}), {
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue