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를 다루고 둘 사이에서 데이터를 전송하는데 많은 어려움을 느끼고 있구나 깨달을 수 있었다ㅠㅠ 더 공부해야지...
안녕하세요 블록체인 공부중인 한 학생입니다.
올리신 코드 따라하다보니까 잘 안 되는데 혹시 코드 올라가있는 곳이 있나요? 있다면 알려주실 수 있을까요?