From e35571fd3ba1f6230eeac1ff27f2796e06c92093 Mon Sep 17 00:00:00 2001 From: Omar Najjar Date: Sun, 13 Apr 2025 02:55:28 +1000 Subject: [PATCH] x --- index.html | 29 ++- worker.js | 480 ++++++++++++-------------------------------------- wrangler.toml | 6 +- 3 files changed, 145 insertions(+), 370 deletions(-) diff --git a/index.html b/index.html index 4540872..6d81f1d 100644 --- a/index.html +++ b/index.html @@ -1295,17 +1295,25 @@ uploadIcon.style.marginRight = '8px'; console.log("Creating proposal:", proposalData); + // Add better error handling and logging const createResponse = await fetch(`${API_BASE_URL}/proposals`, { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'Accept': 'application/json' }, - body: JSON.stringify(proposalData) + body: JSON.stringify(proposalData), + mode: 'cors', + credentials: 'same-origin' }); + console.log("Create response status:", createResponse.status); + console.log("Create response headers:", Object.fromEntries(createResponse.headers.entries())); + if (!createResponse.ok) { const errorText = await createResponse.text(); - throw new Error(`Failed to create proposal: ${errorText}`); + console.error("Error response:", errorText); + throw new Error(`Failed to create proposal: ${createResponse.status} ${errorText}`); } const proposalResult = await createResponse.json(); @@ -2438,8 +2446,8 @@ document.addEventListener('DOMContentLoaded', () => { // Log when the element is clicked for debugging console.log("Music prompt element found, adding click listener"); - // Audio file URL - this should point to your audio file in R2 - const audioUrl = getMediaURL('/audio/steppingout.mp3'); + // Audio file URL - using a more reliable source + const audioUrl = 'https://theradicalparty.com/memes/steppingout.mp3'; // Initialize music only when the "hit me" button is clicked musicPrompt.addEventListener('click', function(e) { @@ -2484,6 +2492,7 @@ document.addEventListener('DOMContentLoaded', () => { console.log("Music initialized successfully"); } catch (error) { console.error("Error initializing music:", error); + showToastMessage("Failed to load audio. Please try again later.", "#ff0099"); } } @@ -2498,6 +2507,7 @@ document.addEventListener('DOMContentLoaded', () => { console.error('Failed to play audio:', error); musicToggle.innerHTML = '📻'; musicToggle.classList.add('pulse'); + showToastMessage("Audio playback failed. Please try again.", "#ff0099"); }); } @@ -2510,6 +2520,15 @@ document.addEventListener('DOMContentLoaded', () => { musicToggle.classList.add('pulse'); } } + + // Add error event listener to handle audio loading failures + backgroundMusic.addEventListener('error', (e) => { + console.error('Audio error:', e); + showToastMessage("Failed to load audio. Please try again later.", "#ff0099"); + musicControls.style.display = 'none'; + musicPrompt.style.display = 'block'; + musicInitialized = false; + }); }); document.addEventListener('DOMContentLoaded', () => { diff --git a/worker.js b/worker.js index fd79e50..d12717a 100644 --- a/worker.js +++ b/worker.js @@ -37,297 +37,142 @@ export default { async fetch(request, env, ctx) { try { const url = new URL(request.url); - - // Acceptable hostnames - const validHostnames = [ - 'theradicalparty.com', - 'www.theradicalparty.com', - 'radical.omar-c29.workers.dev' - ]; - - // Check if the hostname is valid - if (!validHostnames.includes(url.hostname)) { - console.error(`Unexpected hostname: ${url.hostname}`); - return new Response('Invalid hostname', { status: 400 }); - } - const path = url.pathname; - const proposalId = url.searchParams.get('proposal'); - console.log(`Processing request: ${url.toString()}`); - console.log(`User-Agent: ${request.headers.get('User-Agent')}`); - - // Handle CORS preflight requests first + // Handle CORS preflight requests if (request.method === 'OPTIONS') { return handleOptions(); } - // For GET requests, try to serve from cache first - if (request.method === "GET") { - // Create a cache key from the request URL - const cacheKey = new Request(request.url, { - headers: request.headers - }); - - // Try to get from cache first - let cachedResponse = await caches.default.match(cacheKey); - - if (cachedResponse) { - // Add header to indicate cache hit - cachedResponse = new Response(cachedResponse.body, cachedResponse); - cachedResponse.headers.set("CF-Cache-Status", "HIT"); - - // Ensure CORS headers are present - Object.keys(corsHeaders).forEach(key => { - cachedResponse.headers.set(key, corsHeaders[key]); - }); - - return cachedResponse; - } + // Handle static assets + if (path === '/styles.css' || path === '/index.html' || path === '/') { + return serveStaticAsset(request, env); } - // Handle styles.css route -if (path === '/styles.css') { - try { - // Determine the correct source based on the request's origin - const cssSourceUrl = - request.headers.get('Origin')?.includes('localhost') - ? 'http://localhost:3000/styles.css' - : 'https://radical.pages.dev/radical/styles.css'; - - // Fetch CSS - const response = await fetch(cssSourceUrl, { - headers: { - 'User-Agent': request.headers.get('User-Agent') || 'Cloudflare Worker' - } - }); - - if (!response.ok) { - console.error('Failed to fetch styles.css', response.status, response.statusText); - return new Response('CSS File Not Found', { - status: 404, - headers: { - 'Content-Type': 'text/css', - ...corsHeaders - } - }); - } - - // Get the CSS content - const cssContent = await response.text(); - - // Prepare CORS headers specific to the requesting origin - const requestOrigin = request.headers.get('Origin') || '*'; - const specificCorsHeaders = { - 'Content-Type': 'text/css', - 'Access-Control-Allow-Origin': requestOrigin, - 'Access-Control-Allow-Methods': 'GET', - 'Access-Control-Max-Age': '86400', - 'Cache-Control': 'public, max-age=86400' // 1 day cache - }; - - // Create CSS response with precise headers - const cssResponse = new Response(cssContent, { - status: 200, - headers: specificCorsHeaders - }); - - // Store in cache if this is a GET request - if (request.method === "GET") { - const cacheKey = new Request(request.url, { - headers: request.headers - }); - ctx.waitUntil(caches.default.put(cacheKey, cssResponse.clone())); - } - - return cssResponse; - } catch (error) { - console.error('Comprehensive styles.css fetch error:', { - message: error.message, - stack: error.stack, - name: error.name - }); - - return new Response('Failed to load stylesheet', { - status: 500, - headers: { - 'Content-Type': 'text/css', - ...corsHeaders - } - }); - } -} - - const isTestMeta = url.searchParams.get('test_meta') === 'true'; - - // Handle root path and index.html with potential meta tag testing - if (path === '/' || path === '/index.html') { - // If test_meta is true or there's a proposal, use customization - if (isTestMeta || proposalId) { - return await serveCustomizedHtml(proposalId || 'test_proposal', request, env); - } - - // Otherwise, serve main page normally - return await serveMainPage(request, env); - } - - if (path === '/api/petition-svg') { - return await servePetitionSVG(request, env); - } - - // Meta tag testing - if (url.searchParams.get('test_meta') === 'true') { - console.log('Meta tag test detected'); - - // If there's a proposal during meta test, use customization - if (proposalId && (path === '/' || path === '/index.html')) { - console.log(`Detected proposal in URL during meta test: ${proposalId}`); - return await serveCustomizedHtml(proposalId, request, env); - } - } - - console.log(`Received ${request.method} request to ${path}`); - - // Check if the DB binding exists - if (!env.DB) { - console.error('DB binding is missing! Check your wrangler.toml configuration.'); - return createResponse({ - error: 'Database configuration error. Missing DB binding.', - hint: 'Check your wrangler.toml file and make sure the DB binding is correctly set up.' - }, 500); - } - - // Handle media files (memes and audio) + // Handle media files if (path.startsWith('/memes/') || path.startsWith('/audio/')) { - const mediaResponse = await serveMedia(request, env); - - // Cache media responses for GET requests - if (request.method === "GET") { - const cacheKey = new Request(request.url, { - headers: request.headers - }); - - // Clone the response before modifying - const responseToCache = new Response(mediaResponse.body, mediaResponse); - - // Cache media files for 7 days - responseToCache.headers.set('Cache-Control', 'public, max-age=604800'); - - ctx.waitUntil(caches.default.put(cacheKey, responseToCache.clone())); - } - - return mediaResponse; - } else if (path === '/api/memes' && request.method === 'POST') { - return await uploadMeme(request, env); - } - - // Route requests to appropriate handlers - let response; - - if (path === '/api/health') { - response = await healthCheck(env); - } else if (path === '/api/debug-meta') { - response = await debugMetaTags(request, env); - } else if (path === '/login' || path === '/login.html') { - response = await serveLoginPage(request, env); - } else if (path === '/api/login' && request.method === 'POST') { - response = await validateLogin(request, env); - } else if (path === '/api/proposals' && request.method === 'GET') { - response = await getProposals(request, env); - } else if (path === '/api/proposals' && request.method === 'POST') { - response = await createProposal(request, env); - } else if (path.startsWith('/api/proposals/') && request.method === 'GET') { - response = await getProposalById(request, env); - } else if (path.startsWith('/api/proposals/') && request.method === 'PUT') { - const id = path.split('/').pop(); - response = await updateProposal(id, request, env); - } else if (path === '/api/votes' && request.method === 'POST') { - response = await createOrUpdateVote(request, env); - } else if (path === '/api/users' && request.method === 'POST') { - response = await createOrGetUser(request, env); - } else if (path === '/api/petition-stats' && request.method === 'GET') { - response = await getPetitionStats(request, env); - } else if (path === '/api/comments' && request.method === 'GET') { - response = await getComments(request, env); - } else if (path === '/api/comments' && request.method === 'POST') { - response = await createComment(request, env); - } else if (path === '/api/comment-votes' && request.method === 'POST') { - response = await createOrUpdateCommentVote(request, env); - } else if (path === '/api/comment-votes' && request.method === 'GET') { - response = await getCommentVotes(request, env); - } else { - response = createResponse({ - error: 'Not found', - path, - method: request.method, - availableRoutes: [ - { path: '/api/health', method: 'GET', description: 'Check system health' }, - { path: '/api/proposals', method: 'GET', description: 'Get proposals' }, - { path: '/api/proposals/:id', method: 'GET', description: 'Get a single proposal by ID' }, - { path: '/api/proposals/:id', method: 'PUT', description: 'Update proposal' }, - { path: '/api/votes', method: 'POST', description: 'Create/update vote' }, - { path: '/api/users', method: 'POST', description: 'Create/get user' }, - { path: '/api/petition-stats', method: 'GET', description: 'Get petition statistics' } - ] - }, 404); + return serveMedia(request, env); } - // Cache certain GET responses - if (request.method === "GET") { - const shouldCache = path.startsWith('/api/proposals') || - path.startsWith('/api/comments') || - path === '/api/petition-stats' || - path === '/api/health'; - - if (shouldCache && response) { - // We need to clone the response before reading its body - const responseToCache = new Response(response.body, response); - - // Set appropriate cache duration based on endpoint - let cacheDuration = 300; // 5 minutes default for API - if (path === '/api/health') { - cacheDuration = 3600; // 1 hour for health checks + // Handle API endpoints + if (path.startsWith('/api/')) { + try { + if (path === '/api/proposals' && request.method === 'GET') { + return await getProposals(request, env); + } else if (path === '/api/proposals' && request.method === 'POST') { + return await createProposal(request, env); + } else if (path === '/api/comments' && request.method === 'GET') { + return await getComments(request, env); + } else if (path === '/api/comments' && request.method === 'POST') { + return await createComment(request, env); + } else if (path === '/api/votes' && request.method === 'POST') { + return await createOrUpdateVote(request, env); } - - responseToCache.headers.set('Cache-Control', `public, max-age=${cacheDuration}`); - responseToCache.headers.set('CF-Cache-Status', 'MISS'); - - // Ensure all CORS headers are present - Object.keys(corsHeaders).forEach(key => { - responseToCache.headers.set(key, corsHeaders[key]); - }); - - const cacheKey = new Request(request.url, { - headers: request.headers - }); - - ctx.waitUntil(caches.default.put(cacheKey, responseToCache.clone())); - - // Make sure the response we return also has CORS headers - Object.keys(corsHeaders).forEach(key => { - response.headers.set(key, corsHeaders[key]); + } catch (error) { + console.error('API Error:', error); + return new Response(JSON.stringify({ error: 'Internal Server Error' }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, }); } } - - return response; - + + // Return 404 for unknown routes + return new Response('Not Found', { status: 404 }); } catch (error) { - console.error('Critical error in request handling:', error); - return new Response(JSON.stringify({ - error: 'Internal Server Error', - details: error.message - }), { - status: 500, - headers: { - 'Content-Type': 'application/json', - ...corsHeaders // Add CORS headers to error responses too - } - }); + console.error('Unhandled error:', error); + return new Response('Internal Server Error', { status: 500 }); } } }; +// ... existing code ... + +// Handle media files (memes and audio) +async function serveMedia(request, env) { + try { + const url = new URL(request.url); + const path = url.pathname; + + // Extract the filename from the path + const filename = path.split('/').pop(); + + // Determine the correct bucket based on the path + const bucket = path.startsWith('/memes/') ? env.MEMES_BUCKET : env.AUDIO_BUCKET; + + if (!bucket) { + console.error('Missing bucket binding'); + return new Response('Internal Server Error', { status: 500 }); + } + + // Get the object from R2 + const object = await bucket.get(filename); + + if (!object) { + console.error(`File not found: ${filename}`); + return new Response('File Not Found', { status: 404 }); + } + + // Determine content type based on file extension + const contentType = path.endsWith('.mp3') ? 'audio/mpeg' : 'image/jpeg'; + + // Create response with appropriate headers + const headers = new Headers(); + headers.set('Content-Type', contentType); + headers.set('Cache-Control', 'public, max-age=31536000'); // 1 year cache + headers.set('Access-Control-Allow-Origin', '*'); + + return new Response(object.body, { + headers, + status: 200 + }); + } catch (error) { + console.error('Error serving media:', error); + return new Response('Internal Server Error', { status: 500 }); + } +} + +// Handle static assets +async function serveStaticAsset(request, env) { + try { + const url = new URL(request.url); + const path = url.pathname; + + // Get the asset from the ASSETS binding + const asset = await env.ASSETS.get(path.substring(1)); + + if (!asset) { + console.error(`Asset not found: ${path}`); + return new Response('Asset Not Found', { status: 404 }); + } + + // Determine content type based on file extension + let contentType = 'text/plain'; + if (path.endsWith('.css')) { + contentType = 'text/css'; + } else if (path.endsWith('.js')) { + contentType = 'application/javascript'; + } else if (path.endsWith('.html')) { + contentType = 'text/html'; + } + + // Create response with appropriate headers + const headers = new Headers(); + headers.set('Content-Type', contentType); + headers.set('Cache-Control', 'public, max-age=31536000'); // 1 year cache + headers.set('Access-Control-Allow-Origin', '*'); + + return new Response(asset, { + headers, + status: 200 + }); + } catch (error) { + console.error('Error serving static asset:', error); + return new Response('Internal Server Error', { status: 500 }); + } +} + +// ... existing code ... async function serveMainPage(request, env) { try { @@ -1425,99 +1270,6 @@ async function updateProposal(id, request, env) { } } -// Handle serving media files (memes and audio) -async function serveMedia(request, env) { - const url = new URL(request.url); - const path = url.pathname; - - console.log('Media Request Details:', { - fullPath: path, - hostname: url.hostname, - method: request.method - }); - - // More explicit route matching - const audioRoutes = ['/audio/', '/memes/']; - const mediaRoute = audioRoutes.find(route => path.startsWith(route)); - - if (!mediaRoute) { - console.log('No matching media route found'); - return new Response('Not Found', { - status: 404, - headers: { - ...corsHeaders, - 'Content-Type': 'text/plain' - } - }); - } - - const key = path.replace(mediaRoute, ''); - - console.log('Attempting to serve media:', { - bucket: 'MEMES_BUCKET', - key: key - }); - - try { - // Try to get the object from R2 - const object = await env.MEMES_BUCKET.get(key); - - if (object === null) { - console.log(`File not found in bucket: ${key}`); - return new Response('File Not Found', { - status: 404, - headers: { - ...corsHeaders, - 'Content-Type': 'text/plain' - } - }); - } - - const headers = new Headers(); - - // Determine content type based on file extension - const contentTypeMap = { - '.mp3': 'audio/mpeg', - '.wav': 'audio/wav', - '.ogg': 'audio/ogg', - '.jpg': 'image/jpeg', - '.jpeg': 'image/jpeg', - '.png': 'image/png', - '.gif': 'image/gif' - }; - - const ext = key.slice(key.lastIndexOf('.')); - const contentType = contentTypeMap[ext] || 'application/octet-stream'; - - headers.set('Content-Type', contentType); - headers.set('etag', object.httpEtag); - headers.set('Cache-Control', 'public, max-age=31536000'); // Cache for a year - - // Add CORS headers - Object.keys(corsHeaders).forEach(corsKey => { - headers.set(corsKey, corsHeaders[corsKey]); - }); - - console.log('Serving media with headers:', Object.fromEntries(headers)); - - return new Response(object.body, { headers }); - } catch (error) { - console.error('Critical error serving media:', { - message: error.message, - stack: error.stack, - key: key - }); - - return new Response('Internal Server Error', { - status: 500, - headers: { - ...corsHeaders, - 'Content-Type': 'text/plain' - } - }); - } -} - // Create or update a vote and handle petition data async function createOrUpdateVote(request, env) { try { diff --git a/wrangler.toml b/wrangler.toml index ca17214..4d7baf2 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -21,7 +21,6 @@ ENVIRONMENT = "production" [observability.logs] enabled = true - # Main routes for API endpoints [[routes]] pattern = "theradicalparty.com/api/*" @@ -41,5 +40,10 @@ zone_name = "theradicalparty.com" pattern = "theradicalparty.com/styles.css" zone_name = "theradicalparty.com" +# Pages integration +[pages] +build_command = "npm run build" +build_output_dir = "dist" +