Debug upload: add error handling

This commit is contained in:
Manohar Gupta 2026-05-10 14:07:14 +05:30
parent d5168316a7
commit e3c33bb0dc
2 changed files with 60 additions and 23 deletions

View file

@ -2,63 +2,81 @@ import { S3Client, PutObjectCommand, ListObjectsV2Command } from "@aws-sdk/clien
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
const r2 = new S3Client({ function getR2() {
region: "auto", const accountId = process.env.R2_ACCOUNT_ID;
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`, const accessKeyId = process.env.R2_ACCESS_KEY_ID;
credentials: { const secretKey = process.env.R2_SECRET_ACCESS_KEY;
accessKeyId: process.env.R2_ACCESS_KEY_ID!, const bucket = process.env.R2_BUCKET_NAME;
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
});
const BUCKET = process.env.R2_BUCKET_NAME!; if (!accountId || !accessKeyId || !secretKey || !bucket) {
const BASE_URL = `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`; 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) { export async function POST(req: NextRequest) {
let body;
try { try {
const { filename, contentType, childId } = await req.json(); body = await req.json();
} catch {
return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
}
const { filename, contentType, childId } = body;
if (!filename || !contentType) { if (!filename || !contentType) {
return NextResponse.json({ error: "Missing filename or contentType" }, { status: 400 }); return NextResponse.json({ error: "Missing filename or contentType" }, { status: 400 });
} }
try {
const { client, bucket, baseUrl } = getR2();
const ext = filename.split(".").pop() || "jpg"; const ext = filename.split(".").pop() || "jpg";
const key = `memories/${childId || "default"}/${Date.now()}-${Math.random().toString(36).slice(2)}.${ext}`; const key = `memories/${childId || "default"}/${Date.now()}-${Math.random().toString(36).slice(2)}.${ext}`;
const command = new PutObjectCommand({ const command = new PutObjectCommand({
Bucket: BUCKET, Bucket: bucket,
Key: key, Key: key,
ContentType: contentType, ContentType: contentType,
}); });
const url = await getSignedUrl(r2, command, { expiresIn: 60 }); const url = await getSignedUrl(client, command, { expiresIn: 60 });
return NextResponse.json({ return NextResponse.json({
uploadUrl: url, uploadUrl: url,
key, key,
publicUrl: `${BASE_URL}/${key}`, publicUrl: `${baseUrl}/${key}`,
}); });
} catch (error) { } catch (error) {
console.error("R2 upload error:", 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) { export async function GET(req: NextRequest) {
try { try {
const { client, bucket, baseUrl } = getR2();
const { searchParams } = new URL(req.url); const { searchParams } = new URL(req.url);
const childId = searchParams.get("childId") || "default"; const childId = searchParams.get("childId") || "default";
const prefix = `memories/${childId}`; const prefix = `memories/${childId}`;
const command = new ListObjectsV2Command({ const command = new ListObjectsV2Command({
Bucket: BUCKET, Bucket: bucket,
Prefix: prefix, Prefix: prefix,
}); });
const res = await r2.send(command); const res = await client.send(command);
const objects = (res.Contents || []).map((obj) => ({ const objects = (res.Contents || []).map((obj) => ({
key: obj.Key, key: obj.Key,
url: `${BASE_URL}/${obj.Key}`, url: `${baseUrl}/${obj.Key}`,
size: obj.Size, size: obj.Size,
lastModified: obj.LastModified?.toISOString(), lastModified: obj.LastModified?.toISOString(),
})).sort((a, b) => new Date(b.lastModified!).getTime() - new Date(a.lastModified!).getTime()); })).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 }); return NextResponse.json({ memories: objects });
} catch (error) { } catch (error) {
console.error("R2 list error:", 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 });
} }
} }

View file

@ -46,7 +46,17 @@ export default function MemoriesPage() {
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ filename: file.name, contentType: file.type, childId }), 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 // Upload to R2
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
@ -57,11 +67,19 @@ export default function MemoriesPage() {
setUploadProgress(Math.round((e.loaded / e.total) * 100)); setUploadProgress(Math.round((e.loaded / e.total) * 100));
} }
}; };
xhr.onerror = () => {
console.error("Upload XHR error");
alert("Upload failed");
setUploading(false);
};
xhr.send(file); xhr.send(file);
xhr.onload = () => { xhr.onload = () => {
if (xhr.status === 200) { if (xhr.status === 200) {
setMemories([{ key, url: publicUrl, size: file.size, lastModified: new Date().toISOString() }, ...memories]); 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); setUploading(false);
setUploadProgress(0); setUploadProgress(0);
@ -69,6 +87,7 @@ export default function MemoriesPage() {
}; };
} catch (err) { } catch (err) {
console.error("Upload failed:", err); console.error("Upload failed:", err);
alert("Error: " + err);
setUploading(false); setUploading(false);
} }
}; };