Clean up upload API

This commit is contained in:
Manohar Gupta 2026-05-10 14:41:18 +05:30
parent 3e63930e21
commit bdd2d3967b

View file

@ -1,4 +1,4 @@
import { S3Client, PutObjectCommand, ListObjectsV2Command, GetObjectCommand } from "@aws-sdk/client-s3"; import { S3Client, PutObjectCommand, ListObjectsV2Command, ListBucketsCommand } from "@aws-sdk/client-s3";
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";
@ -8,24 +8,22 @@ function getR2() {
const secretKey = process.env.R2_SECRET_ACCESS_KEY; const secretKey = process.env.R2_SECRET_ACCESS_KEY;
const bucket = process.env.R2_BUCKET_NAME; 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) { if (!accountId || !accessKeyId || !secretKey || !bucket) {
throw new Error(`Missing R2 config: accountId=${!!accountId}, accessKeyId=${!!accessKeyId}, secretKey=${!!secretKey}, bucket=${!!bucket}`); throw new Error(`Missing R2 config: ${!!accountId} ${!!accessKeyId} ${!!secretKey} ${!!bucket}`);
} }
// Use public development URL (from R2 bucket settings) // S3 API endpoint includes bucket name: https://<accountId>.r2.cloudflarestorage.com/<bucket>
const pubBaseUrl = `https://pub-37a76fd657c94d1dbc521a109c087a11.r2.dev/tia`; const endpoint = `https://${accountId}.r2.cloudflarestorage.com/${bucket}`;
return { return {
client: new S3Client({ client: new S3Client({
region: "auto", region: "auto",
endpoint: `https://${accountId}.r2.cloudflarestorage.com/tia`, endpoint,
credentials: { accessKeyId, secretAccessKey: secretKey }, credentials: { accessKeyId, secretAccessKey: secretKey },
}), }),
bucket, bucket,
baseUrl: pubBaseUrl, // Public URL uses pub- subdomain format
baseUrl: `https://pub-37a76fd657c94d1dbc521a109c087a11.r2.dev/tia`,
}; };
} }
@ -33,23 +31,23 @@ function getR2() {
export async function GET(req: NextRequest) { export async function GET(req: NextRequest) {
try { try {
const { client, bucket, baseUrl } = getR2(); const { client, bucket, baseUrl } = getR2();
const { searchParams } = new URL(req.url); const childId = req.nextUrl.searchParams.get("childId") || "default";
const prefix = searchParams.get("childId") || "default";
// List all memories for this child
const command = new ListObjectsV2Command({
Bucket: bucket,
Prefix: `memories/${childId}`,
MaxKeys: 50
});
// List all objects with memories/ prefix
const command = new ListObjectsV2Command({ Bucket: bucket, Prefix: "memories/", MaxKeys: 50 });
const res = await client.send(command); const res = await client.send(command);
if (!res.Contents || res.Contents.length === 0) { const objects = (res.Contents || []).map((obj) => ({
return NextResponse.json({ memories: [], debug: "no files found" });
}
const objects = res.Contents.map((obj) => ({
key: obj.Key, key: obj.Key,
url: `${baseUrl}/${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()); }));
return NextResponse.json({ memories: objects }); return NextResponse.json({ memories: objects });
} catch (error) { } catch (error) {
@ -58,7 +56,7 @@ export async function GET(req: NextRequest) {
} }
} }
// POST: Upload (proxy through server to avoid CORS) // POST: Get upload URL
export async function POST(req: NextRequest) { export async function POST(req: NextRequest) {
let body; let body;
try { try {
@ -76,19 +74,15 @@ export async function POST(req: NextRequest) {
const { client, bucket, baseUrl } = getR2(); 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, 8)}.${ext}`;
// Instead of presigned URL, we're using PUT to our own server
// The frontend willPOST the file here directly
const command = new PutObjectCommand({ const command = new PutObjectCommand({
Bucket: bucket, Bucket: bucket,
Key: key, Key: key,
ContentType: contentType, ContentType: contentType,
}); });
// Generate short-lived presigned URL that bypasses CORS issues const url = await getSignedUrl(client, command, { expiresIn: 3600 });
// 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({ return NextResponse.json({
uploadUrl: url, uploadUrl: url,
@ -101,13 +95,12 @@ export async function POST(req: NextRequest) {
} }
} }
// PUT: Direct upload from file content // PUT: Direct upload
export async function PUT(req: NextRequest) { export async function PUT(req: NextRequest) {
try { try {
const { client, bucket, baseUrl } = getR2(); const { client, bucket, baseUrl } = getR2();
const { searchParams } = new URL(req.url); const key = req.nextUrl.searchParams.get("key");
const key = searchParams.get("key"); const contentType = req.nextUrl.searchParams.get("contentType") || "image/jpeg";
const contentType = searchParams.get("contentType") || "image/jpeg";
if (!key) { if (!key) {
return NextResponse.json({ error: "Missing key" }, { status: 400 }); return NextResponse.json({ error: "Missing key" }, { status: 400 });