tia/src/app/api/upload/route.ts
2026-05-10 14:24:17 +05:30

126 lines
No EOL
3.9 KiB
TypeScript

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;
if (!accountId || !accessKeyId || !secretKey || !bucket) {
throw new Error(`Missing R2 config`);
}
return {
client: new S3Client({
region: "auto",
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
credentials: { accessKeyId, secretAccessKey: secretKey },
}),
bucket,
baseUrl: `https://${accountId}.r2.cloudflarestorage.com`,
};
}
// 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 });
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(),
})).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) }, { 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 });
}
}