import { S3Client, PutObjectCommand, ListObjectsV2Command, GetObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import { NextRequest, NextResponse } from "next/server"; 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; // Debug: log what's available console.log("R2 envs:", { accountId: !!accountId, accessKeyId: !!accessKeyId, secretKey: !!secretKey, bucket: !!bucket }); if (!accountId || !accessKeyId || !secretKey || !bucket) { throw new Error(`Missing R2 config: accountId=${!!accountId}, accessKeyId=${!!accessKeyId}, secretKey=${!!secretKey}, bucket=${!!bucket}`); } // Use public development URL (from R2 bucket settings) const pubBaseUrl = `https://pub-37a76fd657c94d1dbc521a109c087a11.r2.dev/tia`; return { client: new S3Client({ region: "auto", endpoint: `https://${accountId}.r2.cloudflarestorage.com/${bucket}`, credentials: { accessKeyId, secretAccessKey: secretKey }, }), bucket, baseUrl: pubBaseUrl, }; } // GET: List memories 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, Prefix: prefix, MaxKeys: 10 }); const res = await client.send(command); // Debug: log what we found console.log("List result:", JSON.stringify(res)); if (!res.Contents || res.Contents.length === 0) { return NextResponse.json({ memories: [], debug: { bucket, prefix, count: 0 } }); } const objects = res.Contents.map((obj) => ({ key: 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()); return NextResponse.json({ memories: objects }); } catch (error) { console.error("R2 list error:", error); return NextResponse.json({ error: String(error), stack: error?.stack }, { status: 500 }); } } // POST: Upload (proxy through server to avoid CORS) export async function POST(req: NextRequest) { let body; try { body = await req.json(); } catch { return NextResponse.json({ error: "Invalid JSON" }, { 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}`; // Instead of presigned URL, we're using PUT to our own server // The frontend willPOST the file here directly const command = new PutObjectCommand({ Bucket: bucket, Key: key, ContentType: contentType, }); // Generate short-lived presigned URL that bypasses CORS issues // R2 needs proper signing - let's use direct put with our server as proxy const url = await getSignedUrl(client, command, { expiresIn: 3600 }); // 1 hour return NextResponse.json({ uploadUrl: url, key, publicUrl: `${baseUrl}/${key}`, }); } catch (error) { console.error("R2 error:", error); return NextResponse.json({ error: String(error) }, { status: 500 }); } } // PUT: Direct upload from file content export async function PUT(req: NextRequest) { try { const { client, bucket, baseUrl } = getR2(); const { searchParams } = new URL(req.url); const key = searchParams.get("key"); const contentType = searchParams.get("contentType") || "image/jpeg"; if (!key) { return NextResponse.json({ error: "Missing key" }, { status: 400 }); } const arrayBuffer = await req.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); const command = new PutObjectCommand({ Bucket: bucket, Key: key, Body: buffer, ContentType: contentType, }); await client.send(command); return NextResponse.json({ success: true, key, url: `${baseUrl}/${key}`, }); } catch (error) { console.error("R2 upload error:", error); return NextResponse.json({ error: String(error) }, { status: 500 }); } }