#How are you uploading them

1 messages · Page 1 of 1 (latest)

golden oyster
#

Pulled this into a Thread

vivid light
#

Thanks a bunch

#

so looks like the code from that link you posted is depreciated (At least VSCode is warning me it's depreciated) so I'm going to try this:
let decodedImage = Buffer.from(req.body, 'base64');

#

Look about right?

golden oyster
#

Yes

vivid light
#

ok let me try that

#
    const response = await fetch(url, {
        method: "PUT",
        body: decodedImage,
        headers: {
            "Content-Type": req.headers['content-type'],
            "Content-Disposition": req.headers['content-disposition'],
          }
    })```
#

fraid it's still getting corrupted

#

does the frontend -> backend API need to do anything different?

#

frontend code for ref: js let file = event.target.files[0] console.log("file: ",file) let res = await fetch("/api/uploadImage", { method:"PUT", body:file, headers:{ "Content-Type":file.type, "Content-Disposition": `attachment; filename="${file.name}"`, "name":file.name, } }) let data = await res.json() < for ref

golden oyster
#

Which one is frontend?

#

What is your content-type set to?

vivid light
#

"image/jpeg"

golden oyster
#

Try making it binary/octet-stream

vivid light
#

backend code is now:

let newFileName = crypto.randomUUID()+"."+req.headers['content-type'].split("/")[1];
    let decodedImage = Buffer.from(req.body, 'base64');
    const response = await fetch(url, {
        method: "PUT",
        body: decodedImage,
        headers: {
            "Content-Type": "binary/octet-stream",
            "Content-Disposition": req.headers['content-disposition'],
          }
    })```
#

Still no

golden oyster
#

Whats the value of decodedImage?

vivid light
#

this is the image I'm uploading (Just a dummy image, I know it's silly, sorry). I'm going to send the file I'm getting back out of it

#

Corrupted image I get out of S3

#

ohai

#

that's too small

#
$R  �=� �&����e�,��^��k]�]�vŵ�f��
�    ��i��k�inq��I{�f�a��a�Z���av�inq��Hj(�W��ʹנ����j��ݻ-��{
��IGUߵ��dP���&�����~P=���:�X�6H8�,```
golden oyster
#

Okay. So once you upload the picture. Are you able to go into the S3 bucket and view the pic?

vivid light
#

no, it is corrupted in S3

golden oyster
#

Hmmm okay.

vivid light
#

before I was trying to bufferize it I was getting the correct size image at least

#

even if the file is still corrupt

#

maybe "invalid" is what I should be saying instead of corrupt lol

golden oyster
vivid light
#

There must be something weird about my AWS sdk because

const s3 = new AWS.S3({

I don't think S3 is a member of AWS

golden oyster
#

Let me boot up a quick app real quick to test this

vivid light
#

the big exception being that there's that API call instead of directly uploading from the frontend

#

also I don't know what my problem was with this function signature before, it looks like it's getting through the type checker at least this time so 👍

golden oyster
#

Thats good!

vivid light
#

ikr haha but it's still not uploading. This is my present backend code:

    const response = await s3.upload({
        Bucket: Bucket.PortfoliizeImageBucket.bucketName,
        Key: crypto.randomUUID(),
        Body: decodedImage,
        ACL: "public-read"
    }).promise();
    console.log("response: ",response)```
golden oyster
#

Whats your response?

vivid light
#

Fraid still not getting useful files. They're still the 153 byte thing

#

Are we sure that's the correct way to get the decodedImage? I suspect that might be wrong as it's consistently producing tiny files

golden oyster
#

Im checking to see.

vivid light
#

maybe it should be converted to a buffer/B64 before upload to the backend

golden oyster
#

Yes. I thought thats what you were doing? lol

vivid light
#

Oh no

#

the frontend code is this:

        console.log("file: ",file)
        let res = await fetch("/api/uploadImage", {
            method:"PUT",
            body:file,
            headers:{
                "Content-Type":file.type,
                "Content-Disposition": `attachment; filename="${file.name}"`,
                "name":file.name,
            }
        })
        let data = await res.json()```
golden oyster
vivid light
#

Frankly? Because that's what all the tutorials are written for

#

The other thing is since this is SST, it lives on the cloud

golden oyster
#

Ahh.

vivid light
#

I mean, I've been following the SST NextJS tutorial

golden oyster
#

Im deploying the bootstrap stack now. I cloned the repo from the tutorial.

vivid light
#

ty

#

Where I'm at rn:
Frontend code:

        const toBase64 = (file:any) => new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onload = () => resolve(reader.result);
            reader.onerror = reject;
        });
        
        let fileBase64 = await toBase64(file)

        let res = await fetch("/api/uploadImage", {
            method:"PUT",
            body:fileBase64 as string,
            headers:{
                "name":file.name,
                "type":file.type
            }
        })
        let data = await res.json()```
golden oyster
vivid light
#

Yes, it most likely is, but I didn't do it that exact way

#

I want the upload code to live on the backend of my server instead of on the frontend

golden oyster
#

Okay. So what does your /api/uploadImage endpoint look like?

vivid light
#

right now:
backend:

const response = await s3.upload({
        Bucket: Bucket.PortfoliizeImageBucket.bucketName,
        Key: crypto.randomUUID(),
        Body: req.body,
        ACL: "public-read",
        ContentEncoding: 'base64',
        ContentType: req.headers['type'],
    }).promise();```
#

There's likely some kind of confusion between the encoding type and so on so bear with me.

golden oyster
#

Why are you doing s3.upload ?

#

What does your package.json look like?

vivid light
#

that's just one of the links I've seen go by, that's the one I've happened to settle on, they don't seem to do much differently

#
{
  "name": "portfoliize",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "sst bind next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@aws-sdk/client-s3": "^3.395.0",
    "@aws-sdk/s3-request-presigner": "^3.395.0",
    "@prisma/client": "^5.1.1",
    "@types/node": "20.5.0",
    "@types/react": "18.2.20",
    "@types/react-dom": "18.2.7",
    "eslint": "8.47.0",
    "eslint-config-next": "13.4.17",
    "next": "13.4.17",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "typescript": "5.1.6"
  },
  "devDependencies": {
    "aws-cdk-lib": "2.91.0",
    "constructs": "10.2.69",
    "prisma": "^5.1.1",
    "sst": "^2.24.7"
  }
}
#

Try setting stuff up with the extra layer between the file upload and the upload to S3 and see if you can get that to work

#

and if you can, how the hecc lol

golden oyster
#

Do you have your code on Github?

#

Okay. So I made an api Endpoint called uploadImage.ts

#

This is what my page looks like

import {Inter} from 'next/font/google';
import styles from '@/styles/Home.module.css';

const inter = Inter({subsets: ['latin']});

export default function Home() {
    async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
        e.preventDefault();

        const file = (e.target as HTMLFormElement).file.files?.[0]!;

        // First, get the signed URL:
        const response = await fetch('/api/uploadImage', {method: 'POST'});
        const data = await response.json();
        const {url} = data;

        const image = await fetch(url, {
            body: file,
            method: 'PUT',
            headers: {
                'Content-Type': file.type,
                'Content-Disposition': `attachment; filename="${file.name}"`,
            },
        });

        window.location.href = image.url.split('?')[0];
    }

    return (
        <main className={styles.main}>
            <form className={styles.form} onSubmit={handleSubmit}>
                <input name="file" type="file" accept="image/png, image/jpeg" />
                <button type="submit" className={inter.className}>
                    Upload
                </button>
            </form>
        </main>
    );
}
#

My API

import crypto from 'crypto';
import {Bucket} from 'sst/node/bucket';
import {getSignedUrl} from '@aws-sdk/s3-request-presigner';
import {S3Client, PutObjectCommand} from '@aws-sdk/client-s3';
import {NextApiRequest, NextApiResponse} from 'next';

interface Data {
    url?: string;
    error?: string;
}

export default async function handler(
    req: NextApiRequest,
    res: NextApiResponse<Data>
) {
    if (req.method !== 'POST') {
        return res.status(405).end();
    }

    try {
        const command = new PutObjectCommand({
            ACL: 'public-read',
            Key: crypto.randomUUID(),
            Bucket: Bucket.public.bucketName,
        });
        const url = await getSignedUrl(new S3Client({}), command);

        return res.status(200).json({url});
    } catch (error) {
        return res.status(500).json({error: 'Failed to get signed URL'});
    }
}
vivid light
#

And this is working?

golden oyster
#

Yes

vivid light
#

Sweet. Let me see if I can do the old copy paste

#

Wait

#

the frontend is still doing the uploading here

#

I'm trying to avoid having the upload code living on the frontend at all

#

the file gets sent to the API and the API takes care of it

golden oyster
#

How will it get sent to the API?

vivid light
#

That part is fine

#

it's fine if it goes through my frontend to my backend

#

but I don't want AWS code living on the frontend is all

#

if we're sending upload urls to the frontend that makes me uncomfortable

vivid light
#

fair enough... Yeah I'm beginning to think through the attack vectors I'm picturing and maybe I'm exaggerating the threat

golden oyster
vivid light
#

if using the frontend for image uploads really is best practices, hell, think I should probably stop worring lol

golden oyster
#

Well how else would you upload images? Lol.

vivid light
#

It's just weird. First you need to call the backend to get the signed URL. Then the frontend needs to call S3 to get the final submission URL. then the frontend needs to call yet another API in order to create a fileupload ORM or whatever.

#

As opposed to frontend just sending the file to the backend, backend uploading to S3 and doing all the backend stuff, including ORM and all that

#

I admit that the frontend stuff is simpler and easier to explain in a quickstart tutorial, but it feels like it can't be best practices

#

but if it is, I mean, sure.

golden oyster
#

Alright. Got any more questions? 🙂

vivid light
#

Thank you, I should be good