import { v4 as uuidv4 } from "uuid"; import mime from "mime-types"; import {Env, Job} from "./types"; import {downloadImage, processJob} from "./helpers"; // Define handler functions as constants outside the export const handleRoot = (): Response => { return new Response("Hello", { status: 200 }); }; const handleEnqueue = async (request: Request, env: Env): Promise => { const form = await request.formData(); const prompt = form.get("prompt") as string; if (!prompt) { return new Response(JSON.stringify({ error: "Prompt is required." }), { status: 400 }); } const webhook_url = (form.get("webhook_url") as string) || undefined; let imageKey: string | null = null; // 1️⃣ File upload const file = form.get("image") as File; if (file && file.size) { const ext = mime.extension(file.type) || "bin"; const key = `images/${uuidv4()}.${ext}`; await env.IMAGES_BUCKET.put(key, file.stream(), { httpMetadata: { contentType: file.type }, }); imageKey = key; } // 2️⃣ URL download else if (form.get("image_url")) { const imageUrl = form.get("image_url") as string; try { const { buffer, contentType } = await downloadImage(imageUrl); const ext = mime.extension(contentType) || "bin"; const key = `images/${uuidv4()}.${ext}`; await env.IMAGES_BUCKET.put(key, buffer, { httpMetadata: { contentType }, }); imageKey = key; } catch (error) { return new Response(JSON.stringify({ error: "Failed to download image URL." }), { status: 400 }); } } // Create the job record in R2 const id = uuidv4(); const job: Job = { status: "queued", prompt, imageKey, created_at: Date.now(), webhook_url, }; const metaKey = `jobs/${id}.json`; await env.JOBS_BUCKET.put(metaKey, JSON.stringify(job)); // Enqueue the job for processing console.log(`[handleEnqueue] Sending job ${id} to queue`); try { await env.IMAGE_QUEUE.send({ jobId: id }); console.log(`[handleEnqueue] Successfully queued job ${id}`); } catch (error) { console.error(`[handleEnqueue] Failed to queue job ${id}: ${error instanceof Error ? error.message : String(error)}`); throw error; // Re-throw to return error to client } return new Response( JSON.stringify({ status: "queued", request_id: id, check_url: `/${id}`, }), { status: 202, headers: { "Content-Type": "application/json" } } ); }; const handleJobStatus = async (id: string, env: Env): Promise => { const metaKey = `jobs/${id}.json`; const obj = await env.JOBS_BUCKET.get(metaKey); if (!obj) { return new Response(JSON.stringify({ error: "Request not found" }), { status: 404 }); } const job = JSON.parse(await obj.text()) as Job; return new Response(JSON.stringify(job.status === "done" ? { status: "done", image: job.imageUrl } : { status: job.status } ), { status: job.status === "queued" || job.status === "processing" ? 202 : 200, headers: { "Content-Type": "application/json" }, }); }; const handleNotFound = (): Response => { return new Response("Not found", { status: 404 }); }; export default { // HTTP API async fetch(request: Request, env: Env): Promise { const url = new URL(request.url); const segments = url.pathname.split("/").filter(Boolean); // Route to the appropriate handler based on method and path if (request.method === "GET" && segments.length === 0) { return handleRoot(); } if (request.method === "POST" && segments.length === 0) { return await handleEnqueue(request, env); } if (request.method === "GET" && segments.length === 1) { return await handleJobStatus(segments[0], env); } return handleNotFound(); }, // Queue handler for processing image generation jobs async queue(batch: MessageBatch, env: Env): Promise { console.log(`[queue] Queue handler triggered with ${batch.messages.length} messages`); try { // Process each message in the batch for (const message of batch.messages) { console.log(`[queue] Processing message: ${JSON.stringify(message.body)}`); const jobId = message.body.jobId; if (jobId) { console.log(`[queue] Found valid jobId: ${jobId}, calling processJob`); await processJob(jobId, env); console.log(`[queue] Finished processing jobId: ${jobId}`); } else { console.warn(`[queue] Message missing jobId: ${JSON.stringify(message.body)}`); } } console.log(`[queue] Successfully processed all messages in batch`); } catch (error) { console.error(`[queue] Error in queue handler: ${error instanceof Error ? error.message : String(error)}`); console.error(`[queue] Error stack: ${error instanceof Error ? error.stack : 'No stack trace'}`); throw error; // Re-throw to ensure Cloudflare knows there was an error } } } satisfies ExportedHandler;