Clean up upload API
This commit is contained in:
parent
3e63930e21
commit
bdd2d3967b
1 changed files with 23 additions and 30 deletions
|
|
@ -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 });
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue