React + Nodejs 로 이미지 업로드 받아 IPFS 와 S3에 등록하기

zenaislee·2022년 3월 17일
0

NFT

목록 보기
1/1

NFT 발행을 구현 하는 중 클라이언트(react)에서 이미지와 데이터를 업로드 받아 서버 (nodejs) 에 전달 후 IPFS 와 S3 에 등록하는 과정에서 삽질을 좀 했었기에 기록 할 겸 적어본다.

React 부분

// 파일이 업로드 되면 호출 되는 함수
const handleChange = (event) => {
        const fileUploaded = event.target.files[0];
        
        // 이미지와 동영상만 올릴 수 있게 처리한 부분
        if (fileUploaded.type === "image/jpeg" || fileUploaded.type === "image/svg+xml" || fileUploaded.type === "image/png" || fileUploaded.type === "video/mp4") {
       
            setSelectedFile(fileUploaded);
            setFileName(fileUploaded.name);


        } else {
            alert("파일형식은 png, gif, mp4, jpg로 선택해 주세요.");
            return;
        }


    };


    const  saveImage = async () => {
    	// 이미지는 formData를 생성해서 image 라는 이름으로 노드에 전달해줌
        let formData = new FormData();
        formData.append('image', selectedFile);

		// 다른 데이터는 object에 담아 datas 라는 이름으로 넘김
        let variables = {
            name,
            category,
            type,
            content,
            fileName
        }
        formData.append("datas", JSON.stringify(variables))
       
       // 여기는 axios로 api 호출하는 부분을 saga로 뺐음
        dispatch({
            type: UPLOAD_NFT_REQUEST,
            data: formData

        })
    }

    return (
        <Container>
            <PageTitle></PageTitle>
            <form encType="multipart/form-data">
                <UploadWrapper>
                    <InputWrapper>
                        <TitleSpan>파일업로드</TitleSpan>
                        <HiddenFile type={"file"} ref={fileInput} onChange={handleChange} />
                        <FileInput onClick={uploadingFile} value={fileName}  accept="image/*" readOnly placeholder={"파일형식은 png, gif, mp4, jpg로 선택해 주세요."} />
                        <FileInputButton onClick={uploadingFile}>파일찾기</FileInputButton>
                    </InputWrapper>
                    <InputWrapper>
                        <TitleSpan>레어도(등급)</TitleSpan>
                        <SelectBox onChange={changeCategory}>
                            <option value="Normal">Normal</option>
                            <option value="art">Rare</option>
                            <option value="game">Super Rare</option>
                        </SelectBox>
                    </InputWrapper>
                    <InputWrapper>
                        <TitleSpan>속성</TitleSpan>
                        <SelectBox onChange={changeCategory}>
                            <option value="fire">Fire</option>
                            <option value="water">Water</option>
                            <option value="wind">Wind</option>
                        </SelectBox>
                    </InputWrapper>
                    <InputWrapper>
                        <TitleSpan>NFT 제목</TitleSpan>
                        <TitleInput placeholder={"제목을 입력해 주세요."} value={name} onChange={changeName}/>
                    </InputWrapper>
                    <InputWrapper>
                        <TitleSpan >NFT 설명글</TitleSpan>
                        <ContentText value={content}  placeholder={"설명글을 입력해 주세요."} onChange={changeContent} />
                    </InputWrapper>
                    <ButtonWrapper>
                        <TitleSpan />
                        <UploadButton onClick={saveImage}>업로드</UploadButton>
                    </ButtonWrapper>
                </UploadWrapper>
            </form>


        </Container>
    )
};

export default Upload;

의외로 골머리 많이 썩은 부분이 이미지와 함께 일반 데이터를 함께 넘기는 방법이었음.

이미지만 넘기기 -> 개쉬움
데이터만 넘기기 -> 개개쉬움
이미지와 데이터 함께 넘기기 -> 쉬워 보이는데 왜 안돼?? 상황이 계속 되는게 진짜 뭐가 이렇게 맘대로 안되지?? 싶었음

Nodejs 부분


router.post('/upload', async (req, res, next) => {
    try {

		const user = req.user[0];
        
		// formData로 넘어 온 datas를 받아옴
        const data = JSON.parse(req.body.datas);
        const {  name, category, type, content} = data;
        // 넘어온 이미지 파일을 받음
        let imageFile = req.files.image;
        // 파일 원본 이름 추출
        let original_name = imageFile.name.split('.');
        original_name = original_name[0];
		
        // ipfs.add 를 통해 ipfs 서버에 업로드 한다. path 는 키고 content가 실제 이미지 파일. buffer 나 arraybuffer 로 넘겨야 한다.
        await ipfs.add({path: imageFile.name, content: imageFile.data}, async (err, ipfsHash) => {
            if (err) {
                console.log('err', err);
                return res.status(501).json({ message: err });
            }

            let imageKey = ipfsHash[0].hash +  path.extname(imageFile.name);


			// ipfs 업로드 후 그 해쉬를 파일명으로 해서 s3에 업로드
            const params = {
                Bucket: S3_BUCKET,
                Key: imageKey,
                Body: imageFile.data,
                ACL:'public-read', // 이거 안해주면 버킷이 퍼블릭이어도 안보임
                ContentType: imageFile.mimetype
            };
            
            await s3.upload(params, async function(err, data) {
                if (err) {
                    console.log(err);
                }
                imageFile.url = data;

            });

            imageFile.hash = ipfsHash[0].hash;

			// 메타데이터 정의
            const metadata = {
                attributes: [
  
                    {
                        "trait_type": "MINING",
                        "value": "20"
                    },
                    
                ],
                name: "--",
                key: imageKey,
                description : "---",
                image : 'https://ipfs.io/ipfs/' + imageFile.hash,
                external_url : S3_URI + 'nft-files/' + imageKey
            }

            const src = Readable.from(JSON.stringify(metadata))
            // 메타데이터를 따로 ipfs에 업로드
            await ipfs.add({
                path: 'metadata.json',
                content: src
            },async  (err, hash) => {
                if (err) {
                    console.log('err', err);
                }

               
					// 여기선 디비에 업로드 함
                });

            })


        });

        //return res.status(502).json({ message: 'server error'});


    } catch (e) {
        console.error(e);
        res.status(500).json({ result: -100, data: null, err: "서버 에러입니다." });
    }
});

이렇게 쉬운(?)데 대체 왜 그렇게 오래 걸렸을까? 는... ipfs.add 가 정말 애를 많이 썩였음. 처음에 multer 로 받으면 끝나지 않나??? 했다가 content.once is not a function 인가만 실컷 보고 buffer 부분이 빈 값이 오고 하는걸 겪으며 멘붕의 멘붕과 삽질을 거의 5일간 했었다..... 아직 내가 node와 react를 다루고 둘 사이에서 데이터를 전송하는데 많은 어려움을 느끼고 있구나 깨달을 수 있었다ㅠㅠ 더 공부해야지...

profile
의욕(만) 넘치는 개발자

1개의 댓글

comment-user-thumbnail
2023년 2월 23일

안녕하세요 블록체인 공부중인 한 학생입니다.
올리신 코드 따라하다보니까 잘 안 되는데 혹시 코드 올라가있는 곳이 있나요? 있다면 알려주실 수 있을까요?

답글 달기