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"
+