Skip to content

Commit

Permalink
feat: 댓글 필터링 기능 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
hardlife0 committed Nov 7, 2024
1 parent 3969199 commit 00b58d1
Show file tree
Hide file tree
Showing 16 changed files with 276 additions and 94 deletions.
4 changes: 2 additions & 2 deletions src/api/useGetComment.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ export const getPostComments = async (postId) => {
}
};

// transformCommentData 함수도 약간 수정
const transformCommentData = (commentData) => ({
id: commentData.id,
postId: commentData.postId,
parentId: commentData.parentId,
content: commentData.content,
clean: commentData.clean ?? true, // clean 필드 추가, 기본값은 true
depth: commentData.depth,
createdAt: commentData.createdAt,
updatedAt: commentData.updatedAt,
Expand All @@ -94,5 +94,5 @@ const transformCommentData = (commentData) => ({
name: commentData.member.name,
nameEnglish: commentData.member.nameEnglish,
},
replies: [], // 초기 replies 배열 추가
replies: [],
});
60 changes: 4 additions & 56 deletions src/api/useGetPost.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const getPost = async (postId) => {
"createdAt",
"updatedAt",
"member",
"clean", // clean 필드로 수정
];

for (const field of requiredFields) {
Expand All @@ -45,9 +46,10 @@ export const getPost = async (postId) => {
content: postData.content,
createdAt: postData.createdAt,
updatedAt: postData.updatedAt,
imageUrls: postData.cloudFrontPaths || [], // cloudFrontPath 사용
s3ImageUrls: postData.s3ImagePaths || [], // s3ImagePaths 보관
imageUrls: postData.cloudFrontPaths || [],
s3ImageUrls: postData.s3ImagePaths || [],
likes: (postData.likes !== undefined ? postData.likes : 0).toString(),
clean: postData.clean, // clean 필드로 수정
author: {
id: postData.member.id,
role: postData.member.role,
Expand All @@ -66,57 +68,3 @@ export const getPost = async (postId) => {
throw error;
}
};

// useGetPosts.jsx
export const getPosts = async (pageNo = 0, pageSize = 10) => {
try {
const response = await api.get("/api/v1/posts", {
params: {
pageNo,
pageSize,
},
});

const transformedData = {
content: response.data.content.map((post) => ({
post_id: post.id,
post_title: post.title,
post_content: post.content,
created_at: post.createdAt,
updated_at: post.updatedAt,
member: {
member_id: post.member.id,
member_name: post.member.name,
member_name_english: post.member.nameEnglish,
course: post.member.course,
role: post.member.role,
},
// cloudFrontPaths 사용
imageUrls: post.cloudFrontPaths || [],
// 첫 번째 이미지를 대표 이미지로 사용
mainImageUrl: post.cloudFrontPaths?.[0] || "",
// S3 URL도 보관
s3ImageUrls: post.s3ImagePaths || [],
})),
totalPages: response.data.totalPages,
totalElements: response.data.totalElements,
size: response.data.size,
number: response.data.number,
first: response.data.first,
last: response.data.last,
};

return transformedData;
} catch (error) {
console.error("Error fetching posts:", error);
return {
content: [],
totalPages: 0,
totalElements: 0,
size: pageSize,
number: pageNo,
first: true,
last: true,
};
}
};
42 changes: 26 additions & 16 deletions src/api/useGetPosts.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,31 @@ export const getPosts = async (pageNo = 0, pageSize = 10) => {
},
});

// API 응답을 데이터베이스 스키마에 맞게 변환
console.log("API Response:", response.data); // API 응답 로깅 추가

const transformedData = {
content: response.data.content.map((post) => ({
post_id: post.id,
post_title: post.title,
post_content: post.content,
created_at: post.createdAt,
updated_at: post.updatedAt,
member: {
member_id: post.member.id,
member_name: post.member.name,
member_name_english: post.member.nameEnglish,
course: post.member.course,
role: post.member.role,
},
imageUrls: post.imageUrls || [],
})),
content: response.data.content.map((post) => {
console.log("Individual post data:", post); // 개별 게시물 데이터 로깅
return {
post_id: post.id,
post_title: post.title,
post_content: post.content,
created_at: post.createdAt,
updated_at: post.updatedAt,
clean: post.clean === undefined ? true : post.clean, // clean 필드 처리 수정
member: {
member_id: post.member.id,
member_name: post.member.name,
member_name_english: post.member.nameEnglish,
course: post.member.course,
role: post.member.role,
},
imageUrls: post.cloudFrontPaths || [],
mainImageUrl: post.cloudFrontPaths?.[0] || "",
s3ImageUrls: post.s3ImagePaths || [],
likes: post.likes?.toString() || "0",
};
}),
totalPages: response.data.totalPages,
totalElements: response.data.totalElements,
size: response.data.size,
Expand All @@ -35,6 +43,8 @@ export const getPosts = async (pageNo = 0, pageSize = 10) => {
last: response.data.last,
};

console.log("Transformed data:", transformedData); // 변환된 데이터 로깅

return transformedData;
} catch (error) {
console.error("Error fetching posts:", error);
Expand Down
35 changes: 35 additions & 0 deletions src/component/ FilteredComment.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useState } from "react";
import PropTypes from "prop-types";
import "./styles/FilteredComment.scss";

const FilteredCommentContent = ({ content, clean = true }) => {
const [isRevealed, setIsRevealed] = useState(false);

if (clean) {
return <div className="comment-content">{content}</div>;
}

return (
<div className="filtered-comment">
{!isRevealed ? (
<div className="filtered-message">
<span className="warning-text">
[구름봇에 의해 가려진 댓글입니다]
</span>
<button onClick={() => setIsRevealed(true)} className="reveal-button">
확인하기
</button>
</div>
) : (
<div className="comment-content">{content}</div>
)}
</div>
);
};

FilteredCommentContent.propTypes = {
content: PropTypes.string.isRequired,
clean: PropTypes.bool,
};

export default FilteredCommentContent;
7 changes: 6 additions & 1 deletion src/component/Comment.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from "prop-types";
import { MoreHorizontal } from "lucide-react";
import CommentInput from "./CommentInput";
import { getCommentDetail } from "../api/useGetCommentDetail";
import FilteredCommentContent from "./ FilteredComment";
import {
useCreateComment,
useEditComment,
Expand Down Expand Up @@ -255,7 +256,10 @@ const Comment = ({
</div>
) : (
<>
<div className="content-text">{comment.content}</div>
<FilteredCommentContent
content={comment.content}
clean={comment.clean}
/>
<div className="comment-footer">
<span className="comment-date">
{formatDate(comment.createdAt)}
Expand Down Expand Up @@ -310,6 +314,7 @@ Comment.propTypes = {
comment: PropTypes.shape({
id: PropTypes.number.isRequired,
content: PropTypes.string.isRequired,
clean: PropTypes.bool, // clean 필드 추가
createdAt: PropTypes.string.isRequired,
parentId: PropTypes.number,
depth: PropTypes.number.isRequired,
Expand Down
44 changes: 44 additions & 0 deletions src/component/FilteredContent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useState } from "react";
import PropTypes from "prop-types";
import "./styles/FilteredContent.scss";

const FilteredContent = ({ title, content, clean = true }) => {
const [isRevealed, setIsRevealed] = useState(false);

if (clean) {
return (
<div className="content-wrapper">
<div className="title">{title}</div>
<div className="content">{content}</div>
</div>
);
}

return (
<div className="filtered-content">
{!isRevealed ? (
<div className="filtered-message">
<span className="warning-text">
[구름봇에 의해 가려진 게시물입니다]
</span>
<button onClick={() => setIsRevealed(true)} className="reveal-button">
확인하기
</button>
</div>
) : (
<div className="revealed-content">
<div className="title">{title}</div>
<div className="content">{content}</div>
</div>
)}
</div>
);
};

FilteredContent.propTypes = {
title: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
clean: PropTypes.bool,
};

export default FilteredContent;
16 changes: 8 additions & 8 deletions src/component/Postlist.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import PropTypes from "prop-types";
import "./styles/Postlist.scss";
import Hearticon from "../image/Hearticon.svg";
//pr test
const Postlist = ({ id, title, imageUrl, likes }) => {
import SimpleFilteredContent from "./SimpleFilteredContent";

const Postlist = ({ id, title, imageUrl = "", likes, clean = true }) => {
return (
<div className="postlist-container" data-post-id={id}>
<div className="postlist-img">
Expand All @@ -13,14 +14,16 @@ const Postlist = ({ id, title, imageUrl, likes }) => {
onError={(e) => {
console.error("Image load error:", imageUrl);
e.target.onerror = null;
e.target.src = "/default-image.png"; // 기본 이미지 경로
e.target.src = "/default-image.png";
}}
/>
) : (
<div className="no-image"></div>
)}
</div>
<div className="postlist-title">{title}</div>
<div className="postlist-title">
<SimpleFilteredContent content={title} clean={clean} />
</div>

<div className="postlist-heart">
<img src={Hearticon} alt="하트 아이콘" />
Expand All @@ -35,10 +38,7 @@ Postlist.propTypes = {
title: PropTypes.string.isRequired,
imageUrl: PropTypes.string,
likes: PropTypes.string.isRequired,
};

Postlist.defaultProps = {
imageUrl: "",
clean: PropTypes.bool,
};

export default Postlist;
20 changes: 20 additions & 0 deletions src/component/SimpleFilteredContent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import PropTypes from "prop-types";

const SimpleFilteredContent = ({ content, clean = true }) => {
if (clean) {
return <div>{content}</div>;
}

return (
<div className="simple-filtered-content">
<span className="warning-text">[구름봇에 의해 가려졌습니다]</span>
</div>
);
};

SimpleFilteredContent.propTypes = {
content: PropTypes.string.isRequired,
clean: PropTypes.bool,
};

export default SimpleFilteredContent;
9 changes: 7 additions & 2 deletions src/component/ViewPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { deletePost } from "../api/useDeletePost";
import WriteForm from "../containers/WriteForm";
import CommentInput from "./CommentInput";
import { useCreateComment } from "../hooks/useComment";
import FilteredContent from "./FilteredContent";

const ViewPage = ({
post,
Expand Down Expand Up @@ -257,8 +258,11 @@ const ViewPage = ({
)}
</div>
<div className="form-group">
<div className="title">제목: {post.title}</div>
<div className="content">{post.content}</div>
<FilteredContent
title={post.title}
content={post.content}
clean={post.clean}
/>
<div className="reaction">
<img className="hearts" src={Hearticon} alt="하트 아이콘" />
<span>{post.likes}</span>
Expand Down Expand Up @@ -310,6 +314,7 @@ ViewPage.propTypes = {
imageUrl: PropTypes.string,
likes: PropTypes.string.isRequired,
updatedAt: PropTypes.string.isRequired,
clean: PropTypes.bool.isRequired, // isClean에서 clean으로 변경
author: PropTypes.shape({
id: PropTypes.number.isRequired,
role: PropTypes.string.isRequired,
Expand Down
5 changes: 3 additions & 2 deletions src/component/styles/Comment.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
margin-bottom: 15px;

.comment-header {
margin-bottom: 8px;
margin-bottom: 0px;

.author-line {
display: flex;
Expand Down Expand Up @@ -55,7 +55,8 @@
}

.comment-content {
margin: 8px 0;
margin-top: 3px;
margin-bottom: 8px;
word-break: break-word;
}

Expand Down
Loading

0 comments on commit 00b58d1

Please sign in to comment.