From 5937d871b54ef4465fafd024a70f87354a79bdfb Mon Sep 17 00:00:00 2001 From: PgmJun Date: Mon, 4 Mar 2024 00:59:03 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8:=20feat=20Like=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EA=B5=AC=ED=98=84=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plu-api/src/main/resources/sql/schema.sql | 11 +++++ .../com/th/plu/domain/domain/answer/Answer.kt | 30 +++++++------ .../com/th/plu/domain/domain/like/Like.kt | 43 +++++++++++++++++++ .../domain/like/repository/LikeRepository.kt | 7 +++ .../like/repository/LikeRepositoryCustom.kt | 8 ++++ .../like/repository/LikeRepositoryImpl.kt | 16 +++++++ 6 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/Like.kt create mode 100644 plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/repository/LikeRepository.kt create mode 100644 plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/repository/LikeRepositoryCustom.kt create mode 100644 plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/repository/LikeRepositoryImpl.kt diff --git a/plu-api/src/main/resources/sql/schema.sql b/plu-api/src/main/resources/sql/schema.sql index bc80224..2d0fe24 100644 --- a/plu-api/src/main/resources/sql/schema.sql +++ b/plu-api/src/main/resources/sql/schema.sql @@ -3,6 +3,7 @@ DROP TABLE IF EXISTS `settings`; DROP TABLE IF EXISTS `questions`; DROP TABLE IF EXISTS `answers`; DROP TABLE IF EXISTS `onboardings`; +DROP TABLE IF EXISTS `likes`; CREATE TABLE `members` @@ -54,3 +55,13 @@ CREATE TABLE `onboardings` `created_at` datetime NOT NULL, `modified_at` datetime NOT NULL ); + +CREATE TABLE `likes` +( + `like_id` bigint auto_increment primary key, + `question_id` bigint NOT NULL, + `answer_id` bigint NOT NULL, + `member_id` bigint NOT NULL, + `created_at` datetime NOT NULL, + `modified_at` datetime NOT NULL +); \ No newline at end of file diff --git a/plu-domain/src/main/kotlin/com/th/plu/domain/domain/answer/Answer.kt b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/answer/Answer.kt index 656825d..c6411df 100644 --- a/plu-domain/src/main/kotlin/com/th/plu/domain/domain/answer/Answer.kt +++ b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/answer/Answer.kt @@ -1,6 +1,7 @@ package com.th.plu.domain.domain.answer import com.th.plu.domain.domain.common.BaseEntity +import com.th.plu.domain.domain.like.Like import com.th.plu.domain.domain.member.Member import com.th.plu.domain.domain.question.Question import jakarta.persistence.* @@ -17,23 +18,26 @@ import lombok.NoArgsConstructor @Builder(access = AccessLevel.PRIVATE) class Answer( - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "answer_id") - private var id: Long? = null, + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "answer_id") + private var id: Long? = null, - @ManyToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) - @JoinColumn(name = "member_id", nullable = false) - private var member: Member, + @ManyToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) + @JoinColumn(name = "member_id", nullable = false) + private var member: Member, - @ManyToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) - @JoinColumn(name = "question_id", nullable = false) - private var question: Question, + @ManyToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) + @JoinColumn(name = "question_id", nullable = false) + private var question: Question, - @Column(name = "answer_content", nullable = false) - private var content: String, + @Column(name = "answer_content", nullable = false) + private var content: String, - @Column(name = "is_public", nullable = false) - private var isPublic: Boolean + @Column(name = "is_public", nullable = false) + private var isPublic: Boolean, + + @OneToMany(mappedBy = "answer", fetch = FetchType.LAZY, cascade = [CascadeType.ALL], orphanRemoval = true) + var likes: List = mutableListOf() ) : BaseEntity() { } diff --git a/plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/Like.kt b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/Like.kt new file mode 100644 index 0000000..e7097ea --- /dev/null +++ b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/Like.kt @@ -0,0 +1,43 @@ +package com.th.plu.domain.domain.like + +import com.th.plu.domain.domain.answer.Answer +import com.th.plu.domain.domain.common.BaseEntity +import com.th.plu.domain.domain.member.Member +import com.th.plu.domain.domain.question.Question +import jakarta.persistence.* + +@Table(name = "likes") +@Entity +class Like( + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "like_id") + var id: Long? = null, + + @ManyToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) + @JoinColumn(name = "member_id", nullable = false) + var member: Member, + + @ManyToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) + @JoinColumn(name = "answer_id", nullable = false) + var answer: Answer, + + @ManyToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) + @JoinColumn(name = "question_id", nullable = false) + var question: Question + +) : BaseEntity() { + + companion object { + fun newInstance( + member: Member, answer: Answer, question: Question + ): Like { + return Like( + member = member, + answer = answer, + question = question + ) + } + } +} \ No newline at end of file diff --git a/plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/repository/LikeRepository.kt b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/repository/LikeRepository.kt new file mode 100644 index 0000000..fd88cc9 --- /dev/null +++ b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/repository/LikeRepository.kt @@ -0,0 +1,7 @@ +package com.th.plu.domain.domain.like.repository + +import com.th.plu.domain.domain.member.Member +import org.springframework.data.jpa.repository.JpaRepository + +interface LikeRepository : JpaRepository, LikeRepositoryCustom { +} diff --git a/plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/repository/LikeRepositoryCustom.kt b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/repository/LikeRepositoryCustom.kt new file mode 100644 index 0000000..36da531 --- /dev/null +++ b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/repository/LikeRepositoryCustom.kt @@ -0,0 +1,8 @@ +package com.th.plu.domain.domain.like.repository + +import com.th.plu.domain.domain.like.Like + +interface LikeRepositoryCustom { + + fun findLikeById(id: Long): Like? +} diff --git a/plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/repository/LikeRepositoryImpl.kt b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/repository/LikeRepositoryImpl.kt new file mode 100644 index 0000000..2f6042c --- /dev/null +++ b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/like/repository/LikeRepositoryImpl.kt @@ -0,0 +1,16 @@ +package com.th.plu.domain.domain.like.repository + +import com.querydsl.jpa.impl.JPAQueryFactory +import com.th.plu.domain.domain.like.Like +import com.th.plu.domain.domain.like.QLike.like +import org.springframework.stereotype.Repository + +@Repository +class LikeRepositoryImpl(private val queryFactory: JPAQueryFactory) : LikeRepositoryCustom { + override fun findLikeById(id: Long): Like? { + return queryFactory + .selectFrom(like) + .where(like.id.eq(id)) + .fetchOne(); + } +} From c0b830f0a01832472eb9970799a90461a79cfe1a Mon Sep 17 00:00:00 2001 From: PgmJun Date: Mon, 4 Mar 2024 01:35:44 +0900 Subject: [PATCH 2/5] =?UTF-8?q?=E2=9C=A8:=20feat=20=EC=A7=88=EB=AC=B8=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/answer/AnswerController.kt | 24 +++++++++++++++++ .../answer/dto/response/AnswerInfoResponse.kt | 27 +++++++++++++++++++ .../plu/api/service/answer/AnswerService.kt | 21 +++++++++++++++ .../th/plu/common/exception/code/ErrorCode.kt | 2 ++ .../com/th/plu/domain/domain/answer/Answer.kt | 18 +++++++++---- .../domain/answer/explorer/AnswerExplorer.kt | 17 ++++++++++++ .../plu/domain/domain/question/ElementType.kt | 6 ++--- .../question/explorer/QuestionExplorer.kt | 17 ++++++++++++ 8 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 plu-api/src/main/kotlin/com/th/plu/api/controller/answer/AnswerController.kt create mode 100644 plu-api/src/main/kotlin/com/th/plu/api/controller/answer/dto/response/AnswerInfoResponse.kt create mode 100644 plu-api/src/main/kotlin/com/th/plu/api/service/answer/AnswerService.kt create mode 100644 plu-domain/src/main/kotlin/com/th/plu/domain/domain/answer/explorer/AnswerExplorer.kt create mode 100644 plu-domain/src/main/kotlin/com/th/plu/domain/domain/question/explorer/QuestionExplorer.kt diff --git a/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/AnswerController.kt b/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/AnswerController.kt new file mode 100644 index 0000000..3ce9d89 --- /dev/null +++ b/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/AnswerController.kt @@ -0,0 +1,24 @@ +package com.th.plu.api.controller.answer + +import com.th.plu.api.controller.answer.dto.response.AnswerInfoResponse +import com.th.plu.api.service.answer.AnswerService +import com.th.plu.common.dto.response.ApiResponse +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@Tag(name = "Answer") +@RestController +@RequestMapping("/api") +class AnswerController( + private val answerService: AnswerService +) { + @Operation(summary = "답변 조회") + @GetMapping("/v1/answer/{answerId}") + fun findAnswerById(@PathVariable answerId: Long): ApiResponse { + return ApiResponse.success(answerService.findAnswerInfoById(answerId)) + } +} \ No newline at end of file diff --git a/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/dto/response/AnswerInfoResponse.kt b/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/dto/response/AnswerInfoResponse.kt new file mode 100644 index 0000000..e302f7c --- /dev/null +++ b/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/dto/response/AnswerInfoResponse.kt @@ -0,0 +1,27 @@ +package com.th.plu.api.controller.answer.dto.response + +import com.th.plu.domain.domain.answer.Answer +import com.th.plu.domain.domain.question.Question +import java.time.LocalDateTime + +class AnswerInfoResponse( + val questionDate: LocalDateTime, + val questionTitle: String, + val answer: String, + val likeCount: Int, + val elementImageUrl: String, + val colorCode: String +) { + companion object { + fun of(question: Question, answer: Answer): AnswerInfoResponse { + return AnswerInfoResponse( + questionDate = question.modifiedAt, + questionTitle = question.title, + answer = answer.content, + likeCount = answer.getLikeCount(), + elementImageUrl = question.elementType.elementImageUrl, + colorCode = question.elementType.colorCode + ) + } + } +} \ No newline at end of file diff --git a/plu-api/src/main/kotlin/com/th/plu/api/service/answer/AnswerService.kt b/plu-api/src/main/kotlin/com/th/plu/api/service/answer/AnswerService.kt new file mode 100644 index 0000000..2da6ef6 --- /dev/null +++ b/plu-api/src/main/kotlin/com/th/plu/api/service/answer/AnswerService.kt @@ -0,0 +1,21 @@ +package com.th.plu.api.service.answer + +import com.th.plu.api.controller.answer.dto.response.AnswerInfoResponse +import com.th.plu.domain.domain.answer.explorer.AnswerExplorer +import com.th.plu.domain.domain.answer.explorer.QuestionExplorer +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class AnswerService( + private val questionExplorer: QuestionExplorer, + private val answerExplorer: AnswerExplorer, +) { + @Transactional(readOnly = true) + fun findAnswerInfoById(id: Long): AnswerInfoResponse { + val answer = answerExplorer.findAnswerById(id) + val question = questionExplorer.findQuestionById(answer.getQuestionId()) + + return AnswerInfoResponse.of(question, answer) + } +} \ No newline at end of file diff --git a/plu-common/src/main/kotlin/com/th/plu/common/exception/code/ErrorCode.kt b/plu-common/src/main/kotlin/com/th/plu/common/exception/code/ErrorCode.kt index dd62104..14df21f 100644 --- a/plu-common/src/main/kotlin/com/th/plu/common/exception/code/ErrorCode.kt +++ b/plu-common/src/main/kotlin/com/th/plu/common/exception/code/ErrorCode.kt @@ -21,6 +21,8 @@ enum class ErrorCode(val code: String, val message: String) { // NotFound Exception NOT_FOUND_EXCEPTION("N001", "존재하지 않습니다."), NOT_FOUND_MEMBER_EXCEPTION("N002", "탈퇴했거나 존재하지 않는 회원입니다."), + NOT_FOUND_ANSWER_EXCEPTION("N003", "존재하지 않는 답변입니다."), + NOT_FOUND_QUESTION_EXCEPTION("N004", "존재하지 않는 질문입니다."), NOT_FOUND_ARTICLE_CONTENT_EXCEPTION("N003", "아티클의 컨텐츠가 존재하지 않습니다."), NOT_FOUND_CHALLENGE_EXCEPTION("N004", "존재하지 않는 챌린지입니다."), NOT_FOUND_ARTICLE_EXCEPTION("N005", "삭제되었거나 존재하지 않는 아티클입니다."), diff --git a/plu-domain/src/main/kotlin/com/th/plu/domain/domain/answer/Answer.kt b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/answer/Answer.kt index c6411df..6a3b829 100644 --- a/plu-domain/src/main/kotlin/com/th/plu/domain/domain/answer/Answer.kt +++ b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/answer/Answer.kt @@ -20,24 +20,32 @@ class Answer( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "answer_id") - private var id: Long? = null, + var id: Long? = null, @ManyToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) @JoinColumn(name = "member_id", nullable = false) - private var member: Member, + var member: Member, @ManyToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) @JoinColumn(name = "question_id", nullable = false) - private var question: Question, + var question: Question, @Column(name = "answer_content", nullable = false) - private var content: String, + var content: String, @Column(name = "is_public", nullable = false) - private var isPublic: Boolean, + var isPublic: Boolean, @OneToMany(mappedBy = "answer", fetch = FetchType.LAZY, cascade = [CascadeType.ALL], orphanRemoval = true) var likes: List = mutableListOf() ) : BaseEntity() { + + fun getLikeCount(): Int { + return likes.size + } + + fun getQuestionId(): Long { + return question.id!! + } } diff --git a/plu-domain/src/main/kotlin/com/th/plu/domain/domain/answer/explorer/AnswerExplorer.kt b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/answer/explorer/AnswerExplorer.kt new file mode 100644 index 0000000..ac48469 --- /dev/null +++ b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/answer/explorer/AnswerExplorer.kt @@ -0,0 +1,17 @@ +package com.th.plu.domain.domain.answer.explorer + +import com.th.plu.common.exception.code.ErrorCode +import com.th.plu.common.exception.model.NotFoundException +import com.th.plu.domain.domain.answer.Answer +import com.th.plu.domain.domain.answer.repository.AnswerRepository +import org.springframework.stereotype.Component + +@Component +class AnswerExplorer( + private val answerRepository: AnswerRepository +) { + fun findAnswerById(id: Long): Answer { + return answerRepository.findAnswerById(id) + ?: throw NotFoundException(ErrorCode.NOT_FOUND_ANSWER_EXCEPTION, "존재하지 않는 답변(ID: $id) 입니다") + } +} \ No newline at end of file diff --git a/plu-domain/src/main/kotlin/com/th/plu/domain/domain/question/ElementType.kt b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/question/ElementType.kt index b913698..c89eea3 100644 --- a/plu-domain/src/main/kotlin/com/th/plu/domain/domain/question/ElementType.kt +++ b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/question/ElementType.kt @@ -1,9 +1,9 @@ package com.th.plu.domain.domain.question enum class ElementType( - private val characterImageUrl: String, - private val elementImageUrl: String, - private val colorCode: String + val characterImageUrl: String, + val elementImageUrl: String, + val colorCode: String ) { // TODO: 엘리먼트 네이밍 체크 필요 WATER("", "", ""), diff --git a/plu-domain/src/main/kotlin/com/th/plu/domain/domain/question/explorer/QuestionExplorer.kt b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/question/explorer/QuestionExplorer.kt new file mode 100644 index 0000000..2821e80 --- /dev/null +++ b/plu-domain/src/main/kotlin/com/th/plu/domain/domain/question/explorer/QuestionExplorer.kt @@ -0,0 +1,17 @@ +package com.th.plu.domain.domain.answer.explorer + +import com.th.plu.common.exception.code.ErrorCode +import com.th.plu.common.exception.model.NotFoundException +import com.th.plu.domain.domain.question.Question +import com.th.plu.domain.domain.question.repository.QuestionRepository +import org.springframework.stereotype.Component + +@Component +class QuestionExplorer( + private val questionRepository: QuestionRepository +) { + fun findQuestionById(id: Long): Question { + return questionRepository.findQuestionById(id) + ?: throw NotFoundException(ErrorCode.NOT_FOUND_QUESTION_EXCEPTION, "존재하지 않는 질문(ID: $id) 입니다") + } +} \ No newline at end of file From 4b469d773a695a984b269182dd28864c2980eca7 Mon Sep 17 00:00:00 2001 From: PgmJun Date: Mon, 4 Mar 2024 01:40:03 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=E2=9A=A1=EF=B8=8F:=20fix=20@Auth=20?= =?UTF-8?q?=EC=95=A0=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/th/plu/api/controller/answer/AnswerController.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/AnswerController.kt b/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/AnswerController.kt index 3ce9d89..4274b06 100644 --- a/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/AnswerController.kt +++ b/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/AnswerController.kt @@ -1,5 +1,6 @@ package com.th.plu.api.controller.answer +import com.th.plu.api.config.interceptor.Auth import com.th.plu.api.controller.answer.dto.response.AnswerInfoResponse import com.th.plu.api.service.answer.AnswerService import com.th.plu.common.dto.response.ApiResponse @@ -16,6 +17,7 @@ import org.springframework.web.bind.annotation.RestController class AnswerController( private val answerService: AnswerService ) { + @Auth @Operation(summary = "답변 조회") @GetMapping("/v1/answer/{answerId}") fun findAnswerById(@PathVariable answerId: Long): ApiResponse { From f2140ce000f0d9de4c89648130f1e029ed02bbd7 Mon Sep 17 00:00:00 2001 From: PgmJun Date: Mon, 4 Mar 2024 10:06:31 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F:=20refactor=20dto?= =?UTF-8?q?=EA=B0=80=20data=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/answer/dto/response/AnswerInfoResponse.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/dto/response/AnswerInfoResponse.kt b/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/dto/response/AnswerInfoResponse.kt index e302f7c..8a9fc91 100644 --- a/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/dto/response/AnswerInfoResponse.kt +++ b/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/dto/response/AnswerInfoResponse.kt @@ -4,7 +4,7 @@ import com.th.plu.domain.domain.answer.Answer import com.th.plu.domain.domain.question.Question import java.time.LocalDateTime -class AnswerInfoResponse( +data class AnswerInfoResponse( val questionDate: LocalDateTime, val questionTitle: String, val answer: String, From 1a5b5a9dd9e3189723235a387a699bcceabac673 Mon Sep 17 00:00:00 2001 From: PgmJun Date: Sat, 9 Mar 2024 17:50:39 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=E2=9C=A8:=20feat=20=EB=82=98=EB=A7=8C=20?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=20=EB=8B=B5=EB=B3=80=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=B4=20=EB=8B=B5=EB=B3=80=20=EC=86=8C=EC=9C=A0=EA=B6=8C=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/answer/AnswerController.kt | 5 +++-- .../plu/api/service/answer/AnswerService.kt | 8 +++++-- .../plu/api/service/answer/AnswerValidator.kt | 21 +++++++++++++++++++ .../th/plu/common/exception/code/ErrorCode.kt | 1 + 4 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 plu-api/src/main/kotlin/com/th/plu/api/service/answer/AnswerValidator.kt diff --git a/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/AnswerController.kt b/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/AnswerController.kt index 4274b06..98ac8d4 100644 --- a/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/AnswerController.kt +++ b/plu-api/src/main/kotlin/com/th/plu/api/controller/answer/AnswerController.kt @@ -1,6 +1,7 @@ package com.th.plu.api.controller.answer import com.th.plu.api.config.interceptor.Auth +import com.th.plu.api.config.resolver.MemberId import com.th.plu.api.controller.answer.dto.response.AnswerInfoResponse import com.th.plu.api.service.answer.AnswerService import com.th.plu.common.dto.response.ApiResponse @@ -20,7 +21,7 @@ class AnswerController( @Auth @Operation(summary = "답변 조회") @GetMapping("/v1/answer/{answerId}") - fun findAnswerById(@PathVariable answerId: Long): ApiResponse { - return ApiResponse.success(answerService.findAnswerInfoById(answerId)) + fun findAnswerById(@PathVariable answerId: Long, @MemberId memberId: Long): ApiResponse { + return ApiResponse.success(answerService.findAnswerInfoById(answerId, memberId)) } } \ No newline at end of file diff --git a/plu-api/src/main/kotlin/com/th/plu/api/service/answer/AnswerService.kt b/plu-api/src/main/kotlin/com/th/plu/api/service/answer/AnswerService.kt index 2da6ef6..3a6f737 100644 --- a/plu-api/src/main/kotlin/com/th/plu/api/service/answer/AnswerService.kt +++ b/plu-api/src/main/kotlin/com/th/plu/api/service/answer/AnswerService.kt @@ -10,10 +10,14 @@ import org.springframework.transaction.annotation.Transactional class AnswerService( private val questionExplorer: QuestionExplorer, private val answerExplorer: AnswerExplorer, + private val answerValidator: AnswerValidator ) { @Transactional(readOnly = true) - fun findAnswerInfoById(id: Long): AnswerInfoResponse { - val answer = answerExplorer.findAnswerById(id) + fun findAnswerInfoById(answerId: Long, memberId: Long): AnswerInfoResponse { + val answer = answerExplorer.findAnswerById(answerId) + if (!answer.isPublic) { + answerValidator.validateIsMemberOwnerOfAnswer(answerId, memberId) + } val question = questionExplorer.findQuestionById(answer.getQuestionId()) return AnswerInfoResponse.of(question, answer) diff --git a/plu-api/src/main/kotlin/com/th/plu/api/service/answer/AnswerValidator.kt b/plu-api/src/main/kotlin/com/th/plu/api/service/answer/AnswerValidator.kt new file mode 100644 index 0000000..f9df3b5 --- /dev/null +++ b/plu-api/src/main/kotlin/com/th/plu/api/service/answer/AnswerValidator.kt @@ -0,0 +1,21 @@ +package com.th.plu.api.service.answer + +import com.th.plu.common.exception.code.ErrorCode +import com.th.plu.common.exception.model.ValidationException +import com.th.plu.domain.domain.answer.explorer.AnswerExplorer +import com.th.plu.domain.domain.answer.repository.AnswerRepository +import org.springframework.stereotype.Component + +@Component +class AnswerValidator( + private val answerExplorer: AnswerExplorer, + private val answerRepository: AnswerRepository +) { + fun validateIsMemberOwnerOfAnswer(answerId: Long, memberId: Long) { + val answer = answerExplorer.findAnswerById(answerId) + if (answer.member.id != memberId) { + throw ValidationException(ErrorCode.INVALID_ANSWER_OWNER, + "멤버 (ID: ${memberId})는 답변 (ID: ${answerId})의 답변자가 아니기 때문에 답변 정보에 접근할 수 없습니다.") + } + } +} \ No newline at end of file diff --git a/plu-common/src/main/kotlin/com/th/plu/common/exception/code/ErrorCode.kt b/plu-common/src/main/kotlin/com/th/plu/common/exception/code/ErrorCode.kt index 14df21f..cb1ac81 100644 --- a/plu-common/src/main/kotlin/com/th/plu/common/exception/code/ErrorCode.kt +++ b/plu-common/src/main/kotlin/com/th/plu/common/exception/code/ErrorCode.kt @@ -10,6 +10,7 @@ enum class ErrorCode(val code: String, val message: String) { BIND_EXCEPTION("V005", "요청 값을 바인딩하는 과정에서 오류가 발생하였습니다."), METHOD_ARGUMENT_NOT_VALID_EXCEPTION("V006", "요청 값이 검증되지 않은 값 입니다."), INVALID_FORMAT_EXCEPTION("V007", "요청 값이 유효하지 않은 데이터입니다."), + INVALID_ANSWER_OWNER("V008", "질문의 소유자가 아닙니다."), // Unauthorized Exception UNAUTHORIZED_EXCEPTION("U001", "토큰이 만료되었습니다. 다시 로그인 해주세요."),