-
Notifications
You must be signed in to change notification settings - Fork 0
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
[FEAT] 질문 답변 조회 기능 #30
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
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 | ||
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 | ||
) { | ||
@Auth | ||
@Operation(summary = "답변 조회") | ||
@GetMapping("/v1/answer/{answerId}") | ||
fun findAnswerById(@PathVariable answerId: Long, @MemberId memberId: Long): ApiResponse<AnswerInfoResponse> { | ||
return ApiResponse.success(answerService.findAnswerInfoById(answerId, memberId)) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
||
data 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 | ||
) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
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, | ||
private val answerValidator: AnswerValidator | ||
) { | ||
@Transactional(readOnly = true) | ||
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) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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})의 답변자가 아니기 때문에 답변 정보에 접근할 수 없습니다.") | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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,34 @@ 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") | ||
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) | ||
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) | ||
var question: Question, | ||
|
||
@Column(name = "answer_content", nullable = false) | ||
private var content: String, | ||
@Column(name = "answer_content", nullable = false) | ||
var content: String, | ||
Comment on lines
+21
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 인텔리제이 기본 포매터인가용? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 인텔리제이 기본 포매터+Tap size 4칸 으로 설정해주면 될 것 같아요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아 기본 포매터로 가는군요! 확인이엽! |
||
|
||
@Column(name = "is_public", nullable = false) | ||
private var isPublic: Boolean | ||
@Column(name = "is_public", nullable = false) | ||
var isPublic: Boolean, | ||
|
||
@OneToMany(mappedBy = "answer", fetch = FetchType.LAZY, cascade = [CascadeType.ALL], orphanRemoval = true) | ||
var likes: List<Like> = mutableListOf() | ||
|
||
) : BaseEntity() { | ||
|
||
fun getLikeCount(): Int { | ||
return likes.size | ||
} | ||
|
||
fun getQuestionId(): Long { | ||
return question.id!! | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) 입니다") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Member, Long>, LikeRepositoryCustom { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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? | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) 입니다") | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
명세서와 동일하게 [인증] 질문 답변 조회 로 바꿔주면 좋을 것 같습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
체킹 감사합니다:)