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

[BLOOM-040] 온보딩 api 개발 #110

Merged
merged 11 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dnd11th.blooming.api.controller.onboard

import dnd11th.blooming.api.dto.onboard.OnboardResultRequest
import dnd11th.blooming.api.dto.onboard.OnboardResultResponse
import dnd11th.blooming.api.dto.onboard.OnboardScriptResponse
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.parameters.RequestBody
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.tags.Tag

@Tag(name = "10. [온보딩]")
interface OnboardApi {
@Operation(summary = "질문지를 조회하는 API 입니다.")
@ApiResponse(responseCode = "200", description = "질문지 조회 성공")
fun findScripts(): OnboardScriptResponse

@Operation(summary = "응답을 제출하는 API 입니다.")
@ApiResponse(responseCode = "200", description = "응답 제출 성공")
fun submitScripts(
@Parameter(description = "응답 버전", required = true)
version: Int,
@RequestBody(description = "응답 요청", required = true)
request: List<OnboardResultRequest>,
): OnboardResultResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package dnd11th.blooming.api.controller.onboard

import dnd11th.blooming.api.dto.onboard.OnboardResultRequest
import dnd11th.blooming.api.dto.onboard.OnboardResultResponse
import dnd11th.blooming.api.dto.onboard.OnboardScriptResponse
import dnd11th.blooming.api.service.onboard.OnboardService
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api/v1/onboarding")
class OnboardController(
private val onboardService: OnboardService,
) : OnboardApi {
@GetMapping
override fun findScripts(): OnboardScriptResponse = onboardService.findScripts()

@PostMapping
override fun submitScripts(
@RequestParam version: Int,
@RequestBody request: List<OnboardResultRequest>,
): OnboardResultResponse = onboardService.submitScripts(version, request)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dnd11th.blooming.api.dto.onboard

import dnd11th.blooming.domain.entity.onboard.OnboardingAnswer
import io.swagger.v3.oas.annotations.media.Schema

@Schema(
name = "Answer Responses",
description = "선택지 응답",
)
data class AnswerResponse(
@field:Schema(description = "선택지 번호", example = "1")
val answerNumber: Int,
@field:Schema(description = "선택지 내용", example = "집에 누워있을래")
val answer: String,
) {
companion object {
fun from(onboardingAnswers: List<OnboardingAnswer>): List<AnswerResponse> {
return onboardingAnswers.map { onboardingAnswers ->
AnswerResponse(
answerNumber = onboardingAnswers.answerNumber,
answer = onboardingAnswers.answer,
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dnd11th.blooming.api.dto.onboard

import io.swagger.v3.oas.annotations.media.Schema

@Schema(
name = "Onboard Result Request",
description = "온보딩 결과 요청",
)
data class OnboardResultRequest(
@field:Schema(description = "질문 번호", example = "1")
val questionNumber: Int,
@field:Schema(description = "응답 번호", example = "2")
val answerNumber: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package dnd11th.blooming.api.dto.onboard

import dnd11th.blooming.domain.entity.onboard.OnboardingResult
import io.swagger.v3.oas.annotations.media.Schema

@Schema(
name = "Onboard Result Response",
description = "온보딩 결과 응답",
)
data class OnboardResultResponse(
@field:Schema(description = "접두사", example = "당신의 존재감을 확실히 비추는")
val subTitle: String,
@field:Schema(description = "결과", example = "매력적인 몬스테라")
val result: String,
@field:Schema(description = "일러스트 url", example = "illust.com/3")
val illustUrl: String,
@field:Schema(description = "설명", example = "몬스테라의 특징인 독특하고 큰 잎처럼 어느 장소에서든 돋보이는 매력덩어리인 당신, 몬스테라 식물과 가장 잘 어울려요.")
val description: String,
) {
companion object {
fun from(result: OnboardingResult): OnboardResultResponse =
OnboardResultResponse(
subTitle = result.subTitle,
result = result.result,
illustUrl = result.illustUrl,
description = result.description,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dnd11th.blooming.api.dto.onboard

import dnd11th.blooming.domain.entity.onboard.OnboardingAnswer
import io.swagger.v3.oas.annotations.media.Schema

@Schema(
name = "Onboard Script Response",
description = "온보딩 질문지 응답",
)
data class OnboardScriptResponse(
@field:Schema(description = "온보딩 버전", example = "1")
val version: Int,
@field:Schema(description = "질문지")
val questions: List<QuestionResponse>,
) {
companion object {
fun of(
version: Int,
onboardingAnswers: List<OnboardingAnswer>,
): OnboardScriptResponse =
OnboardScriptResponse(
version = version,
questions = QuestionResponse.from(onboardingAnswers),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package dnd11th.blooming.api.dto.onboard

import dnd11th.blooming.domain.entity.onboard.OnboardingAnswer
import dnd11th.blooming.domain.entity.onboard.OnboardingQuestion
import io.swagger.v3.oas.annotations.media.Schema

@Schema(
name = "Question Responses",
description = "질문지 응답",
)
data class QuestionResponse(
@field:Schema(description = "질문 번호", example = "1")
val questionNumber: Int,
@field:Schema(description = "질문 내용", example = "휴일에 내가 하고 싶은 것은?")
val question: String,
@field:Schema(description = "선택지 문항")
val answers: List<AnswerResponse>,
) {
companion object {
fun from(onboardingAnswers: List<OnboardingAnswer>): List<QuestionResponse> {
val onboardingQuestions: List<OnboardingQuestion> =
onboardingAnswers
.mapNotNull { answer -> answer.onboardingQuestion }
.distinctBy { oq -> oq.questionNumber }

return onboardingQuestions.map { oq ->
QuestionResponse(
questionNumber = oq.questionNumber,
question = oq.question,
answers = AnswerResponse.from(onboardingAnswers.filter { oa -> oa.onboardingQuestion == oq }),
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package dnd11th.blooming.api.service.onboard

import dnd11th.blooming.api.dto.onboard.OnboardResultRequest
import dnd11th.blooming.api.dto.onboard.OnboardResultResponse
import dnd11th.blooming.api.dto.onboard.OnboardScriptResponse
import dnd11th.blooming.domain.entity.onboard.OnboardingAnswer
import dnd11th.blooming.domain.repository.onboard.OnboardAnswerRepository
import dnd11th.blooming.domain.repository.onboard.OnboardAnswerToResultRepository
import dnd11th.blooming.domain.repository.onboard.OnboardQuestionRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
class OnboardService(
private val onboardQuestionRepository: OnboardQuestionRepository,
private val onboardAnswerToResultRepository: OnboardAnswerToResultRepository,
private val onboardAnswerRepository: OnboardAnswerRepository,
) {
@Transactional(readOnly = true)
fun findScripts(): OnboardScriptResponse {
val latestVersion = onboardQuestionRepository.findLatestVersion()

val onboardingAnswers = onboardAnswerRepository.findAllByVersion(latestVersion)

return OnboardScriptResponse.of(latestVersion, onboardingAnswers)
}

@Transactional(readOnly = true)
fun submitScripts(
version: Int,
request: List<OnboardResultRequest>,
): OnboardResultResponse {
val selectedAnswers: List<OnboardingAnswer> =
onboardAnswerRepository.findAllByQuestionNumberAndAnswerNumberIn(
request.map { Pair(it.questionNumber, it.answerNumber) },
)

val selectedResult = onboardAnswerToResultRepository.findMostSelectedResult(selectedAnswers)

return OnboardResultResponse.from(selectedResult)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package dnd11th.blooming.domain.entity.onboard

import dnd11th.blooming.domain.entity.BaseEntity
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.FetchType
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne

@Entity
class OnboardingAnswer(
@Column
var answerNumber: Int,
@Column
var answer: String,
) : BaseEntity() {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "question_id")
var onboardingQuestion: OnboardingQuestion? = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dnd11th.blooming.domain.entity.onboard

import dnd11th.blooming.domain.entity.BaseEntity
import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne

@Entity
class OnboardingAnswerToResult : BaseEntity() {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null

@ManyToOne
@JoinColumn(name = "answer_id")
var onboardingAnswer: OnboardingAnswer? = null

@ManyToOne
@JoinColumn(name = "result_id")
var onboardingResult: OnboardingResult? = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package dnd11th.blooming.domain.entity.onboard

import dnd11th.blooming.domain.entity.BaseEntity
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id

@Entity
class OnboardingQuestion(
@Column
var version: Int,
@Column
var questionNumber: Int,
@Column
var question: String,
) : BaseEntity() {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dnd11th.blooming.domain.entity.onboard

import dnd11th.blooming.domain.entity.BaseEntity
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id

@Entity
class OnboardingResult(
@Column
var version: Int,
@Column
var result: String,
@Column
var subTitle: String,
@Column
var illustUrl: String,
@Column
var description: String,
) : BaseEntity() {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dnd11th.blooming.domain.repository.onboard

import dnd11th.blooming.domain.entity.onboard.OnboardingAnswer

interface OnboardAnswerQueryDslRepository {
fun findAllByQuestionNumberAndAnswerNumberIn(resultPairs: List<Pair<Int, Int>>): List<OnboardingAnswer>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package dnd11th.blooming.domain.repository.onboard

import com.querydsl.core.types.dsl.BooleanExpression
import com.querydsl.jpa.impl.JPAQueryFactory
import dnd11th.blooming.domain.entity.onboard.OnboardingAnswer
import dnd11th.blooming.domain.entity.onboard.QOnboardingAnswer
import org.springframework.stereotype.Repository

@Repository
class OnboardAnswerQueryDslRepositoryImpl(
private val queryFactory: JPAQueryFactory,
) : OnboardAnswerQueryDslRepository {
override fun findAllByQuestionNumberAndAnswerNumberIn(resultPairs: List<Pair<Int, Int>>): List<OnboardingAnswer> {
val onboardingAnswer = QOnboardingAnswer.onboardingAnswer

return queryFactory
.select(onboardingAnswer)
.from(onboardingAnswer)
.where(inResultPairs(onboardingAnswer, resultPairs))
.fetch()
}

private fun inResultPairs(
qOnboardingAnswer: QOnboardingAnswer,
resultPairs: List<Pair<Int, Int>>,
): BooleanExpression {
return resultPairs
.map { (questionNumber, answerNumber) ->
qOnboardingAnswer.onboardingQuestion.questionNumber.eq(questionNumber)
.and(qOnboardingAnswer.answerNumber.eq(answerNumber))
}
.reduce { acc, expr -> acc.or(expr) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package dnd11th.blooming.domain.repository.onboard

import dnd11th.blooming.domain.entity.onboard.OnboardingAnswer
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param

interface OnboardAnswerRepository : JpaRepository<OnboardingAnswer, Long>, OnboardAnswerQueryDslRepository {
@Query("SELECT oa FROM OnboardingAnswer oa JOIN FETCH oa.onboardingQuestion WHERE oa.onboardingQuestion.version = :version")
fun findAllByVersion(
@Param("version") version: Int,
): List<OnboardingAnswer>
}
Loading
Loading