Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pr feat: question api #30

Merged
merged 26 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
99a9349
feat: Question, Image Entity (#21)
toychip Jan 30, 2024
2cc6c93
fix: 확장자 없을 때 파일명 오류 해결 (#21)
toychip Jan 30, 2024
c41d52e
feat: Question, Image Entity - static factory method (#21)
toychip Jan 30, 2024
9acee5a
feat: Create Question (#21)
toychip Jan 30, 2024
137071d
fix: Cart의 Image와 Question의 Image가 겹치는 엔티티 분리 (#21)
toychip Jan 30, 2024
3fa8483
feat: soft delete 구현 (#21)
toychip Jan 30, 2024
fc0f378
feat: 질문해요 게시글 신고 기능 (#21)
toychip Jan 30, 2024
9b37163
fix: Report 명세서 작성 및 Controller 미사용 api 제거 (#21)
toychip Jan 30, 2024
1f85121
refactor: 질문해요 꿀팁 공유해요 통합 (#21)
toychip Jan 30, 2024
a2ce16d
feat: 다양한 댓글 공통 댓글 상속받는 단일 테이블 전략으로 구현 (#21)
toychip Jan 30, 2024
8b2b8d5
refactor: 공통 comment common으로 이동 (#21)
toychip Jan 30, 2024
bce03d4
feat: Question Comment 생성 기능 (#21)
toychip Jan 30, 2024
df01186
feat: delete QuestionComment (#21)
toychip Jan 30, 2024
d8ca0b0
feat: 누락 @Transactional 추가 (#21)
toychip Jan 30, 2024
a4e77dc
feat: 공통 & Question Comment 수정 기능 (#21)
toychip Jan 30, 2024
6c4be2e
fix: Comment가 BaseEntity를 상속받기 위해 @SuperBuilder 제거 (#21)
toychip Jan 30, 2024
73bc85d
feat: 단건 Question 조회 기능 - 댓글, 사진과 함께 반환 (#21)
toychip Jan 30, 2024
bfc6327
feat: 댓글 신고 기능 (#21)
toychip Jan 30, 2024
2ac46ec
fix: 검색 조건 내용으로만 검색으로 수정 (#21)
toychip Jan 30, 2024
71af44b
fix: Question 생성 오류 수정 (#21)
toychip Jan 31, 2024
a0ab8f9
feat: 신고 기능 신고 타입 검증 기능 추가 (#21)
toychip Jan 31, 2024
51152cd
fix: 댓글 삭제시 Report 삭제 방지, comment soft delete 적용 (#21)
toychip Jan 31, 2024
e1b7de4
setting: 누락 jpa 설정 추가 (#21)
toychip Jan 31, 2024
8989472
setting: jpa 설정 create -> validate (#21)
toychip Jan 31, 2024
4f1bd82
fix: 연관관계 매핑 수정 Question - Comment -> Question - QuestionComment (#21)
toychip Jan 31, 2024
9f4720a
fix: ErrorType 오타 수정 (#21)
toychip Jan 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified gradlew
100644 → 100755
Empty file.
8 changes: 8 additions & 0 deletions src/main/java/com/api/ttoklip/domain/common/Category.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ public enum Category {
private final String name;
private final int code;

public static Category findCategoryByValue(final String value) {
try {
return Category.valueOf(value.toUpperCase());
} catch (IllegalArgumentException e) {
throw new ApiException(ErrorType.CATEGORY_NOT_FOUNT);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CATEGORY_NOT_FOUNT 오타가 있네요. CATEGORY_NOT_FOUND로 수정하면 좋을 것 같습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇 제가 놓친 부분이 있었네요 감사합니다!

}
}

public static Category findCategoryByName(final String name) {
for (Category category : Category.values()) {
if (category.getName().equals(name)) {
Expand Down
14 changes: 12 additions & 2 deletions src/main/java/com/api/ttoklip/domain/common/base/BaseEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,23 @@
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity extends BaseTimeEntity{
public class BaseEntity extends BaseTimeEntity {
@CreatedBy
@Column(updatable = false)
private String createdBy;

@LastModifiedBy
private String lastModifiedBy;

private boolean status;
private boolean deleted;

// 재활성화 - soft delete
public void activate() {
this.deleted = false;
}

// 비활성화 - soft delete
public void deactivate() {
this.deleted = true;
}
Comment on lines +22 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

soft delete 개념에서 enum 타입만 생각했었는데 deleted로 객체의 재활성화, 비활성화 상태를 관리하니 좋은 것 같네용

}
59 changes: 59 additions & 0 deletions src/main/java/com/api/ttoklip/domain/common/comment/Comment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.api.ttoklip.domain.common.comment;

import com.api.ttoklip.domain.common.base.BaseEntity;
import com.api.ttoklip.domain.common.comment.editor.CommentEditor;
import com.api.ttoklip.domain.question.post.domain.Question;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn
public class Comment extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String content; // 댓글 내용

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "question_id")
private Question question;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한 개의 댓글 엔티티를 공유하기 때문에 null이 많아지는 문제가 향후 발생할 수도 있을것 같습니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공통 댓글 엔티티에서 직접 연관관계 매핑을 하는 것이 아닌, 이를 상속 받는 Question Entity에서 QuestionComment Entity 클래스로 이동하도록 수정하겠습니다.
단일테이블 전략이기 때문에 테이블 자체는 똑같지만 Comment를 유연하게 사용할 수 있겠네요!


// @ManyToOne(fetch = FetchType.LAZY)
// @JoinColumn(name = "member_id")
// private Member member;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private Comment parent;

protected Comment(final String content, final Question question, final Comment parent) {
this.content = content;
this.question = question;
this.parent = parent;
}

public CommentEditor.CommentEditorBuilder toEditor() {
return CommentEditor.builder()
.comment(content);
}
Comment on lines +46 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

댓글을 편집하기 위해 빌더 패턴과 CommentEditor를 사용하니 매개변수 파악하는 가독성이 올라가는 것 같습니다.


public void edit(final CommentEditor commentEditor) {
this.content = commentEditor.getContent();
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.api.ttoklip.domain.question.post.comment.dto.request;
package com.api.ttoklip.domain.common.comment.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class CommentCreateRequest {

@Schema(description = "댓글 내용", example = "댓글 내용 예시")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.api.ttoklip.domain.question.post.comment.dto.request;
package com.api.ttoklip.domain.common.comment.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.api.ttoklip.domain.common.comment.dto.response;

import com.api.ttoklip.domain.common.comment.Comment;
import com.api.ttoklip.global.util.TimeUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class CommentResponse {

@Schema(description = "댓글 ID", example = "101")
private Long commentId;

@Schema(description = "댓글 내용", example = "댓글 내용 예시")
private String commentContent;

@Schema(description = "부모 댓글 ID (대댓글의 경우)", example = "1")
private Long parentId;

@Schema(description = "댓글 작성자", example = "댓글 작성자 예시")
private String writer;

@Schema(description = "댓글 작성 시간", example = "2024-01-11 11:00:00")
private String writtenTime;


public static CommentResponse from(final Comment questionComment) {
LocalDateTime createdDate = questionComment.getCreatedDate();
String formatCreatedDate = TimeUtil.formatCreatedDate(createdDate);

if (questionComment.getParent() == null) {
return getCommentResponse(questionComment, null, formatCreatedDate);
}

return getCommentResponse(questionComment, questionComment.getParent().getId(), formatCreatedDate);
}

private static CommentResponse getCommentResponse(final Comment questionComment, final Long parentCommentId,
final String formatCreatedDate) {
return CommentResponse.builder()
.commentId(questionComment.getId())
.commentContent(questionComment.getContent())
.parentId(parentCommentId) // 부모 댓글이 있는 경우
// .writer(questionComment.getMember().getName()) // ToDo Member Entity 생성 후 수정
.writtenTime(formatCreatedDate)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.api.ttoklip.domain.common.comment.editor;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.util.StringUtils;

@Getter
@RequiredArgsConstructor
public class CommentEditor {

private final String content;

public static CommentEditorBuilder builder() {
return new CommentEditorBuilder();
}
public static class CommentEditorBuilder {
private String content;

CommentEditorBuilder() {
}

public CommentEditorBuilder comment(final String content) {
if (StringUtils.hasText(content)) {
this.content = content;
}
return this;
}

public CommentEditor build() {
return new CommentEditor(content);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.api.ttoklip.domain.common.comment.repository;

import com.api.ttoklip.domain.common.comment.Comment;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CommentRepository extends JpaRepository<Comment, Long>, CommentRepositoryCustom {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.api.ttoklip.domain.common.comment.repository;

import com.api.ttoklip.domain.common.comment.Comment;

public interface CommentRepositoryCustom {
Comment findByIdActivated(final Long commentId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.api.ttoklip.domain.common.comment.repository;

import static com.api.ttoklip.domain.common.comment.QComment.comment;

import com.api.ttoklip.domain.common.comment.Comment;
import com.api.ttoklip.global.exception.ApiException;
import com.api.ttoklip.global.exception.ErrorType;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.Optional;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class CommentRepositoryImpl implements CommentRepositoryCustom {

private final JPAQueryFactory jpaQueryFactory;

@Override
public Comment findByIdActivated(final Long commentId) {
Comment findComment = jpaQueryFactory
.selectFrom(comment)
.where(
matchId(commentId), getCommentActivate()
)
.fetchOne();
return Optional.ofNullable(findComment)
.orElseThrow(() -> new ApiException(ErrorType.COMMENT_NOT_FOUNT));
}

private BooleanExpression matchId(final Long commentId) {
return comment.id.eq(commentId);
}

private BooleanExpression getCommentActivate() {
return comment.deleted.isFalse();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.api.ttoklip.domain.common.comment.service;

import com.api.ttoklip.domain.common.comment.Comment;
import com.api.ttoklip.domain.common.comment.dto.request.CommentEditRequest;
import com.api.ttoklip.domain.common.comment.editor.CommentEditor;
import com.api.ttoklip.domain.common.comment.editor.CommentEditor.CommentEditorBuilder;
import com.api.ttoklip.domain.common.comment.repository.CommentRepository;
import com.api.ttoklip.global.exception.ApiException;
import com.api.ttoklip.global.exception.ErrorType;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class CommentService {

private final CommentRepository commentRepository;

/* -------------------------------------------- CREATE -------------------------------------------- */
@Transactional
public void register(final Comment comment) {
commentRepository.save(comment);
}
/* -------------------------------------------- CREATE 끝 -------------------------------------------- */


/* -------------------------------------------- READ 부모 댓글 -------------------------------------------- */
public Optional<Comment> findParentComment(final Long parentCommentId) {
if (parentCommentId != null) {
return commentRepository.findById(parentCommentId);
}
return Optional.empty();
}

/* -------------------------------------------- READ 부모 댓글 끝 -------------------------------------------- */

/* -------------------------------------------- EDIT -------------------------------------------- */
@Transactional
public void edit(final Long commentId, final CommentEditRequest request) {

Comment comment = findComment(commentId);
// ToDo 작성자가 맞는지 검증 필요
CommentEditor commentEditor = getCommentEditor(request, comment);
comment.edit(commentEditor);
}

public Comment findComment(final Long commentId) {
return commentRepository.findByIdActivated(commentId);
}

private CommentEditor getCommentEditor(final CommentEditRequest request, final Comment comment) {
CommentEditorBuilder editorBuilder = comment.toEditor();
CommentEditor commentEditor = editorBuilder
.comment(request.getComment())
.build();
return commentEditor;
}

/* -------------------------------------------- EDIT 끝 -------------------------------------------- */

/* -------------------------------------------- DELETE -------------------------------------------- */
@Transactional
public void deleteById(final Long commentId) {
// ToDo 본인이 썼는지 검증 과정 필요
Comment comment = findComment(commentId);
comment.deactivate();
}
/* -------------------------------------------- DELETE 끝 -------------------------------------------- */
}
Loading