From e3c33bb0dcb6684fe56b4aa5151d2401e7f3d180 Mon Sep 17 00:00:00 2001 From: Mannu Date: Sun, 10 May 2026 14:07:14 +0530 Subject: [PATCH] Debug upload: add error handling --- src/app/api/upload/route.ts | 62 ++++++++++++++++++++++++------------- src/app/memories/page.tsx | 21 ++++++++++++- 2 files changed, 60 insertions(+), 23 deletions(-) diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index 6a79d06..305b29f 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -2,63 +2,81 @@ import { S3Client, PutObjectCommand, ListObjectsV2Command } from "@aws-sdk/clien import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import { NextRequest, NextResponse } from "next/server"; -const r2 = new S3Client({ - region: "auto", - endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`, - credentials: { - accessKeyId: process.env.R2_ACCESS_KEY_ID!, - secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!, - }, -}); +function getR2() { + const accountId = process.env.R2_ACCOUNT_ID; + const accessKeyId = process.env.R2_ACCESS_KEY_ID; + const secretKey = process.env.R2_SECRET_ACCESS_KEY; + const bucket = process.env.R2_BUCKET_NAME; -const BUCKET = process.env.R2_BUCKET_NAME!; -const BASE_URL = `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`; + if (!accountId || !accessKeyId || !secretKey || !bucket) { + throw new Error(`Missing R2 config: accountId=${!!accountId}, accessKeyId=${!!accessKeyId}, secretKey=${!!secretKey}, bucket=${!!bucket}`); + } + + return { + client: new S3Client({ + region: "auto", + endpoint: `https://${accountId}.r2.cloudflarestorage.com`, + credentials: { accessKeyId, secretAccessKey: secretKey }, + }), + bucket, + baseUrl: `https://${accountId}.r2.cloudflarestorage.com`, + }; +} export async function POST(req: NextRequest) { + let body; try { - const { filename, contentType, childId } = await req.json(); + body = await req.json(); + } catch { + return NextResponse.json({ error: "Invalid JSON" }, { status: 400 }); + } - if (!filename || !contentType) { - return NextResponse.json({ error: "Missing filename or contentType" }, { status: 400 }); - } + const { filename, contentType, childId } = body; + if (!filename || !contentType) { + return NextResponse.json({ error: "Missing filename or contentType" }, { status: 400 }); + } + + try { + const { client, bucket, baseUrl } = getR2(); const ext = filename.split(".").pop() || "jpg"; const key = `memories/${childId || "default"}/${Date.now()}-${Math.random().toString(36).slice(2)}.${ext}`; const command = new PutObjectCommand({ - Bucket: BUCKET, + Bucket: bucket, Key: key, ContentType: contentType, }); - const url = await getSignedUrl(r2, command, { expiresIn: 60 }); + const url = await getSignedUrl(client, command, { expiresIn: 60 }); return NextResponse.json({ uploadUrl: url, key, - publicUrl: `${BASE_URL}/${key}`, + publicUrl: `${baseUrl}/${key}`, }); } catch (error) { console.error("R2 upload error:", error); - return NextResponse.json({ error: "Failed to create upload URL" }, { status: 500 }); + return NextResponse.json({ error: String(error) }, { status: 500 }); } } export async function GET(req: NextRequest) { try { + const { client, bucket, baseUrl } = getR2(); const { searchParams } = new URL(req.url); const childId = searchParams.get("childId") || "default"; const prefix = `memories/${childId}`; const command = new ListObjectsV2Command({ - Bucket: BUCKET, + Bucket: bucket, Prefix: prefix, }); - const res = await r2.send(command); + const res = await client.send(command); const objects = (res.Contents || []).map((obj) => ({ key: obj.Key, - url: `${BASE_URL}/${obj.Key}`, + url: `${baseUrl}/${obj.Key}`, size: obj.Size, lastModified: obj.LastModified?.toISOString(), })).sort((a, b) => new Date(b.lastModified!).getTime() - new Date(a.lastModified!).getTime()); @@ -66,6 +84,6 @@ export async function GET(req: NextRequest) { return NextResponse.json({ memories: objects }); } catch (error) { console.error("R2 list error:", error); - return NextResponse.json({ error: "Failed to list memories" }, { status: 500 }); + return NextResponse.json({ error: String(error) }, { status: 500 }); } } \ No newline at end of file diff --git a/src/app/memories/page.tsx b/src/app/memories/page.tsx index 1e72a7d..5a65032 100644 --- a/src/app/memories/page.tsx +++ b/src/app/memories/page.tsx @@ -46,7 +46,17 @@ export default function MemoriesPage() { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ filename: file.name, contentType: file.type, childId }), }); - const { uploadUrl, key, publicUrl } = await res.json(); + const data = await res.json(); + + if (data.error) { + console.error("API error:", data.error); + alert("Error: " + data.error); + setUploading(false); + return; + } + + const { uploadUrl, key, publicUrl } = data; + console.log("Got presigned URL:", uploadUrl); // Upload to R2 const xhr = new XMLHttpRequest(); @@ -57,11 +67,19 @@ export default function MemoriesPage() { setUploadProgress(Math.round((e.loaded / e.total) * 100)); } }; + xhr.onerror = () => { + console.error("Upload XHR error"); + alert("Upload failed"); + setUploading(false); + }; xhr.send(file); xhr.onload = () => { if (xhr.status === 200) { setMemories([{ key, url: publicUrl, size: file.size, lastModified: new Date().toISOString() }, ...memories]); + } else { + console.error("Upload failed status:", xhr.status, xhr.statusText); + alert("Upload failed: " + xhr.statusText); } setUploading(false); setUploadProgress(0); @@ -69,6 +87,7 @@ export default function MemoriesPage() { }; } catch (err) { console.error("Upload failed:", err); + alert("Error: " + err); setUploading(false); } };