Skip to content

Commit

Permalink
Merge branch 'feat-question-comment' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
phjppo0918 committed Oct 27, 2023
2 parents 6390655 + 85841f3 commit caf7b65
Show file tree
Hide file tree
Showing 15 changed files with 313 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package site.qbox.qboxserver.domain.answer.command

import org.springframework.data.jpa.repository.JpaRepository
import site.qbox.qboxserver.domain.answer.command.entity.AnswerComment

interface AnswerCommentRepo : JpaRepository<AnswerComment, Long>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package site.qbox.qboxserver.domain.answer.command
import org.springframework.http.HttpStatus
import org.springframework.security.core.Authentication
import org.springframework.web.bind.annotation.*
import site.qbox.qboxserver.domain.answer.command.dto.CreateAnswerCommentReq
import site.qbox.qboxserver.domain.answer.command.dto.CreateAnswerReq

@RestController
Expand All @@ -14,4 +15,9 @@ class AnswerCtrl(
@ResponseStatus(HttpStatus.CREATED)
fun createAnswer(@RequestBody req: CreateAnswerReq, auth: Authentication) =
answerSvc.create(req, auth.name)

@PostMapping("comments")
@ResponseStatus(HttpStatus.CREATED)
fun createAnswerComment(@RequestBody req: CreateAnswerCommentReq, auth:Authentication) =
answerSvc.addAnswer(req, auth.name)
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package site.qbox.qboxserver.domain.answer.command

import site.qbox.qboxserver.domain.answer.command.dto.CreateAnswerCommentReq
import site.qbox.qboxserver.domain.answer.command.dto.CreateAnswerReq
import site.qbox.qboxserver.domain.answer.command.entity.Answer
import site.qbox.qboxserver.domain.answer.command.entity.AnswerComment
import site.qbox.qboxserver.global.annotation.CommandService

@CommandService
class AnswerSvc (
private val answerRepo: AnswerRepo,
private val answerCommentRepo: AnswerCommentRepo,
){
fun create(req: CreateAnswerReq, writer: String) {
answerRepo.save(Answer(req.content, req.question, writer))
}

fun addAnswer(req: CreateAnswerCommentReq, writer: String) {
answerCommentRepo.save(AnswerComment(req.content, writer, req.answer))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package site.qbox.qboxserver.domain.answer.command.dto

import site.qbox.qboxserver.domain.answer.command.entity.AnswerId

data class CreateAnswerCommentReq (
val answer: AnswerId,
val content: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package site.qbox.qboxserver.domain.answer.command.entity

import jakarta.persistence.*

@Entity
class AnswerComment(
@Lob var content: String,
var commentWriterId: String,
@Embedded val answer: AnswerId,
@Id @GeneratedValue val id: Long = 0L,
)
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package site.qbox.qboxserver.domain.answer.query

import com.querydsl.core.group.GroupBy.groupBy
import com.querydsl.core.group.GroupBy.list
import com.querydsl.jpa.impl.JPAQueryFactory
import site.qbox.qboxserver.domain.answer.command.entity.AnswerId
import site.qbox.qboxserver.domain.answer.command.entity.QAnswer.answer
import site.qbox.qboxserver.domain.answer.query.dto.AnswerRes
import site.qbox.qboxserver.domain.answer.query.dto.QAnswerRes
import site.qbox.qboxserver.domain.answer.command.entity.QAnswerComment.answerComment
import site.qbox.qboxserver.domain.answer.query.dto.*
import site.qbox.qboxserver.domain.member.command.entity.QMember.member
import site.qbox.qboxserver.domain.member.query.dto.QMemberRes
import site.qbox.qboxserver.global.annotation.QueryService
Expand All @@ -13,17 +16,44 @@ class AnswerDao(
private val queryFactory: JPAQueryFactory,
) {
fun findAllByQuestion(questionId: Long): List<AnswerRes> {
return queryFactory
.select(
QAnswerRes(
answer.content,
answer.id.questionId,
QMemberRes(member.email, member.nickname)
)
)
val comments = getCommentsGroup(questionId)
val summary = getSummary(questionId)

return mapToRes(summary, comments)

}

private fun getCommentsGroup(questionId: Long): MutableMap<AnswerId, MutableList<AnswerCommentRes>> =
queryFactory.from(answerComment)
.where(answerComment.answer.questionId.eq(questionId))
.join(member).on(answerComment.commentWriterId.eq(member.email))
.transform(
groupBy(answerComment.answer).`as`(
list(
QAnswerCommentRes(
answerComment.id,
answerComment.content,
QMemberRes(member.email, member.nickname)))))

private fun getSummary(questionId: Long): MutableList<AnswerSummary> =
queryFactory.select(
QAnswerSummary(
answer.content,
answer.id.questionId,
QMemberRes(member.email, member.nickname)
))
.from(answer)
.where(answer.id.questionId.eq(questionId))
.join(member).on(answer.id.writerId.eq(member.email))
.fetch()

private fun mapToRes(
summary: MutableList<AnswerSummary>,
comments: MutableMap<AnswerId, MutableList<AnswerCommentRes>>
) = summary.map {
AnswerRes(it, comments[getAnswerId(it)] ?: emptyList())
}

private fun getAnswerId(it: AnswerSummary) =
AnswerId(it.questionId, it.writer.email)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package site.qbox.qboxserver.domain.answer.query.dto

import com.querydsl.core.annotations.QueryProjection
import site.qbox.qboxserver.domain.member.query.dto.MemberRes

data class AnswerCommentRes @QueryProjection constructor(
val id: Long,
val content: String,
val writer: MemberRes,
)
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package site.qbox.qboxserver.domain.answer.query.dto

import com.querydsl.core.annotations.QueryProjection
import site.qbox.qboxserver.domain.member.query.dto.MemberRes

data class AnswerRes @QueryProjection constructor(
data class AnswerRes(
val content: String,
val questionId: Long,
val writer: MemberRes,
)
val comments: List<AnswerCommentRes>
) {
constructor(res: AnswerSummary, comments: List<AnswerCommentRes>) :
this(res.content, res.questionId, res.writer, comments)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package site.qbox.qboxserver.domain.answer.query.dto

import com.querydsl.core.annotations.QueryProjection
import site.qbox.qboxserver.domain.member.query.dto.MemberRes

data class AnswerSummary @QueryProjection constructor(
val content: String,
val questionId: Long,
val writer: MemberRes
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package site.qbox.qboxserver.global.querydsl

import com.querydsl.jpa.JPQLTemplates
import com.querydsl.jpa.impl.JPAQueryFactory
import jakarta.persistence.EntityManager

Expand All @@ -12,6 +13,6 @@ class QuerydslConfig(
) {
@Bean
fun querydsl(): JPAQueryFactory {
return JPAQueryFactory(em)
return JPAQueryFactory(JPQLTemplates.DEFAULT, em)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath
import org.springframework.restdocs.payload.PayloadDocumentation.requestFields
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import site.qbox.qboxserver.config.WebClientDocsTest
import site.qbox.qboxserver.domain.answer.command.dto.CreateAnswerCommentReq
import site.qbox.qboxserver.domain.answer.command.dto.CreateAnswerReq
import site.qbox.qboxserver.domain.answer.command.entity.AnswerId

@WebMvcTest(AnswerCtrl::class)
@DisplayName("AnswerCtrl")
Expand All @@ -32,6 +34,23 @@ class AnswerCtrlTest : WebClientDocsTest() {
))
)
}

it("answer에 대한 댓글을 생성한다.") {
val req = CreateAnswerCommentReq(AnswerId(4, "[email protected]"), "댓글내용")

val action = performPost("/answers/comments", req)

action.andExpect(status().isCreated)

action.andDo(
print("create-answer-comment",
requestFields(
fieldWithPath("answer.questionId").type(JsonFieldType.NUMBER).description("답변의 Question ID"),
fieldWithPath("answer.writerId").type(JsonFieldType.STRING).description("답변 작성자 email"),
fieldWithPath("content").type(JsonFieldType.STRING).description("댓글 내용"),
))
)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import site.qbox.qboxserver.domain.answer.command.dto.CreateAnswerCommentReq
import site.qbox.qboxserver.domain.answer.command.dto.CreateAnswerReq
import site.qbox.qboxserver.domain.answer.command.entity.AnswerId

Expand All @@ -17,6 +18,8 @@ class AnswerSvcTest : DescribeSpec() {

@Autowired
lateinit var answerRepo: AnswerRepo
@Autowired
lateinit var answerCommentRepo: AnswerCommentRepo

@Autowired
lateinit var answerSvc: AnswerSvc
Expand All @@ -36,5 +39,24 @@ class AnswerSvcTest : DescribeSpec() {
result.id.questionId shouldBe question
result.id.writerId shouldBe writer
}

it("answer comment 생성을 수행한다") {
val content = "댓글내용"
val req = CreateAnswerCommentReq(AnswerId(4, "[email protected]"), content)

answerSvc.addAnswer(req, "댓글작성자")
answerSvc.addAnswer(req, "댓글작성자")
answerSvc.addAnswer(req, "댓글작성자")

val result = answerCommentRepo.findAll()

result.size shouldBe 3
result.map { it.content }.containsAll(listOf(content, content, content))
}

afterEach {
answerRepo.deleteAll()
answerCommentRepo.deleteAll()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package site.qbox.qboxserver.domain.answer.query

import com.querydsl.core.group.GroupBy.groupBy
import com.querydsl.core.group.GroupBy.list
import com.querydsl.jpa.impl.JPAQueryFactory
import io.kotest.core.spec.DisplayName
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.extensions.spring.SpringExtension
import io.kotest.matchers.shouldBe
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.security.crypto.password.PasswordEncoder
import site.qbox.qboxserver.domain.answer.command.AnswerCommentRepo
import site.qbox.qboxserver.domain.answer.command.AnswerRepo
import site.qbox.qboxserver.domain.answer.command.entity.Answer
import site.qbox.qboxserver.domain.answer.command.entity.AnswerComment
import site.qbox.qboxserver.domain.answer.command.entity.AnswerId
import site.qbox.qboxserver.domain.answer.command.entity.QAnswer.*
import site.qbox.qboxserver.domain.answer.command.entity.QAnswerComment.answerComment
import site.qbox.qboxserver.domain.answer.query.dto.QAnswerCommentRes
import site.qbox.qboxserver.domain.member.command.MemberRepo
import site.qbox.qboxserver.domain.member.command.entity.Member
import site.qbox.qboxserver.domain.member.command.entity.QMember.member
import site.qbox.qboxserver.domain.member.query.dto.QMemberRes

@DisplayName("querydsl-answer")
@SpringBootTest
class AnswerDaoQueryDslTest : DescribeSpec() {
override fun extensions() = listOf(SpringExtension)

@Autowired
lateinit var queryFactory: JPAQueryFactory

@Autowired
lateinit var answerRepo: AnswerRepo

@Autowired
lateinit var answerCommentRepo: AnswerCommentRepo

@Autowired
lateinit var memberRepo: MemberRepo

@Autowired
lateinit var passwordEncoder: PasswordEncoder

lateinit var members: List<Member>
lateinit var answers: List<Answer>
lateinit var answerComments: List<AnswerComment>

init {
describe("querydsl 에서") {
beforeEach {
members = memberRepo.saveAll(
listOf(
Member("[email protected]", "n1", "p1", passwordEncoder),
Member("[email protected]", "n2", "p2", passwordEncoder),
Member("[email protected]", "n3", "p4", passwordEncoder),
Member("[email protected]", "n4", "32", passwordEncoder),
Member("[email protected]", "n5", "42", passwordEncoder),
)
)

answers = answerRepo.saveAll(
listOf(
Answer("내용1", AnswerId(1, members[0].email)),
Answer("내용2", AnswerId(1, members[1].email)),
Answer("내용3", AnswerId(1, members[2].email)),
Answer("내용4", AnswerId(1, members[3].email)),
Answer("내용5", AnswerId(1, members[4].email)),
Answer("내용6", AnswerId(2, members[0].email)),
Answer("내용7", AnswerId(2, members[1].email)),
Answer("내용8", AnswerId(2, members[2].email)),
)
)

answerComments = answerCommentRepo.saveAll(
listOf(
AnswerComment("댓글1", members[0].email, answers[1].id),
AnswerComment("댓글2", members[1].email, answers[1].id),
AnswerComment("댓글3", members[1].email, answers[1].id),
AnswerComment("댓글4", members[2].email, answers[1].id),
AnswerComment("댓글5", members[1].email, answers[2].id),
AnswerComment("댓글6", members[3].email, answers[2].id),
AnswerComment("댓글7", members[3].email, answers[7].id),
)
)
}

it("question별 answer comment 조회를 수행한다.") {
val result = queryFactory.from(answerComment)
.where(answerComment.answer.questionId.eq(1))
.join(member).on(answerComment.commentWriterId.eq(member.email))
.transform(
groupBy(answerComment.answer).`as`(list(
QAnswerCommentRes(
answerComment.id,
answerComment.content,
QMemberRes(member.email, member.nickname)
)
))
)

result[answers[1].id]?.size shouldBe 4
result[answers[1].id]?.map { it.content }?.containsAll(listOf("댓글1", "댓글2","댓글3", "댓글4"))
result[answers[1].id]?.map { it.writer.nickname }?.containsAll(listOf("n1", "n2","n3", "n4"))
}
}


afterEach {
memberRepo.deleteAll()
answerRepo.deleteAll()
answerCommentRepo.deleteAll()
}
}


}
Loading

0 comments on commit caf7b65

Please sign in to comment.