diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/SurveyAcceptanceValidator.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/SurveyAcceptanceValidator.java index ff4d20c8..cf28f572 100644 --- a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/SurveyAcceptanceValidator.java +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/SurveyAcceptanceValidator.java @@ -103,15 +103,7 @@ public static void assertIsSurveyDoesNotExists(ResultActions resultActions) thro } public static void assertIsBookmarked(ResultActions resultActions) throws Exception { - resultActions.andExpectAll( - status().isOk(), - content().contentType(MediaType.APPLICATION_JSON), - jsonPath("$.target_id").isString(), - jsonPath("$.survey_id").isString(), - jsonPath("$.nickname").isString(), - jsonPath("$.position").doesNotExist(), - jsonPath("$.image_url").doesNotExist() - ); + resultActions.andExpectAll(status().isOk()); } } 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 84bc105d..a84da892 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/surveys/*/bookmarks/cancels", "/v1/gallerys/logins", "/v1/gallerys", }; diff --git a/gallery/src/main/kotlin/me/nalab/gallery/app/GalleryGetApp.kt b/gallery/src/main/kotlin/me/nalab/gallery/app/GalleryGetApp.kt index 4e02e8f0..5df4ff6b 100644 --- a/gallery/src/main/kotlin/me/nalab/gallery/app/GalleryGetApp.kt +++ b/gallery/src/main/kotlin/me/nalab/gallery/app/GalleryGetApp.kt @@ -51,9 +51,9 @@ class GalleryGetApp( } private fun getPage(page: Int, count: Int, orderType: String): Pageable { - return when (orderType.lowercase()) { - "update" -> PageRequest.of(page, count, Sort.by("updateOrder").descending()) - "job" -> PageRequest.of(page, count, Sort.by("survey.bookmarkedCount").descending()) + return when (orderType.uppercase()) { + "UPDATE" -> PageRequest.of(page, count, Sort.by("updateOrder").descending()) + "BOOKMARK" -> PageRequest.of(page, count, Sort.by("survey.bookmarkedCount").descending()) else -> throw IllegalArgumentException("orderType 은 update와 bookmark중 하나여야 합니다. 현재 orderType \"$orderType\"") } } 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 be01c988..18c21259 100644 --- a/gallery/src/main/kotlin/me/nalab/gallery/controller/GalleryController.kt +++ b/gallery/src/main/kotlin/me/nalab/gallery/controller/GalleryController.kt @@ -40,10 +40,10 @@ class GalleryController( @GetMapping @ResponseStatus(HttpStatus.OK) fun getGalleries( - @RequestParam(name = "job", defaultValue = "all") job: String, + @RequestParam(name = "job", defaultValue = "ALL") job: String, @RequestParam(name = "page", defaultValue = "0") page: Int, @RequestParam(name = "count", defaultValue = "5") count: Int, - @RequestParam(name = "order-type", defaultValue = "update") orderType: String + @RequestParam(name = "order-type", defaultValue = "UPDATE") orderType: String ): GalleriesDto { return galleryGetApp.getGalleries(job, page, count, orderType) } 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 bc268ef4..c5848d8a 100644 --- a/gallery/src/main/kotlin/me/nalab/gallery/domain/Gallery.kt +++ b/gallery/src/main/kotlin/me/nalab/gallery/domain/Gallery.kt @@ -40,7 +40,13 @@ class Gallery( fun getBookmarkedCount(): Int = survey.bookmarkedCount - fun increaseBookmarkedCount() { + fun increaseBookmarkCount() { survey.bookmarkedCount++ } + + fun decreaseBookmarkCount() { + survey.bookmarkedCount-- + } + + } diff --git a/gallery/src/main/kotlin/me/nalab/gallery/domain/GalleryService.kt b/gallery/src/main/kotlin/me/nalab/gallery/domain/GalleryService.kt index 78f2610c..2ded8e7f 100644 --- a/gallery/src/main/kotlin/me/nalab/gallery/domain/GalleryService.kt +++ b/gallery/src/main/kotlin/me/nalab/gallery/domain/GalleryService.kt @@ -27,12 +27,17 @@ class GalleryService( @Transactional fun increaseBookmarkCount(targetId: Long) { - galleryRepository.findByTargetIdOrNull(targetId)?.increaseBookmarkedCount() + galleryRepository.findByTargetIdOrNull(targetId)?.increaseBookmarkCount() + } + + @Transactional + fun decreaseBookmarkCount(targetId: Long) { + galleryRepository.findByTargetIdOrNull(targetId)?.decreaseBookmarkCount() } fun getGalleries(job: String, pageable: Pageable): Page { - val jobs = when (job) { - "all" -> Job.entries.toList() + val jobs = when (job.uppercase()) { + "ALL" -> Job.entries.toList() else -> listOf(Job.valueOf(job.uppercase())) } diff --git a/gallery/src/main/kotlin/me/nalab/gallery/infra/SurveyBookmarkedListener.kt b/gallery/src/main/kotlin/me/nalab/gallery/infra/SurveyBookmarkedListener.kt index 1623d0e5..c6ab1249 100644 --- a/gallery/src/main/kotlin/me/nalab/gallery/infra/SurveyBookmarkedListener.kt +++ b/gallery/src/main/kotlin/me/nalab/gallery/infra/SurveyBookmarkedListener.kt @@ -13,14 +13,14 @@ class SurveyBookmarkedListener( ) : SurveyBookmarkListenPort { @Async - override fun listenBookmarked(targetId: Long) { + override fun increaseBookmarked(targetId: Long) { runCatching { galleryService.increaseBookmarkCount(targetId) }.recoverCatching { when (it is OptimisticLockingFailureException) { true -> { waitJitter() - listenBookmarked(targetId) + increaseBookmarked(targetId) } false -> throw it @@ -28,6 +28,23 @@ class SurveyBookmarkedListener( } } + @Async + override fun decreaseBookmarked(targetId: Long) { + runCatching { + galleryService.decreaseBookmarkCount(targetId) + }.recoverCatching { + when (it is OptimisticLockingFailureException) { + true -> { + waitJitter() + decreaseBookmarked(targetId) + } + + false -> throw it + } + } + } + + private fun waitJitter() { Thread.sleep(Random.nextLong(500, 1000)) } diff --git a/support/e2e/v1_10_get_logined_gallery.hurl b/support/e2e/v1_10_get_logined_gallery.hurl index 49eb0ab5..549e2f38 100644 --- a/support/e2e/v1_10_get_logined_gallery.hurl +++ b/support/e2e/v1_10_get_logined_gallery.hurl @@ -110,11 +110,6 @@ 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" == "logined_gallery" ########## diff --git a/support/e2e/v1_11_cancel_bookmark_survey.hurl b/support/e2e/v1_11_cancel_bookmark_survey.hurl new file mode 100644 index 00000000..fa240f16 --- /dev/null +++ b/support/e2e/v1_11_cancel_bookmark_survey.hurl @@ -0,0 +1,116 @@ +POST http://nalab-server:8080/v1/oauth/default # Default provider를 통해서 로그인 진행 +{ + "nickname": "cancel_bookmark_survey", + "email": "cancel_bookmark_survey@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" == "cancel_bookmark_survey" + +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" + +########## + +POST http://nalab-server:8080/v1/surveys/{{ survey_id }}/bookmarks +Authorization: {{ token_type }} {{ auth_token }} + +HTTP 200 +[Asserts] + +########## + +POST http://nalab-server:8080/v1/surveys/{{ survey_id }}/bookmarks/cancels +Authorization: {{ token_type }} {{ auth_token }} + +HTTP 200 +[Asserts] diff --git a/support/e2e/v1_7_bookmark_survey.hurl b/support/e2e/v1_7_bookmark_survey.hurl index 22f20ec8..5893f033 100644 --- a/support/e2e/v1_7_bookmark_survey.hurl +++ b/support/e2e/v1_7_bookmark_survey.hurl @@ -106,8 +106,3 @@ 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" == "bookmark_survey" diff --git a/support/e2e/v1_9_register_gallery.hurl b/support/e2e/v1_9_register_gallery.hurl index c9108087..5816f9c6 100644 --- a/support/e2e/v1_9_register_gallery.hurl +++ b/support/e2e/v1_9_register_gallery.hurl @@ -110,11 +110,6 @@ 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" ########## diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/bookmark/SurveyBookmarkUseCase.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/bookmark/SurveyBookmarkUseCase.java index a363a9b2..1c32fcc9 100644 --- a/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/bookmark/SurveyBookmarkUseCase.java +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/bookmark/SurveyBookmarkUseCase.java @@ -1,13 +1,15 @@ package me.nalab.survey.application.port.in.web.bookmark; -import me.nalab.survey.application.common.survey.dto.SurveyBookmarkDto; - public interface SurveyBookmarkUseCase { /** * targetId에 해당하는 유저에게 survey를 북마크합니다. - * 이미 북마크되어있다면 북마크를 취소합니다. */ - SurveyBookmarkDto bookmark(Long targetId, Long surveyId); + void bookmark(Long targetId, Long surveyId); + + /** + * targetId에 해당하는 유저에게 survey를 북마크 취소합니다. 북마크 되어있지 않다면, 아무동작도 하지 않습니다. + */ + void cancelBookmark(Long targetId, Long surveyId); } 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 index 5935c6b5..16678c5c 100644 --- 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 @@ -2,5 +2,7 @@ public interface SurveyBookmarkListenPort { - void listenBookmarked(Long targetId); + void increaseBookmarked(Long targetId); + + void decreaseBookmarked(Long targetId); } diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/SurveyBookmarkPort.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/SurveyBookmarkPort.java index 3d940bd4..ccfb8648 100644 --- a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/SurveyBookmarkPort.java +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/SurveyBookmarkPort.java @@ -4,5 +4,5 @@ public interface SurveyBookmarkPort { - void bookmark(Target target); + void updateBookmark(Target target); } 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 afb3dc0e..9b60e3b1 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 @@ -1,7 +1,6 @@ package me.nalab.survey.application.service.bookmark; import lombok.RequiredArgsConstructor; -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; @@ -22,7 +21,7 @@ public class SurveyBookmarkService implements SurveyBookmarkUseCase { @Override @Transactional - public SurveyBookmarkDto bookmark(Long targetId, Long surveyId) { + public void bookmark(Long targetId, Long surveyId) { var target = targetFindPort.getTargetById(targetId); if (!surveyExistCheckPort.isExistSurveyBySurveyId(surveyId)) { @@ -30,10 +29,23 @@ public SurveyBookmarkDto bookmark(Long targetId, Long surveyId) { } target.bookmark(surveyId); - surveyBookmarkPort.bookmark(target); + surveyBookmarkPort.updateBookmark(target); - surveyBookmarkListener.listenBookmarked(targetId); + surveyBookmarkListener.increaseBookmarked(targetId); + } + + @Override + @Transactional + public void cancelBookmark(Long targetId, Long surveyId) { + var target = targetFindPort.getTargetById(targetId); + + if (!surveyExistCheckPort.isExistSurveyBySurveyId(surveyId)) { + throw new SurveyDoesNotExistException(surveyId); + } + + target.cancelBookmark(surveyId); + surveyBookmarkPort.updateBookmark(target); - return SurveyBookmarkDto.from(surveyId, target); + surveyBookmarkListener.decreaseBookmarked(targetId); } } diff --git a/survey/survey-domain/src/main/java/me/nalab/survey/domain/target/Target.java b/survey/survey-domain/src/main/java/me/nalab/survey/domain/target/Target.java index db791c8d..530e2878 100644 --- a/survey/survey-domain/src/main/java/me/nalab/survey/domain/target/Target.java +++ b/survey/survey-domain/src/main/java/me/nalab/survey/domain/target/Target.java @@ -2,6 +2,7 @@ import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.function.LongSupplier; import lombok.Builder; @@ -45,4 +46,11 @@ public void bookmark(Long surveyId) { var bookmark = new SurveyBookmark(surveyId); bookmarkedSurveys.add(bookmark); } + + public void cancelBookmark(Long surveyId) { + bookmarkedSurveys.stream() + .filter(surveyBookmark -> Objects.equals(surveyBookmark.surveyId(), surveyId)) + .findFirst() + .ifPresent(bookmarkedSurveys::remove); + } } diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/SurveyBookmarkAdaptor.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/SurveyBookmarkAdaptor.java index 7e069df8..961d418b 100644 --- a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/SurveyBookmarkAdaptor.java +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/SurveyBookmarkAdaptor.java @@ -15,7 +15,7 @@ public class SurveyBookmarkAdaptor implements SurveyBookmarkPort { private final TargetFindRepository targetFindRepository; @Override - public void bookmark(Target target) { + public void updateBookmark(Target target) { var savedTarget = targetFindRepository.findById(target.getId()) .orElseThrow(() -> new TargetDoesNotExistException(target.getId())); diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/SurveyBookmarkReplaceController.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/SurveyBookmarkController.java similarity index 64% rename from survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/SurveyBookmarkReplaceController.java rename to survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/SurveyBookmarkController.java index 9daf08cf..1ffe0b8f 100644 --- a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/SurveyBookmarkReplaceController.java +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/SurveyBookmarkController.java @@ -2,7 +2,6 @@ import lombok.RequiredArgsConstructor; import me.nalab.survey.application.port.in.web.bookmark.SurveyBookmarkUseCase; -import me.nalab.survey.web.adaptor.bookmark.response.SurveyBookmarkResponse; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -14,17 +13,21 @@ @RestController @RequestMapping("/v1") @RequiredArgsConstructor -public class SurveyBookmarkReplaceController { +public class SurveyBookmarkController { private final SurveyBookmarkUseCase surveyBookmarkReplaceUseCase; @ResponseStatus(HttpStatus.OK) @PostMapping("/surveys/{survey_id}/bookmarks") - public SurveyBookmarkResponse replaceBookmark(@RequestAttribute("logined") Long targetId, + public void bookmark(@RequestAttribute("logined") Long targetId, @PathVariable("survey_id") Long surveyId) { - var surveyBookmarked = surveyBookmarkReplaceUseCase.bookmark(targetId, surveyId); - - return SurveyBookmarkResponse.of(surveyBookmarked); + surveyBookmarkReplaceUseCase.bookmark(targetId, surveyId); } + @ResponseStatus(HttpStatus.OK) + @PostMapping("/surveys/{survey_id}/bookmarks/cancels") + public void cancelBookmark(@RequestAttribute("logined") Long targetId, + @PathVariable("survey_id") Long surveyId) { + surveyBookmarkReplaceUseCase.cancelBookmark(targetId, surveyId); + } } diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/response/SurveyBookmarkedResponse.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/response/SurveyBookmarkedResponse.java new file mode 100644 index 00000000..5194c150 --- /dev/null +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/response/SurveyBookmarkedResponse.java @@ -0,0 +1,13 @@ +package me.nalab.survey.web.adaptor.bookmark.response; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record SurveyBookmarkedResponse( + @JsonProperty("survey_id") + String surveyId +) { + + public static SurveyBookmarkedResponse of(Long surveyId) { + return new SurveyBookmarkedResponse(surveyId.toString()); + } +}