From 8cfb986b4157812cabde53242db52026504a9033 Mon Sep 17 00:00:00 2001 From: devxb Date: Mon, 26 Feb 2024 13:47:35 +0900 Subject: [PATCH] =?UTF-8?q?[feat]=20:=20=EB=82=B4=20Gallery=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EA=B8=B0=EB=8A=A5=EC=9D=84=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migration/V9__gallery_target_id_idx.sql | 3 + .../JwtDecryptInterceptorConfigurer.java | 1 + gallery/build.gradle | 2 + .../me/nalab/gallery/app/GalleryDtoMapper.kt | 24 ++- .../gallery/app/GalleryPreviewDtoMapper.kt | 97 +++++++++ .../nalab/gallery/app/GalleryRegisterApp.kt | 51 +++++ .../gallery/controller/GalleryController.kt | 19 +- .../request/GalleryRegisterRequest.kt | 9 + .../kotlin/me/nalab/gallery/domain/Gallery.kt | 25 ++- .../nalab/gallery/domain/GalleryRepository.kt | 14 ++ .../me/nalab/gallery/domain/GalleryService.kt | 24 +++ .../gallery/domain/response/GalleryDto.kt | 38 ++++ .../gallery/infra/SurveyBookmarkedListener.kt | 23 ++ .../me/nalab/gallery/domain/GalleryFixture.kt | 19 ++ .../gallery/domain/GalleryServiceTest.kt | 54 +++++ support/e2e/v1_9_register_gallery.hurl | 198 ++++++++++++++++++ .../common/survey/dto/TargetDto.java | 3 +- .../common/survey/mapper/TargetDtoMapper.java | 4 + .../bookmark/SurveyBookmarkListenPort.java | 6 + .../bookmark/SurveyBookmarkService.java | 4 + 20 files changed, 601 insertions(+), 17 deletions(-) create mode 100644 api/src/main/resources/db/migration/V9__gallery_target_id_idx.sql create mode 100644 gallery/src/main/kotlin/me/nalab/gallery/app/GalleryPreviewDtoMapper.kt create mode 100644 gallery/src/main/kotlin/me/nalab/gallery/app/GalleryRegisterApp.kt create mode 100644 gallery/src/main/kotlin/me/nalab/gallery/controller/request/GalleryRegisterRequest.kt create mode 100644 gallery/src/main/kotlin/me/nalab/gallery/domain/GalleryRepository.kt create mode 100644 gallery/src/main/kotlin/me/nalab/gallery/domain/GalleryService.kt create mode 100644 gallery/src/main/kotlin/me/nalab/gallery/domain/response/GalleryDto.kt create mode 100644 gallery/src/main/kotlin/me/nalab/gallery/infra/SurveyBookmarkedListener.kt create mode 100644 gallery/src/test/kotlin/me/nalab/gallery/domain/GalleryFixture.kt create mode 100644 gallery/src/test/kotlin/me/nalab/gallery/domain/GalleryServiceTest.kt create mode 100644 support/e2e/v1_9_register_gallery.hurl create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/SurveyBookmarkListenPort.java diff --git a/api/src/main/resources/db/migration/V9__gallery_target_id_idx.sql b/api/src/main/resources/db/migration/V9__gallery_target_id_idx.sql new file mode 100644 index 00000000..4e2f84a2 --- /dev/null +++ b/api/src/main/resources/db/migration/V9__gallery_target_id_idx.sql @@ -0,0 +1,3 @@ +ALTER TABLE gallery ADD `version` BIGINT; + +CREATE UNIQUE INDEX galley_idx_target_id ON gallery(target_id) diff --git a/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptorConfigurer.java b/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptorConfigurer.java index a5e357c8..fde209cd 100644 --- a/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptorConfigurer.java +++ b/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptorConfigurer.java @@ -29,6 +29,7 @@ public class JwtDecryptInterceptorConfigurer implements WebMvcConfigurer { "/v1/users", "/v1/gallerys/previews", "/v1/surveys/*/bookmarks", + "/v1/gallerys", }; @Override diff --git a/gallery/build.gradle b/gallery/build.gradle index 9673fa6d..1a0e5aae 100644 --- a/gallery/build.gradle +++ b/gallery/build.gradle @@ -4,6 +4,8 @@ repositories { dependencies { implementation project(":core:data") + implementation project(":core:id-generator:id-core") + implementation project(":core:time") implementation project(":survey:survey-application") implementation "org.springframework.boot:spring-boot-starter-web" diff --git a/gallery/src/main/kotlin/me/nalab/gallery/app/GalleryDtoMapper.kt b/gallery/src/main/kotlin/me/nalab/gallery/app/GalleryDtoMapper.kt index 613fc679..8cd4b77c 100644 --- a/gallery/src/main/kotlin/me/nalab/gallery/app/GalleryDtoMapper.kt +++ b/gallery/src/main/kotlin/me/nalab/gallery/app/GalleryDtoMapper.kt @@ -1,6 +1,7 @@ package me.nalab.gallery.app -import me.nalab.gallery.domain.response.GalleryPreviewDto +import me.nalab.gallery.domain.Gallery +import me.nalab.gallery.domain.response.GalleryDto 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 @@ -10,21 +11,24 @@ 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( +fun toGalleryDto( + gallery: Gallery, target: TargetDto, survey: SurveyDto, feedbacks: List, -): GalleryPreviewDto { - return GalleryPreviewDto( - target = GalleryPreviewDto.Target( - targetId = target.id.toString(), +): GalleryDto { + return GalleryDto( + galleryId = gallery.id, + target = GalleryDto.Target( + targetId = gallery.getTargetId().toString(), nickname = target.nickname, + job = gallery.getJob(), position = target.position, ), - survey = GalleryPreviewDto.Survey( + survey = GalleryDto.Survey( surveyId = survey.id.toString(), feedbackCount = feedbacks.size, - bookmarkedCount = 0, + bookmarkedCount = gallery.getBookmarkedCount(), feedbacks = findLatestBookmarkedReply(feedbacks), tendencies = findTendencies(survey, feedbacks), ) @@ -58,7 +62,7 @@ private fun List.mapBookmarkedWithReply(): List, -): List { +): List { val tendencyQuestion = survey.formQuestionDtoableList .filterIsInstance() .find { it.choiceFormQuestionDtoType == ChoiceFormQuestionDtoType.TENDENCY } @@ -80,7 +84,7 @@ private fun findTendencies( } return countPerTendency.map { (name, count) -> - GalleryPreviewDto.Survey.Tendency(name, count) + GalleryDto.Survey.Tendency(name, count) } } diff --git a/gallery/src/main/kotlin/me/nalab/gallery/app/GalleryPreviewDtoMapper.kt b/gallery/src/main/kotlin/me/nalab/gallery/app/GalleryPreviewDtoMapper.kt new file mode 100644 index 00000000..613fc679 --- /dev/null +++ b/gallery/src/main/kotlin/me/nalab/gallery/app/GalleryPreviewDtoMapper.kt @@ -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, +): 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): List { + return feedbacks.filterBookmarkedFeedback() + .mapBookmarkedWithReply() + .sortedByDescending { (bookmark, _) -> bookmark.bookmarkedAt } + .firstOrNull() + ?.second ?: emptyList() +} + +private fun List.filterBookmarkedFeedback(): List { + return this.filter { feedback -> + feedback.formQuestionFeedbackDtoableList.any { formQuestionFeedback -> + formQuestionFeedback.bookmarkDto.isBookmarked + } + } +} + +private fun List.mapBookmarkedWithReply(): List>> { + return this.flatMap { bookmarkedFeedback -> + bookmarkedFeedback.formQuestionFeedbackDtoableList + .filterIsInstance() + .map { shortQuestionFeedback -> shortQuestionFeedback.bookmarkDto to shortQuestionFeedback.replyList } + } +} + +private fun findTendencies( + survey: SurveyDto, + feedbacks: List, +): List { + val tendencyQuestion = survey.formQuestionDtoableList + .filterIsInstance() + .find { it.choiceFormQuestionDtoType == ChoiceFormQuestionDtoType.TENDENCY } + ?: error("필수 유형 Tendency 를 찾을 수 없습니다.") + + val countPerTendency = mutableMapOf() + + 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.mapTendencyFeedbacks( + tendencyQuestion: ChoiceFormQuestionDto, +): List { + return this.flatMap { + it.formQuestionFeedbackDtoableList + .filterIsInstance() + .filter { choiceQuestionFeedback -> + choiceQuestionFeedback.questionId == tendencyQuestion.id + } + } +} diff --git a/gallery/src/main/kotlin/me/nalab/gallery/app/GalleryRegisterApp.kt b/gallery/src/main/kotlin/me/nalab/gallery/app/GalleryRegisterApp.kt new file mode 100644 index 00000000..a2bed44c --- /dev/null +++ b/gallery/src/main/kotlin/me/nalab/gallery/app/GalleryRegisterApp.kt @@ -0,0 +1,51 @@ +package me.nalab.gallery.app + +import me.nalab.core.idgenerator.idcore.IdGenerator +import me.nalab.core.time.TimeUtil +import me.nalab.gallery.domain.Gallery +import me.nalab.gallery.domain.GalleryService +import me.nalab.gallery.domain.Job +import me.nalab.gallery.domain.response.GalleryDto +import me.nalab.survey.application.common.survey.dto.SurveyDto +import me.nalab.survey.application.common.survey.dto.TargetDto +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 GalleryRegisterApp( + private val idGenerator: IdGenerator, + private val targetFindUseCase: TargetFindUseCase, + private val surveyFindUseCase: SurveyFindUseCase, + private val feedbackFindUseCase: FeedbackFindUseCase, + private val galleryService: GalleryService, +) { + + fun registerGalleryByTargetId(targetId: Long, job: String): GalleryDto { + val target = targetFindUseCase.findTarget(targetId) + val survey = surveyFindUseCase.getSurveyByTargetId(targetId) + + val gallery = galleryService.registerGallery(toGallery(job, target, survey)) + + val feedbacks = feedbackFindUseCase.findAllFeedbackDtoBySurveyId(survey.id) + + return toGalleryDto(gallery, target, survey, feedbacks) + } + + private fun toGallery( + job: String, + target: TargetDto, + survey: SurveyDto, + ): Gallery { + return Gallery( + id = idGenerator.generate(), + targetId = target.id, + job = Job.valueOf(job.uppercase()), + surveyId = survey.id, + bookmarkedCount = target.bookmarkedSurveys.size, + updateOrder = TimeUtil.toInstant(), + ) + } + +} diff --git a/gallery/src/main/kotlin/me/nalab/gallery/controller/GalleryController.kt b/gallery/src/main/kotlin/me/nalab/gallery/controller/GalleryController.kt index 2dc0c479..96ef14cf 100644 --- a/gallery/src/main/kotlin/me/nalab/gallery/controller/GalleryController.kt +++ b/gallery/src/main/kotlin/me/nalab/gallery/controller/GalleryController.kt @@ -1,18 +1,18 @@ package me.nalab.gallery.controller import me.nalab.gallery.app.GalleryPreviewApp +import me.nalab.gallery.app.GalleryRegisterApp +import me.nalab.gallery.controller.request.GalleryRegisterRequest +import me.nalab.gallery.domain.response.GalleryDto 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 +import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/v1/gallerys") class GalleryController( private val galleryPreviewApp: GalleryPreviewApp, + private val galleryRegisterApp: GalleryRegisterApp, ) { @GetMapping("/previews") @@ -20,4 +20,13 @@ class GalleryController( fun getGalleryPreview(@RequestAttribute("logined") targetId: Long): GalleryPreviewDto = galleryPreviewApp.findGalleryPreview(targetId) + @PostMapping + @ResponseStatus(HttpStatus.OK) + fun registerGallery( + @RequestAttribute("logined") targetId: Long, + @RequestBody request: GalleryRegisterRequest, + ): GalleryDto { + return galleryRegisterApp.registerGalleryByTargetId(targetId, request.job) + } + } diff --git a/gallery/src/main/kotlin/me/nalab/gallery/controller/request/GalleryRegisterRequest.kt b/gallery/src/main/kotlin/me/nalab/gallery/controller/request/GalleryRegisterRequest.kt new file mode 100644 index 00000000..318feda5 --- /dev/null +++ b/gallery/src/main/kotlin/me/nalab/gallery/controller/request/GalleryRegisterRequest.kt @@ -0,0 +1,9 @@ +package me.nalab.gallery.controller.request + +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonProperty + +data class GalleryRegisterRequest @JsonCreator constructor( + @JsonProperty("job") + val job: String, +) diff --git a/gallery/src/main/kotlin/me/nalab/gallery/domain/Gallery.kt b/gallery/src/main/kotlin/me/nalab/gallery/domain/Gallery.kt index a61000f5..a86c86f7 100644 --- a/gallery/src/main/kotlin/me/nalab/gallery/domain/Gallery.kt +++ b/gallery/src/main/kotlin/me/nalab/gallery/domain/Gallery.kt @@ -19,4 +19,27 @@ class Gallery( @Column(name = "update_order", columnDefinition = "TIMESTAMP(6)", nullable = false) private var updateOrder: Instant, -) : TimeBaseEntity() + + @Version + private var version: Long? = null, +) : TimeBaseEntity() { + + constructor( + id: Long, + targetId: Long, + job: Job, + surveyId: Long, + bookmarkedCount: Int, + updateOrder: Instant, + ): this(id, Target(targetId, job), Survey(surveyId, bookmarkedCount), updateOrder) + + fun getTargetId(): Long = target.targetId + + fun getJob(): Job = target.job + + fun getBookmarkedCount(): Int = survey.bookmarkedCount + + fun increaseBookmarkedCount() { + survey.bookmarkedCount++ + } +} diff --git a/gallery/src/main/kotlin/me/nalab/gallery/domain/GalleryRepository.kt b/gallery/src/main/kotlin/me/nalab/gallery/domain/GalleryRepository.kt new file mode 100644 index 00000000..508f51f0 --- /dev/null +++ b/gallery/src/main/kotlin/me/nalab/gallery/domain/GalleryRepository.kt @@ -0,0 +1,14 @@ +package me.nalab.gallery.domain + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Lock +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.query.Param +import javax.persistence.LockModeType + +interface GalleryRepository : JpaRepository { + + @Lock(LockModeType.OPTIMISTIC) + @Query("select g from Gallery as g where g.target.targetId = :targetId") + fun findByTargetIdOrNull(@Param("targetId") targetId: Long): Gallery? +} diff --git a/gallery/src/main/kotlin/me/nalab/gallery/domain/GalleryService.kt b/gallery/src/main/kotlin/me/nalab/gallery/domain/GalleryService.kt new file mode 100644 index 00000000..f22f3aa3 --- /dev/null +++ b/gallery/src/main/kotlin/me/nalab/gallery/domain/GalleryService.kt @@ -0,0 +1,24 @@ +package me.nalab.gallery.domain + +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class GalleryService( + private val galleryRepository: GalleryRepository, +) { + + @Transactional + fun registerGallery(gallery: Gallery): Gallery { + require(galleryRepository.findByTargetIdOrNull(gallery.getTargetId()) == null) { + "target \"${gallery.getTargetId()}\" 이 이미 Gallery에 등록되어있습니다." + } + + return galleryRepository.save(gallery) + } + + @Transactional + fun increaseBookmarkCount(targetId: Long) { + galleryRepository.findByTargetIdOrNull(targetId)?.increaseBookmarkedCount() + } +} diff --git a/gallery/src/main/kotlin/me/nalab/gallery/domain/response/GalleryDto.kt b/gallery/src/main/kotlin/me/nalab/gallery/domain/response/GalleryDto.kt new file mode 100644 index 00000000..a54a1810 --- /dev/null +++ b/gallery/src/main/kotlin/me/nalab/gallery/domain/response/GalleryDto.kt @@ -0,0 +1,38 @@ +package me.nalab.gallery.domain.response + +import com.fasterxml.jackson.annotation.JsonProperty +import me.nalab.gallery.domain.Job + +data class GalleryDto( + @JsonProperty("gallery_id") + val galleryId: Long, + val target: Target, + val survey: Survey, +) { + + data class Target( + @JsonProperty("target_id") + val targetId: String, + val nickname: String, + val position: String?, + val job: Job, + @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, + val tendencies: List, + ) { + data class Tendency( + val name: String, + val count: Int, + ) + } +} diff --git a/gallery/src/main/kotlin/me/nalab/gallery/infra/SurveyBookmarkedListener.kt b/gallery/src/main/kotlin/me/nalab/gallery/infra/SurveyBookmarkedListener.kt new file mode 100644 index 00000000..8b9ad8e0 --- /dev/null +++ b/gallery/src/main/kotlin/me/nalab/gallery/infra/SurveyBookmarkedListener.kt @@ -0,0 +1,23 @@ +package me.nalab.gallery.infra + +import me.nalab.gallery.domain.GalleryService +import me.nalab.survey.application.port.out.persistence.bookmark.SurveyBookmarkListenPort +import org.springframework.dao.OptimisticLockingFailureException +import org.springframework.stereotype.Service + +@Service +class SurveyBookmarkedListener( + private val galleryService: GalleryService, +): SurveyBookmarkListenPort { + + override fun listenBookmarked(targetId: Long) { + runCatching { + galleryService.increaseBookmarkCount(targetId) + }.recoverCatching { + when(it is OptimisticLockingFailureException) { + true -> listenBookmarked(targetId) + false -> throw it + } + } + } +} diff --git a/gallery/src/test/kotlin/me/nalab/gallery/domain/GalleryFixture.kt b/gallery/src/test/kotlin/me/nalab/gallery/domain/GalleryFixture.kt new file mode 100644 index 00000000..2728cd14 --- /dev/null +++ b/gallery/src/test/kotlin/me/nalab/gallery/domain/GalleryFixture.kt @@ -0,0 +1,19 @@ +package me.nalab.gallery.domain + +import java.time.Instant + +fun gallery( + id: Long = 0L, + targetId: Long = 0L, + job: Job = Job.OTHERS, + surveyId: Long = 0L, + bookmarkedCount: Int = 0, + updateOrder: Instant = Instant.now(), +): Gallery = Gallery( + id = id, + targetId = targetId, + job = job, + surveyId = surveyId, + bookmarkedCount = bookmarkedCount, + updateOrder = updateOrder, +) diff --git a/gallery/src/test/kotlin/me/nalab/gallery/domain/GalleryServiceTest.kt b/gallery/src/test/kotlin/me/nalab/gallery/domain/GalleryServiceTest.kt new file mode 100644 index 00000000..d19a2551 --- /dev/null +++ b/gallery/src/test/kotlin/me/nalab/gallery/domain/GalleryServiceTest.kt @@ -0,0 +1,54 @@ +package me.nalab.gallery.domain + +import io.kotest.assertions.throwables.shouldThrowMessage +import io.kotest.core.annotation.DisplayName +import io.kotest.core.spec.style.DescribeSpec +import io.kotest.matchers.equality.shouldBeEqualUsingFields +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.test.context.ContextConfiguration + +@DataJpaTest +@EnableJpaRepositories +@EntityScan(value = ["me.nalab.core.data", "me.nalab.gallery.domain"]) +@DisplayName("GalleryService 클래스의") +@ContextConfiguration(classes = [GalleryService::class]) +internal class GalleryServiceTest( + private val galleryService: GalleryService, +) : DescribeSpec({ + + beforeSpec { + galleryService.registerGallery(gallery(id = EXIST_GALLERY_ID, targetId = EXIST_TARGET_ID, surveyId = EXIST_SURVEY_ID)) + } + + describe("registerGallery 메소드는") { + context("Gallery에 등록되지 않은 target의 Gallery를 입력받으면,") { + val gallery = gallery(targetId = NOT_EXIST_TARGET_ID) + + it("Gallery를 저장하고 반환한다.") { + val result = galleryService.registerGallery(gallery) + + result shouldBeEqualUsingFields gallery + } + } + + context("이미 Gallery에 등록된 target의 Gallery를 입력받으면,") { + val existGallery = gallery(targetId = EXIST_TARGET_ID) + + it("IllegalArgumentException을 던진다.") { + shouldThrowMessage("target \"$EXIST_GALLERY_ID\" 이 이미 Gallery에 등록되어있습니다.") { + galleryService.registerGallery(existGallery) + } + } + } + } +}) { + + companion object { + private const val NOT_EXIST_TARGET_ID = 1L + private const val EXIST_GALLERY_ID = 100L + private const val EXIST_TARGET_ID = 100L + private const val EXIST_SURVEY_ID = 100L + } +} diff --git a/support/e2e/v1_9_register_gallery.hurl b/support/e2e/v1_9_register_gallery.hurl new file mode 100644 index 00000000..c9108087 --- /dev/null +++ b/support/e2e/v1_9_register_gallery.hurl @@ -0,0 +1,198 @@ +POST http://nalab-server:8080/v1/oauth/default # Default provider를 통해서 로그인 진행 +{ + "nickname": "register_gallery", + "email": "registergallery@123456" +} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.access_token" exists +jsonpath "$.token_type" exists + +[Captures] +token_type: jsonpath "$.token_type" +auth_token: jsonpath "$.access_token" + +########## + +POST http://nalab-server:8080/v1/surveys # 발급받은 토큰으로 survey를 생성한다. +Authorization: {{ token_type }} {{ auth_token }} +{ + "question_count": 2, + "question": [ + { + "type": "choice", + "form_type": "tendency", + "title": "저는 UI, UI, GUI 중에 어떤 분야를 가장 잘하는 것 같나요?", + "choices": [ + { + "content": "UI", + "order": 1 + }, + { + "content": "UX", + "order": 2 + }, + { + "content": "GUI", + "order": 3 + } + ], + "max_selectable_count": 1, + "order": 1 + }, + { + "type": "short", + "form_type": "strength", + "title": "저는 UX, UI, GUI 중에 어떤 분야에 더 강점이 있나요?", + "order": 2 + } + ] +} + +HTTP 201 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.survey_id" exists + +[Captures] +survey_id: jsonpath "$.survey_id" + +########## + +GET http://nalab-server:8080/v1/surveys/{{ survey_id }} # 생성된 survey를 조회한다. + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.survey_id" exists + +jsonpath "$.target.id" exists +jsonpath "$.target.nickname" == "register_gallery" + +jsonpath "$.question_count" == 2 +jsonpath "$.question.[0].question_id" exists +jsonpath "$.question.[0].type" == "choice" +jsonpath "$.question.[0].form_type" == "tendency" +jsonpath "$.question.[0].title" == "저는 UI, UI, GUI 중에 어떤 분야를 가장 잘하는 것 같나요?" +jsonpath "$.question.[0].order" == 1 +jsonpath "$.question.[0].max_selectable_count" == 1 +jsonpath "$.question.[0].choices.[0].choice_id" exists +jsonpath "$.question.[0].choices.[0].content" == "UI" +jsonpath "$.question.[0].choices.[0].order" == 1 +jsonpath "$.question.[0].choices.[1].choice_id" exists +jsonpath "$.question.[0].choices.[1].content" == "UX" +jsonpath "$.question.[0].choices.[1].order" == 2 +jsonpath "$.question.[0].choices.[2].choice_id" exists +jsonpath "$.question.[0].choices.[2].content" == "GUI" +jsonpath "$.question.[0].choices.[2].order" == 3 +jsonpath "$.question.[1].question_id" exists +jsonpath "$.question.[1].type" == "short" +jsonpath "$.question.[1].form_type" == "strength" +jsonpath "$.question.[1].title" == "저는 UX, UI, GUI 중에 어떤 분야에 더 강점이 있나요?" +jsonpath "$.question.[1].order" == 2 + +[Captures] +target_id: jsonpath "$.target.id" +tendency_question_id: jsonpath "$.question.[0].question_id" +tendency_question_choice_id: jsonpath "$.question.[0].choices.[0].choice_id" +strength_question_id: jsonpath "$.question.[1].question_id" + + +########## + +POST http://nalab-server:8080/v1/surveys/{{ survey_id }}/bookmarks # survey_id를 북마크한다. +Authorization: {{ token_type }} {{ auth_token }} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.target_id" == {{ target_id }} +jsonpath "$.survey_id" == {{ survey_id }} +jsonpath "$.nickname" == "register_gallery" + +########## + +POST http://nalab-server:8080/v1/feedbacks # 생성된 survey에 feedback을 남긴다. + +[QueryStringParams] +survey-id: {{ survey_id }} + +{ + "reviewer": { + "collaboration_experience": true, + "position": "pm" + }, + "question_feedback": [ + { + "question_id": {{ tendency_question_id }}, + "type": "choice", + "choices": [ + {{ tendency_question_choice_id }} + ] + }, + { + "question_id": {{ strength_question_id }}, + "type": "short", + "reply": [ + "Hello world" + ] + } + ] +} + +HTTP 201 +[Asserts] + +########## + +GET http://nalab-server:8080/v1/feedbacks # 북마크를 위해 feedback id 저장 +Authorization: {{ token_type }} {{ auth_token }} + +[QueryStringParams] +survey-id: {{ survey_id }} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +[Captures] +form_question_feedback_id: jsonpath "$.question_feedback.[0].feedbacks.[0].form_question_feedback_id" + +########## + +PATCH http://nalab-server:8080/v1/feedbacks/bookmarks # 북마크를 진행한다. +Authorization: {{ token_type }} {{ auth_token }} + +[QueryStringParams] +form-question-feedback-id: {{form_question_feedback_id}} + +########## + +POST http://nalab-server:8080/v1/gallerys # gallery를 등록한다 +Authorization: {{ token_type }} {{ auth_token }} +{ + "job": "designer" +} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.target.target_id" == {{ target_id }} +jsonpath "$.target.nickname" == "register_gallery" +jsonpath "$.target.position" == null +jsonpath "$.target.job" == "DESIGNER" +jsonpath "$.target.image_url" == "empty_image" + +jsonpath "$.survey.survey_id" == {{ survey_id }} +jsonpath "$.survey.feedback_count" == 1 +jsonpath "$.survey.bookmarked_count" == 1 +jsonpath "$.survey.feedbacks.[0]" == "Hello world" +jsonpath "$.survey.tendencies.[0].name" == "UI" +jsonpath "$.survey.tendencies.[0].count" == 1 diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/common/survey/dto/TargetDto.java b/survey/survey-application/src/main/java/me/nalab/survey/application/common/survey/dto/TargetDto.java index 0ca96ae0..835ada55 100644 --- a/survey/survey-application/src/main/java/me/nalab/survey/application/common/survey/dto/TargetDto.java +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/common/survey/dto/TargetDto.java @@ -1,5 +1,6 @@ package me.nalab.survey.application.common.survey.dto; +import java.util.List; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -14,5 +15,5 @@ public class TargetDto { private final Long id; private final String nickname; private final String position; - + private final List bookmarkedSurveys; } diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/common/survey/mapper/TargetDtoMapper.java b/survey/survey-application/src/main/java/me/nalab/survey/application/common/survey/mapper/TargetDtoMapper.java index 9deb596d..a7ca0f9b 100644 --- a/survey/survey-application/src/main/java/me/nalab/survey/application/common/survey/mapper/TargetDtoMapper.java +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/common/survey/mapper/TargetDtoMapper.java @@ -1,6 +1,7 @@ package me.nalab.survey.application.common.survey.mapper; import me.nalab.survey.application.common.survey.dto.TargetDto; +import me.nalab.survey.domain.target.SurveyBookmark; import me.nalab.survey.domain.target.Target; public class TargetDtoMapper { @@ -21,6 +22,9 @@ public static TargetDto toTargetDto(Target target) { .id(target.getId()) .nickname(target.getNickname()) .position(target.getPosition()) + .bookmarkedSurveys(target.getBookmarkedSurveys().stream() + .map(SurveyBookmark::surveyId) + .toList()) .build(); } } diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/SurveyBookmarkListenPort.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/SurveyBookmarkListenPort.java new file mode 100644 index 00000000..5935c6b5 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/SurveyBookmarkListenPort.java @@ -0,0 +1,6 @@ +package me.nalab.survey.application.port.out.persistence.bookmark; + +public interface SurveyBookmarkListenPort { + + void listenBookmarked(Long targetId); +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/service/bookmark/SurveyBookmarkService.java b/survey/survey-application/src/main/java/me/nalab/survey/application/service/bookmark/SurveyBookmarkService.java index 21528b06..afb3dc0e 100644 --- a/survey/survey-application/src/main/java/me/nalab/survey/application/service/bookmark/SurveyBookmarkService.java +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/service/bookmark/SurveyBookmarkService.java @@ -4,6 +4,7 @@ import me.nalab.survey.application.common.survey.dto.SurveyBookmarkDto; import me.nalab.survey.application.exception.SurveyDoesNotExistException; import me.nalab.survey.application.port.in.web.bookmark.SurveyBookmarkUseCase; +import me.nalab.survey.application.port.out.persistence.bookmark.SurveyBookmarkListenPort; import me.nalab.survey.application.port.out.persistence.bookmark.SurveyBookmarkPort; import me.nalab.survey.application.port.out.persistence.findfeedback.SurveyExistCheckPort; import me.nalab.survey.application.port.out.persistence.findtarget.TargetFindPort; @@ -17,6 +18,7 @@ public class SurveyBookmarkService implements SurveyBookmarkUseCase { private final TargetFindPort targetFindPort; private final SurveyExistCheckPort surveyExistCheckPort; private final SurveyBookmarkPort surveyBookmarkPort; + private final SurveyBookmarkListenPort surveyBookmarkListener; @Override @Transactional @@ -30,6 +32,8 @@ public SurveyBookmarkDto bookmark(Long targetId, Long surveyId) { target.bookmark(surveyId); surveyBookmarkPort.bookmark(target); + surveyBookmarkListener.listenBookmarked(targetId); + return SurveyBookmarkDto.from(surveyId, target); } }