diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index d82b164..f4f7fa0 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -1,133 +1,50 @@ -import { S3Client, PutObjectCommand, ListObjectsV2Command, ListBucketsCommand } from "@aws-sdk/client-s3"; +import { S3Client, PutObjectCommand, ListObjectsV2Command } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import { NextRequest, NextResponse } from "next/server"; -function getR2() { - // Debug: explicitly hardcode for now since env might not be passed to container - const accountId = "e71f22a2f8614fb3ba6d9b28a264d8ce"; - const accessKeyId = "6606d525c8e647d94e051b6d6565803b"; - const secretKey = "244fcc44041f452241cbc68374cd7a6ca651cb71f361bc36cc932407bbe37863"; - const bucket = "tia"; +// Hardcoded R2 config - same as Cloudflare dashboard S3 API +const R2 = { + accountId: "e71f22a2f8614fb3ba6d9b28a264d8ce", + accessKeyId: "6606d525c8e647d94e051b6d6565803b", + secretKey: "244fcc44041f452241cbc68374cd7a6ca651cb71f361bc36cc932407bbe37863", + bucket: "tia", +}; - // Try just account-level endpoint (no bucket in path) - const endpoint = `https://${accountId}.r2.cloudflarestorage.com`; +const client = new S3Client({ + region: "auto", + endpoint: `https://${R2.accountId}.r2.cloudflarestorage.com`, + credentials: { + accessKeyId: R2.accessKeyId, + secretAccessKey: R2.secretKey, + }, +}); - if (!accountId || !accessKeyId || !secretKey || !bucket) { - throw new Error(`Missing R2 config`); - } +const baseUrl = `https://pub-37a76fd657c94d1dbc521a109c087a11.r2.dev/tia`; - // S3 API endpoint includes bucket name: https://.r2.cloudflarestorage.com/ - const endpoint = `https://${accountId}.r2.cloudflarestorage.com/${bucket}`; - - return { - client: new S3Client({ - region: "auto", - endpoint, - credentials: { accessKeyId, secretAccessKey: secretKey }, - }), - bucket, - // Public URL uses pub- subdomain format - baseUrl: `https://pub-37a76fd657c94d1dbc521a109c087a11.r2.dev/tia`, - }; -} - -// GET: List memories -export async function GET(req: NextRequest) { +// GET: List all memories +export async function GET() { try { - const { client, bucket, baseUrl } = getR2(); - const childId = req.nextUrl.searchParams.get("childId") || "default"; - - // List ALL objects in bucket (no prefix filtering for debugging) - const command = new ListObjectsV2Command({ - Bucket: bucket, - MaxKeys: 100 - }); - - const res = await client.send(command); - - const objects = (res.Contents || []).map((obj) => ({ - key: obj.Key, - url: `${baseUrl}/${obj.Key}`, - size: obj.Size, - lastModified: obj.LastModified?.toISOString(), - })); - - return NextResponse.json({ memories: objects }); - } catch (error) { - console.error("R2 list error:", error); - return NextResponse.json({ error: String(error) }, { status: 500 }); - } -} - -// POST: Get upload URL -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, 8)}.${ext}`; - - const command = new PutObjectCommand({ - Bucket: bucket, - Key: key, - ContentType: contentType, - }); - - const url = await getSignedUrl(client, command, { expiresIn: 3600 }); - - 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 -export async function PUT(req: NextRequest) { - try { - const { client, bucket, baseUrl } = getR2(); - const key = req.nextUrl.searchParams.get("key"); - const contentType = req.nextUrl.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); + // List all objects in bucket + const command = new ListObjectsV2Command({ Bucket: R2.bucket }); + const response = await client.send(command); return NextResponse.json({ success: true, - key, - url: `${baseUrl}/${key}`, + bucket: R2.bucket, + count: response.Contents?.length || 0, + items: response.Contents?.map((o) => ({ + key: o.Key, + size: o.Size, + })), }); - } catch (error) { - console.error("R2 upload error:", error); - return NextResponse.json({ error: String(error) }, { status: 500 }); + } catch (e) { + return NextResponse.json({ error: e.message, name: e.name, code: e.$metadata }, { status: 500 }); } +} + +export { GET as default }; + +// Also exportGET for explicit call +export async function handler(req: NextRequest) { + return GET(); } \ No newline at end of file