code

I'm following the example but I seem to get nested folders, is this intentional?
25 Replies
Harris
HarrisOP3y ago
import { NextRequest, NextResponse } from 'next/server';

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

if (!process.env.S3_ENDPOINT) {
throw new Error('S3_ENDPOINT is not defined');
}

if (!process.env.S3_ACCESS_KEY_ID) {
throw new Error('S3_ACCESS_KEY_ID is not defined');
}

if (!process.env.S3_SECRET_ACCESS_KEY) {
throw new Error('CLOUDFLARE_SECRET_ACCESS_KEY is not defined');
}

const S3 = new S3Client({
region: 'auto',
endpoint: process.env.S3_ENDPOINT,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
},
});

// JSON body: { "filename": "my-file.txt" }
export async function POST(request: NextRequest) {
const data = await request.json();
const filename = data.filename;

const url = await generateSignedUrl(filename);

return NextResponse.json({ url });
}

async function generateSignedUrl(filename: string) {
const command = new PutObjectCommand({
Bucket: process.env.S3_BUCKET_NAME,
Key: filename,
});

const url = await getSignedUrl(S3, command, {
expiresIn: 60 * 60 * 24 * 7, // 7 days
});
return url;
}
import { NextRequest, NextResponse } from 'next/server';

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

if (!process.env.S3_ENDPOINT) {
throw new Error('S3_ENDPOINT is not defined');
}

if (!process.env.S3_ACCESS_KEY_ID) {
throw new Error('S3_ACCESS_KEY_ID is not defined');
}

if (!process.env.S3_SECRET_ACCESS_KEY) {
throw new Error('CLOUDFLARE_SECRET_ACCESS_KEY is not defined');
}

const S3 = new S3Client({
region: 'auto',
endpoint: process.env.S3_ENDPOINT,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
},
});

// JSON body: { "filename": "my-file.txt" }
export async function POST(request: NextRequest) {
const data = await request.json();
const filename = data.filename;

const url = await generateSignedUrl(filename);

return NextResponse.json({ url });
}

async function generateSignedUrl(filename: string) {
const command = new PutObjectCommand({
Bucket: process.env.S3_BUCKET_NAME,
Key: filename,
});

const url = await getSignedUrl(S3, command, {
expiresIn: 60 * 60 * 24 * 7, // 7 days
});
return url;
}
Sid
Sid3y ago
I suppose you read Kian’s response, but yeah this is expected. There’s no such thing as “folders” really, the dashboard just uses / as a delimiter to fake them.
Harris
HarrisOP3y ago
I'm a bit confused, when I then try to get the info of the file
HEAD /camcorder-ai/406458e4-e71a-47de-800e-7ff910190619.mp4
NotFound: UnknownError
HEAD /camcorder-ai/406458e4-e71a-47de-800e-7ff910190619.mp4
NotFound: UnknownError
const headObjectCommand = new HeadObjectCommand({
Bucket: process.env.S3_BUCKET_NAME,
Key: parsedUrl.pathname,
});

const headObjectResponse = await S3.send(headObjectCommand);
const headObjectCommand = new HeadObjectCommand({
Bucket: process.env.S3_BUCKET_NAME,
Key: parsedUrl.pathname,
});

const headObjectResponse = await S3.send(headObjectCommand);
even though /camcorder-ai/file.mp4 is what I was returned back from the S3 command
Sid
Sid3y ago
I think the leading ‘/‘ is what your problem is camcorder-ai/file.mp4 exists, but /camcorder-ai/file.mp4 does not. I’m going off the dashboard screenshot above
Harris
HarrisOP3y ago
I don't add the /camcorder-ai/ to any of my file upload code at all so I'm confused where it's coming from
Sid
Sid3y ago
How are you uploading these files? Some tools like to prefix object names with the bucket’s name. Is this the S3 SDK?
Harris
HarrisOP3y ago
aws s3 sdk yeah code here
Sid
Sid3y ago
Try setting the “forcePathStyle” key in your config to false?
Harris
HarrisOP3y ago
didn't know that was a thing, will try it!
Sid
Sid3y ago
Yeah so S3 (and consequentially R2) has two ways of addressing buckets, one where the bucket name is in the hostname, bland another where the bucket name is in the path. Hostname style domains would look like: <bucketName>.<accountId>.r2.cloudflarestorage.com, and “path style” hostname would look like: <accountId>.r2.cloudflarestorage.com/<bucketName>. I bet you use the hostname variant as your S3_ENDPOINT env var, but S3 thinks you’re using the pathname variant (which is the default)
Harris
HarrisOP3y ago
ooohh that explains a lot
Sid
Sid3y ago
So it sends requests to <bucketName>.<accountId>.r2.cloudfalrestorage.com/<bucketName>/<objectKey>. That’s where the extra “camcorder-ai” is likely coming from. You can either set forcePathStyle to false, or switch your endpoint URL
Harris
HarrisOP3y ago
const S3 = new S3Client({
region: 'auto',
endpoint: process.env.S3_ENDPOINT,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
},
forcePathStyle: false,
});
const S3 = new S3Client({
region: 'auto',
endpoint: process.env.S3_ENDPOINT,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
},
forcePathStyle: false,
});
I set it to false but it still seems to do path style
# These are the values for Cloudflare R2, they should be able to swapped to any other S3 compatible storage provider with minimal changes
S3_ENDPOINT='https://c38bc3761ea690e1a45693a40ce4fb2f.r2.cloudflarestorage.com/camcorder-ai'
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_BUCKET_NAME='camcorder-ai'
S3_HOSTNAME="camcorder-ai.c38bc3761ea690e1a45693a40ce4fb2f.r2.cloudflarestorage.com"
# These are the values for Cloudflare R2, they should be able to swapped to any other S3 compatible storage provider with minimal changes
S3_ENDPOINT='https://c38bc3761ea690e1a45693a40ce4fb2f.r2.cloudflarestorage.com/camcorder-ai'
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_BUCKET_NAME='camcorder-ai'
S3_HOSTNAME="camcorder-ai.c38bc3761ea690e1a45693a40ce4fb2f.r2.cloudflarestorage.com"
ohhh wait is it because the endpoint here?
Sid
Sid3y ago
Ha yeah Remove the bucket name from there, the SDK will add it automatically
Harris
HarrisOP3y ago
as an environment var? oh you mean in the endpoint
S3_ENDPOINT='https://c38bc3761ea690e1a45693a40ce4fb2f.r2.cloudflarestorage.com/'
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_BUCKET_NAME='camcorder-ai'
S3_HOSTNAME="camcorder-ai.c38bc3761ea690e1a45693a40ce4fb2f.r2.cloudflarestorage.com"
S3_ENDPOINT='https://c38bc3761ea690e1a45693a40ce4fb2f.r2.cloudflarestorage.com/'
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_BUCKET_NAME='camcorder-ai'
S3_HOSTNAME="camcorder-ai.c38bc3761ea690e1a45693a40ce4fb2f.r2.cloudflarestorage.com"
Sid
Sid3y ago
Yep, this should work as expected
Harris
HarrisOP3y ago
thank you my god this has been annoying to debug i was like I'm uploading files to root but a folder gets added but then when I retrieve files I can't have the folder in the string!
Sid
Sid3y ago
Yeah haha unfortunately we can't make this "easier", this is just the S3 API contract
Harris
HarrisOP3y ago
gotcha well thank you for explaining it to me
Sid
Sid3y ago
No probs! I think this should explain your 404s as well
Harris
HarrisOP3y ago
perfect, I can now go through and remove the weird parsing I had to do on my keys to remove the "fake" folder
Harris
HarrisOP3y ago
Harris
HarrisOP3y ago
@sdnts happydance
Harris
HarrisOP3y ago
i finally got something to upload to root
Sid
Sid3y ago
Yay, nice!

Did you find this page helpful?