들어가기 전
이 글을 작성하게 된 계기는 내가 해당 기능을 만들때 구글링을 해봐도 대부분이 그냥 텍스트 데이터만 전송하거나 사진 파일을 포함하여 전송하더라도 하나의 사진만 보내는 것이였다. 하지만 내가 참여한 프로젝트는 여러 형식의 데이터와 여러장의 파일을 한꺼번에 수정하는 경우에 였고 그에 맞는 레퍼런스나 글은 찾지 못했다. 그래서 그냥 내가 만들었는 방식을 소개하고자 이 글을 작성하게 되었다.
게시판 수정을 위한 순서는 다음과 같다.
- 게시글 작성자와 사용자가 일치하는지 판단 후 일치하면 수정 페이지로 접근 가능
- 수정 페이지에서 기존 글 및 사진 파일 수정
이번 글에서 말하자 하는 것은 2번에 대한 과정을 자세하게 백엔드와 프론트엔드에서 처리했는지 얘기해보려고 한다. 단 필자가 백엔드 담당이였기 때문에 프론트엔드 부분은 제대로 설명은 못하고 어떤 방식으로 했는지에 대해서만 얘기한다.
1. 게시글 작성자와 사용자 일치 여부 판단
이 프로젝트에서 게시글 작성자와 사용자가 같은 id인지 판단하는 과정은 모두 프론트엔드에서 이루어졌다.
두 사람이 일치하는지 판단할려면 당연히 중복이 안되는 데이터로 판단을 해야했고 우리는 그것을 아이디로 하기로 하였다.
먼저 게시글 작성자에 대한 정보는 애초에 게시글에 대한 정보에 포함되어 있으므로 프론트에서 추출하여 사용하기로 했고, 그럼 현재 사용자의 아이디는 어떻게 얻었냐면 로그인 당시 받은 JWT토큰으로 아이디를 얻기로 했다. 그래서 간단하게 코드를 보자면
useEffect(() => {
const jwt = localStorage.getItem("jwt");
setCurrentUser(jwt);
const fetchData = async () => {
try {
const responsePost = await axios.get(baseURL + `community/${postId}`); // postId를 API 호출에 사용하여 게시글 데이터 가져오기
console.log(responsePost)
setData(responsePost.data);
const responseComments = await axios.get(
baseURL + `community/comments/${postId}`
); // postId를 API 호출에 사용하여 댓글 데이터 가져오기
console.log(responseComments)
setComments(responseComments.data);
} catch (error) {
console.log(error);
}
};
fetchData();
}, []);
이게 게시글 상세페이지의 useEffect이다. 처음에 페이지에 들어오면 localstorage에 있는 jwt를 꺼내 현재 사용자에 대한 아이디를 얻는다. 그리고 바로 백엔드에 해당 게시물에 대한 상세 정보를 요구하는 api요청을 보내고 받아 상세 페이지를 띄운다.
그 다음은 간단하다. 현재 사용자를 알고 게시글 작성자를 알기 때문에 두 개의 아이디를 비교하기만 하면 이 과정은 끝이다.
<Top>
<StyledButton className="back_btn" onClick={() => navigate(-1)}>{"<"}</StyledButton>
{currentUser && data?.post?.userID === currentUser && (
<>
<FontAwesomeIcon
icon={faBars}
size="2x"
color={"#f97800"}
onClick={openModal}
style={{ cursor: "pointer" , marginRight: "25px" }}
/>
{isModalOpen && (
<ModalBackground onClick={handleOutsideClick}>
<ModalBox onClick={(e) => e.stopPropagation()}>
<ModalButton onClick={EditCommunity}>수정</ModalButton>
<ModalButton
onClick={() => {
deleteCommunity(data.post.tpostID);
closeModal(); // Optional: Close modal after delete action
}}
>
삭제
</ModalButton>
</ModalBox>
</ModalBackground>
)}
</>
)}
</Top>
위 코드는 페이지 코드의 일부분을 가져온건데 보면 현재 유저와 게시글 작성자가 일치한 경우에만 수정과 삭제에 대한 아이콘이 나오게 만듦으로서 애초에 일치하지 않으면 접근 조차 못하게 만들었다. 이제 수정 버튼을 누르면 수정을 하는 페이지로 이동할 것이다.
2. 게시글 수정하기
이제 본격적인 게시글 수정에 대해 얘기해보자 일단 수정 페이지에 기존 게시물에 대한 데이터들은 모두 있다고 생각하자(진짜로 나오긴하는데 지금 db를 옮기는 중이라 보여줄수가 없다)
그럼 사용자가 사진 파일들을 제외한 다른 데이터들을 수정했다고 생각하자 그럼 사실 간단하다. 그냥 기존에 글을 등록할때처럼 데이터들을 백엔드에 전송을 하면 백엔드가 받아 업데이트를 하면 되는 것이다.
const handleEditsubmit = async () => {
if (titleRef.current.value.length < 1) {
titleRef.current.focus();
return;
} else if (locationRef.current.value.length < 1) {
locationRef.current.focus();
return;
} else if (data.some((item, i) => item.content.length < 1)) {
contentRefs.current.find((ref, i) => data[i].content.length < 1)?.focus();
return;
} else if (window.confirm("게시글을 수정하시겠습니까?")) {
const postId = detail.tpostID;
const contentArray = data.map((item) => item.content);
try {
await axios.put(`${baseURL}community/${postId}`, {
title: titleRef.current.value,
location: locationRef.current.value,
tags: JSON.stringify(tagList),
contents: contentArray,
});
navigate("/Community", { replace: true }); // 작성하는 페이지로 뒤로오기 금지
} catch (error) {
console.log(error);
}
}
};
이게 프론트에서 사진 파일을 제외하고 나머지 데이터들을 수정하여 전송하는 메서드이다. 그냥 필수 항목들이 있는지 여부를 확인하고 해당 데이터들을 JSON형식으로 보내면 되는 것이다. 그럼 이제 백엔드에서 어떻게 받는지 보자
// 사용자 검증 통과후 게시물 수정하는 메서드
async function updatePost(req, res) {
try {
const tags = JSON.parse(req.body.tags);
const user = req.decode.userID;
const { tpostID } = req.params;
const existedPost = await tUpload.tPost.findOne({
where: { tpostID: tpostID },
}); // DB에서 postId가 같은 데이터 찾기
if (user !== existedPost.userID) {
// 로그인 정보와 게시글 작성자가 같은지 확인
res.json({
result: false,
message: "사용자가 작성한 게시글이 아닙니다.",
});
} else {
// 게시물 내용은 그냥 업데이트
await tUpload.tPost.update(
{
title: req.body.title,
location: req.body.location,
},
{
where: { tpostID: tpostID },
}
);
const currentArray = await tUpload.TTagging.findAll({
where: { tpostID: tpostID },
});
const toDelete = currentArray.filter((x) => !tags.includes(x));
const toAdd = tags.filter((x) => !currentArray.includes(x));
await TTagging.destroy({ where: { tpostID: tpostID } }); // id는 실제로 사용되는 키로 대체해야합니다.
for (const tagName of toAdd) {
const [tag, created] = await Tag.findOrCreate({
where: { content: tagName },
defaults: { content: tagName },
});
const tagging = await TTagging.create({
tpostID: tpostID,
tagID: tag.tagID,
});
// savedTags.push(tag);
}
const imageIDArray = await tUpload.tPostImage.findAll({
attributes: ["imageId"],
where: { tpostID: tpostID },
});
const updatePromises = req.body.contents.map((content, index) => {
return tUpload.tPostImage.update(
{ content: content },
{
where: {
tpostID: tpostID,
imageId: imageIDArray[index].dataValues.imageId,
},
}
);
});
res.status(200).json({ result: true, message: "게시글 수정 완료" });
}
} catch (err) {
console.log(err);
res.status(400).json({ result: false });
}
}
이 코드가 백엔드에서의 게시글 업데이트 로직이다. 간략하게 말하자면 먼저 게시글ID로 해당 게시글이 존재하는지 검사를 하고 게시물의 작성자가 api요청자와 같은지 확인을 하고 일치하면 게시글에 대한 데이터인 제목, 위치, 태그 등을 업데이트 하고 마지막으로 사진 파일의 목록을 업데이트 하는 방식이다.
이제 가장 중요한게 사진 파일의 업데이트인데 이 메서드에서는 그냥 간단하게 사진 파일의 id를 업데이트한다고 했지만 사실 그전에 과정이 있다. 이제 이 과정이 무엇인지 알아보자
3. 사진 파일 업데이트
이제 이번 글을 작성한 주된 이유인 사진이 여러개일때 사진을 업데이트하는 방식에 대해 말해보겠다.
여행에 관한 정보를 올리는 게시판이였기 때문에 사진을 여러개 올릴 수 있게 만들었다.
그러나 게시물을 수정을 할려고 하면 사진 수정에서 다음과 같은 몇가지 문제가 발생했다.
- 사진 파일을 추가하거나 교체를 하고 난 뒤에 게시글 작성때와 같이 다른 데이터들과 같이 보낼 시 문제
-> 사실 수정 페이지에서 보이는 사진들을 실제 파일들이 아닌 서버 측에서 제공한 URL로 미리보기를 띄우는 방식이다. 그러므로 한꺼번에 데이터를 보내버리면 서버 측에서는 건드리지 않은 파일들은 지워졌다고 판단하고 수정하거나 추가한 사진들로만 게시글의 사진들을 바꾼다는 문제가 생겼다. - 사진에 순서가 있기 때문에 기존 사진 파일들에 순서를 부여하고 새로운 사진 파일들에 순서를 부여하여 업데이트
-> 기존 사진들의 순서와 사진 상태가 뒤죽박죽인 문제가 발생
이런 문제와 촉박한 개발 기간에 대한 문제로 다른 방식을 도입해 문제를 해결하고자 하였다. 기존 사진들을 그대로 가지면서 중간에 추가되거나 수정되는 사진들도 반영을 잘하는 방식으로 해결을 해야했다.
그래서 생각한 방식이 사진 파일을 하나씩 올릴때 마다 서버로 사진을 전송하여 관리하는 방식이였다. 이제 이 방식에 대해 소개하겠다.
async function updateImage(req, res) {
const jsonData = JSON.parse(req.body.jsonData);
const number = jsonData.number;
const tpostID = jsonData.tpostID;
try {
const imageArray = await tUpload.tPostImage.findAll({
attributes: ["imageUrl"],
where: { tpostID: tpostID },
});
const existedURL = imageArray[number].dataValues.imageUrl;
const imageSavePromises = req.files.map(async (file, index) => {
const posting = await tUpload.tPostImage.update(
{
imageURL: file.key,
},
{
where: { tpostID: tpostID, imageUrl: existedURL },
}
);
if (imageSavePromises) {
res.status(200).json({ result: true, message: "업데이트 성공" });
} else {
res
.status(404)
.json({ result: false, message: "업데이트 중 문제 발생" });
}
});
} catch (err) {
console.error(err);
res.status(500).json({ result: false, message: "이미지 삭제 실패" });
}
}
async function newImage(req, res) {
const jsonData = JSON.parse(req.body.jsonData);
try {
const imageSavePromises = req.files.map(async (file, index) => {
const posting = await tUpload.tPostImage.create({
imageURL: file.key,
tpostID: jsonData.tpostID,
});
const imageID = posting.getDataValue("imageID");
if (posting) {
res
.status(200)
.json({ imageID: imageID, message: "이미지 업로드 성공" });
} else {
res.status(404).json({
imageID: null,
message: "이미지 업로드에 문제가 발생했습니다.",
});
}
});
} catch (err) {
console.error(err);
res.status(500).json({ imageID: null, message: "이미지 업로드 실패" });
}
}
이 두 메서드가 수정할때 기존 사진을 수정하는 메서드와 기존 게시글에 사진을 추가하는 메서드이다. 먼저 기존 사진을 수정하는 경우에는 프론트로 부터 몇번째 사진인지를 받아서 해당 사진의 URL을 업데이트하는 방식으로 해결을 하였다. 그리고 사진을 추가하는 경우에는 기존 사진 테이블의 마지막에 생성만 하면 되므로 간단하게 끝났다. 참고로 저기서 반환을 할때 사진의 고유ID도 같이 보내는 이유는 이 게시글은 사진 하나당 본문이 하나씩이라서 본문의 순서때문에 나중에 수정을 완료할때 해당 사진의 ID가 필요하기 때문이다.
이제 이렇게 하면 사진을 여러개 가지는 게시물을 수정할때 생기는 문제를 해결하며 게시글 수정을 할 수 있다.
정리
글을 굉장히 두서없게 쓴거 같긴한데 다시 정리를 하자면
- 게시글 수정시 사진이 여러개면 사진 파일과 다른 데이터를 한번에 수정 시 사진 파일 쪽에 문제가 생김
- 그래서 각 사진을 수정하거나 추가할때 마다 서버로 바로 전송하여 업데이트하는 방식으로 문제 해결
이런 방식이다. 물론 이 방식 말고도 다른 기발한 방식이 더 있을 수 있다. 사실 나도 기존 사진에 순서와 상태를 관리하는 데이터와 새롭게 추가되는 사진에도 상태를 나타내는 데이터를 추가하여 한번에 업데이트를 하는 방식도 고민하였지만 이렇게 하면 백엔드에서의 로직이 너무 복잡해질거 같아 포기하고 현재의 방식을 채택한 것이다.
사실 나는 해당 문제를 좀 길게 끌었는데 그 이유가 위 방식처럼 로직이 복잡해도 한번에 업데이트를 할지 아님 사진을 하나씩 전송하여 해결할지 고민을 굉장히 많이 했기 때문이다. 하지만 결국 후자의 방식을 채택하여 개발하였는데 이를 통해 가끔씩은 너무 복잡하게 생각해서 가장 최고의 결과를 내는 것 보다는 생각보다 간단하게 해결하는 것이 더 좋을 수 도 있다는 것을 깨달았다.
'Node.js(Express)' 카테고리의 다른 글
| [Node.js] Modbus TCP를 이용한 공장 원격 관리 서버 제작 (0) | 2024.08.06 |
|---|---|
| [Node.js] SNS 만들기 - Multer로 Multipart/Form-Data로 사진 전송 게시글 작성 (0) | 2024.02.02 |
| [Node.js] SNS 만들기 - Nodemailer를 이용한 사용자 인증 (0) | 2024.01.22 |
| [Node.js] JWT 사용하기 - access token, refresh token (0) | 2024.01.20 |
| [Node.js] JWT에 대해 알아보기 (0) | 2024.01.18 |