Skip to content

Commit

Permalink
[feat]: Gallery Preview of logged user (#375)
Browse files Browse the repository at this point in the history
  • Loading branch information
devxb authored Feb 26, 2024
1 parent 860c61f commit 6c8a6ee
Show file tree
Hide file tree
Showing 17 changed files with 685 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class JwtDecryptInterceptorConfigurer implements WebMvcConfigurer {
"/v2/surveys/*/feedbacks",
"/v1/feedbacks/bookmarks",
"/v1/users",
"/v1/gallerys/previews",
"/v1/surveys/*/bookmarks",
};

Expand Down
3 changes: 3 additions & 0 deletions gallery/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ repositories {

dependencies {
implementation project(":core:data")
implementation project(":survey:survey-application")

implementation "org.springframework.boot:spring-boot-starter-web"
implementation "org.springframework.boot:spring-boot-starter-data-jpa"

testImplementation "com.ninja-squad:springmockk:${springMockkVersion}"

testRuntimeOnly "com.h2database:h2"
}
97 changes: 97 additions & 0 deletions gallery/src/main/kotlin/me/nalab/gallery/app/GalleryDtoMapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package me.nalab.gallery.app

import me.nalab.gallery.domain.response.GalleryPreviewDto
import me.nalab.survey.application.common.feedback.dto.BookmarkDto
import me.nalab.survey.application.common.feedback.dto.ChoiceFormQuestionFeedbackDto
import me.nalab.survey.application.common.feedback.dto.FeedbackDto
import me.nalab.survey.application.common.feedback.dto.ShortFormQuestionFeedbackDto
import me.nalab.survey.application.common.survey.dto.ChoiceFormQuestionDto
import me.nalab.survey.application.common.survey.dto.ChoiceFormQuestionDtoType
import me.nalab.survey.application.common.survey.dto.SurveyDto
import me.nalab.survey.application.common.survey.dto.TargetDto

fun toGalleryPreviewDto(
target: TargetDto,
survey: SurveyDto,
feedbacks: List<FeedbackDto>,
): GalleryPreviewDto {
return GalleryPreviewDto(
target = GalleryPreviewDto.Target(
targetId = target.id.toString(),
nickname = target.nickname,
position = target.position,
),
survey = GalleryPreviewDto.Survey(
surveyId = survey.id.toString(),
feedbackCount = feedbacks.size,
bookmarkedCount = 0,
feedbacks = findLatestBookmarkedReply(feedbacks),
tendencies = findTendencies(survey, feedbacks),
)
)
}

private fun findLatestBookmarkedReply(feedbacks: List<FeedbackDto>): List<String> {
return feedbacks.filterBookmarkedFeedback()
.mapBookmarkedWithReply()
.sortedByDescending { (bookmark, _) -> bookmark.bookmarkedAt }
.firstOrNull()
?.second ?: emptyList()
}

private fun List<FeedbackDto>.filterBookmarkedFeedback(): List<FeedbackDto> {
return this.filter { feedback ->
feedback.formQuestionFeedbackDtoableList.any { formQuestionFeedback ->
formQuestionFeedback.bookmarkDto.isBookmarked
}
}
}

private fun List<FeedbackDto>.mapBookmarkedWithReply(): List<Pair<BookmarkDto, List<String>>> {
return this.flatMap { bookmarkedFeedback ->
bookmarkedFeedback.formQuestionFeedbackDtoableList
.filterIsInstance<ShortFormQuestionFeedbackDto>()
.map { shortQuestionFeedback -> shortQuestionFeedback.bookmarkDto to shortQuestionFeedback.replyList }
}
}

private fun findTendencies(
survey: SurveyDto,
feedbacks: List<FeedbackDto>,
): List<GalleryPreviewDto.Survey.Tendency> {
val tendencyQuestion = survey.formQuestionDtoableList
.filterIsInstance<ChoiceFormQuestionDto>()
.find { it.choiceFormQuestionDtoType == ChoiceFormQuestionDtoType.TENDENCY }
?: error("필수 유형 Tendency 를 찾을 수 없습니다.")

val countPerTendency = mutableMapOf<String, Int>()

feedbacks.mapTendencyFeedbacks(tendencyQuestion)
.forEach { tendencyFeedback ->
tendencyQuestion.choiceDtoList
.filter { choice ->
tendencyFeedback.selectedChoiceIdSet.contains(choice.id)
}
.map { it.content }
.forEach { tendencyContent ->
countPerTendency[tendencyContent] =
countPerTendency.getOrDefault(tendencyContent, 0) + 1
}
}

return countPerTendency.map { (name, count) ->
GalleryPreviewDto.Survey.Tendency(name, count)
}
}

private fun List<FeedbackDto>.mapTendencyFeedbacks(
tendencyQuestion: ChoiceFormQuestionDto,
): List<ChoiceFormQuestionFeedbackDto> {
return this.flatMap {
it.formQuestionFeedbackDtoableList
.filterIsInstance<ChoiceFormQuestionFeedbackDto>()
.filter { choiceQuestionFeedback ->
choiceQuestionFeedback.questionId == tendencyQuestion.id
}
}
}
23 changes: 23 additions & 0 deletions gallery/src/main/kotlin/me/nalab/gallery/app/GalleryPreviewApp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package me.nalab.gallery.app

import me.nalab.gallery.domain.response.GalleryPreviewDto
import me.nalab.survey.application.port.`in`.web.findfeedback.FeedbackFindUseCase
import me.nalab.survey.application.port.`in`.web.survey.find.SurveyFindUseCase
import me.nalab.survey.application.port.`in`.web.target.find.TargetFindUseCase
import org.springframework.stereotype.Service

@Service
class GalleryPreviewApp(
private val targetFindUseCase: TargetFindUseCase,
private val surveyFindUseCase: SurveyFindUseCase,
private val feedbackFindUseCase: FeedbackFindUseCase,
) {

fun findGalleryPreview(targetId: Long): GalleryPreviewDto {
val target = targetFindUseCase.findTarget(targetId)
val survey = surveyFindUseCase.getSurveyByTargetId(targetId)
val feedbacks = feedbackFindUseCase.findAllFeedbackDtoBySurveyId(survey.id)

return toGalleryPreviewDto(target, survey, feedbacks)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package me.nalab.gallery.controller

import me.nalab.gallery.app.GalleryPreviewApp
import me.nalab.gallery.domain.response.GalleryPreviewDto
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestAttribute
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/v1/gallerys")
class GalleryController(
private val galleryPreviewApp: GalleryPreviewApp,
) {

@GetMapping("/previews")
@ResponseStatus(HttpStatus.OK)
fun getGalleryPreview(@RequestAttribute("logined") targetId: Long): GalleryPreviewDto =
galleryPreviewApp.findGalleryPreview(targetId)

}
1 change: 1 addition & 0 deletions gallery/src/main/kotlin/me/nalab/gallery/domain/Job.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ enum class Job {
PM,
DEVELOPER,
DESIGNER,
OTHERS,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package me.nalab.gallery.domain.response

import com.fasterxml.jackson.annotation.JsonProperty
import me.nalab.gallery.domain.Job

data class GalleryPreviewDto(
val target: Target,
val survey: Survey,
) {

data class Target(
@JsonProperty("target_id")
val targetId: String,
val nickname: String,
val position: String?,
val job: Job = Job.OTHERS,
@JsonProperty("image_url")
val imageUrl: String = "empty_image",
)

data class Survey(
@JsonProperty("survey_id")
val surveyId: String,
@JsonProperty("feedback_count")
val feedbackCount: Int,
@JsonProperty("bookmarked_count")
val bookmarkedCount: Int,
val feedbacks: List<String>,
val tendencies: List<Tendency>,
) {
data class Tendency(
val name: String,
val count: Int,
)
}

}
113 changes: 113 additions & 0 deletions gallery/src/test/kotlin/me/nalab/gallery/app/GalleryPreviewAppTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package me.nalab.gallery.app

import com.ninjasquad.springmockk.MockkBean
import io.kotest.core.annotation.DisplayName
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.equality.shouldBeEqualUsingFields
import io.mockk.every
import me.nalab.gallery.domain.galleryPreviewDto
import me.nalab.gallery.domain.galleryPreviewDtoSurvey
import me.nalab.gallery.domain.galleryPreviewDtoTarget
import me.nalab.survey.application.port.`in`.web.findfeedback.FeedbackFindUseCase
import me.nalab.survey.application.port.`in`.web.survey.find.SurveyFindUseCase
import me.nalab.survey.application.port.`in`.web.target.find.TargetFindUseCase
import org.springframework.test.context.ContextConfiguration

@ContextConfiguration(
classes = [
GalleryPreviewApp::class,
]
)
@DisplayName("GalleryPreviewApp 의")
internal class GalleryPreviewAppTest(
private val galleryPreviewApp: GalleryPreviewApp,
@MockkBean(relaxed = true) private val targetFindUseCase: TargetFindUseCase,
@MockkBean(relaxed = true) private val surveyFindUseCase: SurveyFindUseCase,
@MockkBean(relaxed = true) private val feedbackFindUseCase: FeedbackFindUseCase,
) : DescribeSpec({

beforeEach {
every { targetFindUseCase.findTarget(DEFAULT_TARGET_ID) } returns defaultTargetDto
every { surveyFindUseCase.getSurveyByTargetId(DEFAULT_TARGET_ID) } returns defaultSurveyDto
every { feedbackFindUseCase.findAllFeedbackDtoBySurveyId(DEFAULT_SURVEY_ID) } returns listOf(
defaultFeedbackDto
)
}

describe("getGalleryPreview 메소드는") {
context("존재하는 타겟의 id를 받으면,") {
val expected = galleryPreviewDto(
target = galleryPreviewDtoTarget(DEFAULT_TARGET_ID.toString()),
survey = galleryPreviewDtoSurvey(
surveyId = DEFAULT_SURVEY_ID.toString(),
bookmarkedCount = 0
),
)

it("GalleryPreviewDto를 반환한다.") {
val response = galleryPreviewApp.findGalleryPreview(DEFAULT_TARGET_ID)

response shouldBeEqualUsingFields expected
}
}

context("피드백이 하나도 없다면,") {
val expected = galleryPreviewDto(
target = galleryPreviewDtoTarget(DEFAULT_TARGET_ID.toString()),
survey = galleryPreviewDtoSurvey(
surveyId = DEFAULT_SURVEY_ID.toString(),
feedbackCount = 0,
bookmarkedCount = 0,
feedbacks = emptyList(),
tendencies = emptyList(),
)
)


it("feedback이 비어있는 GalleryPreviewDto를 반환한다.") {
every { surveyFindUseCase.getSurveyByTargetId(DEFAULT_TARGET_ID) } returns surveyDto(
id = DEFAULT_SURVEY_ID, formQuestionDtos = listOf(choiceFormQuestionDto())
)
every { feedbackFindUseCase.findAllFeedbackDtoBySurveyId(DEFAULT_SURVEY_ID) } returns emptyList()

val response = galleryPreviewApp.findGalleryPreview(DEFAULT_TARGET_ID)

response shouldBeEqualUsingFields expected
}
}
}

}) {
private companion object {
private const val DEFAULT_TARGET_ID = 1L
private const val DEFAULT_SURVEY_ID = 2L
private const val DEFAULT_FEEDBACK_ID = 3L
private const val TENDENCY_QUESTION_ID = 4L
private const val CUSTOM_SHORT_FORM_QUESTION_ID = 5L

private val defaultTargetDto = targetDto(id = DEFAULT_TARGET_ID)

private val formQuestionDtos = listOf(
choiceFormQuestionDto(id = TENDENCY_QUESTION_ID),
shortFormQuestionDto(id = CUSTOM_SHORT_FORM_QUESTION_ID),
)

private val defaultSurveyDto =
surveyDto(id = DEFAULT_SURVEY_ID, formQuestionDtos = formQuestionDtos)

private val formQuestionFeedbackDtos = listOf(
choiceFormQuestionFeedbackDto(questionId = TENDENCY_QUESTION_ID),
shortFormQuestionFeedbackDto(
questionId = CUSTOM_SHORT_FORM_QUESTION_ID,
bookmarkDto = bookmarkDto(isBookmarked = true)
),
)

private val defaultFeedbackDto =
feedbackDto(
id = DEFAULT_FEEDBACK_ID,
surveyId = DEFAULT_SURVEY_ID,
formQuestionFeedbackDtos = formQuestionFeedbackDtos,
)
}
}
Loading

0 comments on commit 6c8a6ee

Please sign in to comment.