diff --git a/index.html b/index.html index 6d81f1d..c22c173 100644 --- a/index.html +++ b/index.html @@ -1311,17 +1311,35 @@ uploadIcon.style.marginRight = '8px'; console.log("Create response headers:", Object.fromEntries(createResponse.headers.entries())); if (!createResponse.ok) { - const errorText = await createResponse.text(); - console.error("Error response:", errorText); - throw new Error(`Failed to create proposal: ${createResponse.status} ${errorText}`); + let errorData; + try { + errorData = await createResponse.json(); + console.error("Error response JSON:", errorData); + } catch (parseError) { + const errorText = await createResponse.text(); + console.error("Error response text:", errorText); + errorData = { error: errorText || 'Unknown error' }; + } + throw new Error(`Failed to create proposal: ${errorData.error || createResponse.statusText}`); } - const proposalResult = await createResponse.json(); - console.log("Proposal created successfully:", proposalResult); + let proposalResult; + try { + proposalResult = await createResponse.json(); + console.log("Proposal created successfully:", proposalResult); + } catch (jsonError) { + console.error("Error parsing success response:", jsonError); + throw new Error("Server returned invalid response format"); + } // Get the proposal ID from the result const proposalId = proposalResult.id; + if (!proposalId) { + console.error("Missing proposal ID in response:", proposalResult); + throw new Error("Server returned incomplete response"); + } + // If there's a meme, upload it if (selectedMeme) { // Show progress bar diff --git a/worker.js b/worker.js index a30d8e5..d312f90 100644 --- a/worker.js +++ b/worker.js @@ -1162,85 +1162,101 @@ async function getProposalById(request, env) { async function generateAndStoreShareImage(proposal, env) { try { + // Check if we have the necessary bindings + if (!env.MEMES_BUCKET) { + console.error('Missing MEMES_BUCKET binding'); + return null; + } + // Get base text for the image const text = proposal.text || "Unknown petition"; const authorName = proposal.author_name || "Anonymous"; - // Create Cloudinary URL for transformation - const cloudinaryUrl = "https://res.cloudinary.com/dh8apmjya/image/fetch"; + // Instead of using Cloudinary, let's use our SVG generation which we know works + const imageId = `share_${proposal.id}`; + const fallbackImageUrl = "https://theradicalparty.com/memes/petition-template.png"; - // Encode text for URL parameters - const encodedText = encodeURIComponent(text.substring(0, 80)) - .replace(/'/g, '%27') - .replace(/"/g, '%22'); + // Create a simple text-based SVG directly + const svgMarkup = ` + + + RADICAL + ${text.substring(0, 80)} + by ${authorName} + `; - // Create transformation parameters - const params = `b_black,w_1200,h_630,c_fill/l_text:Arial_32_bold:${encodedText},co_white,w_1000,c_fit,g_center`; + // Convert SVG to a blob + const svgBlob = new Blob([svgMarkup], { type: 'image/svg+xml' }); - // Template image URL - const templateUrl = encodeURIComponent("https://theradicalparty.com/memes/petition-template.png"); - - // Create a unique identifier for this image - const imageId = `share_${proposal.id}_${Date.now()}`; - - // Final Cloudinary URL - const cloudinaryImageUrl = `${cloudinaryUrl}/${params}/${templateUrl}`; - - console.log(`Generating share image: ${cloudinaryImageUrl}`); - - // Fetch the image from Cloudinary - const imageResponse = await fetch(cloudinaryImageUrl); - - if (!imageResponse.ok) { - throw new Error(`Failed to fetch image: ${imageResponse.status}`); + // Upload the SVG to R2 + try { + await env.MEMES_BUCKET.put(imageId, svgBlob, { + httpMetadata: { + contentType: 'image/svg+xml', + }, + }); + + // Generate the public URL for the stored image + const shareImageUrl = `https://theradicalparty.com/memes/${imageId}`; + console.log(`Created share image at: ${shareImageUrl}`); + return shareImageUrl; + } catch (uploadError) { + console.error('Error uploading share image:', uploadError); + return fallbackImageUrl; } - - // Get the image as a blob - const imageBlob = await imageResponse.blob(); - - // Upload the image to your R2 bucket - await env.MEMES_BUCKET.put(imageId, imageBlob, { - httpMetadata: { - contentType: 'image/png', - }, - }); - - // Generate the public URL for the stored image - const shareImageUrl = `https://theradicalparty.com/memes/${imageId}`; - - console.log(`Stored share image at: ${shareImageUrl}`); - - // Update the proposal in the database with the share image URL - await env.DB.prepare(` - UPDATE proposals - SET share_image_url = ? - WHERE id = ? - `).bind(shareImageUrl, proposal.id).run(); - - return shareImageUrl; } catch (error) { - console.error('Error generating share image:', error); + console.error('Error in share image generation:', error); // Return a fallback image URL in case of failure - return "https://theradicalparty.com/memes/mickeymeta.PNG"; + return "https://theradicalparty.com/memes/petition-template.png"; } } // Create a new proposal async function createProposal(request, env) { try { - const data = await request.json(); + // Log request details for debugging + console.log(`Received proposal creation request at ${Date.now()}`); + + // Check if DB binding exists + if (!env.DB) { + console.error('Database binding is missing'); + return createResponse({ + error: 'Database configuration error', + details: 'Database binding is not available' + }, 500); + } + + // Parse request data + let data; + try { + data = await request.json(); + console.log(`Parsed request data: ${JSON.stringify(data)}`); + } catch (parseError) { + console.error('Failed to parse request JSON:', parseError); + return createResponse({ + error: 'Invalid request format', + details: 'Could not parse JSON body' + }, 400); + } + const { text, authorId, trending = false } = data; - console.log(`Creating proposal: ${JSON.stringify(data)}`); - + // Validate required fields if (!text || !authorId) { - return createResponse({ error: 'Missing required fields', received: { text, authorId } }, 400); + console.error('Missing required fields:', { text: !!text, authorId: !!authorId }); + return createResponse({ + error: 'Missing required fields', + received: { text: text ? 'present' : 'missing', authorId: authorId ? 'present' : 'missing' } + }, 400); } // Check if user exists first try { + console.log(`Checking if user exists: ${authorId}`); const userCheck = await env.DB.prepare(`SELECT id, name FROM users WHERE id = ?`).bind(authorId).first(); + if (!userCheck) { + console.error(`User not found: ${authorId}`); return createResponse({ error: 'Author does not exist', authorId, @@ -1248,38 +1264,67 @@ async function createProposal(request, env) { }, 400); } + console.log(`User found: ${userCheck.id}, ${userCheck.name}`); + // Create a temporary proposal object with author name for image generation + const id = 'proposal_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5); + const timestamp = Date.now(); + + console.log(`Generated proposal ID: ${id}`); + + // Create temporary proposal object const tempProposal = { - id: 'proposal_' + Date.now(), + id: id, text: text, author_name: userCheck.name }; - const id = tempProposal.id; - const timestamp = Date.now(); - // Insert the proposal first + console.log('Inserting proposal into database'); const query = ` INSERT INTO proposals (id, author_id, text, timestamp, trending) VALUES (?, ?, ?, ?, ?) `; - await env.DB.prepare(query).bind(id, authorId, text, timestamp, trending ? 1 : 0).run(); + try { + await env.DB.prepare(query) + .bind(id, authorId, text, timestamp, trending ? 1 : 0) + .run(); + + console.log('Proposal inserted successfully'); + } catch (insertError) { + console.error('Database insertion error:', insertError); + return createResponse({ + error: 'Failed to insert proposal into database', + details: logError(insertError, { action: 'insert_proposal' }) + }, 500); + } - // Generate and store the share image - const shareImageUrl = await generateAndStoreShareImage(tempProposal, env); + // Generate and store the share image (but don't block on it) + console.log('Generating share image'); + let shareImageUrl = null; - // Update the proposal with the share image URL - if (shareImageUrl) { - await env.DB.prepare(` - UPDATE proposals - SET share_image_url = ? - WHERE id = ? - `).bind(shareImageUrl, id).run(); + try { + shareImageUrl = await generateAndStoreShareImage(tempProposal, env); + console.log(`Generated share image: ${shareImageUrl}`); + + // Update the proposal with the share image URL if we got one + if (shareImageUrl) { + console.log('Updating proposal with share image URL'); + await env.DB.prepare(` + UPDATE proposals + SET share_image_url = ? + WHERE id = ? + `).bind(shareImageUrl, id).run(); + } + } catch (imageError) { + // Log but don't fail the whole request if image generation fails + console.error('Image generation error (non-fatal):', imageError); } console.log(`Successfully created proposal with ID: ${id}`); + // Return success response with created proposal data return createResponse({ id, authorId, @@ -1288,16 +1333,15 @@ async function createProposal(request, env) { trending, shareImageUrl }); - } catch (dbError) { + } catch (userCheckError) { + console.error('Error checking user existence:', userCheckError); return createResponse({ - error: 'Failed to create proposal in database', - details: logError(dbError, { - action: 'insert_proposal', - proposal: { id, authorId, timestamp, trending } - }) + error: 'Failed to verify user', + details: logError(userCheckError, { action: 'check_user', authorId }) }, 500); } } catch (error) { + console.error('Unhandled error in proposal creation:', error); return createResponse({ error: 'Failed to process proposal creation', details: logError(error, { action: 'create_proposal_outer' })