From 214a1bce2981fae1e227a76237817aec28b5567f Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 14 Aug 2024 17:56:51 +0900 Subject: [PATCH 001/201] =?UTF-8?q?[SBL-103]=20=EB=B6=80=EC=A0=95=ED=95=9C?= =?UTF-8?q?=20=EC=A0=91=EA=B7=BC=20=EA=B0=90=EC=A7=80=20=EC=95=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=EC=A6=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/fingerprint/FingerprintApi.kt | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt index aab382ff..59daf628 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt @@ -1,6 +1,9 @@ package com.sbl.sulmun2yong.global.fingerprint import com.fingerprint.api.FingerprintApi +import com.fingerprint.model.BotdDetectionResult +import com.fingerprint.model.EventResponse +import com.fingerprint.model.ProductsResponse import com.fingerprint.model.Response import com.fingerprint.model.ResponseVisits import com.fingerprint.sdk.ApiClient @@ -30,11 +33,17 @@ class FingerprintApi( private fun getVisits(visitorId: String): MutableList? { val response: Response = api.getVisits(visitorId, null, null, 1, null, null) + val product = getEvent(response.visits[0].requestId) + if (product.tampering.data.result == true || + product.botd.data.bot.result !== BotdDetectionResult.ResultEnum.NOT_DETECTED + ) { + throw Exception("부정한 접근이 감지되었습니다") + } return response.visits } -// fun getEvent(requestId: String) { -// val response: EventResponse = api.getEvent(requestId) -// println(response.products.toString()) -// } + fun getEvent(requestId: String): ProductsResponse { + val response: EventResponse = api.getEvent(requestId) + return response.products + } } From 1fc4b7ff2e0d2330d359402e68e9b3df706fdfa4 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 14 Aug 2024 20:12:07 +0900 Subject: [PATCH 002/201] =?UTF-8?q?[SBL-103]=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt | 3 +++ .../sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt | 3 ++- .../global/fingerprint/UncleanVisitorException.kt | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/UncleanVisitorException.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index 3f62dda9..7cf44c85 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -30,6 +30,9 @@ enum class ErrorCode( ALREADY_PARTICIPATED(HttpStatus.BAD_REQUEST, "SV0017", "이미 참여한 설문입니다."), INVALID_SURVEY_START(HttpStatus.BAD_REQUEST, "SV0018", "설문 시작에 실패했습니다."), + // FingerPrint (FP) + UNCLEAN_VISITOR(HttpStatus.BAD_REQUEST, "FP0001", "유효하지 않은 방문자입니다."), + // Drawing (DR) INVALID_DRAWING_BOARD(HttpStatus.BAD_REQUEST, "DR0001", "유효하지 않은 추첨 보드입니다."), INVALID_DRAWING(HttpStatus.BAD_REQUEST, "DR0002", "유효하지 않은 추첨입니다."), diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt index 59daf628..5c597788 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt @@ -34,10 +34,11 @@ class FingerprintApi( private fun getVisits(visitorId: String): MutableList? { val response: Response = api.getVisits(visitorId, null, null, 1, null, null) val product = getEvent(response.visits[0].requestId) + println(product) if (product.tampering.data.result == true || product.botd.data.bot.result !== BotdDetectionResult.ResultEnum.NOT_DETECTED ) { - throw Exception("부정한 접근이 감지되었습니다") + throw UncleanVisitorException() } return response.visits } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/UncleanVisitorException.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/UncleanVisitorException.kt new file mode 100644 index 00000000..f0d59e10 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/UncleanVisitorException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.global.fingerprint + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class UncleanVisitorException : BusinessException(ErrorCode.UNCLEAN_VISITOR) From 363c429a4de5ef8f8c74bab5e5487d33067d05ff Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 14 Aug 2024 20:14:46 +0900 Subject: [PATCH 003/201] =?UTF-8?q?[SBL-103]=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=A1=9C=EA=B7=B8=20=EC=B6=9C=EB=A0=A5=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt index 5c597788..900b072e 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt @@ -34,7 +34,6 @@ class FingerprintApi( private fun getVisits(visitorId: String): MutableList? { val response: Response = api.getVisits(visitorId, null, null, 1, null, null) val product = getEvent(response.visits[0].requestId) - println(product) if (product.tampering.data.result == true || product.botd.data.bot.result !== BotdDetectionResult.ResultEnum.NOT_DETECTED ) { From 60e5efdda217981e8c19427554d7dbe4e6aa362d Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 14 Aug 2024 23:34:12 +0900 Subject: [PATCH 004/201] =?UTF-8?q?[SBL-98]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20API=EC=9D=98=20RequestBody=EB=A5=BC=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/dto/request/SurveySaveRequest.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt index 44ea69d6..2084ad0e 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt @@ -32,7 +32,7 @@ data class SurveySaveRequest( ) data class SectionCreateRequest( - val id: UUID, + val sectionId: UUID, val title: String, val description: String, val questions: List, @@ -40,7 +40,7 @@ data class SurveySaveRequest( ) { fun toDomain(sectionIds: SectionIds) = Section( - id = SectionId.Standard(id), + id = SectionId.Standard(sectionId), title = title, description = description, routingStrategy = getRoutingStrategy(), @@ -76,7 +76,7 @@ data class SurveySaveRequest( ) data class QuestionCreateRequest( - val id: UUID, + val questionId: UUID, val type: QuestionType, val title: String, val description: String, @@ -88,14 +88,14 @@ data class SurveySaveRequest( when (type) { QuestionType.TEXT_RESPONSE -> StandardTextQuestion( - id = id, + id = questionId, title = title, description = description, isRequired = isRequired, ) QuestionType.SINGLE_CHOICE -> StandardSingleChoiceQuestion( - id = id, + id = questionId, title = title, description = description, isRequired = isRequired, @@ -107,7 +107,7 @@ data class SurveySaveRequest( ) QuestionType.MULTIPLE_CHOICE -> StandardMultipleChoiceQuestion( - id = id, + id = questionId, title = title, description = description, isRequired = isRequired, From 9aac6f8357ddbf4c617eaf3a930b3b256cb37745 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 14 Aug 2024 23:36:05 +0900 Subject: [PATCH 005/201] =?UTF-8?q?[SBL-98]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=A0=9C=EC=9E=91=20=EC=A0=95=EB=B3=B4=20API=EC=9D=98=20Respon?= =?UTF-8?q?seBody=EB=A5=BC=20=ED=98=95=EC=8B=9D=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/dto/response/SurveyMakeInfoResponse.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt index 04931859..2b2b17f8 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt @@ -45,7 +45,7 @@ data class SurveyMakeInfoResponse( ) data class SectionMakeInfoResponse( - val id: UUID, + val sectionId: UUID, val title: String, val description: String, val questions: List, @@ -54,7 +54,7 @@ data class SurveyMakeInfoResponse( companion object { fun from(section: Section) = SectionMakeInfoResponse( - id = section.id.value, + sectionId = section.id.value, title = section.title, description = section.description, questions = section.questions.map { QuestionMakeInfoResponse.from(it) }, @@ -96,7 +96,7 @@ data class SurveyMakeInfoResponse( ) data class QuestionMakeInfoResponse( - val id: UUID, + val questionId: UUID, val type: QuestionType, val title: String, val description: String, @@ -107,7 +107,7 @@ data class SurveyMakeInfoResponse( companion object { fun from(question: Question) = QuestionMakeInfoResponse( - id = question.id, + questionId = question.id, type = question.questionType, title = question.title, description = question.description, From 09aa950164755ef4670b4e97283f55735c3eaac3 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 15 Aug 2024 00:07:25 +0900 Subject: [PATCH 006/201] =?UTF-8?q?[SBL-98]=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/service/SurveyManagementService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index 9fc6a876..c522eaf3 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -37,7 +37,7 @@ class SurveyManagementService( val rewards = surveySaveRequest.rewards.map { Reward(name = it.name, category = it.category, count = it.count) } val newSurvey = with(surveySaveRequest) { - val sectionIds = SectionIds.from(surveySaveRequest.sections.map { SectionId.Standard(it.id) }) + val sectionIds = SectionIds.from(surveySaveRequest.sections.map { SectionId.Standard(it.sectionId) }) survey.updateContent( title = this.title, description = this.description, From 0da3b8a181b8dc96724d16d2fdf31e7aec53d8f7 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 28 Aug 2024 12:55:19 +0900 Subject: [PATCH 007/201] =?UTF-8?q?[SBL-106]=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/LoginController.kt | 24 +++++++++++++++++++ .../user/controller/doc/LoginApiDoc.kt | 20 ++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt new file mode 100644 index 00000000..dfe0b092 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt @@ -0,0 +1,24 @@ +package com.sbl.sulmun2yong.user.controller + +import com.sbl.sulmun2yong.user.controller.doc.LoginApiDoc +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.ResponseBody +import org.springframework.web.bind.annotation.RestController +import java.net.URI + +@RestController("/api/v1/login") +class LoginController : LoginApiDoc { + @GetMapping("/oauth/{provider}") + @ResponseBody + override fun login( + @PathVariable provider: String, + ): ResponseEntity { + val httpHeaders = HttpHeaders() + httpHeaders.location = URI.create("http://localhost:8080/oauth2/authorization/$provider") + return ResponseEntity(httpHeaders, HttpStatus.FOUND) + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt new file mode 100644 index 00000000..598ba056 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt @@ -0,0 +1,20 @@ +package com.sbl.sulmun2yong.user.controller.doc + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.ResponseBody + +@Tag(name = "Login", description = "로그인 API") +@RequestMapping("/api/v1/login") +interface LoginApiDoc { + @Operation(summary = "oauth 로그인") + @GetMapping("/login/{provider}") + @ResponseBody + fun login( + @PathVariable provider: String, + ): ResponseEntity +} From 513430d44d772031237dafd684daadc22d1669fc Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 29 Aug 2024 17:38:22 +0900 Subject: [PATCH 008/201] =?UTF-8?q?[SBL-106]=20=EC=BF=A0=ED=82=A4=EB=A5=BC?= =?UTF-8?q?=20=ED=86=B5=ED=95=9C=20=EB=A6=AC=EB=94=94=EB=A0=89=EC=85=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/SecurityConfig.kt | 34 ++++++--- .../sulmun2yong/global/config/WebMvcConfig.kt | 4 +- ...kieOAuth2AuthorizationRequestRepository.kt | 62 ++++++++++++++++ .../CustomAuthenticationSuccessHandler.kt | 8 +- .../sulmun2yong/global/util/CookieUtils.kt | 73 +++++++++++++++++++ .../user/controller/LoginController.kt | 15 +++- .../user/controller/doc/LoginApiDoc.kt | 4 + 7 files changed, 184 insertions(+), 16 deletions(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/util/CookieUtils.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt index b57f161d..851f6fd7 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt @@ -1,6 +1,7 @@ package com.sbl.sulmun2yong.global.config import com.sbl.sulmun2yong.global.config.oauth2.CustomOAuth2Service +import com.sbl.sulmun2yong.global.config.oauth2.HttpCookieOAuth2AuthorizationRequestRepository import com.sbl.sulmun2yong.global.config.oauth2.handler.CustomAuthenticationSuccessHandler import com.sbl.sulmun2yong.global.config.oauth2.handler.CustomLogoutSuccessHandler import com.sbl.sulmun2yong.global.config.oauth2.strategy.CustomExpiredSessionStrategy @@ -21,6 +22,8 @@ import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest import org.springframework.security.provisioning.InMemoryUserDetailsManager import org.springframework.security.web.AuthenticationEntryPoint import org.springframework.security.web.SecurityFilterChain @@ -44,6 +47,10 @@ class SecurityConfig( @Bean fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder() + @Bean + fun cookieAuthorizationRequestRepository(): AuthorizationRequestRepository = + HttpCookieOAuth2AuthorizationRequestRepository() + @Bean fun userDetailsService(): UserDetailsService { val user = @@ -56,6 +63,16 @@ class SecurityConfig( return InMemoryUserDetailsManager(user) } + @Bean + fun forwardedHeaderFilter(): FilterRegistrationBean { + val filterRegistrationBean = FilterRegistrationBean() + + filterRegistrationBean.filter = ForwardedHeaderFilter() + filterRegistrationBean.order = Ordered.HIGHEST_PRECEDENCE + + return filterRegistrationBean + } + @ConditionalOnProperty(prefix = "swagger", name = ["login"], havingValue = "true") @Order(0) @Bean @@ -87,6 +104,13 @@ class SecurityConfig( disable() } oauth2Login { + authorizationEndpoint { + baseUri = "/oauth2/authorization" + authorizationRequestRepository = cookieAuthorizationRequestRepository() + } + redirectionEndpoint { + baseUri = "/login/oauth2/code/*" + } userInfoEndpoint { userService = customOAuth2Service } @@ -121,14 +145,4 @@ class SecurityConfig( } return http.build() } - - @Bean - fun forwardedHeaderFilter(): FilterRegistrationBean { - val filterRegistrationBean = FilterRegistrationBean() - - filterRegistrationBean.filter = ForwardedHeaderFilter() - filterRegistrationBean.order = Ordered.HIGHEST_PRECEDENCE - - return filterRegistrationBean - } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/WebMvcConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/WebMvcConfig.kt index b4c584cd..aec49eb3 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/WebMvcConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/WebMvcConfig.kt @@ -11,14 +11,14 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer @Configuration class WebMvcConfig( @Value("\${frontend.base-url}") - private val baseUrl: String, + private val frontendBaseUrl: String, private val loginUserArgumentResolver: LoginUserArgumentResolver, private val isAdminArgumentResolver: IsAdminArgumentResolver, ) : WebMvcConfigurer { override fun addCorsMappings(registry: CorsRegistry) { registry .addMapping("/**") - .allowedOrigins(baseUrl) // 프론트엔드 도메인 + .allowedOrigins(frontendBaseUrl) // 프론트엔드 도메인 .allowedMethods("GET", "POST", "PUT", "DELETE") .allowCredentials(true) .allowedHeaders("*") diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.kt new file mode 100644 index 00000000..efb81c00 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.kt @@ -0,0 +1,62 @@ +package com.sbl.sulmun2yong.global.config.oauth2 + +import com.nimbusds.oauth2.sdk.util.StringUtils +import com.sbl.sulmun2yong.global.util.CookieUtils +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest +import org.springframework.stereotype.Component + +@Component +class HttpCookieOAuth2AuthorizationRequestRepository : AuthorizationRequestRepository { + override fun loadAuthorizationRequest(request: HttpServletRequest): OAuth2AuthorizationRequest? = + CookieUtils + .getCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME) + .map { cookie -> CookieUtils.deserialize(cookie, OAuth2AuthorizationRequest::class.java) } + .orElse(null) + + override fun removeAuthorizationRequest( + request: HttpServletRequest, + response: HttpServletResponse, + ): OAuth2AuthorizationRequest? = this.loadAuthorizationRequest(request) + + override fun saveAuthorizationRequest( + authorizationRequest: OAuth2AuthorizationRequest?, + request: HttpServletRequest, + response: HttpServletResponse, + ) { + if (authorizationRequest == null) { + CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME) + CookieUtils.deleteCookie(request, response, REDIRECT_URI_PARAM_COOKIE_NAME) + return + } + + CookieUtils.addCookie( + response, + OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, + CookieUtils.serialize(authorizationRequest), + COOKIE_EXPIRE_SECONDS, + ) + val redirectUriAfterLogin = request.getParameter(REDIRECT_URI_PARAM_COOKIE_NAME) + if (StringUtils.isNotBlank(redirectUriAfterLogin)) { + CookieUtils.addCookie(response, REDIRECT_URI_PARAM_COOKIE_NAME, redirectUriAfterLogin, COOKIE_EXPIRE_SECONDS) + } + } + + fun removeAuthorizationRequest(request: HttpServletRequest): OAuth2AuthorizationRequest? = this.loadAuthorizationRequest(request) + + fun removeAuthorizationRequestCookies( + request: HttpServletRequest, + response: HttpServletResponse, + ) { + CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME) + CookieUtils.deleteCookie(request, response, REDIRECT_URI_PARAM_COOKIE_NAME) + } + + companion object { + const val OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME: String = "oauth2_auth_request" + const val REDIRECT_URI_PARAM_COOKIE_NAME: String = "redirect_uri" + private const val COOKIE_EXPIRE_SECONDS = 180 + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/handler/CustomAuthenticationSuccessHandler.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/handler/CustomAuthenticationSuccessHandler.kt index dd297af9..24c065fd 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/handler/CustomAuthenticationSuccessHandler.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/handler/CustomAuthenticationSuccessHandler.kt @@ -1,10 +1,11 @@ package com.sbl.sulmun2yong.global.config.oauth2.handler import com.sbl.sulmun2yong.global.config.oauth2.CustomOAuth2User +import com.sbl.sulmun2yong.global.config.oauth2.HttpCookieOAuth2AuthorizationRequestRepository.Companion.REDIRECT_URI_PARAM_COOKIE_NAME +import com.sbl.sulmun2yong.global.util.CookieUtils import jakarta.servlet.http.Cookie import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse -import org.springframework.http.HttpStatus import org.springframework.security.core.Authentication import org.springframework.security.web.authentication.AuthenticationSuccessHandler @@ -17,7 +18,10 @@ class CustomAuthenticationSuccessHandler( authentication: Authentication, ) { // 상태 코드 설정 - response.status = HttpStatus.OK.value() + val redirectUri = + CookieUtils + .getCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME) + .map(Cookie::getValue) val principal = authentication.principal val defaultUserProfile = diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/CookieUtils.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/CookieUtils.kt new file mode 100644 index 00000000..e7a7dfd9 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/CookieUtils.kt @@ -0,0 +1,73 @@ +package com.sbl.sulmun2yong.global.util + +import jakarta.servlet.http.Cookie +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.util.SerializationUtils +import java.util.Base64 +import java.util.Optional + +object CookieUtils { + fun getCookie( + request: HttpServletRequest, + name: String?, + ): Optional { + val cookies: Array? = request.cookies + + if (cookies != null && cookies.size > 0) { + for (cookie in cookies) { + if (cookie.getName().equals(name)) { + return Optional.of(cookie) + } + } + } + + return Optional.empty() + } + + fun addCookie( + response: HttpServletResponse, + name: String?, + value: String?, + maxAge: Int, + ) { + val cookie: Cookie = Cookie(name, value) + cookie.setPath("/") + cookie.setHttpOnly(true) + cookie.setMaxAge(maxAge) + response.addCookie(cookie) + } + + fun deleteCookie( + request: HttpServletRequest, + response: HttpServletResponse, + name: String?, + ) { + val cookies: Array? = request.cookies + if (cookies != null && cookies.size > 0) { + for (cookie in cookies) { + if (cookie.getName().equals(name)) { + cookie.setValue("") + cookie.setPath("/") + cookie.setMaxAge(0) + response.addCookie(cookie) + } + } + } + } + + fun serialize(`object`: Any?): String = + Base64 + .getUrlEncoder() + .encodeToString(SerializationUtils.serialize(`object`)) + + fun deserialize( + cookie: Cookie, + cls: Class, + ): T = + cls.cast( + SerializationUtils.deserialize( + Base64.getUrlDecoder().decode(cookie.getValue()), + ), + ) +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt index dfe0b092..a953b879 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt @@ -1,6 +1,9 @@ package com.sbl.sulmun2yong.user.controller import com.sbl.sulmun2yong.user.controller.doc.LoginApiDoc +import jakarta.servlet.http.HttpServletRequest +import jakarta.ws.rs.QueryParam +import org.springframework.beans.factory.annotation.Value import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity @@ -11,14 +14,22 @@ import org.springframework.web.bind.annotation.RestController import java.net.URI @RestController("/api/v1/login") -class LoginController : LoginApiDoc { +class LoginController( + @Value("\${backend.base-url}") + private val backendBaseUrl: String, +) : LoginApiDoc { @GetMapping("/oauth/{provider}") @ResponseBody override fun login( @PathVariable provider: String, + @QueryParam("redirect_uri") redirectUri: String?, + request: HttpServletRequest, ): ResponseEntity { + val userRedirectUrl = redirectUri ?: backendBaseUrl val httpHeaders = HttpHeaders() - httpHeaders.location = URI.create("http://localhost:8080/oauth2/authorization/$provider") + + val oauth2RedirectUrl = "/oauth2/authorization/$provider?redirect_uri=$userRedirectUrl" + httpHeaders.location = URI.create(oauth2RedirectUrl) return ResponseEntity(httpHeaders, HttpStatus.FOUND) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt index 598ba056..10d57362 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt @@ -2,6 +2,8 @@ package com.sbl.sulmun2yong.user.controller.doc import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.servlet.http.HttpServletRequest +import jakarta.ws.rs.QueryParam import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -16,5 +18,7 @@ interface LoginApiDoc { @ResponseBody fun login( @PathVariable provider: String, + @QueryParam("redirectUrl") redirectUrl: String?, + request: HttpServletRequest, ): ResponseEntity } From 7a4fa3bce0d6ee7022341c19c7982a312276b832 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Fri, 30 Aug 2024 15:47:49 +0900 Subject: [PATCH 009/201] =?UTF-8?q?[SBL-106]=20=EA=B6=8C=ED=95=9C=EC=97=90?= =?UTF-8?q?=20=EB=94=B0=EB=A5=B8=20=EB=A6=AC=EB=94=94=EB=A0=89=EC=85=98=20?= =?UTF-8?q?=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/SecurityConfig.kt | 8 +-- .../config/oauth2/CustomOAuth2Service.kt | 3 +- .../global/config/oauth2/CustomOAuth2User.kt | 1 + ...kieOAuth2AuthorizationRequestRepository.kt | 11 ++-- .../CustomAuthenticationSuccessHandler.kt | 20 ++++--- .../sulmun2yong/global/util/CookieUtils.kt | 52 ++++++++++--------- .../user/controller/LoginController.kt | 4 +- .../user/dto/DefaultUserProfile.kt | 2 + 8 files changed, 56 insertions(+), 45 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt index 851f6fd7..d21b2e84 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt @@ -33,7 +33,9 @@ import org.springframework.web.filter.ForwardedHeaderFilter @Configuration class SecurityConfig( @Value("\${frontend.base-url}") - private val frontEndBaseUrl: String, + private val frontendBaseUrl: String, + @Value("\${backend.base-url}") + private val backendBaseUrl: String, @Value("\${swagger.username}") private val username: String?, @Value("\${swagger.password}") @@ -114,12 +116,12 @@ class SecurityConfig( userInfoEndpoint { userService = customOAuth2Service } - authenticationSuccessHandler = CustomAuthenticationSuccessHandler(frontEndBaseUrl) + authenticationSuccessHandler = CustomAuthenticationSuccessHandler(frontendBaseUrl, backendBaseUrl) } logout { logoutUrl = "/user/logout" invalidateHttpSession = false - logoutSuccessHandler = CustomLogoutSuccessHandler(frontEndBaseUrl, sessionRegistry()) + logoutSuccessHandler = CustomLogoutSuccessHandler(frontendBaseUrl, sessionRegistry()) } authorizeHttpRequests { authorize("/api/v1/admin/**", hasRole("ADMIN")) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/CustomOAuth2Service.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/CustomOAuth2Service.kt index 99b692f4..1a77dea7 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/CustomOAuth2Service.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/CustomOAuth2Service.kt @@ -33,8 +33,7 @@ class CustomOAuth2Service( val providerId = oAuth2UserInfo.getProviderId() val phoneNumber: String? = oAuth2UserInfo.getPhoneNumber() - // TODO: 전화번호가 등록되어 있는 유저가 소셜로그인에서 전화번호를 제공하지 않는 Google이나 Kakao로 로그인 시 - // 전화번호가 사라지고 ROLE이 변경되는 문제 수정하기 + // TODO: 전화번호가 등록되어 있는 유저가 소셜로그인에서 전화번호를 제공하지 않는 Google이나 Kakao로 로그인 시 전화번호가 사라지고 ROLE이 변경되는 문제 수정하기 val user: User? = userAdapter.findByProviderAndProviderId(provider, providerId) val upsertedUser = user?.withUpdatePhoneNumber(phoneNumber) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/CustomOAuth2User.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/CustomOAuth2User.kt index 4913448b..a80289ef 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/CustomOAuth2User.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/CustomOAuth2User.kt @@ -27,5 +27,6 @@ data class CustomOAuth2User( DefaultUserProfile( id = id, nickname = nickname, + role = role, ) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.kt index efb81c00..97ac74c7 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.kt @@ -10,11 +10,12 @@ import org.springframework.stereotype.Component @Component class HttpCookieOAuth2AuthorizationRequestRepository : AuthorizationRequestRepository { - override fun loadAuthorizationRequest(request: HttpServletRequest): OAuth2AuthorizationRequest? = - CookieUtils - .getCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME) - .map { cookie -> CookieUtils.deserialize(cookie, OAuth2AuthorizationRequest::class.java) } - .orElse(null) + override fun loadAuthorizationRequest(request: HttpServletRequest): OAuth2AuthorizationRequest? { + val cookie = CookieUtils.findCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME) + return cookie?.let { + CookieUtils.deserialize(cookie, OAuth2AuthorizationRequest::class.java) + } + } override fun removeAuthorizationRequest( request: HttpServletRequest, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/handler/CustomAuthenticationSuccessHandler.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/handler/CustomAuthenticationSuccessHandler.kt index 24c065fd..a32f496f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/handler/CustomAuthenticationSuccessHandler.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/handler/CustomAuthenticationSuccessHandler.kt @@ -3,6 +3,7 @@ package com.sbl.sulmun2yong.global.config.oauth2.handler import com.sbl.sulmun2yong.global.config.oauth2.CustomOAuth2User import com.sbl.sulmun2yong.global.config.oauth2.HttpCookieOAuth2AuthorizationRequestRepository.Companion.REDIRECT_URI_PARAM_COOKIE_NAME import com.sbl.sulmun2yong.global.util.CookieUtils +import com.sbl.sulmun2yong.user.domain.UserRole import jakarta.servlet.http.Cookie import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse @@ -10,19 +11,14 @@ import org.springframework.security.core.Authentication import org.springframework.security.web.authentication.AuthenticationSuccessHandler class CustomAuthenticationSuccessHandler( - private val frontEndBaseUrl: String, + private val frontendBaseUrl: String, + private val backendBaseUrl: String, ) : AuthenticationSuccessHandler { override fun onAuthenticationSuccess( request: HttpServletRequest, response: HttpServletResponse, authentication: Authentication, ) { - // 상태 코드 설정 - val redirectUri = - CookieUtils - .getCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME) - .map(Cookie::getValue) - val principal = authentication.principal val defaultUserProfile = if (principal is CustomOAuth2User) { @@ -31,12 +27,20 @@ class CustomAuthenticationSuccessHandler( throw IllegalArgumentException("CustomOAuth2User 타입이 아닙니다.") } + val redirectUriCookieValue = CookieUtils.getCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME).value + val redirectUri = + if (redirectUriCookieValue == backendBaseUrl && defaultUserProfile.role != UserRole.ROLE_ADMIN) { + frontendBaseUrl + } else { + redirectUriCookieValue + } + // 기본 프로필 쿠키 생성 val cookie = Cookie("user-profile", defaultUserProfile.toBase64Json()) cookie.path = "/" response.addCookie(cookie) // 리디렉트 - response.sendRedirect("$frontEndBaseUrl/") + response.sendRedirect(redirectUri) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/CookieUtils.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/CookieUtils.kt index e7a7dfd9..ce367c5f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/util/CookieUtils.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/CookieUtils.kt @@ -5,24 +5,24 @@ import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import org.springframework.util.SerializationUtils import java.util.Base64 -import java.util.Optional object CookieUtils { fun getCookie( request: HttpServletRequest, name: String?, - ): Optional { - val cookies: Array? = request.cookies + ): Cookie { + val cookies: Array = request.cookies - if (cookies != null && cookies.size > 0) { - for (cookie in cookies) { - if (cookie.getName().equals(name)) { - return Optional.of(cookie) - } - } - } + return cookies.firstOrNull { it.name == name } + ?: throw IllegalArgumentException("존재하지 않는 쿠키입니다") + } - return Optional.empty() + fun findCookie( + request: HttpServletRequest, + name: String?, + ): Cookie? { + val cookies: Array = request.cookies + return cookies.firstOrNull { it.name == name } } fun addCookie( @@ -31,10 +31,10 @@ object CookieUtils { value: String?, maxAge: Int, ) { - val cookie: Cookie = Cookie(name, value) - cookie.setPath("/") - cookie.setHttpOnly(true) - cookie.setMaxAge(maxAge) + val cookie = Cookie(name, value) + cookie.path = "/" + cookie.isHttpOnly = true + cookie.maxAge = maxAge response.addCookie(cookie) } @@ -44,22 +44,24 @@ object CookieUtils { name: String?, ) { val cookies: Array? = request.cookies - if (cookies != null && cookies.size > 0) { - for (cookie in cookies) { - if (cookie.getName().equals(name)) { - cookie.setValue("") - cookie.setPath("/") - cookie.setMaxAge(0) - response.addCookie(cookie) - } + if (cookies.isNullOrEmpty()) { + return + } + + for (cookie in cookies) { + if (cookie.name == name) { + cookie.value = "" + cookie.path = "/" + cookie.maxAge = 0 + response.addCookie(cookie) } } } - fun serialize(`object`: Any?): String = + fun serialize(cookieObject: Any?): String = Base64 .getUrlEncoder() - .encodeToString(SerializationUtils.serialize(`object`)) + .encodeToString(SerializationUtils.serialize(cookieObject)) fun deserialize( cookie: Cookie, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt index a953b879..30faa555 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt @@ -22,10 +22,10 @@ class LoginController( @ResponseBody override fun login( @PathVariable provider: String, - @QueryParam("redirect_uri") redirectUri: String?, + @QueryParam("redirect_uri") redirectUrl: String?, request: HttpServletRequest, ): ResponseEntity { - val userRedirectUrl = redirectUri ?: backendBaseUrl + val userRedirectUrl = redirectUrl ?: backendBaseUrl val httpHeaders = HttpHeaders() val oauth2RedirectUrl = "/oauth2/authorization/$provider?redirect_uri=$userRedirectUrl" diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/dto/DefaultUserProfile.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/dto/DefaultUserProfile.kt index 16fdb966..8305efc2 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/dto/DefaultUserProfile.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/dto/DefaultUserProfile.kt @@ -1,12 +1,14 @@ package com.sbl.sulmun2yong.user.dto import com.fasterxml.jackson.databind.ObjectMapper +import com.sbl.sulmun2yong.user.domain.UserRole import java.util.Base64 import java.util.UUID data class DefaultUserProfile( val id: UUID, val nickname: String, + val role: UserRole, ) { fun toBase64Json(): String { val objectMapper = ObjectMapper() From ae6796ac1f32f72001419a327c5023e8d19acee3 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Fri, 30 Aug 2024 15:48:49 +0900 Subject: [PATCH 010/201] =?UTF-8?q?[SBL-106]=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20nullable=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/global/util/CookieUtils.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/CookieUtils.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/CookieUtils.kt index ce367c5f..5db23b6d 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/util/CookieUtils.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/CookieUtils.kt @@ -9,7 +9,7 @@ import java.util.Base64 object CookieUtils { fun getCookie( request: HttpServletRequest, - name: String?, + name: String, ): Cookie { val cookies: Array = request.cookies @@ -19,7 +19,7 @@ object CookieUtils { fun findCookie( request: HttpServletRequest, - name: String?, + name: String, ): Cookie? { val cookies: Array = request.cookies return cookies.firstOrNull { it.name == name } @@ -27,8 +27,8 @@ object CookieUtils { fun addCookie( response: HttpServletResponse, - name: String?, - value: String?, + name: String, + value: String, maxAge: Int, ) { val cookie = Cookie(name, value) @@ -41,7 +41,7 @@ object CookieUtils { fun deleteCookie( request: HttpServletRequest, response: HttpServletResponse, - name: String?, + name: String, ) { val cookies: Array? = request.cookies if (cookies.isNullOrEmpty()) { @@ -58,7 +58,7 @@ object CookieUtils { } } - fun serialize(cookieObject: Any?): String = + fun serialize(cookieObject: Any): String = Base64 .getUrlEncoder() .encodeToString(SerializationUtils.serialize(cookieObject)) From 497d903b96ddc058b992b010f8b38b3581d910a2 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Fri, 30 Aug 2024 15:50:18 +0900 Subject: [PATCH 011/201] =?UTF-8?q?[SBL-106]=20return=20=EC=95=9E=EC=97=90?= =?UTF-8?q?=20=EA=B0=9C=ED=96=89=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/user/controller/LoginController.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt index 30faa555..74657e0f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt @@ -30,6 +30,7 @@ class LoginController( val oauth2RedirectUrl = "/oauth2/authorization/$provider?redirect_uri=$userRedirectUrl" httpHeaders.location = URI.create(oauth2RedirectUrl) + return ResponseEntity(httpHeaders, HttpStatus.FOUND) } } From 3af37ed64e70b30437625367a8ed6178a05af31d Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Fri, 30 Aug 2024 15:59:59 +0900 Subject: [PATCH 012/201] =?UTF-8?q?[SBL-106]=20phonenumber=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/global/config/oauth2/CustomOAuth2Service.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/CustomOAuth2Service.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/CustomOAuth2Service.kt index 1a77dea7..de31bea2 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/CustomOAuth2Service.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/CustomOAuth2Service.kt @@ -31,10 +31,12 @@ class CustomOAuth2Service( val oAuth2UserInfo = getOAuth2UserInfo(oAuth2UserRequest, oAuth2User) val provider = oAuth2UserInfo.getProvider() val providerId = oAuth2UserInfo.getProviderId() - val phoneNumber: String? = oAuth2UserInfo.getPhoneNumber() - // TODO: 전화번호가 등록되어 있는 유저가 소셜로그인에서 전화번호를 제공하지 않는 Google이나 Kakao로 로그인 시 전화번호가 사라지고 ROLE이 변경되는 문제 수정하기 val user: User? = userAdapter.findByProviderAndProviderId(provider, providerId) + val currentPhoneNumber = user?.phoneNumber?.value + + val phoneNumber: String? = currentPhoneNumber ?: oAuth2UserInfo.getPhoneNumber() + val upsertedUser = user?.withUpdatePhoneNumber(phoneNumber) ?: User.create( From b1889a9438f37259fab101071a3a9d448e2d1441 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Sun, 1 Sep 2024 18:19:43 +0900 Subject: [PATCH 013/201] =?UTF-8?q?[SBL-106]=20=EA=B3=84=EC=A0=95=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=EB=B3=84=20=EB=B6=84=EA=B8=B0=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/SecurityConfig.kt | 10 +++----- ...kieOAuth2AuthorizationRequestRepository.kt | 4 --- .../CustomAuthenticationSuccessHandler.kt | 20 +++++++++------ .../user/controller/LoginController.kt | 25 +++++++++++++------ .../user/controller/doc/LoginApiDoc.kt | 2 +- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt index d21b2e84..98fb1362 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt @@ -22,8 +22,6 @@ import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder -import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest import org.springframework.security.provisioning.InMemoryUserDetailsManager import org.springframework.security.web.AuthenticationEntryPoint import org.springframework.security.web.SecurityFilterChain @@ -50,7 +48,7 @@ class SecurityConfig( fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder() @Bean - fun cookieAuthorizationRequestRepository(): AuthorizationRequestRepository = + fun cookieAuthorizationRequestRepository(): HttpCookieOAuth2AuthorizationRequestRepository = HttpCookieOAuth2AuthorizationRequestRepository() @Bean @@ -110,13 +108,11 @@ class SecurityConfig( baseUri = "/oauth2/authorization" authorizationRequestRepository = cookieAuthorizationRequestRepository() } - redirectionEndpoint { - baseUri = "/login/oauth2/code/*" - } userInfoEndpoint { userService = customOAuth2Service } - authenticationSuccessHandler = CustomAuthenticationSuccessHandler(frontendBaseUrl, backendBaseUrl) + authenticationSuccessHandler = + CustomAuthenticationSuccessHandler(frontendBaseUrl, backendBaseUrl, cookieAuthorizationRequestRepository()) } logout { logoutUrl = "/user/logout" diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.kt index 97ac74c7..395a181b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.kt @@ -6,9 +6,7 @@ import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest -import org.springframework.stereotype.Component -@Component class HttpCookieOAuth2AuthorizationRequestRepository : AuthorizationRequestRepository { override fun loadAuthorizationRequest(request: HttpServletRequest): OAuth2AuthorizationRequest? { val cookie = CookieUtils.findCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME) @@ -45,8 +43,6 @@ class HttpCookieOAuth2AuthorizationRequestRepository : AuthorizationRequestRepos } } - fun removeAuthorizationRequest(request: HttpServletRequest): OAuth2AuthorizationRequest? = this.loadAuthorizationRequest(request) - fun removeAuthorizationRequestCookies( request: HttpServletRequest, response: HttpServletResponse, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/handler/CustomAuthenticationSuccessHandler.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/handler/CustomAuthenticationSuccessHandler.kt index a32f496f..c9f6fc04 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/handler/CustomAuthenticationSuccessHandler.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/oauth2/handler/CustomAuthenticationSuccessHandler.kt @@ -1,6 +1,7 @@ package com.sbl.sulmun2yong.global.config.oauth2.handler import com.sbl.sulmun2yong.global.config.oauth2.CustomOAuth2User +import com.sbl.sulmun2yong.global.config.oauth2.HttpCookieOAuth2AuthorizationRequestRepository import com.sbl.sulmun2yong.global.config.oauth2.HttpCookieOAuth2AuthorizationRequestRepository.Companion.REDIRECT_URI_PARAM_COOKIE_NAME import com.sbl.sulmun2yong.global.util.CookieUtils import com.sbl.sulmun2yong.user.domain.UserRole @@ -13,6 +14,7 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand class CustomAuthenticationSuccessHandler( private val frontendBaseUrl: String, private val backendBaseUrl: String, + private val httpCookieOAuth2AuthorizationRequestRepository: HttpCookieOAuth2AuthorizationRequestRepository, ) : AuthenticationSuccessHandler { override fun onAuthenticationSuccess( request: HttpServletRequest, @@ -27,13 +29,15 @@ class CustomAuthenticationSuccessHandler( throw IllegalArgumentException("CustomOAuth2User 타입이 아닙니다.") } - val redirectUriCookieValue = CookieUtils.getCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME).value - val redirectUri = - if (redirectUriCookieValue == backendBaseUrl && defaultUserProfile.role != UserRole.ROLE_ADMIN) { - frontendBaseUrl - } else { - redirectUriCookieValue - } + val redirectUriAfterLogin = + CookieUtils.findCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME)?.value + ?: if (defaultUserProfile.role == UserRole.ROLE_ADMIN) { + backendBaseUrl + } else { + frontendBaseUrl + } + + httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response) // 기본 프로필 쿠키 생성 val cookie = Cookie("user-profile", defaultUserProfile.toBase64Json()) @@ -41,6 +45,6 @@ class CustomAuthenticationSuccessHandler( response.addCookie(cookie) // 리디렉트 - response.sendRedirect(redirectUri) + response.sendRedirect(redirectUriAfterLogin) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt index 74657e0f..795ac842 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt @@ -2,34 +2,45 @@ package com.sbl.sulmun2yong.user.controller import com.sbl.sulmun2yong.user.controller.doc.LoginApiDoc import jakarta.servlet.http.HttpServletRequest -import jakarta.ws.rs.QueryParam import org.springframework.beans.factory.annotation.Value import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.ResponseBody import org.springframework.web.bind.annotation.RestController +import org.springframework.web.util.UriComponentsBuilder import java.net.URI @RestController("/api/v1/login") class LoginController( - @Value("\${backend.base-url}") - private val backendBaseUrl: String, + @Value("\${frontend.base-url}") + private val frontendBaseUrl: String, ) : LoginApiDoc { @GetMapping("/oauth/{provider}") @ResponseBody override fun login( @PathVariable provider: String, - @QueryParam("redirect_uri") redirectUrl: String?, + @RequestParam("redirect_path") redirectPathAfterLogin: String?, request: HttpServletRequest, ): ResponseEntity { - val userRedirectUrl = redirectUrl ?: backendBaseUrl val httpHeaders = HttpHeaders() - val oauth2RedirectUrl = "/oauth2/authorization/$provider?redirect_uri=$userRedirectUrl" - httpHeaders.location = URI.create(oauth2RedirectUrl) + val redirectUriAfterLogin = + redirectPathAfterLogin?. let { + URI.create(frontendBaseUrl + it) + } + + val redirectUriForOAuth2 = + UriComponentsBuilder + .fromPath("/oauth2/authorization/{provider}") + .queryParam("redirect_uri", redirectUriAfterLogin) + .buildAndExpand(provider) + .toUriString() + + httpHeaders.location = URI.create(redirectUriForOAuth2) return ResponseEntity(httpHeaders, HttpStatus.FOUND) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt index 10d57362..046ab404 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt @@ -18,7 +18,7 @@ interface LoginApiDoc { @ResponseBody fun login( @PathVariable provider: String, - @QueryParam("redirectUrl") redirectUrl: String?, + @QueryParam("redirect_path") redirectPathAfterLogin: String?, request: HttpServletRequest, ): ResponseEntity } From a67937bd83adbe1e9f9b56038452cda36e3cbcea Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Sun, 1 Sep 2024 18:59:39 +0900 Subject: [PATCH 014/201] =?UTF-8?q?[SBL-106]=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20phonenumber?= =?UTF-8?q?=EA=B0=80=20null=20=EC=9D=B8=EC=A7=80=20=EA=B2=80=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/sbl/sulmun2yong/user/domain/User.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/domain/User.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/domain/User.kt index 2a2c3c38..e98384ee 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/domain/User.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/domain/User.kt @@ -14,11 +14,13 @@ data class User( var phoneNumber: PhoneNumber?, val role: UserRole, ) { - // TODO: UserRole이 AUTHENTICATED_USER일 경우 phoneNumber는 null이 아닌지 검사 init { if (nickname.length !in 2..10) { throw InvalidUserException() } + if (this.role == UserRole.ROLE_AUTHENTICATED_USER && phoneNumber == null) { + throw InvalidUserException() + } } companion object { From 0ea5d8f6a9e0e72e949e12bf86fc4c6f15a66950 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 3 Sep 2024 22:44:15 +0900 Subject: [PATCH 015/201] =?UTF-8?q?[SBL-110]=20=EC=9C=A0=ED=9A=A8=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EC=B6=94=EC=B2=A8=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=EC=9D=84=20=EC=83=9D=EC=84=B1=ED=95=A0=20=EB=95=8C=20?= =?UTF-8?q?=EB=B0=9C=EC=83=9D=EC=8B=9C=ED=82=AC=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt | 1 + .../survey/exception/InvalidDrawTypeException.kt | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidDrawTypeException.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index 7cf44c85..ec6a3289 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -29,6 +29,7 @@ enum class ErrorCode( INVALID_SURVEY_ACCESS(HttpStatus.FORBIDDEN, "SV0016", "설문 접근 권한이 없습니다."), ALREADY_PARTICIPATED(HttpStatus.BAD_REQUEST, "SV0017", "이미 참여한 설문입니다."), INVALID_SURVEY_START(HttpStatus.BAD_REQUEST, "SV0018", "설문 시작에 실패했습니다."), + INVALID_DRAW_TYPE(HttpStatus.BAD_REQUEST, "SV0019", "유효하지 않은 추첨 방식입니다."), // FingerPrint (FP) UNCLEAN_VISITOR(HttpStatus.BAD_REQUEST, "FP0001", "유효하지 않은 방문자입니다."), diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidDrawTypeException.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidDrawTypeException.kt new file mode 100644 index 00000000..8179c191 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidDrawTypeException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.survey.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class InvalidDrawTypeException : BusinessException(ErrorCode.INVALID_DRAW_TYPE) From f4f8b70abc5b118455f06b3e66de0a7ee7fd9c02 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 3 Sep 2024 22:51:31 +0900 Subject: [PATCH 016/201] =?UTF-8?q?[SBL-110]=20Reward=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=9D=98=20=ED=8C=A8=ED=82=A4=EC=A7=80=EB=A5=BC=20sur?= =?UTF-8?q?vey.reward=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/drawing/domain/DrawingBoard.kt | 2 +- .../com/sbl/sulmun2yong/drawing/service/DrawingBoardService.kt | 2 +- .../com/sbl/sulmun2yong/survey/domain/{ => reward}/Reward.kt | 2 +- .../sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt | 2 +- .../sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt | 2 +- .../kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt | 2 +- .../sbl/sulmun2yong/survey/service/SurveyManagementService.kt | 2 +- .../com/sbl/sulmun2yong/drawing/domain/BoardMakingTest.kt | 2 +- .../sulmun2yong/fixture/drawing/DrawingBoardFixtureFactory.kt | 2 +- .../com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt | 2 +- .../sbl/sulmun2yong/survey/domain/{ => reward}/RewardTest.kt | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) rename src/main/kotlin/com/sbl/sulmun2yong/survey/domain/{ => reward}/Reward.kt (92%) rename src/test/kotlin/com/sbl/sulmun2yong/survey/domain/{ => reward}/RewardTest.kt (96%) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/drawing/domain/DrawingBoard.kt b/src/main/kotlin/com/sbl/sulmun2yong/drawing/domain/DrawingBoard.kt index ec15951a..9129689c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/drawing/domain/DrawingBoard.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/drawing/domain/DrawingBoard.kt @@ -4,7 +4,7 @@ import com.sbl.sulmun2yong.drawing.domain.drawingResult.DrawingResult import com.sbl.sulmun2yong.drawing.domain.ticket.Ticket import com.sbl.sulmun2yong.drawing.exception.AlreadySelectedTicketException import com.sbl.sulmun2yong.drawing.exception.InvalidDrawingBoardException -import com.sbl.sulmun2yong.survey.domain.Reward +import com.sbl.sulmun2yong.survey.domain.reward.Reward import java.util.UUID class DrawingBoard( diff --git a/src/main/kotlin/com/sbl/sulmun2yong/drawing/service/DrawingBoardService.kt b/src/main/kotlin/com/sbl/sulmun2yong/drawing/service/DrawingBoardService.kt index 5ede7432..35ef0f97 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/drawing/service/DrawingBoardService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/drawing/service/DrawingBoardService.kt @@ -13,8 +13,8 @@ import com.sbl.sulmun2yong.drawing.exception.InvalidDrawingBoardAccessException import com.sbl.sulmun2yong.global.data.PhoneNumber import com.sbl.sulmun2yong.survey.adapter.ParticipantAdapter import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter -import com.sbl.sulmun2yong.survey.domain.Reward import com.sbl.sulmun2yong.survey.domain.SurveyStatus +import com.sbl.sulmun2yong.survey.domain.reward.Reward import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.util.UUID diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Reward.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/Reward.kt similarity index 92% rename from src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Reward.kt rename to src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/Reward.kt index 3793987f..6081e2d8 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Reward.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/Reward.kt @@ -1,4 +1,4 @@ -package com.sbl.sulmun2yong.survey.domain +package com.sbl.sulmun2yong.survey.domain.reward import com.sbl.sulmun2yong.survey.exception.InvalidRewardException diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt index d9e15fac..fe874b34 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt @@ -1,8 +1,8 @@ package com.sbl.sulmun2yong.survey.dto.response -import com.sbl.sulmun2yong.survey.domain.Reward import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.SurveyStatus +import com.sbl.sulmun2yong.survey.domain.reward.Reward import java.util.Date // TODO: 설문 제작자 정보도 추가하기 diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt index 2ef3bf99..118dcac3 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt @@ -1,7 +1,7 @@ package com.sbl.sulmun2yong.survey.dto.response -import com.sbl.sulmun2yong.survey.domain.Reward import com.sbl.sulmun2yong.survey.domain.Survey +import com.sbl.sulmun2yong.survey.domain.reward.Reward import java.util.Date import java.util.UUID diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt index 605aa4a7..42f3b2be 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt @@ -1,7 +1,6 @@ package com.sbl.sulmun2yong.survey.entity import com.sbl.sulmun2yong.global.entity.BaseTimeDocument -import com.sbl.sulmun2yong.survey.domain.Reward import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.domain.question.Question @@ -11,6 +10,7 @@ import com.sbl.sulmun2yong.survey.domain.question.choice.Choices import com.sbl.sulmun2yong.survey.domain.question.impl.StandardMultipleChoiceQuestion import com.sbl.sulmun2yong.survey.domain.question.impl.StandardSingleChoiceQuestion import com.sbl.sulmun2yong.survey.domain.question.impl.StandardTextQuestion +import com.sbl.sulmun2yong.survey.domain.reward.Reward import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.routing.RoutingType import com.sbl.sulmun2yong.survey.domain.section.Section diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index c522eaf3..494028ce 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -3,8 +3,8 @@ package com.sbl.sulmun2yong.survey.service import com.sbl.sulmun2yong.drawing.adapter.DrawingBoardAdapter import com.sbl.sulmun2yong.drawing.domain.DrawingBoard import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter -import com.sbl.sulmun2yong.survey.domain.Reward import com.sbl.sulmun2yong.survey.domain.Survey +import com.sbl.sulmun2yong.survey.domain.reward.Reward import com.sbl.sulmun2yong.survey.domain.section.SectionId import com.sbl.sulmun2yong.survey.domain.section.SectionIds import com.sbl.sulmun2yong.survey.dto.request.SurveySaveRequest diff --git a/src/test/kotlin/com/sbl/sulmun2yong/drawing/domain/BoardMakingTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/drawing/domain/BoardMakingTest.kt index ff5a9700..b4c2e59c 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/drawing/domain/BoardMakingTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/drawing/domain/BoardMakingTest.kt @@ -3,7 +3,7 @@ package com.sbl.sulmun2yong.drawing.domain import com.sbl.sulmun2yong.drawing.domain.ticket.Ticket import com.sbl.sulmun2yong.drawing.exception.InvalidDrawingBoardException import com.sbl.sulmun2yong.fixture.drawing.DrawingBoardFixtureFactory.createDrawingBoard -import com.sbl.sulmun2yong.survey.domain.Reward +import com.sbl.sulmun2yong.survey.domain.reward.Reward import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import java.util.UUID diff --git a/src/test/kotlin/com/sbl/sulmun2yong/fixture/drawing/DrawingBoardFixtureFactory.kt b/src/test/kotlin/com/sbl/sulmun2yong/fixture/drawing/DrawingBoardFixtureFactory.kt index 214e6363..17a3a019 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/fixture/drawing/DrawingBoardFixtureFactory.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/fixture/drawing/DrawingBoardFixtureFactory.kt @@ -3,7 +3,7 @@ package com.sbl.sulmun2yong.fixture.drawing import com.sbl.sulmun2yong.drawing.domain.DrawingBoard import com.sbl.sulmun2yong.drawing.domain.ticket.Ticket import com.sbl.sulmun2yong.drawing.exception.InvalidDrawingBoardException -import com.sbl.sulmun2yong.survey.domain.Reward +import com.sbl.sulmun2yong.survey.domain.reward.Reward import java.util.UUID object DrawingBoardFixtureFactory { diff --git a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt index cb26afe0..35c72ca5 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt @@ -1,8 +1,8 @@ package com.sbl.sulmun2yong.fixture.survey -import com.sbl.sulmun2yong.survey.domain.Reward import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.SurveyStatus +import com.sbl.sulmun2yong.survey.domain.reward.Reward import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/RewardTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardTest.kt similarity index 96% rename from src/test/kotlin/com/sbl/sulmun2yong/survey/domain/RewardTest.kt rename to src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardTest.kt index 7e892c95..c9095915 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/RewardTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardTest.kt @@ -1,4 +1,4 @@ -package com.sbl.sulmun2yong.survey.domain +package com.sbl.sulmun2yong.survey.domain.reward import com.sbl.sulmun2yong.survey.exception.InvalidRewardException import org.junit.jupiter.api.Test From e7cfe22786cf3821495e905f43b97b9543cae4e4 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 3 Sep 2024 22:54:04 +0900 Subject: [PATCH 017/201] =?UTF-8?q?[SBL-110]=20=EC=B6=94=EC=B2=A8=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=EA=B3=BC=20=EA=B4=80=EB=A0=A8=EB=90=9C=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EB=8B=B4=EA=B3=A0=20=EC=9E=88?= =?UTF-8?q?=EB=8A=94=20DrawType=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/domain/reward/DrawType.kt | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawType.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawType.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawType.kt new file mode 100644 index 00000000..d7c63257 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawType.kt @@ -0,0 +1,39 @@ +package com.sbl.sulmun2yong.survey.domain.reward + +import com.sbl.sulmun2yong.survey.exception.InvalidDrawTypeException + +/** 설문의 추첨 방식과 관련된 정보를 담고있는 클래스 */ +sealed class DrawType { + abstract val rewards: List + abstract val targetParticipantCount: Int? + + companion object { + fun of( + rewards: List, + targetParticipantCount: Int?, + ) = if (targetParticipantCount != null) Immediate(rewards, targetParticipantCount) else Free(rewards) + } + + fun getRewardCount() = rewards.sumOf { it.count } + + /** 즉시 추첨(설문 참여 후 추첨 보드를 통해 즉시 추첨 진행) */ + data class Immediate( + override val rewards: List, + override val targetParticipantCount: Int, + ) : DrawType() { + init { + require(rewards.isNotEmpty()) { throw InvalidDrawTypeException() } + // 즉시 추첨은 리워드 개수의 총합이 목표 참여자 수보다 적어야함 + require(isTargetParticipantValid()) { throw InvalidDrawTypeException() } + } + + private fun isTargetParticipantValid() = targetParticipantCount >= getRewardCount() + } + + /** 자유(사용자가 직접 추첨 or 추첨을 진행하지 않음) */ + data class Free( + override val rewards: List, + ) : DrawType() { + override val targetParticipantCount = null + } +} From 149d3976e06363c8e88af0fc8b4b5cd0c2d97f52 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 00:23:08 +0900 Subject: [PATCH 018/201] =?UTF-8?q?[SBL-110]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EA=B0=80=20DrawType=EC=9D=84=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20=EC=84=A4=EB=AC=B8=EC=9D=B4=EC=9A=A9=20=EA=B3=B5?= =?UTF-8?q?=EA=B0=9C=20=EC=97=AC=EB=B6=80=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?=EC=86=8D=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/domain/Survey.kt | 35 ++++++------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index a60ba9c9..648e8720 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -2,6 +2,7 @@ package com.sbl.sulmun2yong.survey.domain import com.sbl.sulmun2yong.global.util.DateUtil import com.sbl.sulmun2yong.survey.domain.response.SurveyResponse +import com.sbl.sulmun2yong.survey.domain.reward.DrawType import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId import com.sbl.sulmun2yong.survey.domain.section.SectionIds @@ -14,7 +15,6 @@ import java.time.ZoneId import java.util.Date import java.util.UUID -// TODO: 설문 일정 관련 속성들을 하나의 클래스로 묶기 data class Survey( val id: UUID, val title: String, @@ -24,9 +24,10 @@ data class Survey( val finishedAt: Date, val status: SurveyStatus, val finishMessage: String, - val targetParticipantCount: Int, + val drawType: DrawType, + /** 해당 설문의 설문이용 노출 여부(false면 메인 페이지 노출 X, 링크를 통해서만 접근 가능) */ + val isVisible: Boolean, val makerId: UUID, - val rewards: List, val sections: List
, ) { init { @@ -34,9 +35,6 @@ data class Survey( require(isSectionsUnique()) { throw InvalidSurveyException() } require(isSurveyStatusValid()) { throw InvalidSurveyException() } require(isFinishedAtAfterPublishedAt()) { throw InvalidSurveyException() } - require(isTargetParticipantsEnough()) { throw InvalidSurveyException() } - // TODO: 추후에 리워드가 없는 설문도 생성할 수 있도록 수정하기 - require(rewards.isNotEmpty()) { throw InvalidSurveyException() } require(isSectionIdsValid()) { throw InvalidSurveyException() } } @@ -46,7 +44,6 @@ data class Survey( const val DEFAULT_DESCRIPTION = "" const val DEFAULT_SURVEY_DURATION = 60L const val DEFAULT_FINISH_MESSAGE = "설문에 참여해주셔서 감사합니다." - const val DEFAULT_TARGET_PARTICIPANT_COUNT = 100 fun create(makerId: UUID) = Survey( @@ -58,9 +55,9 @@ data class Survey( finishedAt = getDefaultFinishedAt(), status = SurveyStatus.NOT_STARTED, finishMessage = DEFAULT_FINISH_MESSAGE, - targetParticipantCount = DEFAULT_TARGET_PARTICIPANT_COUNT, + drawType = DrawType.Free(listOf()), + isVisible = true, makerId = makerId, - rewards = listOf(Reward.create()), sections = listOf(Section.create()), ) @@ -100,15 +97,15 @@ data class Survey( thumbnail: String?, finishedAt: Date, finishMessage: String, - targetParticipantCount: Int, - rewards: List, + drawType: DrawType, + isVisible: Boolean, sections: List
, ): Survey { // 설문이 시작 전 상태이거나, 수정 중이면서 리워드 관련 정보가 변경되지 않아야한다. require( status == SurveyStatus.NOT_STARTED || status == SurveyStatus.IN_MODIFICATION && - isRewardInfoEquals(targetParticipantCount, rewards), + drawType == this.drawType, ) { throw InvalidUpdateSurveyException() } @@ -118,18 +115,12 @@ data class Survey( thumbnail = thumbnail, finishedAt = finishedAt, finishMessage = finishMessage, - targetParticipantCount = targetParticipantCount, - rewards = rewards, + drawType = drawType, + isVisible = isVisible, sections = sections, ) } - /** 리워드 관련 정보가 같은지 확인하는 메서드 */ - private fun isRewardInfoEquals( - targetParticipantCount: Int, - rewards: List, - ) = targetParticipantCount == this.targetParticipantCount && rewards == this.rewards - fun finish() = copy(status = SurveyStatus.CLOSED) fun start(): Survey { @@ -137,16 +128,12 @@ data class Survey( return copy(status = SurveyStatus.IN_PROGRESS, publishedAt = DateUtil.getCurrentDate()) } - fun getRewardCount() = rewards.sumOf { it.count } - private fun isSectionsUnique() = sections.size == sections.distinctBy { it.id }.size private fun isSurveyStatusValid() = publishedAt != null || status == SurveyStatus.NOT_STARTED private fun isFinishedAtAfterPublishedAt() = publishedAt == null || finishedAt.after(publishedAt) - private fun isTargetParticipantsEnough() = targetParticipantCount >= getRewardCount() - private fun isSectionIdsValid(): Boolean { val sectionIds = SectionIds.from(sections.map { it.id }) return sections.all { it.sectionIds == sectionIds } From 733a4562544bf06d7e33f7b0b52a27e1f66c3403 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 00:24:19 +0900 Subject: [PATCH 019/201] =?UTF-8?q?[SBL-110]=20=EB=B0=94=EB=80=90=20Survey?= =?UTF-8?q?=20Class=EC=97=90=20=EB=A7=9E=EA=B2=8C=20SurveyDocument=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/entity/SurveyDocument.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt index 42f3b2be..aec29580 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt @@ -10,6 +10,7 @@ import com.sbl.sulmun2yong.survey.domain.question.choice.Choices import com.sbl.sulmun2yong.survey.domain.question.impl.StandardMultipleChoiceQuestion import com.sbl.sulmun2yong.survey.domain.question.impl.StandardSingleChoiceQuestion import com.sbl.sulmun2yong.survey.domain.question.impl.StandardTextQuestion +import com.sbl.sulmun2yong.survey.domain.reward.DrawType import com.sbl.sulmun2yong.survey.domain.reward.Reward import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.routing.RoutingType @@ -32,7 +33,8 @@ data class SurveyDocument( val finishedAt: Date, val status: SurveyStatus, val finishMessage: String, - val targetParticipantCount: Int, + val targetParticipantCount: Int?, + val isVisible: Boolean, val makerId: UUID, val rewards: List, val sections: List, @@ -48,9 +50,10 @@ data class SurveyDocument( finishedAt = survey.finishedAt, status = survey.status, finishMessage = survey.finishMessage, - targetParticipantCount = survey.targetParticipantCount, + targetParticipantCount = survey.drawType.targetParticipantCount, makerId = survey.makerId, - rewards = survey.rewards.map { it.toDocument() }, + rewards = survey.drawType.rewards.map { it.toDocument() }, + isVisible = survey.isVisible, sections = survey.sections.map { it.toDocument() }, ) @@ -156,9 +159,9 @@ data class SurveyDocument( publishedAt = this.publishedAt, status = this.status, finishMessage = this.finishMessage, - targetParticipantCount = this.targetParticipantCount, + drawType = DrawType.of(this.rewards.map { it.toDomain() }, this.targetParticipantCount), + isVisible = this.isVisible, makerId = this.makerId, - rewards = this.rewards.map { it.toDomain() }, sections = this.sections.map { it.toDomain(sectionIds) }, ) } From a486243594ed3d7a2e86586c0e4aa3ad8a50f415 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 00:26:47 +0900 Subject: [PATCH 020/201] =?UTF-8?q?[SBL-110]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=8B=9C=20=EC=B6=94=EC=B2=A8=20=EB=B0=A9=EC=8B=9D=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=9D=BC=20=ED=98=84=EC=9E=AC=20=EC=B0=B8=EA=B0=80?= =?UTF-8?q?=EC=9E=90=20=EC=88=98,=20=EB=AA=A9=ED=91=9C=20=EC=B0=B8?= =?UTF-8?q?=EA=B0=80=EC=9E=90=20=EC=88=98=EB=A5=BC=20=EB=B3=B4=EC=97=AC?= =?UTF-8?q?=EC=A3=BC=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/dto/response/SurveyInfoResponse.kt | 10 +++++----- .../sulmun2yong/survey/service/SurveyInfoService.kt | 13 +++++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt index fe874b34..cb962f7d 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt @@ -11,24 +11,24 @@ data class SurveyInfoResponse( val description: String, val status: SurveyStatus, val finishedAt: Date, - val currentParticipants: Int, - val targetParticipants: Int, + val currentParticipants: Int?, + val targetParticipants: Int?, val thumbnail: String, val rewards: List, ) { companion object { fun of( survey: Survey, - currentParticipants: Int, + currentParticipants: Int?, ) = SurveyInfoResponse( title = survey.title, description = survey.description, status = survey.status, finishedAt = survey.finishedAt, currentParticipants = currentParticipants, - targetParticipants = survey.targetParticipantCount, + targetParticipants = survey.drawType.targetParticipantCount, + rewards = survey.drawType.rewards.map { it.toResponse() }, thumbnail = survey.thumbnail ?: Survey.DEFAULT_THUMBNAIL_URL, - rewards = survey.rewards.map { it.toResponse() }, ) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt index 44efb3c1..0cd29700 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt @@ -3,6 +3,7 @@ package com.sbl.sulmun2yong.survey.service import com.sbl.sulmun2yong.drawing.adapter.DrawingBoardAdapter import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter import com.sbl.sulmun2yong.survey.domain.SurveyStatus +import com.sbl.sulmun2yong.survey.domain.reward.DrawType import com.sbl.sulmun2yong.survey.dto.request.SurveySortType import com.sbl.sulmun2yong.survey.dto.response.SurveyInfoResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyListResponse @@ -35,8 +36,16 @@ class SurveyInfoService( fun getSurveyInfo(surveyId: UUID): SurveyInfoResponse { val survey = surveyAdapter.getSurvey(surveyId) if (survey.status == SurveyStatus.NOT_STARTED) throw InvalidSurveyAccessException() - val drawingBoard = drawingBoardAdapter.getBySurveyId(surveyId) - return SurveyInfoResponse.of(survey, drawingBoard.selectedTicketCount) + val selectedTicketCount = + if (survey.drawType is DrawType.Immediate) { + drawingBoardAdapter + .getBySurveyId( + surveyId, + ).selectedTicketCount + } else { + null + } + return SurveyInfoResponse.of(survey, selectedTicketCount) } fun getSurveyProgressInfo(surveyId: UUID): SurveyProgressInfoResponse? { From 92be087f8231ead2cc3aa19dccbb06dbd914f520 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 00:30:20 +0900 Subject: [PATCH 021/201] =?UTF-8?q?[SBL-110]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EC=8B=9C=20=EB=B0=94=EB=80=90=20Survey=20?= =?UTF-8?q?Class=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95,=20?= =?UTF-8?q?=EA=B3=B5=EA=B0=9C=20=EC=97=AC=EB=B6=80=EB=8F=84=20=EB=B0=9B?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt | 3 ++- .../sulmun2yong/survey/service/SurveyManagementService.kt | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt index 2084ad0e..f0fb6f26 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt @@ -21,8 +21,9 @@ data class SurveySaveRequest( val thumbnail: String?, val finishedAt: Date, val finishMessage: String, - val targetParticipantCount: Int, + val targetParticipantCount: Int?, val rewards: List, + val isVisible: Boolean, val sections: List, ) { data class RewardCreateRequest( diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index 494028ce..240df26b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -4,6 +4,7 @@ import com.sbl.sulmun2yong.drawing.adapter.DrawingBoardAdapter import com.sbl.sulmun2yong.drawing.domain.DrawingBoard import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter import com.sbl.sulmun2yong.survey.domain.Survey +import com.sbl.sulmun2yong.survey.domain.reward.DrawType import com.sbl.sulmun2yong.survey.domain.reward.Reward import com.sbl.sulmun2yong.survey.domain.section.SectionId import com.sbl.sulmun2yong.survey.domain.section.SectionIds @@ -44,8 +45,8 @@ class SurveyManagementService( thumbnail = this.thumbnail, finishedAt = this.finishedAt, finishMessage = this.finishMessage, - targetParticipantCount = this.targetParticipantCount, - rewards = rewards, + drawType = DrawType.of(rewards, this.targetParticipantCount), + isVisible = this.isVisible, sections = this.sections.map { it.toDomain(sectionIds) }, ) } From 5d73d5fb2a79ea487eb7439a0aff3978edc93a27 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 00:31:30 +0900 Subject: [PATCH 022/201] =?UTF-8?q?[SBL-110]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=A0=9C=EC=9E=91=20=EC=A0=95=EB=B3=B4=20Response=20DTO?= =?UTF-8?q?=EB=A5=BC=20=EB=B0=94=EB=80=90=20Survey=20Class=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/dto/response/SurveyMakeInfoResponse.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt index 2b2b17f8..3b6f47c2 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt @@ -18,8 +18,9 @@ data class SurveyMakeInfoResponse( val finishedAt: Date, val status: SurveyStatus, val finishMessage: String, - val targetParticipantCount: Int, + val targetParticipantCount: Int?, val rewards: List, + val isVisible: Boolean, val sections: List, ) { companion object { @@ -32,8 +33,9 @@ data class SurveyMakeInfoResponse( finishedAt = survey.finishedAt, status = survey.status, finishMessage = survey.finishMessage, - targetParticipantCount = survey.targetParticipantCount, - rewards = survey.rewards.map { RewardMakeInfoResponse(it.name, it.category, it.count) }, + targetParticipantCount = survey.drawType.targetParticipantCount, + rewards = survey.drawType.rewards.map { RewardMakeInfoResponse(it.name, it.category, it.count) }, + isVisible = survey.isVisible, sections = survey.sections.map { SectionMakeInfoResponse.from(it) }, ) } From 63bee324a3c92a7662cb917e5f992e3e5963789b Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 00:32:00 +0900 Subject: [PATCH 023/201] =?UTF-8?q?[SBL-110]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=8B=9C=EC=9E=91=20=EC=8B=9C=20=EC=A6=89=EC=8B=9C=20=EC=B6=94?= =?UTF-8?q?=EC=B2=A8=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=EB=A7=8C=20=EC=B6=94?= =?UTF-8?q?=EC=B2=A8=20=EB=B3=B4=EB=93=9C=EB=A5=BC=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/service/SurveyManagementService.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index 240df26b..b88eccbf 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -72,12 +72,14 @@ class SurveyManagementService( // 현재 유저와 설문 제작자가 다를 경우 예외 발생 if (survey.makerId != makerId) throw InvalidSurveyAccessException() surveyAdapter.save(survey.start()) - val drawingBoard = - DrawingBoard.create( - surveyId = survey.id, - boardSize = survey.targetParticipantCount, - rewards = survey.rewards, - ) - drawingBoardAdapter.save(drawingBoard) + if (survey.drawType is DrawType.Immediate) { + val drawingBoard = + DrawingBoard.create( + surveyId = survey.id, + boardSize = survey.drawType.targetParticipantCount, + rewards = survey.drawType.rewards, + ) + drawingBoardAdapter.save(drawingBoard) + } } } From c5bfdca34fa8722778520c9825e867437e0903c9 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 00:32:48 +0900 Subject: [PATCH 024/201] =?UTF-8?q?[SBL-110]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20DTO=EB=A5=BC=20?= =?UTF-8?q?=EB=B0=94=EB=80=90=20Survey=20Class=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/survey/dto/response/SurveyListResponse.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt index 118dcac3..501b265c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt @@ -23,10 +23,10 @@ data class SurveyListResponse( thumbnail = it.thumbnail ?: Survey.DEFAULT_THUMBNAIL_URL, title = it.title, description = it.description, - targetParticipants = it.targetParticipantCount, + targetParticipants = it.drawType.targetParticipantCount, finishedAt = it.finishedAt, - rewardCount = it.getRewardCount(), - rewards = it.rewards.toRewardInfoResponses(), + rewardCount = it.drawType.getRewardCount(), + rewards = it.drawType.rewards.toRewardInfoResponses(), ) }, ) @@ -37,7 +37,7 @@ data class SurveyListResponse( val thumbnail: String, val title: String, val description: String, - val targetParticipants: Int, + val targetParticipants: Int?, val rewardCount: Int, val finishedAt: Date, val rewards: List, From 0f4c4ee8848790c13e3a118ca86e135cf2e3893e Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 00:33:23 +0900 Subject: [PATCH 025/201] =?UTF-8?q?[SBL-110]=20=EC=88=98=EC=A0=95=EB=90=9C?= =?UTF-8?q?=20Survey=20Class=EC=97=90=20=EB=A7=9E=EA=B2=8C=20Survey=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fixture/survey/SurveyFixtureFactory.kt | 11 ++- .../sulmun2yong/survey/domain/SurveyTest.kt | 78 +++++-------------- 2 files changed, 30 insertions(+), 59 deletions(-) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt index 35c72ca5..750e02ae 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt @@ -2,6 +2,7 @@ package com.sbl.sulmun2yong.fixture.survey import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.SurveyStatus +import com.sbl.sulmun2yong.survey.domain.reward.DrawType import com.sbl.sulmun2yong.survey.domain.reward.Reward import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.section.Section @@ -54,6 +55,7 @@ object SurveyFixtureFactory { targetParticipantCount: Int = TARGET_PARTICIPANT_COUNT, makerId: UUID = UUID.randomUUID(), rewards: List = REWARDS, + isVisible: Boolean = true, sections: List
= SECTIONS, ) = Survey( id = id, @@ -64,9 +66,14 @@ object SurveyFixtureFactory { finishedAt = finishedAt, status = status, finishMessage = finishMessage + id, - targetParticipantCount = targetParticipantCount, makerId = makerId, - rewards = rewards, + drawType = createDrawType(rewards, targetParticipantCount), + isVisible = isVisible, sections = sections, ) + + fun createDrawType( + rewards: List = REWARDS, + targetParticipantCount: Int = TARGET_PARTICIPANT_COUNT, + ) = DrawType.Immediate(rewards, targetParticipantCount) } diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt index e7ae941a..3842ada5 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt @@ -5,17 +5,17 @@ import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.DESCRIPTION import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.FINISHED_AT import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.FINISH_MESSAGE import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.PUBLISHED_AT -import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.REWARDS -import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.REWARD_COUNT import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.SECTIONS import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.SURVEY_STATUS -import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.TARGET_PARTICIPANT_COUNT import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.THUMBNAIL import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.TITLE +import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.createDrawType import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.createSurvey import com.sbl.sulmun2yong.global.util.DateUtil import com.sbl.sulmun2yong.survey.domain.response.SectionResponse import com.sbl.sulmun2yong.survey.domain.response.SurveyResponse +import com.sbl.sulmun2yong.survey.domain.reward.DrawType +import com.sbl.sulmun2yong.survey.domain.reward.Reward import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId @@ -35,7 +35,6 @@ import kotlin.test.assertEquals class SurveyTest { private val id = UUID.randomUUID() - private val visitorId = "abcedfg" @Test fun `설문의 응답을 생성하면 정보들이 설정된다`() { @@ -82,9 +81,9 @@ class SurveyTest { assertEquals(PUBLISHED_AT, this.publishedAt) assertEquals(SURVEY_STATUS, this.status) assertEquals(FINISH_MESSAGE + id, this.finishMessage) - assertEquals(TARGET_PARTICIPANT_COUNT, this.targetParticipantCount) + assertEquals(createDrawType(), this.drawType) + assertEquals(true, this.isVisible) assertEquals(makerId, this.makerId) - assertEquals(REWARDS, this.rewards) assertEquals(SECTIONS, this.sections) } @@ -107,9 +106,9 @@ class SurveyTest { assertEquals(null, this.publishedAt) assertEquals(SurveyStatus.NOT_STARTED, this.status) assertEquals(Survey.DEFAULT_FINISH_MESSAGE, this.finishMessage) - assertEquals(Survey.DEFAULT_TARGET_PARTICIPANT_COUNT, this.targetParticipantCount) + assertEquals(DrawType.Free(listOf()), this.drawType) + assertEquals(true, this.isVisible) assertEquals(makerId, this.makerId) - assertEquals(listOf(Reward.create()), this.rewards) assertEquals(listOf(this.sections.first()), this.sections) } } @@ -153,12 +152,6 @@ class SurveyTest { assertThrows { createSurvey(publishedAt = null, status = SurveyStatus.CLOSED) } } - // TODO: 추후에 리워드가 없는 설문도 생성할 수 있도록 수정하기 - @Test - fun `설문에는 최소 한 개의 리워드가 있어야한다`() { - assertThrows { createSurvey(rewards = emptyList()) } - } - @Test fun `설문은 응답의 섹션 순서가 유효한지 검증할 수 있다`() { // given @@ -211,23 +204,6 @@ class SurveyTest { assertThrows { survey.validateResponse(surveyResponse3) } } - @Test - fun `설문은 설문의 리워드 개수를 계산할 수 있다`() { - // given - val survey = createSurvey() - - // when - val count = survey.getRewardCount() - - // then - assertEquals(REWARD_COUNT, count) - } - - @Test - fun `설문의 목표 참여자 수는 설문의 리워드 개수 이상이다`() { - assertThrows { createSurvey(targetParticipantCount = REWARD_COUNT - 1) } - } - @Test fun `각 섹션의 sectionIds는 설문의 섹션 ID들과 같다`() { // given @@ -267,8 +243,8 @@ class SurveyTest { val newDescription = "new description" val newThumbnail = "new thumbnail" val newFinishMessage = "new finish message" - val newTargetParticipantCount = 10 - val newRewards = listOf(Reward("new reward", "new category", 1)) + val newDrawType = DrawType.of(listOf(Reward("new reward", "new category", 1)), 10) + val newIsVisible = false val sectionId = SectionId.Standard(UUID.randomUUID()) val newSections = listOf( @@ -291,8 +267,8 @@ class SurveyTest { thumbnail = newThumbnail, finishedAt = survey.finishedAt, finishMessage = newFinishMessage, - targetParticipantCount = newTargetParticipantCount, - rewards = newRewards, + drawType = newDrawType, + isVisible = newIsVisible, sections = listOf( Section( @@ -313,8 +289,8 @@ class SurveyTest { assertEquals(newThumbnail, this.thumbnail) assertEquals(survey.finishedAt, this.finishedAt) assertEquals(newFinishMessage, this.finishMessage) - assertEquals(newTargetParticipantCount, this.targetParticipantCount) - assertEquals(newRewards, this.rewards) + assertEquals(newDrawType, this.drawType) + assertEquals(isVisible, this.isVisible) assertEquals(newSections, this.sections) } } @@ -335,8 +311,8 @@ class SurveyTest { thumbnail = survey1.thumbnail, finishedAt = survey1.finishedAt, finishMessage = survey1.finishMessage, - targetParticipantCount = survey1.targetParticipantCount, - rewards = survey1.rewards, + drawType = survey1.drawType, + isVisible = survey1.isVisible, sections = survey1.sections, ) } @@ -348,8 +324,8 @@ class SurveyTest { thumbnail = survey2.thumbnail, finishedAt = survey2.finishedAt, finishMessage = survey2.finishMessage, - targetParticipantCount = survey2.targetParticipantCount, - rewards = survey2.rewards, + drawType = survey2.drawType, + isVisible = survey2.isVisible, sections = survey2.sections, ) } @@ -361,8 +337,8 @@ class SurveyTest { thumbnail = survey3.thumbnail, finishedAt = survey3.finishedAt, finishMessage = survey3.finishMessage, - targetParticipantCount = survey3.targetParticipantCount, - rewards = survey3.rewards, + drawType = survey3.drawType, + isVisible = survey3.isVisible, sections = survey3.sections, ) } @@ -374,20 +350,8 @@ class SurveyTest { thumbnail = survey3.thumbnail, finishedAt = survey3.finishedAt, finishMessage = survey3.finishMessage, - targetParticipantCount = survey3.targetParticipantCount, - rewards = listOf(), - sections = survey3.sections, - ) - } - assertThrows { - survey3.updateContent( - title = survey3.title, - description = survey3.description, - thumbnail = survey3.thumbnail, - finishedAt = survey3.finishedAt, - finishMessage = survey3.finishMessage, - targetParticipantCount = 1000, - rewards = survey3.rewards, + drawType = DrawType.Free(listOf()), + isVisible = survey3.isVisible, sections = survey3.sections, ) } From 5084c233a937ca5eb68b427a8a16c9e32c4d6d29 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 00:33:44 +0900 Subject: [PATCH 026/201] =?UTF-8?q?[SBL-110]=20DrawType=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1,=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BB=A4?= =?UTF-8?q?=EB=B2=84=EB=A6=AC=EC=A7=80=20100%=20=EB=8B=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/domain/reward/DrawTypeTest.kt | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawTypeTest.kt diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawTypeTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawTypeTest.kt new file mode 100644 index 00000000..64562aaa --- /dev/null +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawTypeTest.kt @@ -0,0 +1,46 @@ +package com.sbl.sulmun2yong.survey.domain.reward + +import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory +import com.sbl.sulmun2yong.survey.exception.InvalidDrawTypeException +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertEquals + +class DrawTypeTest { + @Test + fun `추첨 방식을 생성하면 정보가 올바르게 설정된다`() { + // given + val rewards = SurveyFixtureFactory.REWARDS + val targetParticipantCount = SurveyFixtureFactory.TARGET_PARTICIPANT_COUNT + + // when + val immediateDrawType1 = DrawType.Immediate(rewards, targetParticipantCount) + val immediateDrawType2 = DrawType.of(rewards, targetParticipantCount) + val freeDrawType1 = DrawType.Free(listOf()) + val freeDrawType2 = DrawType.of(listOf(), null) + + // then + assertEquals(rewards, immediateDrawType1.rewards) + assertEquals(targetParticipantCount, immediateDrawType1.targetParticipantCount) + assertEquals(rewards, immediateDrawType2.rewards) + assertEquals(targetParticipantCount, immediateDrawType2.targetParticipantCount) + assertEquals(emptyList(), freeDrawType1.rewards) + assertEquals(null, freeDrawType1.targetParticipantCount) + assertEquals(emptyList(), freeDrawType2.rewards) + assertEquals(null, freeDrawType2.targetParticipantCount) + } + + @Test + fun `즉시 추첨은 리워드가 하나 이상 존재해야한다`() { + assertThrows { + DrawType.Immediate(listOf(), 0) + } + } + + @Test + fun `즉시 추첨은 리워드 개수의 총합이 목표 참여자 수보다 적어야한다`() { + assertThrows { + DrawType.Immediate(SurveyFixtureFactory.REWARDS, 1) + } + } +} From b376a485c107a003dd233df3a0a67ccc3f7cef55 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 00:43:24 +0900 Subject: [PATCH 027/201] =?UTF-8?q?[SBL-110]=20=EC=84=A4=EB=AC=B8=EC=97=90?= =?UTF-8?q?=20=EC=9D=91=EB=8B=B5=ED=95=9C=20=EB=92=A4=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EB=8A=94=20ResponseDTO=EC=97=90=20=EC=A6=89=EC=8B=9C?= =?UTF-8?q?=20=EC=B6=94=EC=B2=A8=20=EC=97=AC=EB=B6=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt | 2 ++ .../survey/dto/response/SurveyParticipantResponse.kt | 2 ++ .../com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index 648e8720..91a91500 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -128,6 +128,8 @@ data class Survey( return copy(status = SurveyStatus.IN_PROGRESS, publishedAt = DateUtil.getCurrentDate()) } + fun isImmediateDraw() = drawType is DrawType.Immediate + private fun isSectionsUnique() = sections.size == sections.distinctBy { it.id }.size private fun isSurveyStatusValid() = publishedAt != null || status == SurveyStatus.NOT_STARTED diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyParticipantResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyParticipantResponse.kt index 67dc3df0..6d9e1c3e 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyParticipantResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyParticipantResponse.kt @@ -4,4 +4,6 @@ import java.util.UUID data class SurveyParticipantResponse( val participantId: UUID, + /** 즉시 추첨 방식인 경우 True -> False면 추첨 페이지 스킵 */ + val isImmediateDraw: Boolean, ) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt index 6d30d400..4f0355b8 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt @@ -43,7 +43,7 @@ class SurveyResponseService( val participant = Participant.create(visitorId, surveyId, null) participantAdapter.saveParticipant(participant) responseAdapter.saveSurveyResponse(surveyResponse, participant.id) - return SurveyParticipantResponse(participant.id) + return SurveyParticipantResponse(participant.id, survey.isImmediateDraw()) } private fun validateIsAlreadyParticipated( From 5ce4365f6ef1259f7a42afddfa30594076216a81 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 00:44:10 +0900 Subject: [PATCH 028/201] =?UTF-8?q?[SBL-110]=20=EC=84=A4=EB=AC=B8=EC=9D=B4?= =?UTF-8?q?=20=EC=A6=89=EC=8B=9C=20=EC=B6=94=EC=B2=A8=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=EC=9D=B8=EC=A7=80=20=ED=99=95=EC=9D=B8=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fixture/survey/SurveyFixtureFactory.kt | 6 +++--- .../sulmun2yong/survey/domain/SurveyTest.kt | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt index 750e02ae..150f448f 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt @@ -52,7 +52,7 @@ object SurveyFixtureFactory { finishedAt: Date = FINISHED_AT, status: SurveyStatus = SURVEY_STATUS, finishMessage: String = FINISH_MESSAGE, - targetParticipantCount: Int = TARGET_PARTICIPANT_COUNT, + targetParticipantCount: Int? = TARGET_PARTICIPANT_COUNT, makerId: UUID = UUID.randomUUID(), rewards: List = REWARDS, isVisible: Boolean = true, @@ -74,6 +74,6 @@ object SurveyFixtureFactory { fun createDrawType( rewards: List = REWARDS, - targetParticipantCount: Int = TARGET_PARTICIPANT_COUNT, - ) = DrawType.Immediate(rewards, targetParticipantCount) + targetParticipantCount: Int? = TARGET_PARTICIPANT_COUNT, + ) = DrawType.of(rewards, targetParticipantCount) } diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt index 3842ada5..821e5201 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt @@ -397,4 +397,25 @@ class SurveyTest { assertThrows { survey2.start() } assertThrows { survey3.start() } } + + @Test + fun `설문은 설문의 추첨 방식이 즉시 추첨 방식인지 확인할 수 있다`() { + // given + val survey1 = createSurvey(finishedAt = DateUtil.getDateAfterDay(), publishedAt = null, status = SurveyStatus.NOT_STARTED) + val survey2 = + createSurvey( + finishedAt = DateUtil.getDateAfterDay(), + publishedAt = null, + status = SurveyStatus.NOT_STARTED, + targetParticipantCount = null, + ) + + // when + val isImmediateDraw1 = survey1.isImmediateDraw() + val isImmediateDraw2 = survey2.isImmediateDraw() + + // then + assertEquals(true, isImmediateDraw1) + assertEquals(false, isImmediateDraw2) + } } From 55d3d78130157ed35b2dd0a92e922d09c7e633f7 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 00:49:17 +0900 Subject: [PATCH 029/201] =?UTF-8?q?[SBL-110]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20isVisible?= =?UTF-8?q?=EC=9D=B4=20True=EC=9D=B8=20=EC=84=A4=EB=AC=B8=EB=A7=8C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt | 2 +- .../com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt index 2ccaaf80..ece343ff 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt @@ -24,7 +24,7 @@ class SurveyAdapter( isAsc: Boolean, ): Page { val pageRequest = PageRequest.of(page, size, getSurveySort(sortType, isAsc)) - val surveyDocuments = surveyRepository.findByStatus(SurveyStatus.IN_PROGRESS, pageRequest) + val surveyDocuments = surveyRepository.findByStatusAndIsVisibleTrue(SurveyStatus.IN_PROGRESS, pageRequest) val surveys = surveyDocuments.content.map { it.toDomain() } return PageImpl(surveys, pageRequest, surveyDocuments.totalElements) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt index 319e8394..b5864e41 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt @@ -10,7 +10,7 @@ import java.util.UUID @Repository interface SurveyRepository : MongoRepository { - fun findByStatus( + fun findByStatusAndIsVisibleTrue( status: SurveyStatus, pageable: Pageable, ): Page From 3affcede0d86ffd397905089ba04072ebbef0046 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 14:16:31 +0900 Subject: [PATCH 030/201] =?UTF-8?q?[SBL-110]=20DrawType=EC=9D=84=20RewardI?= =?UTF-8?q?nfo=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/global/error/ErrorCode.kt | 2 +- .../sbl/sulmun2yong/survey/domain/Survey.kt | 15 +++--- .../survey/domain/reward/ByUserRewardInfo.kt | 9 ++++ .../survey/domain/reward/DrawType.kt | 39 --------------- .../domain/reward/ImmediateDrawRewardInfo.kt | 19 +++++++ .../survey/domain/reward/RewardInfo.kt | 17 +++++++ .../survey/dto/response/SurveyInfoResponse.kt | 4 +- .../survey/dto/response/SurveyListResponse.kt | 6 +-- .../dto/response/SurveyMakeInfoResponse.kt | 4 +- .../survey/entity/SurveyDocument.kt | 8 +-- ...ption.kt => InvalidRewardInfoException.kt} | 2 +- .../survey/service/SurveyInfoService.kt | 10 +--- .../survey/service/SurveyManagementService.kt | 11 ++-- .../fixture/survey/SurveyFixtureFactory.kt | 8 +-- .../sulmun2yong/survey/domain/SurveyTest.kt | 23 +++++---- .../survey/domain/reward/DrawTypeTest.kt | 46 ----------------- .../survey/domain/reward/RewardInfoTest.kt | 50 +++++++++++++++++++ 17 files changed, 139 insertions(+), 134 deletions(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ByUserRewardInfo.kt delete mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawType.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawRewardInfo.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardInfo.kt rename src/main/kotlin/com/sbl/sulmun2yong/survey/exception/{InvalidDrawTypeException.kt => InvalidRewardInfoException.kt} (64%) delete mode 100644 src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawTypeTest.kt create mode 100644 src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardInfoTest.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index ec6a3289..0ad64809 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -29,7 +29,7 @@ enum class ErrorCode( INVALID_SURVEY_ACCESS(HttpStatus.FORBIDDEN, "SV0016", "설문 접근 권한이 없습니다."), ALREADY_PARTICIPATED(HttpStatus.BAD_REQUEST, "SV0017", "이미 참여한 설문입니다."), INVALID_SURVEY_START(HttpStatus.BAD_REQUEST, "SV0018", "설문 시작에 실패했습니다."), - INVALID_DRAW_TYPE(HttpStatus.BAD_REQUEST, "SV0019", "유효하지 않은 추첨 방식입니다."), + INVALID_REWARD_INFO(HttpStatus.BAD_REQUEST, "SV0019", "유효하지 않은 리워드 정보입니다."), // FingerPrint (FP) UNCLEAN_VISITOR(HttpStatus.BAD_REQUEST, "FP0001", "유효하지 않은 방문자입니다."), diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index 91a91500..30cb31fe 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -2,7 +2,8 @@ package com.sbl.sulmun2yong.survey.domain import com.sbl.sulmun2yong.global.util.DateUtil import com.sbl.sulmun2yong.survey.domain.response.SurveyResponse -import com.sbl.sulmun2yong.survey.domain.reward.DrawType +import com.sbl.sulmun2yong.survey.domain.reward.ByUserRewardInfo +import com.sbl.sulmun2yong.survey.domain.reward.RewardInfo import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId import com.sbl.sulmun2yong.survey.domain.section.SectionIds @@ -24,7 +25,7 @@ data class Survey( val finishedAt: Date, val status: SurveyStatus, val finishMessage: String, - val drawType: DrawType, + val rewardInfo: RewardInfo, /** 해당 설문의 설문이용 노출 여부(false면 메인 페이지 노출 X, 링크를 통해서만 접근 가능) */ val isVisible: Boolean, val makerId: UUID, @@ -55,7 +56,7 @@ data class Survey( finishedAt = getDefaultFinishedAt(), status = SurveyStatus.NOT_STARTED, finishMessage = DEFAULT_FINISH_MESSAGE, - drawType = DrawType.Free(listOf()), + rewardInfo = ByUserRewardInfo(listOf()), isVisible = true, makerId = makerId, sections = listOf(Section.create()), @@ -97,7 +98,7 @@ data class Survey( thumbnail: String?, finishedAt: Date, finishMessage: String, - drawType: DrawType, + rewardInfo: RewardInfo, isVisible: Boolean, sections: List
, ): Survey { @@ -105,7 +106,7 @@ data class Survey( require( status == SurveyStatus.NOT_STARTED || status == SurveyStatus.IN_MODIFICATION && - drawType == this.drawType, + rewardInfo == this.rewardInfo, ) { throw InvalidUpdateSurveyException() } @@ -115,7 +116,7 @@ data class Survey( thumbnail = thumbnail, finishedAt = finishedAt, finishMessage = finishMessage, - drawType = drawType, + rewardInfo = rewardInfo, isVisible = isVisible, sections = sections, ) @@ -128,7 +129,7 @@ data class Survey( return copy(status = SurveyStatus.IN_PROGRESS, publishedAt = DateUtil.getCurrentDate()) } - fun isImmediateDraw() = drawType is DrawType.Immediate + fun isImmediateDraw() = rewardInfo.isImmediateDraw private fun isSectionsUnique() = sections.size == sections.distinctBy { it.id }.size diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ByUserRewardInfo.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ByUserRewardInfo.kt new file mode 100644 index 00000000..2a026da7 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ByUserRewardInfo.kt @@ -0,0 +1,9 @@ +package com.sbl.sulmun2yong.survey.domain.reward + +/** 사용자 설정(사용자가 직접 추첨 or 추첨을 진행하지 않음) */ +data class ByUserRewardInfo( + override val rewards: List, +) : RewardInfo { + override val targetParticipantCount = null + override val isImmediateDraw = false +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawType.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawType.kt deleted file mode 100644 index d7c63257..00000000 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawType.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.sbl.sulmun2yong.survey.domain.reward - -import com.sbl.sulmun2yong.survey.exception.InvalidDrawTypeException - -/** 설문의 추첨 방식과 관련된 정보를 담고있는 클래스 */ -sealed class DrawType { - abstract val rewards: List - abstract val targetParticipantCount: Int? - - companion object { - fun of( - rewards: List, - targetParticipantCount: Int?, - ) = if (targetParticipantCount != null) Immediate(rewards, targetParticipantCount) else Free(rewards) - } - - fun getRewardCount() = rewards.sumOf { it.count } - - /** 즉시 추첨(설문 참여 후 추첨 보드를 통해 즉시 추첨 진행) */ - data class Immediate( - override val rewards: List, - override val targetParticipantCount: Int, - ) : DrawType() { - init { - require(rewards.isNotEmpty()) { throw InvalidDrawTypeException() } - // 즉시 추첨은 리워드 개수의 총합이 목표 참여자 수보다 적어야함 - require(isTargetParticipantValid()) { throw InvalidDrawTypeException() } - } - - private fun isTargetParticipantValid() = targetParticipantCount >= getRewardCount() - } - - /** 자유(사용자가 직접 추첨 or 추첨을 진행하지 않음) */ - data class Free( - override val rewards: List, - ) : DrawType() { - override val targetParticipantCount = null - } -} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawRewardInfo.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawRewardInfo.kt new file mode 100644 index 00000000..6b7f774c --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawRewardInfo.kt @@ -0,0 +1,19 @@ +package com.sbl.sulmun2yong.survey.domain.reward + +import com.sbl.sulmun2yong.survey.exception.InvalidRewardInfoException + +/** 즉시 추첨(설문 참여 후 추첨 보드를 통해 즉시 추첨 진행) */ +data class ImmediateDrawRewardInfo( + override val rewards: List, + override val targetParticipantCount: Int, +) : RewardInfo { + override val isImmediateDraw = true + + init { + require(rewards.isNotEmpty()) { throw InvalidRewardInfoException() } + // 즉시 추첨은 리워드 개수의 총합이 목표 참여자 수보다 적어야함 + require(isTargetParticipantValid()) { throw InvalidRewardInfoException() } + } + + private fun isTargetParticipantValid() = targetParticipantCount >= getRewardCount() +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardInfo.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardInfo.kt new file mode 100644 index 00000000..a733461a --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardInfo.kt @@ -0,0 +1,17 @@ +package com.sbl.sulmun2yong.survey.domain.reward + +/** 설문의 리워드와 관련된 정보를 담고있는 클래스 */ +interface RewardInfo { + val rewards: List + val targetParticipantCount: Int? + val isImmediateDraw: Boolean + + companion object { + fun of( + rewards: List, + targetParticipantCount: Int?, + ) = if (targetParticipantCount != null) ImmediateDrawRewardInfo(rewards, targetParticipantCount) else ByUserRewardInfo(rewards) + } + + fun getRewardCount() = rewards.sumOf { it.count } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt index cb962f7d..5e6a6548 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt @@ -26,8 +26,8 @@ data class SurveyInfoResponse( status = survey.status, finishedAt = survey.finishedAt, currentParticipants = currentParticipants, - targetParticipants = survey.drawType.targetParticipantCount, - rewards = survey.drawType.rewards.map { it.toResponse() }, + targetParticipants = survey.rewardInfo.targetParticipantCount, + rewards = survey.rewardInfo.rewards.map { it.toResponse() }, thumbnail = survey.thumbnail ?: Survey.DEFAULT_THUMBNAIL_URL, ) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt index 501b265c..b6065e24 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt @@ -23,10 +23,10 @@ data class SurveyListResponse( thumbnail = it.thumbnail ?: Survey.DEFAULT_THUMBNAIL_URL, title = it.title, description = it.description, - targetParticipants = it.drawType.targetParticipantCount, + targetParticipants = it.rewardInfo.targetParticipantCount, finishedAt = it.finishedAt, - rewardCount = it.drawType.getRewardCount(), - rewards = it.drawType.rewards.toRewardInfoResponses(), + rewardCount = it.rewardInfo.getRewardCount(), + rewards = it.rewardInfo.rewards.toRewardInfoResponses(), ) }, ) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt index 3b6f47c2..121e3a91 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt @@ -33,8 +33,8 @@ data class SurveyMakeInfoResponse( finishedAt = survey.finishedAt, status = survey.status, finishMessage = survey.finishMessage, - targetParticipantCount = survey.drawType.targetParticipantCount, - rewards = survey.drawType.rewards.map { RewardMakeInfoResponse(it.name, it.category, it.count) }, + targetParticipantCount = survey.rewardInfo.targetParticipantCount, + rewards = survey.rewardInfo.rewards.map { RewardMakeInfoResponse(it.name, it.category, it.count) }, isVisible = survey.isVisible, sections = survey.sections.map { SectionMakeInfoResponse.from(it) }, ) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt index aec29580..0447bce2 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt @@ -10,8 +10,8 @@ import com.sbl.sulmun2yong.survey.domain.question.choice.Choices import com.sbl.sulmun2yong.survey.domain.question.impl.StandardMultipleChoiceQuestion import com.sbl.sulmun2yong.survey.domain.question.impl.StandardSingleChoiceQuestion import com.sbl.sulmun2yong.survey.domain.question.impl.StandardTextQuestion -import com.sbl.sulmun2yong.survey.domain.reward.DrawType import com.sbl.sulmun2yong.survey.domain.reward.Reward +import com.sbl.sulmun2yong.survey.domain.reward.RewardInfo import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.routing.RoutingType import com.sbl.sulmun2yong.survey.domain.section.Section @@ -50,9 +50,9 @@ data class SurveyDocument( finishedAt = survey.finishedAt, status = survey.status, finishMessage = survey.finishMessage, - targetParticipantCount = survey.drawType.targetParticipantCount, + targetParticipantCount = survey.rewardInfo.targetParticipantCount, makerId = survey.makerId, - rewards = survey.drawType.rewards.map { it.toDocument() }, + rewards = survey.rewardInfo.rewards.map { it.toDocument() }, isVisible = survey.isVisible, sections = survey.sections.map { it.toDocument() }, ) @@ -159,7 +159,7 @@ data class SurveyDocument( publishedAt = this.publishedAt, status = this.status, finishMessage = this.finishMessage, - drawType = DrawType.of(this.rewards.map { it.toDomain() }, this.targetParticipantCount), + rewardInfo = RewardInfo.of(this.rewards.map { it.toDomain() }, this.targetParticipantCount), isVisible = this.isVisible, makerId = this.makerId, sections = this.sections.map { it.toDomain(sectionIds) }, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidDrawTypeException.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidRewardInfoException.kt similarity index 64% rename from src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidDrawTypeException.kt rename to src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidRewardInfoException.kt index 8179c191..998ffe9a 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidDrawTypeException.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidRewardInfoException.kt @@ -3,4 +3,4 @@ package com.sbl.sulmun2yong.survey.exception import com.sbl.sulmun2yong.global.error.BusinessException import com.sbl.sulmun2yong.global.error.ErrorCode -class InvalidDrawTypeException : BusinessException(ErrorCode.INVALID_DRAW_TYPE) +class InvalidRewardInfoException : BusinessException(ErrorCode.INVALID_REWARD_INFO) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt index 0cd29700..52e334d4 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt @@ -3,7 +3,6 @@ package com.sbl.sulmun2yong.survey.service import com.sbl.sulmun2yong.drawing.adapter.DrawingBoardAdapter import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter import com.sbl.sulmun2yong.survey.domain.SurveyStatus -import com.sbl.sulmun2yong.survey.domain.reward.DrawType import com.sbl.sulmun2yong.survey.dto.request.SurveySortType import com.sbl.sulmun2yong.survey.dto.response.SurveyInfoResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyListResponse @@ -37,14 +36,7 @@ class SurveyInfoService( val survey = surveyAdapter.getSurvey(surveyId) if (survey.status == SurveyStatus.NOT_STARTED) throw InvalidSurveyAccessException() val selectedTicketCount = - if (survey.drawType is DrawType.Immediate) { - drawingBoardAdapter - .getBySurveyId( - surveyId, - ).selectedTicketCount - } else { - null - } + if (survey.rewardInfo.isImmediateDraw) drawingBoardAdapter.getBySurveyId(surveyId).selectedTicketCount else null return SurveyInfoResponse.of(survey, selectedTicketCount) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index b88eccbf..9c4fdb6f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -4,8 +4,9 @@ import com.sbl.sulmun2yong.drawing.adapter.DrawingBoardAdapter import com.sbl.sulmun2yong.drawing.domain.DrawingBoard import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter import com.sbl.sulmun2yong.survey.domain.Survey -import com.sbl.sulmun2yong.survey.domain.reward.DrawType +import com.sbl.sulmun2yong.survey.domain.reward.ImmediateDrawRewardInfo import com.sbl.sulmun2yong.survey.domain.reward.Reward +import com.sbl.sulmun2yong.survey.domain.reward.RewardInfo import com.sbl.sulmun2yong.survey.domain.section.SectionId import com.sbl.sulmun2yong.survey.domain.section.SectionIds import com.sbl.sulmun2yong.survey.dto.request.SurveySaveRequest @@ -45,7 +46,7 @@ class SurveyManagementService( thumbnail = this.thumbnail, finishedAt = this.finishedAt, finishMessage = this.finishMessage, - drawType = DrawType.of(rewards, this.targetParticipantCount), + rewardInfo = RewardInfo.of(rewards, this.targetParticipantCount), isVisible = this.isVisible, sections = this.sections.map { it.toDomain(sectionIds) }, ) @@ -72,12 +73,12 @@ class SurveyManagementService( // 현재 유저와 설문 제작자가 다를 경우 예외 발생 if (survey.makerId != makerId) throw InvalidSurveyAccessException() surveyAdapter.save(survey.start()) - if (survey.drawType is DrawType.Immediate) { + if (survey.rewardInfo is ImmediateDrawRewardInfo) { val drawingBoard = DrawingBoard.create( surveyId = survey.id, - boardSize = survey.drawType.targetParticipantCount, - rewards = survey.drawType.rewards, + boardSize = survey.rewardInfo.targetParticipantCount, + rewards = survey.rewardInfo.rewards, ) drawingBoardAdapter.save(drawingBoard) } diff --git a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt index 150f448f..8e36c6f7 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt @@ -2,8 +2,8 @@ package com.sbl.sulmun2yong.fixture.survey import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.SurveyStatus -import com.sbl.sulmun2yong.survey.domain.reward.DrawType import com.sbl.sulmun2yong.survey.domain.reward.Reward +import com.sbl.sulmun2yong.survey.domain.reward.RewardInfo import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId @@ -67,13 +67,13 @@ object SurveyFixtureFactory { status = status, finishMessage = finishMessage + id, makerId = makerId, - drawType = createDrawType(rewards, targetParticipantCount), + rewardInfo = createRewardInfo(rewards, targetParticipantCount), isVisible = isVisible, sections = sections, ) - fun createDrawType( + fun createRewardInfo( rewards: List = REWARDS, targetParticipantCount: Int? = TARGET_PARTICIPANT_COUNT, - ) = DrawType.of(rewards, targetParticipantCount) + ) = RewardInfo.of(rewards, targetParticipantCount) } diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt index 821e5201..81926c55 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt @@ -9,13 +9,14 @@ import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.SECTIONS import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.SURVEY_STATUS import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.THUMBNAIL import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.TITLE -import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.createDrawType +import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.createRewardInfo import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.createSurvey import com.sbl.sulmun2yong.global.util.DateUtil import com.sbl.sulmun2yong.survey.domain.response.SectionResponse import com.sbl.sulmun2yong.survey.domain.response.SurveyResponse -import com.sbl.sulmun2yong.survey.domain.reward.DrawType +import com.sbl.sulmun2yong.survey.domain.reward.ByUserRewardInfo import com.sbl.sulmun2yong.survey.domain.reward.Reward +import com.sbl.sulmun2yong.survey.domain.reward.RewardInfo import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId @@ -81,7 +82,7 @@ class SurveyTest { assertEquals(PUBLISHED_AT, this.publishedAt) assertEquals(SURVEY_STATUS, this.status) assertEquals(FINISH_MESSAGE + id, this.finishMessage) - assertEquals(createDrawType(), this.drawType) + assertEquals(createRewardInfo(), this.rewardInfo) assertEquals(true, this.isVisible) assertEquals(makerId, this.makerId) assertEquals(SECTIONS, this.sections) @@ -106,7 +107,7 @@ class SurveyTest { assertEquals(null, this.publishedAt) assertEquals(SurveyStatus.NOT_STARTED, this.status) assertEquals(Survey.DEFAULT_FINISH_MESSAGE, this.finishMessage) - assertEquals(DrawType.Free(listOf()), this.drawType) + assertEquals(ByUserRewardInfo(listOf()), this.rewardInfo) assertEquals(true, this.isVisible) assertEquals(makerId, this.makerId) assertEquals(listOf(this.sections.first()), this.sections) @@ -243,7 +244,7 @@ class SurveyTest { val newDescription = "new description" val newThumbnail = "new thumbnail" val newFinishMessage = "new finish message" - val newDrawType = DrawType.of(listOf(Reward("new reward", "new category", 1)), 10) + val newRewardInfo = RewardInfo.of(listOf(Reward("new reward", "new category", 1)), 10) val newIsVisible = false val sectionId = SectionId.Standard(UUID.randomUUID()) val newSections = @@ -267,7 +268,7 @@ class SurveyTest { thumbnail = newThumbnail, finishedAt = survey.finishedAt, finishMessage = newFinishMessage, - drawType = newDrawType, + rewardInfo = newRewardInfo, isVisible = newIsVisible, sections = listOf( @@ -289,7 +290,7 @@ class SurveyTest { assertEquals(newThumbnail, this.thumbnail) assertEquals(survey.finishedAt, this.finishedAt) assertEquals(newFinishMessage, this.finishMessage) - assertEquals(newDrawType, this.drawType) + assertEquals(newRewardInfo, this.rewardInfo) assertEquals(isVisible, this.isVisible) assertEquals(newSections, this.sections) } @@ -311,7 +312,7 @@ class SurveyTest { thumbnail = survey1.thumbnail, finishedAt = survey1.finishedAt, finishMessage = survey1.finishMessage, - drawType = survey1.drawType, + rewardInfo = survey1.rewardInfo, isVisible = survey1.isVisible, sections = survey1.sections, ) @@ -324,7 +325,7 @@ class SurveyTest { thumbnail = survey2.thumbnail, finishedAt = survey2.finishedAt, finishMessage = survey2.finishMessage, - drawType = survey2.drawType, + rewardInfo = survey2.rewardInfo, isVisible = survey2.isVisible, sections = survey2.sections, ) @@ -337,7 +338,7 @@ class SurveyTest { thumbnail = survey3.thumbnail, finishedAt = survey3.finishedAt, finishMessage = survey3.finishMessage, - drawType = survey3.drawType, + rewardInfo = survey3.rewardInfo, isVisible = survey3.isVisible, sections = survey3.sections, ) @@ -350,7 +351,7 @@ class SurveyTest { thumbnail = survey3.thumbnail, finishedAt = survey3.finishedAt, finishMessage = survey3.finishMessage, - drawType = DrawType.Free(listOf()), + rewardInfo = ByUserRewardInfo(listOf()), isVisible = survey3.isVisible, sections = survey3.sections, ) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawTypeTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawTypeTest.kt deleted file mode 100644 index 64562aaa..00000000 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/DrawTypeTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.sbl.sulmun2yong.survey.domain.reward - -import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory -import com.sbl.sulmun2yong.survey.exception.InvalidDrawTypeException -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertEquals - -class DrawTypeTest { - @Test - fun `추첨 방식을 생성하면 정보가 올바르게 설정된다`() { - // given - val rewards = SurveyFixtureFactory.REWARDS - val targetParticipantCount = SurveyFixtureFactory.TARGET_PARTICIPANT_COUNT - - // when - val immediateDrawType1 = DrawType.Immediate(rewards, targetParticipantCount) - val immediateDrawType2 = DrawType.of(rewards, targetParticipantCount) - val freeDrawType1 = DrawType.Free(listOf()) - val freeDrawType2 = DrawType.of(listOf(), null) - - // then - assertEquals(rewards, immediateDrawType1.rewards) - assertEquals(targetParticipantCount, immediateDrawType1.targetParticipantCount) - assertEquals(rewards, immediateDrawType2.rewards) - assertEquals(targetParticipantCount, immediateDrawType2.targetParticipantCount) - assertEquals(emptyList(), freeDrawType1.rewards) - assertEquals(null, freeDrawType1.targetParticipantCount) - assertEquals(emptyList(), freeDrawType2.rewards) - assertEquals(null, freeDrawType2.targetParticipantCount) - } - - @Test - fun `즉시 추첨은 리워드가 하나 이상 존재해야한다`() { - assertThrows { - DrawType.Immediate(listOf(), 0) - } - } - - @Test - fun `즉시 추첨은 리워드 개수의 총합이 목표 참여자 수보다 적어야한다`() { - assertThrows { - DrawType.Immediate(SurveyFixtureFactory.REWARDS, 1) - } - } -} diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardInfoTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardInfoTest.kt new file mode 100644 index 00000000..a4d8662d --- /dev/null +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardInfoTest.kt @@ -0,0 +1,50 @@ +package com.sbl.sulmun2yong.survey.domain.reward + +import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory +import com.sbl.sulmun2yong.survey.exception.InvalidRewardInfoException +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertEquals + +class RewardInfoTest { + @Test + fun `리워드 정보를 생성하면 정보가 올바르게 설정된다`() { + // given + val rewards = SurveyFixtureFactory.REWARDS + val targetParticipantCount = SurveyFixtureFactory.TARGET_PARTICIPANT_COUNT + + // when + val immediateRewardInfo1 = ImmediateDrawRewardInfo(rewards, targetParticipantCount) + val immediateRewardInfo2 = RewardInfo.of(rewards, targetParticipantCount) + val freeRewardInfo1 = ByUserRewardInfo(listOf()) + val freeRewardInfo2 = RewardInfo.of(listOf(), null) + + // then + assertEquals(rewards, immediateRewardInfo1.rewards) + assertEquals(targetParticipantCount, immediateRewardInfo1.targetParticipantCount) + assertEquals(true, immediateRewardInfo1.isImmediateDraw) + assertEquals(rewards, immediateRewardInfo2.rewards) + assertEquals(targetParticipantCount, immediateRewardInfo2.targetParticipantCount) + assertEquals(true, immediateRewardInfo2.isImmediateDraw) + assertEquals(emptyList(), freeRewardInfo1.rewards) + assertEquals(null, freeRewardInfo1.targetParticipantCount) + assertEquals(false, freeRewardInfo1.isImmediateDraw) + assertEquals(emptyList(), freeRewardInfo2.rewards) + assertEquals(null, freeRewardInfo2.targetParticipantCount) + assertEquals(false, freeRewardInfo2.isImmediateDraw) + } + + @Test + fun `즉시 추첨은 리워드가 하나 이상 존재해야한다`() { + assertThrows { + ImmediateDrawRewardInfo(listOf(), 0) + } + } + + @Test + fun `즉시 추첨은 리워드 개수의 총합이 목표 참여자 수보다 적어야한다`() { + assertThrows { + ImmediateDrawRewardInfo(SurveyFixtureFactory.REWARDS, 1) + } + } +} From 2089ee8fe658497011c87050fcd06b6703c72d30 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 17:43:41 +0900 Subject: [PATCH 031/201] =?UTF-8?q?[SBL-118]=20Adapter=EC=9D=98=20?= =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20save=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EC=97=90=EC=84=9C=20update=EB=A5=BC=20=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20createdAt=EC=9D=84=20=EC=9C=A0=EC=A7=80?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/drawing/adapter/DrawingBoardAdapter.kt | 6 +++++- .../com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt | 6 +++++- .../kotlin/com/sbl/sulmun2yong/user/adapter/UserAdapter.kt | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingBoardAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingBoardAdapter.kt index d299a815..a96e1fdc 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingBoardAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingBoardAdapter.kt @@ -18,6 +18,10 @@ class DrawingBoardAdapter( .toDomain() fun save(drawingBoard: DrawingBoard) { - drawingBoardRepository.save(DrawingBoardDocument.of(drawingBoard)) + val previousDrawingBoardDocument = drawingBoardRepository.findById(drawingBoard.id) + val drawingBoardDocument = DrawingBoardDocument.of(drawingBoard) + // 기존 추첨 보드를 업데이트하는 경우, createdAt을 유지 + if (previousDrawingBoardDocument.isPresent) drawingBoardDocument.createdAt = previousDrawingBoardDocument.get().createdAt + drawingBoardRepository.save(drawingBoardDocument) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt index ece343ff..6ea9ca84 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt @@ -45,6 +45,10 @@ class SurveyAdapter( } fun save(survey: Survey) { - surveyRepository.save(SurveyDocument.from(survey)) + val previousSurveyDocument = surveyRepository.findById(survey.id) + val surveyDocument = SurveyDocument.from(survey) + // 기존 설문을 업데이트하는 경우, createdAt을 유지 + if (previousSurveyDocument.isPresent) surveyDocument.createdAt = previousSurveyDocument.get().createdAt + surveyRepository.save(surveyDocument) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/adapter/UserAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/adapter/UserAdapter.kt index 0a796bef..1d63d0ec 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/adapter/UserAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/adapter/UserAdapter.kt @@ -13,7 +13,11 @@ class UserAdapter( private val userRepository: UserRepository, ) { fun save(user: User) { - userRepository.save(UserDocument.of(user)) + val previousUserDocument = userRepository.findById(user.id) + val userDocument = UserDocument.of(user) + // 기존 유저를 업데이트하는 경우, createdAt을 유지 + if (previousUserDocument.isPresent) userDocument.createdAt = previousUserDocument.get().createdAt + userRepository.save(userDocument) } fun getById(id: UUID): User = From 59e93fc2c8ade3b9c0fa1a263928e5b7a7abf7c8 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 17:44:28 +0900 Subject: [PATCH 032/201] =?UTF-8?q?[SBL-118]=20Adapter=EC=9D=98=20update?= =?UTF-8?q?=EA=B0=80=20=ED=95=84=EC=9A=94=EC=97=86=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20save=20=ED=95=A8=EC=88=98=EB=93=A4=EC=9D=84=20inser?= =?UTF-8?q?t=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/drawing/adapter/DrawingHistoryAdapter.kt | 4 ++-- .../sbl/sulmun2yong/drawing/service/DrawingBoardService.kt | 2 +- .../com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt | 4 ++-- .../com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt | 4 ++-- .../sbl/sulmun2yong/survey/service/SurveyResponseService.kt | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingHistoryAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingHistoryAdapter.kt index b0ac08a8..c61219d1 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingHistoryAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingHistoryAdapter.kt @@ -13,8 +13,8 @@ import java.util.UUID class DrawingHistoryAdapter( private val drawingHistoryRepository: DrawingHistoryRepository, ) { - fun save(drawingHistory: DrawingHistory) { - drawingHistoryRepository.save(DrawingHistoryDocument.of(drawingHistory)) + fun insert(drawingHistory: DrawingHistory) { + drawingHistoryRepository.insert(DrawingHistoryDocument.of(drawingHistory)) } fun findBySurveyIdAndParticipantIdOrPhoneNumber( diff --git a/src/main/kotlin/com/sbl/sulmun2yong/drawing/service/DrawingBoardService.kt b/src/main/kotlin/com/sbl/sulmun2yong/drawing/service/DrawingBoardService.kt index 35ef0f97..0d544bd4 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/drawing/service/DrawingBoardService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/drawing/service/DrawingBoardService.kt @@ -72,7 +72,7 @@ class DrawingBoardService( val changedDrawingBoard = drawingResult.changedDrawingBoard drawingBoardAdapter.save(drawingResult.changedDrawingBoard) // 추첨 기록 저장 - drawingHistoryAdapter.save( + drawingHistoryAdapter.insert( DrawingHistory.create( participantId = participantId, phoneNumber = phoneNumberData, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt index 27a4cfb9..94cde6b1 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt @@ -11,8 +11,8 @@ import java.util.UUID class ParticipantAdapter( val participantRepository: ParticipantRepository, ) { - fun saveParticipant(participant: Participant) { - participantRepository.save(ParticipantDocument.of(participant)) + fun insert(participant: Participant) { + participantRepository.insert(ParticipantDocument.of(participant)) } fun getParticipant(id: UUID): Participant = diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt index ad23b1a5..2272b4a7 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt @@ -10,11 +10,11 @@ import java.util.UUID class ResponseAdapter( private val responseRepository: ResponseRepository, ) { - fun saveSurveyResponse( + fun insertSurveyResponse( surveyResponse: SurveyResponse, participantId: UUID, ) { - responseRepository.saveAll(surveyResponse.toDocuments(participantId)) + responseRepository.insert(surveyResponse.toDocuments(participantId)) } private fun SurveyResponse.toDocuments(participantId: UUID): List = diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt index 4f0355b8..e874a633 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt @@ -41,8 +41,8 @@ class SurveyResponseService( survey.validateResponse(surveyResponse) // TODO: 참가자 객체의 UserId에 실제 유저 값 넣기 val participant = Participant.create(visitorId, surveyId, null) - participantAdapter.saveParticipant(participant) - responseAdapter.saveSurveyResponse(surveyResponse, participant.id) + participantAdapter.insert(participant) + responseAdapter.insertSurveyResponse(surveyResponse, participant.id) return SurveyParticipantResponse(participant.id, survey.isImmediateDraw()) } From 18388d999de67379f58c9f1e23e3962459cf850b Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 4 Sep 2024 22:10:01 +0900 Subject: [PATCH 033/201] =?UTF-8?q?[SBL-118]=20=EC=B6=94=EC=B2=A8=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=20document=EA=B0=80=20BaseTimeDocument?= =?UTF-8?q?=EB=A5=BC=20=EC=83=81=EC=86=8D=EB=B0=9B=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/drawing/entity/DrawingHistoryDocument.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/drawing/entity/DrawingHistoryDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/drawing/entity/DrawingHistoryDocument.kt index da2cf790..6d50528c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/drawing/entity/DrawingHistoryDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/drawing/entity/DrawingHistoryDocument.kt @@ -3,6 +3,7 @@ package com.sbl.sulmun2yong.drawing.entity import com.sbl.sulmun2yong.drawing.domain.DrawingHistory import com.sbl.sulmun2yong.drawing.domain.ticket.Ticket import com.sbl.sulmun2yong.global.data.PhoneNumber +import com.sbl.sulmun2yong.global.entity.BaseTimeDocument import org.springframework.data.annotation.Id import org.springframework.data.mongodb.core.mapping.Document import java.util.UUID @@ -16,7 +17,7 @@ data class DrawingHistoryDocument( val surveyId: UUID, val selectedTicketIndex: Int, val ticket: Ticket, -) { +) : BaseTimeDocument() { companion object { fun of(drawingHistory: DrawingHistory) = DrawingHistoryDocument( From 9abcf962ddbf0182820a6e35199d676e5cb3fe87 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 5 Sep 2024 18:12:01 +0900 Subject: [PATCH 034/201] =?UTF-8?q?[SBL-111]=20=EC=84=A4=EB=AC=B8=EC=9D=98?= =?UTF-8?q?=20=EA=B2=B0=EA=B3=BC=EB=A5=BC=20=EB=8B=A4=EB=A3=A8=EB=8A=94=20?= =?UTF-8?q?SurveyResult=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/domain/result/SurveyResult.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt new file mode 100644 index 00000000..04bd95bd --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt @@ -0,0 +1,15 @@ +package com.sbl.sulmun2yong.survey.domain.result + +import java.util.Date +import java.util.UUID + +data class SurveyResult( + val responses: List, +) { + data class Response( + val questionId: UUID, + val participantId: UUID, + val content: String, + val createdAt: Date, + ) +} From 5cbb7d67960f4770efb19208a635b174a6ca8f37 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 5 Sep 2024 18:12:49 +0900 Subject: [PATCH 035/201] =?UTF-8?q?[SBL-111]=20SurveyResult=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/domain/SurveyResultTest.kt | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyResultTest.kt diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyResultTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyResultTest.kt new file mode 100644 index 00000000..ec620b7f --- /dev/null +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyResultTest.kt @@ -0,0 +1,67 @@ +package com.sbl.sulmun2yong.survey.domain + +import com.sbl.sulmun2yong.survey.domain.result.SurveyResult +import org.junit.jupiter.api.Test +import java.util.Date +import java.util.UUID +import kotlin.test.assertEquals + +class SurveyResultTest { + @Test + fun `설문 결과를 생성하면 정보가 올바르게 설정된다`() { + // given + val questionId1 = UUID.randomUUID() + val questionId2 = UUID.randomUUID() + val participantId1 = UUID.randomUUID() + val participantId2 = UUID.randomUUID() + val content1 = "content1" + val content2 = "content2" + val content3 = "content3" + val date = Date() + + val response1 = + SurveyResult.Response( + questionId = questionId1, + participantId = participantId1, + content = content1, + createdAt = date, + ) + val response2 = + SurveyResult.Response( + questionId = questionId2, + participantId = participantId1, + content = content2, + createdAt = date, + ) + val response3 = + SurveyResult.Response( + questionId = questionId1, + participantId = participantId2, + content = content3, + createdAt = date, + ) + + // when + val surveyResult = SurveyResult(listOf(response1, response2, response3)) + + // then + with(surveyResult.responses[0]) { + assertEquals(questionId1, questionId) + assertEquals(participantId1, participantId) + assertEquals(content1, content) + assertEquals(date, createdAt) + } + with(surveyResult.responses[1]) { + assertEquals(questionId2, questionId) + assertEquals(participantId1, participantId) + assertEquals(content2, content) + assertEquals(date, createdAt) + } + with(surveyResult.responses[2]) { + assertEquals(questionId1, questionId) + assertEquals(participantId2, participantId) + assertEquals(content3, content) + assertEquals(date, createdAt) + } + } +} From 985cfae30141d29c3ac0e9896a9616412dc2ac10 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 5 Sep 2024 18:14:04 +0900 Subject: [PATCH 036/201] =?UTF-8?q?[SBL-111]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20Document=EC=97=90=20=EC=84=A4=EB=AC=B8=20I?= =?UTF-8?q?D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt | 1 + .../kotlin/com/sbl/sulmun2yong/survey/entity/ResponseDocument.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt index 2272b4a7..72b5230d 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt @@ -24,6 +24,7 @@ class ResponseAdapter( ResponseDocument( id = UUID.randomUUID(), participantId = participantId, + surveyId = this.surveyId, questionId = questionResponse.questionId, content = it.content, ) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ResponseDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ResponseDocument.kt index a82bab13..069dc0e7 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ResponseDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ResponseDocument.kt @@ -10,6 +10,7 @@ data class ResponseDocument( @Id val id: UUID, val participantId: UUID, + val surveyId: UUID, val questionId: UUID, val content: String, ) : BaseTimeDocument() From b800af6e4175f8e0a3811c6aa48ce056ef24a82e Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 5 Sep 2024 18:15:37 +0900 Subject: [PATCH 037/201] =?UTF-8?q?[SBL-111]=20=EC=84=A4=EB=AC=B8=EC=9D=98?= =?UTF-8?q?=20ID=EB=A1=9C=20=EC=84=A4=EB=AC=B8=20=EA=B2=B0=EA=B3=BC?= =?UTF-8?q?=EB=A5=BC=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20Adapter=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/survey/adapter/ResponseAdapter.kt | 14 ++++++++++++++ .../survey/repository/ResponseRepository.kt | 4 +++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt index 72b5230d..fdfd5bbc 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt @@ -1,6 +1,7 @@ package com.sbl.sulmun2yong.survey.adapter import com.sbl.sulmun2yong.survey.domain.response.SurveyResponse +import com.sbl.sulmun2yong.survey.domain.result.SurveyResult import com.sbl.sulmun2yong.survey.entity.ResponseDocument import com.sbl.sulmun2yong.survey.repository.ResponseRepository import org.springframework.stereotype.Component @@ -31,4 +32,17 @@ class ResponseAdapter( } } } + + fun getResponses(surveyId: UUID): SurveyResult { + val responses = responseRepository.findBySurveyId(surveyId) + return SurveyResult(responses = responses.map { it.toDomain() }) + } + + private fun ResponseDocument.toDomain() = + SurveyResult.Response( + questionId = this.questionId, + participantId = this.participantId, + content = this.content, + createdAt = this.createdAt, + ) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ResponseRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ResponseRepository.kt index 0b8a9360..81fef8bd 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ResponseRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ResponseRepository.kt @@ -6,4 +6,6 @@ import org.springframework.stereotype.Repository import java.util.UUID @Repository -interface ResponseRepository : MongoRepository +interface ResponseRepository : MongoRepository { + fun findBySurveyId(surveyId: UUID): List +} From 8fcbcaa838be91a7d2a69d3e3e4e5c6c0f3c71f1 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 5 Sep 2024 18:17:02 +0900 Subject: [PATCH 038/201] =?UTF-8?q?[SBL-111]=20=EC=84=A4=EB=AC=B8=20ID?= =?UTF-8?q?=EC=99=80=20=EC=9C=A0=EC=A0=80=20ID=EB=A1=9C=20=EC=84=A4?= =?UTF-8?q?=EB=AC=B8=EC=9D=B4=20=EC=A1=B4=EC=9E=AC=ED=95=98=EB=8A=94?= =?UTF-8?q?=EC=A7=80=20=ED=99=95=EC=9D=B8=ED=95=98=EB=8A=94=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt | 5 +++++ .../sbl/sulmun2yong/survey/repository/SurveyRepository.kt | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt index 6ea9ca84..bb213494 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt @@ -51,4 +51,9 @@ class SurveyAdapter( if (previousSurveyDocument.isPresent) surveyDocument.createdAt = previousSurveyDocument.get().createdAt surveyRepository.save(surveyDocument) } + + fun existsByIdAndMakerId( + surveyId: UUID, + userId: UUID, + ) = surveyRepository.existsByIdAndMakerId(surveyId, userId) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt index b5864e41..a9cb9d65 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt @@ -14,4 +14,9 @@ interface SurveyRepository : MongoRepository { status: SurveyStatus, pageable: Pageable, ): Page + + fun existsByIdAndMakerId( + surveyId: UUID, + userId: UUID, + ): Boolean } From db0b703f8b9c339e4bc1f460ec48de41ce584dba Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 5 Sep 2024 18:17:49 +0900 Subject: [PATCH 039/201] =?UTF-8?q?[SBL-111]=20=EC=84=A4=EB=AC=B8=EC=9D=98?= =?UTF-8?q?=20=EA=B2=B0=EA=B3=BC=EB=A5=BC=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=9C=84=ED=95=9C=20Response=20DTO=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/SurveyResultResponse.kt | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt new file mode 100644 index 00000000..93e438ee --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt @@ -0,0 +1,36 @@ +package com.sbl.sulmun2yong.survey.dto.response + +import com.sbl.sulmun2yong.survey.domain.result.SurveyResult +import java.util.UUID + +data class SurveyResultResponse( + val results: List, +) { + companion object { + fun of(surveyResult: SurveyResult) = + SurveyResultResponse( + results = + surveyResult.responses.groupBy { it.questionId }.map { + Result.from(it.value) + }, + ) + } + + data class Result( + val questionId: UUID, + val responses: List, + ) { + companion object { + fun from(responses: List): Result = + Result( + questionId = responses.first().questionId, + responses = responses.groupBy { it.content }.map { Response(it.key, it.value.size) }, + ) + } + + data class Response( + val content: String, + val count: Int, + ) + } +} From 3469aa7b9a1fb10f742da1a796427d0a93cd2e25 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 5 Sep 2024 18:18:39 +0900 Subject: [PATCH 040/201] =?UTF-8?q?[SBL-111]=20=EC=84=A4=EB=AC=B8=EC=9D=98?= =?UTF-8?q?=20=EA=B2=B0=EA=B3=BC=EB=A5=BC=20=EB=8B=A4=EB=A3=A8=EB=8A=94=20?= =?UTF-8?q?SurveyResultService=EC=99=80=20=EA=B2=B0=EA=B3=BC=EB=A5=BC=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/service/SurveyResultService.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt new file mode 100644 index 00000000..7f9c7ddb --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt @@ -0,0 +1,25 @@ +package com.sbl.sulmun2yong.survey.service + +import com.sbl.sulmun2yong.survey.adapter.ResponseAdapter +import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter +import com.sbl.sulmun2yong.survey.dto.response.SurveyResultResponse +import com.sbl.sulmun2yong.survey.exception.SurveyNotFoundException +import org.springframework.stereotype.Service +import java.util.UUID + +@Service +class SurveyResultService( + private val responseAdapter: ResponseAdapter, + private val surveyAdapter: SurveyAdapter, +) { + fun getSurveyResult( + surveyId: UUID, + userId: UUID, + ): SurveyResultResponse { + val isSurveyExists = surveyAdapter.existsByIdAndMakerId(surveyId, userId) + // 본인이 만든 설문이 아닌 경우 예외 발생 + if (!isSurveyExists) throw SurveyNotFoundException() + val response = responseAdapter.getResponses(surveyId) + return SurveyResultResponse.of(response) + } +} From a8b49ac73b4ed9d79935787cd198a1eba8537d1c Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 5 Sep 2024 18:19:21 +0900 Subject: [PATCH 041/201] =?UTF-8?q?[SBL-111]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84,=20=EC=84=A4=EB=AC=B8=20=EC=A0=9C=EC=9E=91=EC=9E=90?= =?UTF-8?q?=EB=A7=8C=20=EC=A0=91=EA=B7=BC=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/SecurityConfig.kt | 1 + .../controller/SurveyResultController.kt | 23 +++++++++++++++++++ .../controller/doc/SurveyResultApiDoc.kt | 20 ++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResultController.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResultApiDoc.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt index 98fb1362..d24023a9 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt @@ -122,6 +122,7 @@ class SecurityConfig( authorizeHttpRequests { authorize("/api/v1/admin/**", hasRole("ADMIN")) authorize("/api/v1/user/**", authenticated) + authorize("/api/v1/surveys/results/**", authenticated) // TODO: 추후에 AUTHENTICATED_USER 로 수정 authorize("/api/v1/surveys/workbench/**", hasRole("ADMIN")) authorize("/**", permitAll) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResultController.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResultController.kt new file mode 100644 index 00000000..e534d644 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResultController.kt @@ -0,0 +1,23 @@ +package com.sbl.sulmun2yong.survey.controller + +import com.sbl.sulmun2yong.global.annotation.LoginUser +import com.sbl.sulmun2yong.survey.controller.doc.SurveyResultApiDoc +import com.sbl.sulmun2yong.survey.service.SurveyResultService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import java.util.UUID + +@RestController +@RequestMapping("/api/v1/surveys/result") +class SurveyResultController( + private val surveyResultService: SurveyResultService, +) : SurveyResultApiDoc { + @GetMapping("/{survey-id}") + override fun getSurveyResult( + @PathVariable("survey-id") surveyId: UUID, + @LoginUser id: UUID, + ) = ResponseEntity.ok(surveyResultService.getSurveyResult(surveyId, id)) +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResultApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResultApiDoc.kt new file mode 100644 index 00000000..e36db9f3 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResultApiDoc.kt @@ -0,0 +1,20 @@ +package com.sbl.sulmun2yong.survey.controller.doc + +import com.sbl.sulmun2yong.global.annotation.LoginUser +import com.sbl.sulmun2yong.survey.dto.response.SurveyResultResponse +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import java.util.UUID + +@Tag(name = "SurveyResult", description = "설문 결과 관련 API") +interface SurveyResultApiDoc { + @Operation(summary = "설문 결과 조회") + @GetMapping("/{survey-id}") + fun getSurveyResult( + @PathVariable("survey-id") surveyId: UUID, + @LoginUser id: UUID, + ): ResponseEntity +} From e45bbd1a5f72c0f54604dfd18ddf18f5c6949899 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Fri, 6 Sep 2024 16:43:02 +0900 Subject: [PATCH 042/201] =?UTF-8?q?[SBL-120]=20visitorId=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 3 -- .../drawing/service/DrawingBoardService.kt | 29 +---------- .../global/fingerprint/FingerprintApi.kt | 49 ------------------- .../fingerprint/UncleanVisitorException.kt | 6 --- .../survey/adapter/ParticipantAdapter.kt | 9 ++-- .../sulmun2yong/survey/domain/Participant.kt | 4 +- .../survey/entity/ParticipantDocument.kt | 3 -- .../repository/ParticipantRepository.kt | 5 +- .../survey/service/SurveyResponseService.kt | 17 ++----- .../user/controller/doc/LoginApiDoc.kt | 4 +- 10 files changed, 13 insertions(+), 116 deletions(-) delete mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt delete mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/UncleanVisitorException.kt diff --git a/build.gradle.kts b/build.gradle.kts index 22f1cdb1..d6c1a88b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,9 +34,6 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.springframework.boot:spring-boot-starter-actuator") - // fingerprint - implementation("com.github.fingerprintjs:fingerprint-pro-server-api-java-sdk:v6.0.0") - // security implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-oauth2-client") diff --git a/src/main/kotlin/com/sbl/sulmun2yong/drawing/service/DrawingBoardService.kt b/src/main/kotlin/com/sbl/sulmun2yong/drawing/service/DrawingBoardService.kt index 0d544bd4..87f9067b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/drawing/service/DrawingBoardService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/drawing/service/DrawingBoardService.kt @@ -2,7 +2,6 @@ package com.sbl.sulmun2yong.drawing.service import com.sbl.sulmun2yong.drawing.adapter.DrawingBoardAdapter import com.sbl.sulmun2yong.drawing.adapter.DrawingHistoryAdapter -import com.sbl.sulmun2yong.drawing.domain.DrawingBoard import com.sbl.sulmun2yong.drawing.domain.DrawingHistory import com.sbl.sulmun2yong.drawing.domain.drawingResult.DrawingResult import com.sbl.sulmun2yong.drawing.dto.response.DrawingBoardResponse @@ -14,7 +13,6 @@ import com.sbl.sulmun2yong.global.data.PhoneNumber import com.sbl.sulmun2yong.survey.adapter.ParticipantAdapter import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter import com.sbl.sulmun2yong.survey.domain.SurveyStatus -import com.sbl.sulmun2yong.survey.domain.reward.Reward import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.util.UUID @@ -46,7 +44,7 @@ class DrawingBoardService( ): DrawingResultResponse { // 유효성 검증 // 참가했는가 - val participant = participantAdapter.getParticipant(participantId) + val participant = participantAdapter.getByParticipantId(participantId) val surveyId = participant.surveyId // 추첨 기록이 있는가 @@ -94,29 +92,4 @@ class DrawingBoardService( } return drawingResultResponse } - - fun makeDrawingBoard( - surveyId: UUID, - boardSize: Int, - surveyRewards: List, - ) { - // TODO: 적절한 다른 패키지간 도메인 변환 로직 도입 - val rewards = - surveyRewards - .map { - Reward( - name = it.name, - category = it.category, - count = it.count, - ) - } - - val drawingBoard = - DrawingBoard.create( - surveyId = surveyId, - boardSize = boardSize, - rewards = rewards, - ) - drawingBoardAdapter.save(drawingBoard) - } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt deleted file mode 100644 index 900b072e..00000000 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/FingerprintApi.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.sbl.sulmun2yong.global.fingerprint - -import com.fingerprint.api.FingerprintApi -import com.fingerprint.model.BotdDetectionResult -import com.fingerprint.model.EventResponse -import com.fingerprint.model.ProductsResponse -import com.fingerprint.model.Response -import com.fingerprint.model.ResponseVisits -import com.fingerprint.sdk.ApiClient -import com.fingerprint.sdk.Configuration -import com.fingerprint.sdk.Region -import org.springframework.beans.factory.annotation.Value -import org.springframework.stereotype.Component - -@Component -class FingerprintApi( - @Value("\${fingerprint.secret-key}") - private val secretKey: String, -) { - private val client: ApiClient = - Configuration.getDefaultApiClient( - secretKey, - Region.ASIA, - ) - val api = FingerprintApi(client) - - fun validateVisitorId(visitorId: String) { - val visits = getVisits(visitorId) - if (visits.isNullOrEmpty()) { - throw Exception("Invalid visitorId") - } - } - - private fun getVisits(visitorId: String): MutableList? { - val response: Response = api.getVisits(visitorId, null, null, 1, null, null) - val product = getEvent(response.visits[0].requestId) - if (product.tampering.data.result == true || - product.botd.data.bot.result !== BotdDetectionResult.ResultEnum.NOT_DETECTED - ) { - throw UncleanVisitorException() - } - return response.visits - } - - fun getEvent(requestId: String): ProductsResponse { - val response: EventResponse = api.getEvent(requestId) - return response.products - } -} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/UncleanVisitorException.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/UncleanVisitorException.kt deleted file mode 100644 index f0d59e10..00000000 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/fingerprint/UncleanVisitorException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.sbl.sulmun2yong.global.fingerprint - -import com.sbl.sulmun2yong.global.error.BusinessException -import com.sbl.sulmun2yong.global.error.ErrorCode - -class UncleanVisitorException : BusinessException(ErrorCode.UNCLEAN_VISITOR) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt index 94cde6b1..6621bc75 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt @@ -15,18 +15,15 @@ class ParticipantAdapter( participantRepository.insert(ParticipantDocument.of(participant)) } - fun getParticipant(id: UUID): Participant = + fun getByParticipantId(id: UUID): Participant = participantRepository .findById(id) .orElseThrow { InvalidParticipantException() } .toDomain() - fun findBySurveyIdAndVisitorId( - surveyId: UUID, - visitorId: String, - ): Participant? = + fun findBySurveyId(surveyId: UUID): Participant? = participantRepository - .findBySurveyIdAndVisitorId(surveyId, visitorId) + .findBySurveyId(surveyId) .map { it.toDomain() } .orElse(null) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Participant.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Participant.kt index e9b982a5..6189dba3 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Participant.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Participant.kt @@ -4,15 +4,13 @@ import java.util.UUID data class Participant( val id: UUID, - val visitorId: String, val surveyId: UUID, val userId: UUID?, ) { companion object { fun create( - visitorId: String, surveyId: UUID, userId: UUID?, - ) = Participant(UUID.randomUUID(), visitorId, surveyId, userId) + ) = Participant(UUID.randomUUID(), surveyId, userId) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ParticipantDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ParticipantDocument.kt index 40f81a53..f2cd7d90 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ParticipantDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ParticipantDocument.kt @@ -10,7 +10,6 @@ import java.util.UUID data class ParticipantDocument( @Id val id: UUID, - val visitorId: String, val surveyId: UUID, val userId: UUID?, ) : BaseTimeDocument() { @@ -20,14 +19,12 @@ data class ParticipantDocument( id = participant.id, surveyId = participant.surveyId, userId = participant.userId, - visitorId = participant.visitorId, ) } fun toDomain() = Participant( id = this.id, - visitorId = this.visitorId, surveyId = this.surveyId, userId = this.userId, ) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ParticipantRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ParticipantRepository.kt index b99dfd37..79e02c5d 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ParticipantRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ParticipantRepository.kt @@ -8,8 +8,5 @@ import java.util.UUID @Repository interface ParticipantRepository : MongoRepository { - fun findBySurveyIdAndVisitorId( - surveyId: UUID, - visitorId: String, - ): Optional + fun findBySurveyId(surveyId: UUID): Optional } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt index e874a633..58887a01 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt @@ -1,6 +1,5 @@ package com.sbl.sulmun2yong.survey.service -import com.sbl.sulmun2yong.global.fingerprint.FingerprintApi import com.sbl.sulmun2yong.survey.adapter.ParticipantAdapter import com.sbl.sulmun2yong.survey.adapter.ResponseAdapter import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter @@ -18,7 +17,6 @@ class SurveyResponseService( val surveyAdapter: SurveyAdapter, val participantAdapter: ParticipantAdapter, val responseAdapter: ResponseAdapter, - val fingerprintApi: FingerprintApi, ) { // TODO: 트랜잭션 처리 추가하기 fun responseToSurvey( @@ -26,12 +24,10 @@ class SurveyResponseService( surveyResponseRequest: SurveyResponseRequest, isAdmin: Boolean, ): SurveyParticipantResponse { - val visitorId = surveyResponseRequest.visitorId - // 이미 참여한 설문인지 검증(Admin인 경우 스킵) if (!isAdmin) { - validateIsAlreadyParticipated(surveyId, visitorId) - fingerprintApi.validateVisitorId(visitorId) + validateIsAlreadyParticipated(surveyId) } + // 이미 참여한 설문인지 검증(Admin인 경우 스킵) val survey = surveyAdapter.getSurvey(surveyId) if (survey.status != SurveyStatus.IN_PROGRESS) { @@ -40,17 +36,14 @@ class SurveyResponseService( val surveyResponse = surveyResponseRequest.toDomain(surveyId) survey.validateResponse(surveyResponse) // TODO: 참가자 객체의 UserId에 실제 유저 값 넣기 - val participant = Participant.create(visitorId, surveyId, null) + val participant = Participant.create(surveyId, null) participantAdapter.insert(participant) responseAdapter.insertSurveyResponse(surveyResponse, participant.id) return SurveyParticipantResponse(participant.id, survey.isImmediateDraw()) } - private fun validateIsAlreadyParticipated( - surveyId: UUID, - visitorId: String, - ) { - val participant = participantAdapter.findBySurveyIdAndVisitorId(surveyId, visitorId) + private fun validateIsAlreadyParticipated(surveyId: UUID) { + val participant = participantAdapter.findBySurveyId(surveyId) participant?.let { throw AlreadyParticipatedException() } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt index 046ab404..47e0082b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt @@ -3,11 +3,11 @@ package com.sbl.sulmun2yong.user.controller.doc import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import jakarta.servlet.http.HttpServletRequest -import jakarta.ws.rs.QueryParam import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.ResponseBody @Tag(name = "Login", description = "로그인 API") @@ -18,7 +18,7 @@ interface LoginApiDoc { @ResponseBody fun login( @PathVariable provider: String, - @QueryParam("redirect_path") redirectPathAfterLogin: String?, + @RequestParam("redirect_path") redirectPathAfterLogin: String?, request: HttpServletRequest, ): ResponseEntity } From 84a201b5115dc2d4a0aa8af30216125b498af2f8 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Fri, 6 Sep 2024 16:55:42 +0900 Subject: [PATCH 043/201] =?UTF-8?q?[SBL-120]=20dto=EC=97=90=EC=84=9C?= =?UTF-8?q?=EB=8F=84=20visitorId=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/dto/request/SurveyResponseRequest.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveyResponseRequest.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveyResponseRequest.kt index 97a3581f..a9fa4184 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveyResponseRequest.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveyResponseRequest.kt @@ -12,8 +12,6 @@ import java.util.UUID data class SurveyResponseRequest( @field:Valid val sectionResponses: List, - @field:Size(max = 100, message = "visitorId는 최대 100자까지 입력 가능합니다.") - val visitorId: String, ) { data class SectionResponseRequest( val sectionId: UUID, From 9b5d9acce2b7cbb07272bc610fb5e17c3b79c229 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Fri, 6 Sep 2024 16:58:03 +0900 Subject: [PATCH 044/201] =?UTF-8?q?[SBL-120]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=EC=97=90=EC=84=9C=EB=8F=84=20visitorId=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt index 6fc6ee1d..ad584911 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt @@ -18,13 +18,12 @@ class ParticipantTest { mockedUUID.`when` { UUID.randomUUID() }.thenReturn(participantId) // when - val participant = Participant.create(visitorId, surveyId, userId) + val participant = Participant.create(surveyId, userId) // then with(participant) { assertEquals(participantId, this.id) assertEquals(surveyId, this.surveyId) - assertEquals(visitorId, this.visitorId) assertEquals(userId, this.userId) } } From aeda71ba12b62b45e78253833987227dbdb6c9ec Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 7 Sep 2024 00:28:07 +0900 Subject: [PATCH 045/201] =?UTF-8?q?[SBL-120]=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD,=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/service/SurveyResponseService.kt | 4 +--- .../com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt index 58887a01..ab4d489e 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt @@ -24,10 +24,8 @@ class SurveyResponseService( surveyResponseRequest: SurveyResponseRequest, isAdmin: Boolean, ): SurveyParticipantResponse { - if (!isAdmin) { - validateIsAlreadyParticipated(surveyId) - } // 이미 참여한 설문인지 검증(Admin인 경우 스킵) + if (!isAdmin) validateIsAlreadyParticipated(surveyId) val survey = surveyAdapter.getSurvey(surveyId) if (survey.status != SurveyStatus.IN_PROGRESS) { diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt index ad584911..e6d66700 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt @@ -11,7 +11,6 @@ class ParticipantTest { // given val participantId = UUID.randomUUID() val surveyId = UUID.randomUUID() - val visitorId = "abcdefg" val userId = UUID.randomUUID() Mockito.mockStatic(UUID::class.java).use { mockedUUID -> From cf5258613f242199b73d8b5d31b14a2a8b095ebd Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 7 Sep 2024 01:07:28 +0900 Subject: [PATCH 046/201] =?UTF-8?q?HOTFIX:=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/global/error/ErrorCode.kt | 4 ---- .../survey/controller/SurveyResponseController.kt | 8 +------- .../survey/controller/doc/SurveyResponseApiDoc.kt | 2 -- .../survey/exception/AlreadyParticipatedException.kt | 6 ------ .../survey/service/SurveyResponseService.kt | 12 ------------ 5 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/exception/AlreadyParticipatedException.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index 0ad64809..cd5992e2 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -27,13 +27,9 @@ enum class ErrorCode( SURVEY_CLOSED(HttpStatus.BAD_REQUEST, "SV0014", "응답을 받지 않는 설문입니다."), INVALID_UPDATE_SURVEY(HttpStatus.BAD_REQUEST, "SV0015", "설문 정보 갱신에 실패했습니다."), INVALID_SURVEY_ACCESS(HttpStatus.FORBIDDEN, "SV0016", "설문 접근 권한이 없습니다."), - ALREADY_PARTICIPATED(HttpStatus.BAD_REQUEST, "SV0017", "이미 참여한 설문입니다."), INVALID_SURVEY_START(HttpStatus.BAD_REQUEST, "SV0018", "설문 시작에 실패했습니다."), INVALID_REWARD_INFO(HttpStatus.BAD_REQUEST, "SV0019", "유효하지 않은 리워드 정보입니다."), - // FingerPrint (FP) - UNCLEAN_VISITOR(HttpStatus.BAD_REQUEST, "FP0001", "유효하지 않은 방문자입니다."), - // Drawing (DR) INVALID_DRAWING_BOARD(HttpStatus.BAD_REQUEST, "DR0001", "유효하지 않은 추첨 보드입니다."), INVALID_DRAWING(HttpStatus.BAD_REQUEST, "DR0002", "유효하지 않은 추첨입니다."), diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResponseController.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResponseController.kt index 2964be41..b613fc52 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResponseController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResponseController.kt @@ -1,9 +1,7 @@ package com.sbl.sulmun2yong.survey.controller -import com.sbl.sulmun2yong.global.annotation.IsAdmin import com.sbl.sulmun2yong.survey.controller.doc.SurveyResponseApiDoc import com.sbl.sulmun2yong.survey.dto.request.SurveyResponseRequest -import com.sbl.sulmun2yong.survey.dto.response.SurveyParticipantResponse import com.sbl.sulmun2yong.survey.service.SurveyResponseService import jakarta.validation.Valid import org.springframework.http.ResponseEntity @@ -23,9 +21,5 @@ class SurveyResponseController( override fun responseToSurvey( @PathVariable("survey-id") surveyId: UUID, @Valid @RequestBody surveyResponseRequest: SurveyResponseRequest, - @IsAdmin isAdmin: Boolean, - ): ResponseEntity = - ResponseEntity.ok( - surveyResponseService.responseToSurvey(surveyId, surveyResponseRequest, isAdmin), - ) + ) = ResponseEntity.ok(surveyResponseService.responseToSurvey(surveyId, surveyResponseRequest)) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResponseApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResponseApiDoc.kt index dfac482b..51ca7228 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResponseApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResponseApiDoc.kt @@ -1,6 +1,5 @@ package com.sbl.sulmun2yong.survey.controller.doc -import com.sbl.sulmun2yong.global.annotation.IsAdmin import com.sbl.sulmun2yong.survey.dto.request.SurveyResponseRequest import com.sbl.sulmun2yong.survey.dto.response.SurveyParticipantResponse import io.swagger.v3.oas.annotations.Operation @@ -19,6 +18,5 @@ interface SurveyResponseApiDoc { fun responseToSurvey( @PathVariable("survey-id") surveyId: UUID, @Valid @RequestBody surveyResponseRequest: SurveyResponseRequest, - @IsAdmin isAdmin: Boolean, ): ResponseEntity } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/AlreadyParticipatedException.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/AlreadyParticipatedException.kt deleted file mode 100644 index 115168f1..00000000 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/AlreadyParticipatedException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.sbl.sulmun2yong.survey.exception - -import com.sbl.sulmun2yong.global.error.BusinessException -import com.sbl.sulmun2yong.global.error.ErrorCode - -class AlreadyParticipatedException : BusinessException(ErrorCode.ALREADY_PARTICIPATED) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt index ab4d489e..1302ab3a 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt @@ -7,7 +7,6 @@ import com.sbl.sulmun2yong.survey.domain.Participant import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.dto.request.SurveyResponseRequest import com.sbl.sulmun2yong.survey.dto.response.SurveyParticipantResponse -import com.sbl.sulmun2yong.survey.exception.AlreadyParticipatedException import com.sbl.sulmun2yong.survey.exception.SurveyClosedException import org.springframework.stereotype.Service import java.util.UUID @@ -22,11 +21,7 @@ class SurveyResponseService( fun responseToSurvey( surveyId: UUID, surveyResponseRequest: SurveyResponseRequest, - isAdmin: Boolean, ): SurveyParticipantResponse { - // 이미 참여한 설문인지 검증(Admin인 경우 스킵) - if (!isAdmin) validateIsAlreadyParticipated(surveyId) - val survey = surveyAdapter.getSurvey(surveyId) if (survey.status != SurveyStatus.IN_PROGRESS) { throw SurveyClosedException() @@ -39,11 +34,4 @@ class SurveyResponseService( responseAdapter.insertSurveyResponse(surveyResponse, participant.id) return SurveyParticipantResponse(participant.id, survey.isImmediateDraw()) } - - private fun validateIsAlreadyParticipated(surveyId: UUID) { - val participant = participantAdapter.findBySurveyId(surveyId) - participant?.let { - throw AlreadyParticipatedException() - } - } } From 609251370427481e69c8d3af8fce825bc7c975e3 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sun, 8 Sep 2024 22:29:42 +0900 Subject: [PATCH 047/201] =?UTF-8?q?[SBL-123]=20=EC=9C=A0=ED=9A=A8=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EC=84=A4=EB=AC=B8=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EC=83=81=EC=84=B8=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=ED=96=88=EC=9D=84=20=EB=95=8C=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=EC=8B=9C=ED=82=AC=20=EC=98=88=EC=99=B8=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt | 1 + .../survey/exception/InvalidResultDetailsException.kt | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidResultDetailsException.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index cd5992e2..ec082216 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -29,6 +29,7 @@ enum class ErrorCode( INVALID_SURVEY_ACCESS(HttpStatus.FORBIDDEN, "SV0016", "설문 접근 권한이 없습니다."), INVALID_SURVEY_START(HttpStatus.BAD_REQUEST, "SV0018", "설문 시작에 실패했습니다."), INVALID_REWARD_INFO(HttpStatus.BAD_REQUEST, "SV0019", "유효하지 않은 리워드 정보입니다."), + INVALID_RESULT_DETAILS(HttpStatus.BAD_REQUEST, "SV0020", "유효하지 않은 설문 결과입니다."), // Drawing (DR) INVALID_DRAWING_BOARD(HttpStatus.BAD_REQUEST, "DR0001", "유효하지 않은 추첨 보드입니다."), diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidResultDetailsException.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidResultDetailsException.kt new file mode 100644 index 00000000..64a8fcd5 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidResultDetailsException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.survey.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class InvalidResultDetailsException : BusinessException(ErrorCode.INVALID_RESULT_DETAILS) From ab1916a1db128921116b0269ef2d5ff832e37a80 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sun, 8 Sep 2024 22:30:52 +0900 Subject: [PATCH 048/201] =?UTF-8?q?[SBL-123]=20=EC=9C=A0=ED=9A=A8=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EC=A7=88=EB=AC=B8=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=EB=A5=BC=20=EC=83=9D=EC=84=B1=ED=96=88=EC=9D=84=20?= =?UTF-8?q?=EB=95=8C=20=EB=B0=9C=EC=83=9D=EC=8B=9C=ED=82=AC=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt | 1 + .../survey/exception/InvalidQuestionFilterException.kt | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidQuestionFilterException.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index ec082216..fd78febf 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -30,6 +30,7 @@ enum class ErrorCode( INVALID_SURVEY_START(HttpStatus.BAD_REQUEST, "SV0018", "설문 시작에 실패했습니다."), INVALID_REWARD_INFO(HttpStatus.BAD_REQUEST, "SV0019", "유효하지 않은 리워드 정보입니다."), INVALID_RESULT_DETAILS(HttpStatus.BAD_REQUEST, "SV0020", "유효하지 않은 설문 결과입니다."), + INVALID_QUESTION_FILTER(HttpStatus.BAD_REQUEST, "SV0021", "유효하지 않은 질문 필터입니다."), // Drawing (DR) INVALID_DRAWING_BOARD(HttpStatus.BAD_REQUEST, "DR0001", "유효하지 않은 추첨 보드입니다."), diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidQuestionFilterException.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidQuestionFilterException.kt new file mode 100644 index 00000000..e5e7e4c4 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidQuestionFilterException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.survey.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class InvalidQuestionFilterException : BusinessException(ErrorCode.INVALID_QUESTION_FILTER) From cb0fd4870f1aa327f43bc58dee5995619f0b61c9 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sun, 8 Sep 2024 22:37:17 +0900 Subject: [PATCH 049/201] =?UTF-8?q?[SBL-123]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=EB=A5=BC=20=EC=A1=B0=ED=9A=8C=ED=95=A0=20?= =?UTF-8?q?=EB=95=8C=20=EC=82=AC=EC=9A=A9=ED=95=A0=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/domain/result/QuestionFilter.kt | 14 ++++++++++++++ .../survey/domain/result/ResultFilter.kt | 5 +++++ 2 files changed, 19 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/QuestionFilter.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilter.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/QuestionFilter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/QuestionFilter.kt new file mode 100644 index 00000000..7a96452a --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/QuestionFilter.kt @@ -0,0 +1,14 @@ +package com.sbl.sulmun2yong.survey.domain.result + +import com.sbl.sulmun2yong.survey.exception.InvalidQuestionFilterException +import java.util.UUID + +data class QuestionFilter( + val questionId: UUID, + val contents: List, + val isPositive: Boolean, +) { + init { + require(contents.isNotEmpty()) { throw InvalidQuestionFilterException() } + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilter.kt new file mode 100644 index 00000000..52be0ac7 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilter.kt @@ -0,0 +1,5 @@ +package com.sbl.sulmun2yong.survey.domain.result + +data class ResultFilter( + val questionFilters: List, +) From 6a082b47f721e3906a3fdc0a67c77e4d6fd32320 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sun, 8 Sep 2024 22:37:47 +0900 Subject: [PATCH 050/201] =?UTF-8?q?[SBL-123]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=EB=A5=BC=20=EC=A0=80=EC=9E=A5=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=93=A4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/domain/result/ResultDetails.kt | 19 ++++++++++ .../survey/domain/result/SurveyResult.kt | 36 ++++++++++++++----- 2 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultDetails.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultDetails.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultDetails.kt new file mode 100644 index 00000000..626bee43 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultDetails.kt @@ -0,0 +1,19 @@ +package com.sbl.sulmun2yong.survey.domain.result + +import com.sbl.sulmun2yong.survey.exception.InvalidResultDetailsException +import java.util.UUID + +data class ResultDetails( + val questionId: UUID, + val participantId: UUID, + val contents: List, +) { + init { + require(contents.isNotEmpty()) { throw InvalidResultDetailsException() } + } + + fun isMatched(questionFilter: QuestionFilter): Boolean { + if (questionFilter.questionId != questionId) return false + return contents.any { questionFilter.contents.contains(it) } + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt index 04bd95bd..63f36914 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt @@ -1,15 +1,35 @@ package com.sbl.sulmun2yong.survey.domain.result -import java.util.Date import java.util.UUID data class SurveyResult( - val responses: List, + val resultDetails: List, ) { - data class Response( - val questionId: UUID, - val participantId: UUID, - val content: String, - val createdAt: Date, - ) + fun getFilteredResult(resultFilter: ResultFilter): SurveyResult { + val questionFilters = resultFilter.questionFilters + if (questionFilters.isEmpty()) return this + var filteredSurveyResult = copy() + for (questionFilter in questionFilters) { + filteredSurveyResult = filteredSurveyResult.filterByQuestionFilter(questionFilter) + if (filteredSurveyResult.resultDetails.isEmpty()) return filteredSurveyResult + } + return filteredSurveyResult + } + + private fun filterByQuestionFilter(questionFilter: QuestionFilter): SurveyResult { + val participantSet = getMatchedParticipants(questionFilter) + return if (questionFilter.isPositive) { + // isPositive가 true이면 해당 참가자들을 포함 + SurveyResult(resultDetails.filter { participantSet.contains(it.participantId) }) + } else { + // isPositive가 false이면 해당 참가자들을 제외 + SurveyResult(resultDetails.filter { !participantSet.contains(it.participantId) }) + } + } + + private fun getMatchedParticipants(questionFilter: QuestionFilter): Set = + resultDetails + .mapNotNull { response -> + if (response.isMatched(questionFilter)) response.participantId else null + }.toSet() } From 11ea825732304a224621f9c3a0a045d1670ec7d3 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sun, 8 Sep 2024 22:39:59 +0900 Subject: [PATCH 051/201] =?UTF-8?q?[SBL-123]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=EB=93=A4=EC=9D=84=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=ED=95=B4=EC=84=9C=20SurveyResult=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EB=A5=BC=20=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20Adapter=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/adapter/ResponseAdapter.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt index fdfd5bbc..ddabd78b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt @@ -33,16 +33,17 @@ class ResponseAdapter( } } - fun getResponses(surveyId: UUID): SurveyResult { + fun getSurveyResult(surveyId: UUID): SurveyResult { val responses = responseRepository.findBySurveyId(surveyId) - return SurveyResult(responses = responses.map { it.toDomain() }) + val groupingResponses = responses.groupBy { "${it.questionId}|${it.participantId}" }.values + groupingResponses.map { it.toDomain() } + return SurveyResult(resultDetails = groupingResponses.map { it.toDomain() }) } - private fun ResponseDocument.toDomain() = - SurveyResult.Response( - questionId = this.questionId, - participantId = this.participantId, - content = this.content, - createdAt = this.createdAt, + private fun List.toDomain() = + SurveyResult.ResultDetails( + questionId = first().questionId, + participantId = first().participantId, + contents = map { responseDocument -> responseDocument.content }, ) } From 0adbc732cbd2b37440626e6180fed2737f5df9e2 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sun, 8 Sep 2024 22:43:56 +0900 Subject: [PATCH 052/201] =?UTF-8?q?[SBL-123]=20=EC=84=A4=EB=AC=B8=EC=9D=98?= =?UTF-8?q?=20=EA=B2=B0=EA=B3=BC=EB=A5=BC=20=EC=A1=B0=ED=9A=8C=ED=95=A0=20?= =?UTF-8?q?=EB=95=8C=20=ED=95=84=EC=9A=94=ED=95=9C=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=EB=A5=BC=20=EB=8B=B4=EC=9D=80=20SurveyResultRequest=20DTO=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/dto/request/SurveyResultRequest.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveyResultRequest.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveyResultRequest.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveyResultRequest.kt new file mode 100644 index 00000000..7f726b3a --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveyResultRequest.kt @@ -0,0 +1,27 @@ +package com.sbl.sulmun2yong.survey.dto.request + +import com.sbl.sulmun2yong.survey.domain.result.QuestionFilter +import com.sbl.sulmun2yong.survey.domain.result.ResultFilter +import java.util.UUID + +data class SurveyResultRequest( + val questionFilters: List, +) { + data class QuestionFilterRequest( + val questionId: UUID, + val contents: List, + val isPositive: Boolean, + ) { + fun toDomain() = + QuestionFilter( + questionId = questionId, + contents = contents, + isPositive = isPositive, + ) + } + + fun toDomain() = + ResultFilter( + questionFilters = questionFilters.map { it.toDomain() }, + ) +} From 6a62754fa54cdc25e16814a167d2994f3ea021a5 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sun, 8 Sep 2024 22:45:54 +0900 Subject: [PATCH 053/201] =?UTF-8?q?[SBL-123]=20ResultDetails=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=97=90=20=EB=94=B0=EB=9D=BC=20SurveyResultResponse?= =?UTF-8?q?=20DTO=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/dto/response/SurveyResultResponse.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt index 93e438ee..15d2ac46 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt @@ -1,5 +1,6 @@ package com.sbl.sulmun2yong.survey.dto.response +import com.sbl.sulmun2yong.survey.domain.result.ResultDetails import com.sbl.sulmun2yong.survey.domain.result.SurveyResult import java.util.UUID @@ -10,7 +11,7 @@ data class SurveyResultResponse( fun of(surveyResult: SurveyResult) = SurveyResultResponse( results = - surveyResult.responses.groupBy { it.questionId }.map { + surveyResult.resultDetails.groupBy { it.questionId }.map { Result.from(it.value) }, ) @@ -21,10 +22,16 @@ data class SurveyResultResponse( val responses: List, ) { companion object { - fun from(responses: List): Result = + fun from(responses: List): Result = Result( questionId = responses.first().questionId, - responses = responses.groupBy { it.content }.map { Response(it.key, it.value.size) }, + responses = + responses + .map { it.contents } + .flatten() + .groupingBy { it } + .eachCount() + .map { Response(it.key, it.value) }, ) } From 0186b0185dfcc881a43f0d1854e55b75c4f2ff89 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sun, 8 Sep 2024 22:47:05 +0900 Subject: [PATCH 054/201] =?UTF-8?q?[SBL-123]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20=EC=A1=B0=ED=9A=8C=20API=EC=97=90=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=EB=A7=81=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/controller/SurveyResultController.kt | 9 ++++++--- .../survey/controller/doc/SurveyResultApiDoc.kt | 7 +++++-- .../survey/service/SurveyResultService.kt | 12 ++++++++++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResultController.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResultController.kt index e534d644..c755cab6 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResultController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResultController.kt @@ -2,10 +2,12 @@ package com.sbl.sulmun2yong.survey.controller import com.sbl.sulmun2yong.global.annotation.LoginUser import com.sbl.sulmun2yong.survey.controller.doc.SurveyResultApiDoc +import com.sbl.sulmun2yong.survey.dto.request.SurveyResultRequest import com.sbl.sulmun2yong.survey.service.SurveyResultService import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable +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.RestController import java.util.UUID @@ -15,9 +17,10 @@ import java.util.UUID class SurveyResultController( private val surveyResultService: SurveyResultService, ) : SurveyResultApiDoc { - @GetMapping("/{survey-id}") + @PostMapping("/{survey-id}") override fun getSurveyResult( @PathVariable("survey-id") surveyId: UUID, @LoginUser id: UUID, - ) = ResponseEntity.ok(surveyResultService.getSurveyResult(surveyId, id)) + @RequestBody surveyResultRequest: SurveyResultRequest, + ) = ResponseEntity.ok(surveyResultService.getSurveyResult(surveyId, id, surveyResultRequest)) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResultApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResultApiDoc.kt index e36db9f3..1b24efea 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResultApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResultApiDoc.kt @@ -1,20 +1,23 @@ package com.sbl.sulmun2yong.survey.controller.doc import com.sbl.sulmun2yong.global.annotation.LoginUser +import com.sbl.sulmun2yong.survey.dto.request.SurveyResultRequest import com.sbl.sulmun2yong.survey.dto.response.SurveyResultResponse import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody import java.util.UUID @Tag(name = "SurveyResult", description = "설문 결과 관련 API") interface SurveyResultApiDoc { @Operation(summary = "설문 결과 조회") - @GetMapping("/{survey-id}") + @PostMapping("/{survey-id}") fun getSurveyResult( @PathVariable("survey-id") surveyId: UUID, @LoginUser id: UUID, + @RequestBody surveyResultRequest: SurveyResultRequest, ): ResponseEntity } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt index 7f9c7ddb..27107373 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt @@ -2,6 +2,7 @@ package com.sbl.sulmun2yong.survey.service import com.sbl.sulmun2yong.survey.adapter.ResponseAdapter import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter +import com.sbl.sulmun2yong.survey.dto.request.SurveyResultRequest import com.sbl.sulmun2yong.survey.dto.response.SurveyResultResponse import com.sbl.sulmun2yong.survey.exception.SurveyNotFoundException import org.springframework.stereotype.Service @@ -15,11 +16,18 @@ class SurveyResultService( fun getSurveyResult( surveyId: UUID, userId: UUID, + surveyResultRequest: SurveyResultRequest, ): SurveyResultResponse { val isSurveyExists = surveyAdapter.existsByIdAndMakerId(surveyId, userId) // 본인이 만든 설문이 아닌 경우 예외 발생 if (!isSurveyExists) throw SurveyNotFoundException() - val response = responseAdapter.getResponses(surveyId) - return SurveyResultResponse.of(response) + + // DB에서 설문 결과 조회 + val surveyResult = responseAdapter.getSurveyResult(surveyId) + + // 요청에 따라 설문 결과 필터링 + val resultFilter = surveyResultRequest.toDomain() + val filteredSurveyResult = surveyResult.getFilteredResult(resultFilter) + return SurveyResultResponse.of(filteredSurveyResult) } } From 4347a72b377da6d0c45a6c2bc02cecd3dabb8f72 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 9 Sep 2024 01:03:09 +0900 Subject: [PATCH 055/201] =?UTF-8?q?[SBL-123]=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=84=A4=EB=AC=B8=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EA=B4=80=EB=A0=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20=EB=B0=8F=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BB=A4=EB=B2=84=EB=A6=AC=EC=A7=80=20100?= =?UTF-8?q?%=20=EB=8B=AC=EC=84=B1=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/adapter/ResponseAdapter.kt | 3 +- .../survey/SurveyResultConstFactory.kt | 120 +++++++++++++ .../survey/domain/SurveyResultTest.kt | 67 -------- ...seTest.kt => QuestionResultDetailsTest.kt} | 2 +- .../survey/domain/result/ResultFilterTest.kt | 46 +++++ .../survey/domain/result/SurveyResultTest.kt | 161 ++++++++++++++++++ 6 files changed, 330 insertions(+), 69 deletions(-) create mode 100644 src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyResultConstFactory.kt delete mode 100644 src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyResultTest.kt rename src/test/kotlin/com/sbl/sulmun2yong/survey/domain/question/{QuestionResponseTest.kt => QuestionResultDetailsTest.kt} (98%) create mode 100644 src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilterTest.kt create mode 100644 src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResultTest.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt index ddabd78b..da0e35b3 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt @@ -1,6 +1,7 @@ package com.sbl.sulmun2yong.survey.adapter import com.sbl.sulmun2yong.survey.domain.response.SurveyResponse +import com.sbl.sulmun2yong.survey.domain.result.ResultDetails import com.sbl.sulmun2yong.survey.domain.result.SurveyResult import com.sbl.sulmun2yong.survey.entity.ResponseDocument import com.sbl.sulmun2yong.survey.repository.ResponseRepository @@ -41,7 +42,7 @@ class ResponseAdapter( } private fun List.toDomain() = - SurveyResult.ResultDetails( + ResultDetails( questionId = first().questionId, participantId = first().participantId, contents = map { responseDocument -> responseDocument.content }, diff --git a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyResultConstFactory.kt b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyResultConstFactory.kt new file mode 100644 index 00000000..84810032 --- /dev/null +++ b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyResultConstFactory.kt @@ -0,0 +1,120 @@ +package com.sbl.sulmun2yong.fixture.survey + +import com.sbl.sulmun2yong.survey.domain.result.QuestionFilter +import com.sbl.sulmun2yong.survey.domain.result.ResultDetails +import com.sbl.sulmun2yong.survey.domain.result.SurveyResult +import java.util.UUID + +object SurveyResultConstFactory { + val JOB_QUESTION_ID = UUID.randomUUID() + val JOB_QUESTION_CONTENTS = listOf("학생", "직장인", "자영업자", "무직") + + val GENDER_QUESTION_ID = UUID.randomUUID() + val GENDER_QUESTION_CONTENTS = listOf("남자", "여자") + + val FOOD_MULTIPLE_CHOICE_QUESTION_ID = UUID.randomUUID() + val FOOD_MULTIPLE_CHOICE_QUESTION_CONTENTS = listOf("한식", "일식", "패스트푸드", "중식", "분식", "양식") + + val FOOD_TEXT_RESPONSE_QUESTION_ID = UUID.randomUUID() + val FOOD_TEXT_RESPONSE_QUESTION_CONTENTS = listOf("맛있어요!", "매콤해요!", "달아요!", "짭짤해요!") + + val PARTICIPANT_ID_1 = UUID.randomUUID() + + /** 1번 참가자의 응답(학생, 남자, 한식 & 일식, 맛있어요!) */ + val PARTICIPANT_RESULT_DETAILS_1 = + listOf( + ResultDetails( + questionId = JOB_QUESTION_ID, + participantId = PARTICIPANT_ID_1, + contents = listOf(JOB_QUESTION_CONTENTS[0]), + ), + ResultDetails( + questionId = GENDER_QUESTION_ID, + participantId = PARTICIPANT_ID_1, + contents = listOf(GENDER_QUESTION_CONTENTS[0]), + ), + ResultDetails( + questionId = FOOD_MULTIPLE_CHOICE_QUESTION_ID, + participantId = PARTICIPANT_ID_1, + contents = listOf(FOOD_MULTIPLE_CHOICE_QUESTION_CONTENTS[0], FOOD_MULTIPLE_CHOICE_QUESTION_CONTENTS[1]), + ), + ResultDetails( + questionId = FOOD_TEXT_RESPONSE_QUESTION_ID, + participantId = PARTICIPANT_ID_1, + contents = listOf(FOOD_TEXT_RESPONSE_QUESTION_CONTENTS[0]), + ), + ) + + val PARTICIPANT_ID_2 = UUID.randomUUID() + + /** 2번 참가자의 응답(직장인, 여자, 한식 & 패스트푸드 & 중식, 매콥해요!) */ + val PARTICIPANT_RESULT_DETAILS_2 = + listOf( + ResultDetails( + questionId = JOB_QUESTION_ID, + participantId = PARTICIPANT_ID_2, + contents = listOf(JOB_QUESTION_CONTENTS[1]), + ), + ResultDetails( + questionId = GENDER_QUESTION_ID, + participantId = PARTICIPANT_ID_2, + contents = listOf(GENDER_QUESTION_CONTENTS[1]), + ), + ResultDetails( + questionId = FOOD_MULTIPLE_CHOICE_QUESTION_ID, + participantId = PARTICIPANT_ID_2, + contents = + listOf( + FOOD_MULTIPLE_CHOICE_QUESTION_CONTENTS[0], + FOOD_MULTIPLE_CHOICE_QUESTION_CONTENTS[2], + FOOD_MULTIPLE_CHOICE_QUESTION_CONTENTS[3], + ), + ), + ResultDetails( + questionId = FOOD_TEXT_RESPONSE_QUESTION_ID, + participantId = PARTICIPANT_ID_2, + contents = listOf(FOOD_TEXT_RESPONSE_QUESTION_CONTENTS[1]), + ), + ) + + val PARTICIPANT_ID_3 = UUID.randomUUID() + + /** 3번 참가자의 응답(학생, 여자, 패스트푸드 & 분식, 달아요!) */ + val PARTICIPANT_RESULT_DETAILS_3 = + listOf( + ResultDetails( + questionId = JOB_QUESTION_ID, + participantId = PARTICIPANT_ID_3, + contents = listOf(JOB_QUESTION_CONTENTS[0]), + ), + ResultDetails( + questionId = GENDER_QUESTION_ID, + participantId = PARTICIPANT_ID_3, + contents = listOf(GENDER_QUESTION_CONTENTS[1]), + ), + ResultDetails( + questionId = FOOD_MULTIPLE_CHOICE_QUESTION_ID, + participantId = PARTICIPANT_ID_3, + contents = listOf(FOOD_MULTIPLE_CHOICE_QUESTION_CONTENTS[2], FOOD_MULTIPLE_CHOICE_QUESTION_CONTENTS[4]), + ), + ResultDetails( + questionId = FOOD_TEXT_RESPONSE_QUESTION_ID, + participantId = PARTICIPANT_ID_3, + contents = listOf(FOOD_TEXT_RESPONSE_QUESTION_CONTENTS[2]), + ), + ) + + val SURVEY_RESULT = + SurveyResult( + resultDetails = PARTICIPANT_RESULT_DETAILS_1 + PARTICIPANT_RESULT_DETAILS_2 + PARTICIPANT_RESULT_DETAILS_3, + ) + + val EXCEPT_STUDENT_FILTER = QuestionFilter(JOB_QUESTION_ID, listOf(JOB_QUESTION_CONTENTS[0]), false) + val MAN_FILTER = QuestionFilter(GENDER_QUESTION_ID, listOf(GENDER_QUESTION_CONTENTS[0]), true) + val K_J_FOOD_FILTER = + QuestionFilter( + FOOD_MULTIPLE_CHOICE_QUESTION_ID, + listOf(FOOD_MULTIPLE_CHOICE_QUESTION_CONTENTS[0], FOOD_MULTIPLE_CHOICE_QUESTION_CONTENTS[1]), + true, + ) +} diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyResultTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyResultTest.kt deleted file mode 100644 index ec620b7f..00000000 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyResultTest.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.sbl.sulmun2yong.survey.domain - -import com.sbl.sulmun2yong.survey.domain.result.SurveyResult -import org.junit.jupiter.api.Test -import java.util.Date -import java.util.UUID -import kotlin.test.assertEquals - -class SurveyResultTest { - @Test - fun `설문 결과를 생성하면 정보가 올바르게 설정된다`() { - // given - val questionId1 = UUID.randomUUID() - val questionId2 = UUID.randomUUID() - val participantId1 = UUID.randomUUID() - val participantId2 = UUID.randomUUID() - val content1 = "content1" - val content2 = "content2" - val content3 = "content3" - val date = Date() - - val response1 = - SurveyResult.Response( - questionId = questionId1, - participantId = participantId1, - content = content1, - createdAt = date, - ) - val response2 = - SurveyResult.Response( - questionId = questionId2, - participantId = participantId1, - content = content2, - createdAt = date, - ) - val response3 = - SurveyResult.Response( - questionId = questionId1, - participantId = participantId2, - content = content3, - createdAt = date, - ) - - // when - val surveyResult = SurveyResult(listOf(response1, response2, response3)) - - // then - with(surveyResult.responses[0]) { - assertEquals(questionId1, questionId) - assertEquals(participantId1, participantId) - assertEquals(content1, content) - assertEquals(date, createdAt) - } - with(surveyResult.responses[1]) { - assertEquals(questionId2, questionId) - assertEquals(participantId1, participantId) - assertEquals(content2, content) - assertEquals(date, createdAt) - } - with(surveyResult.responses[2]) { - assertEquals(questionId1, questionId) - assertEquals(participantId2, participantId) - assertEquals(content3, content) - assertEquals(date, createdAt) - } - } -} diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/question/QuestionResponseTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/question/QuestionResultDetailsTest.kt similarity index 98% rename from src/test/kotlin/com/sbl/sulmun2yong/survey/domain/question/QuestionResponseTest.kt rename to src/test/kotlin/com/sbl/sulmun2yong/survey/domain/question/QuestionResultDetailsTest.kt index 636c2114..5f52fb6f 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/question/QuestionResponseTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/question/QuestionResultDetailsTest.kt @@ -9,7 +9,7 @@ import org.junit.jupiter.api.assertThrows import java.util.UUID import kotlin.test.assertEquals -class QuestionResponseTest { +class QuestionResultDetailsTest { private val contentA = "A" private val contentB = "B" private val detailA = ResponseDetail(contentA) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilterTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilterTest.kt new file mode 100644 index 00000000..611fd414 --- /dev/null +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilterTest.kt @@ -0,0 +1,46 @@ +package com.sbl.sulmun2yong.survey.domain.result + +import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.EXCEPT_STUDENT_FILTER +import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.MAN_FILTER +import com.sbl.sulmun2yong.survey.exception.InvalidQuestionFilterException +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.util.UUID +import kotlin.test.assertEquals + +class ResultFilterTest { + @Test + fun `질문 필터를 생성하면 정보가 올바르게 설정된다`() { + // given + val questionId = UUID.randomUUID() + val contents = listOf("content1", "content2") + val isPositive = true + + // when + val questionFilter = QuestionFilter(questionId, contents, isPositive) + + // then + with(questionFilter) { + assertEquals(questionId, this.questionId) + assertEquals(contents, this.contents) + assertEquals(isPositive, this.isPositive) + } + } + + @Test + fun `질문 필터의 contents는 비어있을 수 없다`() { + assertThrows { QuestionFilter(UUID.randomUUID(), emptyList(), true) } + } + + @Test + fun `설문 결과 필터를 생성하면 정보가 올바르게 설정된다`() { + // given + val questionFilters = listOf(EXCEPT_STUDENT_FILTER, MAN_FILTER) + + // when + val resultFilter = ResultFilter(questionFilters) + + // then + assertEquals(questionFilters, resultFilter.questionFilters) + } +} diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResultTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResultTest.kt new file mode 100644 index 00000000..bcb5b15f --- /dev/null +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResultTest.kt @@ -0,0 +1,161 @@ +package com.sbl.sulmun2yong.survey.domain.result + +import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.EXCEPT_STUDENT_FILTER +import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.K_J_FOOD_FILTER +import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.MAN_FILTER +import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.PARTICIPANT_RESULT_DETAILS_1 +import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.PARTICIPANT_RESULT_DETAILS_2 +import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.PARTICIPANT_RESULT_DETAILS_3 +import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.SURVEY_RESULT +import com.sbl.sulmun2yong.survey.exception.InvalidResultDetailsException +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.util.UUID +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class SurveyResultTest { + @Test + fun `설문 결과를 생성하면 정보가 올바르게 설정된다`() { + // given + val questionId1 = UUID.randomUUID() + val questionId2 = UUID.randomUUID() + val participantId1 = UUID.randomUUID() + val participantId2 = UUID.randomUUID() + val contents1 = listOf("content1") + val contents2 = listOf("content2") + val contents3 = listOf("content3") + + val response1 = + ResultDetails( + questionId = questionId1, + participantId = participantId1, + contents = contents1, + ) + val response2 = + ResultDetails( + questionId = questionId2, + participantId = participantId1, + contents = contents2, + ) + val response3 = + ResultDetails( + questionId = questionId1, + participantId = participantId2, + contents = contents3, + ) + + // when + val surveyResult = SurveyResult(listOf(response1, response2, response3)) + + // then + with(surveyResult.resultDetails[0]) { + assertEquals(questionId1, questionId) + assertEquals(participantId1, participantId) + assertEquals(contents1, contents) + } + with(surveyResult.resultDetails[1]) { + assertEquals(questionId2, questionId) + assertEquals(participantId1, participantId) + assertEquals(contents2, contents) + } + with(surveyResult.resultDetails[2]) { + assertEquals(questionId1, questionId) + assertEquals(participantId2, participantId) + assertEquals(contents3, contents) + } + } + + @Test + fun `설문 결과 상세의 contents는 비어있을 수 없다`() { + // given + val questionId = UUID.randomUUID() + val participantId = UUID.randomUUID() + val contents = emptyList() + + // when, then + assertThrows { + ResultDetails(questionId, participantId, contents) + } + } + + @Test + fun `설문 결과 상세는 질문 필터를 받으면 필터링되는 응답인지 확인할 수 있다`() { + // given + val jobQuestionDetails1 = PARTICIPANT_RESULT_DETAILS_1[0] + val genderQuestionDetails1 = PARTICIPANT_RESULT_DETAILS_1[1] + val foodQuestionDetails1 = PARTICIPANT_RESULT_DETAILS_1[2] + + val jobQuestionDetails2 = PARTICIPANT_RESULT_DETAILS_2[0] + val genderQuestionDetails2 = PARTICIPANT_RESULT_DETAILS_2[1] + val foodQuestionDetails2 = PARTICIPANT_RESULT_DETAILS_2[2] + + val jobQuestionDetails3 = PARTICIPANT_RESULT_DETAILS_3[0] + val genderQuestionDetails3 = PARTICIPANT_RESULT_DETAILS_3[1] + val foodQuestionDetails3 = PARTICIPANT_RESULT_DETAILS_3[2] + + // when + // isMatched는 질문 필터의 contents에 응답의 contents가 포함되어 있고, + // 질문 필터의 questionId가 응답의 questionId가 같은지 판단 + val isStudent1 = jobQuestionDetails1.isMatched(EXCEPT_STUDENT_FILTER) + val isMan1 = genderQuestionDetails1.isMatched(MAN_FILTER) + val isKOrJFood1 = foodQuestionDetails1.isMatched(K_J_FOOD_FILTER) + + val isStudent2 = jobQuestionDetails2.isMatched(EXCEPT_STUDENT_FILTER) + val isMan2 = genderQuestionDetails2.isMatched(MAN_FILTER) + val isKOrJFood2 = foodQuestionDetails2.isMatched(K_J_FOOD_FILTER) + + val isStudent3 = jobQuestionDetails3.isMatched(EXCEPT_STUDENT_FILTER) + val isMan3 = genderQuestionDetails3.isMatched(MAN_FILTER) + val isKOrJFood3 = foodQuestionDetails3.isMatched(K_J_FOOD_FILTER) + + // 다른 질문인 경우 + val differentQuestion = foodQuestionDetails3.isMatched(MAN_FILTER) + + // then + // 1번 참가자의 응답(학생, 남자, 한식 & 일식, 맛있어요!) + assertEquals(true, isStudent1) + assertEquals(true, isMan1) + assertEquals(true, isKOrJFood1) + + // 2번 참가자의 응답(직장인, 여자, 한식 & 패스트푸드 & 중식, 매콥해요!) + assertEquals(false, isStudent2) + assertEquals(false, isMan2) + assertEquals(true, isKOrJFood2) + + // 3번 참가자의 응답(학생, 여자, 패스트푸드 & 분식, 달아요!) + assertEquals(true, isStudent3) + assertEquals(false, isMan3) + assertEquals(false, isKOrJFood3) + + // 다른 질문인 경우 false + assertEquals(false, differentQuestion) + } + + @Test + fun `설문 결과는 설문 결과 필터를 받으면 필터링된 결과를 반환한다`() { + // given + val surveyResult = SURVEY_RESULT + val exceptStudentAndKOrJFoodFilter = ResultFilter(listOf(EXCEPT_STUDENT_FILTER, K_J_FOOD_FILTER)) + val exceptStudentAndManAndKOrJFoodFilter = ResultFilter(listOf(EXCEPT_STUDENT_FILTER, MAN_FILTER, K_J_FOOD_FILTER)) + val emptyFilter = ResultFilter(listOf()) + + // when + val filteredResult1 = surveyResult.getFilteredResult(exceptStudentAndKOrJFoodFilter) + val filteredResult2 = surveyResult.getFilteredResult(exceptStudentAndManAndKOrJFoodFilter) + val filteredResult3 = surveyResult.getFilteredResult(emptyFilter) + + // then + with(filteredResult1.resultDetails) { + assertEquals(4, size) + assertTrue { this.containsAll(PARTICIPANT_RESULT_DETAILS_2) } + } + with(filteredResult2.resultDetails) { + assertEquals(0, size) + } + with(filteredResult3.resultDetails) { + assertEquals(12, size) + assertTrue { this.containsAll(PARTICIPANT_RESULT_DETAILS_2) } + } + } +} From 4e7097131d17d09011a7885da6eb29f87af39718 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 12 Sep 2024 13:26:05 +0900 Subject: [PATCH 056/201] =?UTF-8?q?[SBL-123]=20=EC=B6=94=ED=9B=84=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=EB=A7=81=20=EB=A1=9C=EC=A7=81=EC=9D=84=20DB?= =?UTF-8?q?=20=EB=A0=88=EB=B2=A8=EC=97=90=EC=84=9C=20=EB=8F=99=EC=A0=81=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=EC=9E=91=EC=84=B1=EC=9D=84=20=ED=86=B5?= =?UTF-8?q?=ED=95=B4=20=ED=95=B4=EA=B2=B0=ED=95=98=EB=8F=84=EB=A1=9D=20TOD?= =?UTF-8?q?O=20=EC=A3=BC=EC=84=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt index da0e35b3..1aaec7d0 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt @@ -36,6 +36,7 @@ class ResponseAdapter( fun getSurveyResult(surveyId: UUID): SurveyResult { val responses = responseRepository.findBySurveyId(surveyId) + // TODO: 추후 DB Level에서 처리하도록 변경 + 필터링을 동적쿼리로 하도록 변경 val groupingResponses = responses.groupBy { "${it.questionId}|${it.participantId}" }.values groupingResponses.map { it.toDomain() } return SurveyResult(resultDetails = groupingResponses.map { it.toDomain() }) From 76748223d91660b2072cdf1e66d5e4aada15a7ae Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 12 Sep 2024 15:19:33 +0900 Subject: [PATCH 057/201] =?UTF-8?q?[SBL-133]=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 4 ++ .../aws/controller/S3Controller.kt | 37 +++++++++++++++++++ .../aws/controller/doc/S3ApiDoc.kt | 17 +++++++++ .../sbl/sulmun2yong/aws/service/S3Service.kt | 25 +++++++++++++ .../sbl/sulmun2yong/global/config/S3Config.kt | 27 ++++++++++++++ .../global/config/SecurityConfig.kt | 30 ++++++++------- 6 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/aws/controller/S3Controller.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/aws/controller/doc/S3ApiDoc.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt diff --git a/build.gradle.kts b/build.gradle.kts index d6c1a88b..9dd048c3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -54,6 +54,10 @@ dependencies { implementation("org.springdoc:springdoc-openapi:2.3.0") implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0") + // AWS + implementation("software.amazon.awssdk:bom:2.27.24") + implementation("software.amazon.awssdk:s3:2.27.24") + // test testImplementation("org.mockito:mockito-core:4.0.0") testImplementation("org.mockito:mockito-inline:4.0.0") diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/controller/S3Controller.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/controller/S3Controller.kt new file mode 100644 index 00000000..f5784692 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/controller/S3Controller.kt @@ -0,0 +1,37 @@ + +import com.sbl.sulmun2yong.aws.controller.doc.S3ApiDoc +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestPart +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.multipart.MultipartFile +import java.nio.file.Files +import java.nio.file.Path + +@RestController +@RequestMapping("/api/v1/s3") +class S3Controller( + @Value("\${aws.s3.bucket-name}") + private val bucketName: String, + private val s3Service: S3Service, +) : S3ApiDoc { + @PostMapping("/upload") + override fun uploadFile( + @RequestPart(value = "file") file: MultipartFile, + ): ResponseEntity { + val tempFile: Path = Files.createTempFile(file.originalFilename, null) + file.transferTo(tempFile) + + val bucketName = bucketName + val keyName = file.originalFilename ?: "default-name" + + s3Service.uploadFile(bucketName, keyName, tempFile.toFile()) + + // 임시 파일 삭제 + Files.delete(tempFile) + + return ResponseEntity.ok("File uploaded successfully") + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/controller/doc/S3ApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/controller/doc/S3ApiDoc.kt new file mode 100644 index 00000000..538ce813 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/controller/doc/S3ApiDoc.kt @@ -0,0 +1,17 @@ +package com.sbl.sulmun2yong.aws.controller.doc + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.multipart.MultipartFile + +@Tag(name = "AWS", description = "AWS 관련 API") +interface S3ApiDoc { + @Operation(summary = "S3 업로드") + @PostMapping("/upload") + fun uploadFile( + @RequestParam("file") file: MultipartFile, + ): ResponseEntity +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt new file mode 100644 index 00000000..e52996f0 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt @@ -0,0 +1,25 @@ +import org.springframework.stereotype.Service +import software.amazon.awssdk.services.s3.S3Client +import software.amazon.awssdk.services.s3.model.PutObjectRequest +import java.io.File +import java.nio.file.Paths + +@Service +class S3Service( + private val s3Client: S3Client, +) { + fun uploadFile( + bucketName: String, + keyName: String, + file: File, + ) { + val putObjectRequest = + PutObjectRequest + .builder() + .bucket(bucketName) + .key(keyName) + .build() + + s3Client.putObject(putObjectRequest, Paths.get(file.toURI())) + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt new file mode 100644 index 00000000..1987a688 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt @@ -0,0 +1,27 @@ +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.s3.S3Client + +@Configuration +class S3Config( + @Value("\${aws.s3.access-key}") + private val accessKey: String, + @Value("\${aws.s3.secret-key}") + private val secretKey: String, +) { + @Bean + fun s3Client(): S3Client { + // 액세스 키와 시크릿 키 설정 + val awsCredentials = AwsBasicCredentials.create(accessKey, secretKey) + + return S3Client + .builder() + .region(Region.AP_NORTHEAST_2) + .credentialsProvider(StaticCredentialsProvider.create(awsCredentials)) + .build() + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt index d24023a9..9939de4d 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt @@ -8,12 +8,12 @@ import com.sbl.sulmun2yong.global.config.oauth2.strategy.CustomExpiredSessionStr import com.sbl.sulmun2yong.global.config.oauth2.strategy.CustomInvalidSessionStrategy import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider import org.springframework.boot.web.servlet.FilterRegistrationBean import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.Ordered import org.springframework.core.annotation.Order -import org.springframework.security.config.Customizer import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.invoke import org.springframework.security.core.session.SessionRegistry @@ -76,19 +76,21 @@ class SecurityConfig( @ConditionalOnProperty(prefix = "swagger", name = ["login"], havingValue = "true") @Order(0) @Bean - fun filterChain(http: HttpSecurity): SecurityFilterChain { - http - .securityMatcher("/swagger-ui/**", "/v3/api-docs/**", "/login") - .csrf { it.disable() } - .authorizeHttpRequests { requests -> - requests - .requestMatchers("/swagger-ui/**") - .hasAnyRole("SWAGGER_USER", "ADMIN") - .requestMatchers("/v3/api-docs/**") - .hasAnyRole("SWAGGER_USER", "ADMIN") - .requestMatchers("/**") - .permitAll() - }.formLogin(Customizer.withDefaults()) + fun filterChain( + http: HttpSecurity, + requestMatcherProvider: RequestMatcherProvider, + ): SecurityFilterChain { + http { + csrf { disable() } + securityMatcher("/swagger-ui/**", "/v3/api-docs/**", "/login") + authorizeHttpRequests { + authorize("/swagger-ui/**", hasAnyRole("SWAGGER_USER", "ADMIN")) + authorize("/v3/api-docs/**", hasAnyRole("SWAGGER_USER", "ADMIN")) + authorize("/api/v1/surveys/results/**", authenticated) + authorize("/**", permitAll) + } + formLogin { loginPage = "/login" } + } return http.build() } From 91d1b9d51c0b984352c832e99e70d2924f7ce167 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 12 Sep 2024 18:09:59 +0900 Subject: [PATCH 058/201] =?UTF-8?q?[SBL-133]=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EA=B5=AC=EC=A1=B0=20=EC=9E=91=EC=84=B1=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 8 +++ .../aws/controller/S3Controller.kt | 31 +++------ .../aws/controller/doc/S3ApiDoc.kt | 11 ++-- .../aws/dto/request/S3UploadRequest.kt | 7 ++ .../aws/dto/response/S3UploadResponse.kt | 5 ++ .../aws/exception/FileNameTooLongException.kt | 6 ++ .../exception/FileNameTooShortException.kt | 6 ++ .../exception/InvalidExtensionException.kt | 6 ++ .../aws/exception/NoFileExistException.kt | 6 ++ .../aws/exception/OutOfFileSizeException.kt | 6 ++ .../sbl/sulmun2yong/aws/service/S3Service.kt | 65 ++++++++++++++++--- .../sbl/sulmun2yong/global/config/S3Config.kt | 3 +- .../global/config/SecurityConfig.kt | 4 +- .../sbl/sulmun2yong/global/error/ErrorCode.kt | 7 ++ .../user/controller/AdminController.kt | 4 +- .../user/controller/LoginController.kt | 4 +- .../user/controller/UserController.kt | 4 +- .../user/controller/doc/AdminApiDoc.kt | 2 - .../user/controller/doc/LoginApiDoc.kt | 2 - .../user/controller/doc/UserApiDoc.kt | 2 - src/main/resources/application.yml | 3 + 21 files changed, 143 insertions(+), 49 deletions(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/aws/dto/request/S3UploadRequest.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/aws/dto/response/S3UploadResponse.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/aws/exception/FileNameTooLongException.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/aws/exception/FileNameTooShortException.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/aws/exception/InvalidExtensionException.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/aws/exception/NoFileExistException.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/aws/exception/OutOfFileSizeException.kt diff --git a/build.gradle.kts b/build.gradle.kts index 9dd048c3..dd50cff7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,4 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.time.LocalDateTime import java.time.format.DateTimeFormatter @@ -76,6 +77,13 @@ kotlin { } } +tasks.withType { + kotlinOptions { + freeCompilerArgs = listOf("-Xjsr305=strict") + jvmTarget = JavaVersion.VERSION_17.toString() + } +} + tasks.withType { useJUnitPlatform() } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/controller/S3Controller.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/controller/S3Controller.kt index f5784692..a42e22e7 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/aws/controller/S3Controller.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/controller/S3Controller.kt @@ -1,37 +1,22 @@ +package com.sbl.sulmun2yong.aws.controller import com.sbl.sulmun2yong.aws.controller.doc.S3ApiDoc -import org.springframework.beans.factory.annotation.Value +import com.sbl.sulmun2yong.aws.dto.request.S3UploadRequest +import com.sbl.sulmun2yong.aws.dto.response.S3UploadResponse +import com.sbl.sulmun2yong.aws.service.S3Service import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.ModelAttribute import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestPart import org.springframework.web.bind.annotation.RestController -import org.springframework.web.multipart.MultipartFile -import java.nio.file.Files -import java.nio.file.Path @RestController @RequestMapping("/api/v1/s3") class S3Controller( - @Value("\${aws.s3.bucket-name}") - private val bucketName: String, private val s3Service: S3Service, ) : S3ApiDoc { @PostMapping("/upload") - override fun uploadFile( - @RequestPart(value = "file") file: MultipartFile, - ): ResponseEntity { - val tempFile: Path = Files.createTempFile(file.originalFilename, null) - file.transferTo(tempFile) - - val bucketName = bucketName - val keyName = file.originalFilename ?: "default-name" - - s3Service.uploadFile(bucketName, keyName, tempFile.toFile()) - - // 임시 파일 삭제 - Files.delete(tempFile) - - return ResponseEntity.ok("File uploaded successfully") - } + override fun upload( + @ModelAttribute request: S3UploadRequest, + ): ResponseEntity = ResponseEntity.ok(s3Service.uploadFile(request.file)) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/controller/doc/S3ApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/controller/doc/S3ApiDoc.kt index 538ce813..2715ed3b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/aws/controller/doc/S3ApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/controller/doc/S3ApiDoc.kt @@ -1,17 +1,18 @@ package com.sbl.sulmun2yong.aws.controller.doc +import com.sbl.sulmun2yong.aws.dto.request.S3UploadRequest +import com.sbl.sulmun2yong.aws.dto.response.S3UploadResponse import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.multipart.MultipartFile +import org.springframework.web.bind.annotation.RequestBody @Tag(name = "AWS", description = "AWS 관련 API") interface S3ApiDoc { @Operation(summary = "S3 업로드") @PostMapping("/upload") - fun uploadFile( - @RequestParam("file") file: MultipartFile, - ): ResponseEntity + fun upload( + @RequestBody request: S3UploadRequest, + ): ResponseEntity } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/dto/request/S3UploadRequest.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/dto/request/S3UploadRequest.kt new file mode 100644 index 00000000..f14c552b --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/dto/request/S3UploadRequest.kt @@ -0,0 +1,7 @@ +package com.sbl.sulmun2yong.aws.dto.request + +import org.springframework.web.multipart.MultipartFile + +class S3UploadRequest( + val file: MultipartFile, +) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/dto/response/S3UploadResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/dto/response/S3UploadResponse.kt new file mode 100644 index 00000000..1b1876b6 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/dto/response/S3UploadResponse.kt @@ -0,0 +1,5 @@ +package com.sbl.sulmun2yong.aws.dto.response + +data class S3UploadResponse( + val fileUrl: String, +) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/FileNameTooLongException.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/FileNameTooLongException.kt new file mode 100644 index 00000000..f1265f0c --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/FileNameTooLongException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.aws.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class FileNameTooLongException : BusinessException(ErrorCode.FILE_NAME_TOO_LONG) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/FileNameTooShortException.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/FileNameTooShortException.kt new file mode 100644 index 00000000..e1b726e7 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/FileNameTooShortException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.aws.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class FileNameTooShortException : BusinessException(ErrorCode.FILE_NAME_TOO_SHORT) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/InvalidExtensionException.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/InvalidExtensionException.kt new file mode 100644 index 00000000..7aaf7235 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/InvalidExtensionException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.aws.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class InvalidExtensionException : BusinessException(ErrorCode.INVALID_EXTENSION) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/NoFileExistException.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/NoFileExistException.kt new file mode 100644 index 00000000..b1ce6ae5 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/NoFileExistException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.aws.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class NoFileExistException : BusinessException(ErrorCode.NO_FILE_EXIST) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/OutOfFileSizeException.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/OutOfFileSizeException.kt new file mode 100644 index 00000000..e214c0e1 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/OutOfFileSizeException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.aws.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class OutOfFileSizeException : BusinessException(ErrorCode.OUT_OF_FILE_SIZE) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt index e52996f0..bc34e1ec 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt @@ -1,18 +1,38 @@ +package com.sbl.sulmun2yong.aws.service + +import com.sbl.sulmun2yong.aws.dto.response.S3UploadResponse +import com.sbl.sulmun2yong.aws.exception.FileNameTooLongException +import com.sbl.sulmun2yong.aws.exception.FileNameTooShortException +import com.sbl.sulmun2yong.aws.exception.InvalidExtensionException +import com.sbl.sulmun2yong.aws.exception.NoFileExistException +import com.sbl.sulmun2yong.aws.exception.OutOfFileSizeException +import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service +import org.springframework.web.multipart.MultipartFile import software.amazon.awssdk.services.s3.S3Client import software.amazon.awssdk.services.s3.model.PutObjectRequest -import java.io.File -import java.nio.file.Paths +import java.nio.file.Files +import java.nio.file.Path +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter @Service class S3Service( + @Value("\${aws.s3.bucket-name}") + private val bucketName: String, + @Value("\${cloudfront.base-url}") + private val cloudFrontUrl: String, private val s3Client: S3Client, ) { - fun uploadFile( - bucketName: String, - keyName: String, - file: File, - ) { + fun uploadFile(receivedFile: MultipartFile): S3UploadResponse { + validateReceivedFile(receivedFile) + + val tempFilePath: Path = Files.createTempFile(receivedFile.originalFilename, null) + receivedFile.transferTo(tempFilePath) + + val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")) + val keyName = "${receivedFile.originalFilename}_$timestamp" + val putObjectRequest = PutObjectRequest .builder() @@ -20,6 +40,35 @@ class S3Service( .key(keyName) .build() - s3Client.putObject(putObjectRequest, Paths.get(file.toURI())) + s3Client.putObject(putObjectRequest, tempFilePath) + Files.deleteIfExists(tempFilePath) + + return S3UploadResponse("$cloudFrontUrl/$keyName") + } +} + +private fun validateReceivedFile(receivedFile: MultipartFile) { + val maxFileSize = 5 * 1024 * 1024 + val maxFileLength = 100 + val allowedExtension = mutableListOf("application/pdf", "image/jpeg", "image/png") + + val fileSize = receivedFile.size + val fileName: String? = receivedFile.originalFilename + val contentType = receivedFile.contentType + + if (receivedFile.isEmpty) { + throw NoFileExistException() + } + if (fileSize > maxFileSize) { + throw OutOfFileSizeException() + } + if (fileName.isNullOrBlank()) { + throw FileNameTooShortException() + } + if (fileName.length > maxFileLength) { + throw FileNameTooLongException() + } + if (!allowedExtension.contains(contentType)) { + throw InvalidExtensionException() } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt index 1987a688..de1ef412 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt @@ -1,3 +1,5 @@ +package com.sbl.sulmun2yong.global.config + import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -15,7 +17,6 @@ class S3Config( ) { @Bean fun s3Client(): S3Client { - // 액세스 키와 시크릿 키 설정 val awsCredentials = AwsBasicCredentials.create(accessKey, secretKey) return S3Client diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt index 9939de4d..5fc1d407 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt @@ -86,12 +86,10 @@ class SecurityConfig( authorizeHttpRequests { authorize("/swagger-ui/**", hasAnyRole("SWAGGER_USER", "ADMIN")) authorize("/v3/api-docs/**", hasAnyRole("SWAGGER_USER", "ADMIN")) - authorize("/api/v1/surveys/results/**", authenticated) authorize("/**", permitAll) } - formLogin { loginPage = "/login" } + formLogin {} } - return http.build() } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index cd5992e2..58def915 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -50,4 +50,11 @@ enum class ErrorCode( // Data (DT) INVALID_PHONE_NUMBER(HttpStatus.BAD_REQUEST, "DT0001", "유효하지 않은 전화번호입니다."), + + // AWS S3 (S3) + INVALID_EXTENSION(HttpStatus.BAD_REQUEST, "S30001", "지원하지 않는 확장자입니다."), + OUT_OF_FILE_SIZE(HttpStatus.BAD_REQUEST, "S30002", "파일 크기가 너무 큽니다."), + FILE_NAME_TOO_SHORT(HttpStatus.BAD_REQUEST, "S30003", "파일 이름이 너무 짧습니다."), + FILE_NAME_TOO_LONG(HttpStatus.BAD_REQUEST, "S30004", "파일 이름이 너무 깁니다."), + NO_FILE_EXIST(HttpStatus.BAD_REQUEST, "S30005", "파일이 존재하지 않습니다."), } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/AdminController.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/AdminController.kt index 0a18fd49..35364cad 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/AdminController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/AdminController.kt @@ -9,12 +9,14 @@ import org.springframework.http.ResponseEntity import org.springframework.security.core.session.SessionRegistry import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.ResponseBody import org.springframework.web.bind.annotation.RestController import java.util.UUID -@RestController("/api/v1/admin") +@RestController +@RequestMapping("/api/v1/admin") class AdminController( private val sessionRegistry: SessionRegistry, private val adminService: AdminService, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt index 795ac842..be0c7349 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/LoginController.kt @@ -8,13 +8,15 @@ import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.ResponseBody import org.springframework.web.bind.annotation.RestController import org.springframework.web.util.UriComponentsBuilder import java.net.URI -@RestController("/api/v1/login") +@RestController +@RequestMapping("/api/v1/login") class LoginController( @Value("\${frontend.base-url}") private val frontendBaseUrl: String, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/UserController.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/UserController.kt index 40fa123e..37b81743 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/UserController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/UserController.kt @@ -6,11 +6,13 @@ import com.sbl.sulmun2yong.user.dto.response.UserProfileResponse import com.sbl.sulmun2yong.user.service.UserService import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.ResponseBody import org.springframework.web.bind.annotation.RestController import java.util.UUID -@RestController("/api/v1/user") +@RestController +@RequestMapping("/api/v1/user") class UserController( private val userService: UserService, ) : UserApiDoc { diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/AdminApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/AdminApiDoc.kt index 854d1239..10813b3e 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/AdminApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/AdminApiDoc.kt @@ -8,12 +8,10 @@ import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import java.util.UUID @Tag(name = "Admin", description = "관리자 API") -@RequestMapping("/api/v1/admin") interface AdminApiDoc { @Operation(summary = "로그인한 사용자 조회") @GetMapping("/sessions/logged-in-users") diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt index 47e0082b..a3c0b3fb 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/LoginApiDoc.kt @@ -6,12 +6,10 @@ import jakarta.servlet.http.HttpServletRequest import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.ResponseBody @Tag(name = "Login", description = "로그인 API") -@RequestMapping("/api/v1/login") interface LoginApiDoc { @Operation(summary = "oauth 로그인") @GetMapping("/login/{provider}") diff --git a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/UserApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/UserApiDoc.kt index efaa6c2f..f0dcaef3 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/UserApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/user/controller/doc/UserApiDoc.kt @@ -6,12 +6,10 @@ import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.ResponseBody import java.util.UUID @Tag(name = "User", description = "회원 API") -@RequestMapping("/api/v1/user") interface UserApiDoc { @Operation(summary = "내 정보 조회") @GetMapping("/profile") diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9b3ff629..c25834f3 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -35,3 +35,6 @@ frontend: backend: base-url: http://localhost:8080 + +cloudfront: + base-url: https://files.sulmoon.io From 1df98bd4c2d219685a807df7d26dc02bab99f7a6 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 12 Sep 2024 23:21:35 +0900 Subject: [PATCH 059/201] =?UTF-8?q?[SBL-137]=20RewardInfo=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=EC=9D=84=20RewardSetting=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/global/error/ErrorCode.kt | 2 +- .../sbl/sulmun2yong/survey/domain/Survey.kt | 16 +++--- ...erRewardInfo.kt => ByUserRewardSetting.kt} | 4 +- ...dInfo.kt => ImmediateDrawRewardSetting.kt} | 10 ++-- .../{RewardInfo.kt => RewardSetting.kt} | 10 +++- .../survey/dto/response/SurveyInfoResponse.kt | 4 +- .../survey/dto/response/SurveyListResponse.kt | 6 +-- .../dto/response/SurveyMakeInfoResponse.kt | 4 +- .../survey/entity/SurveyDocument.kt | 8 +-- ...on.kt => InvalidRewardSettingException.kt} | 2 +- .../survey/service/SurveyInfoService.kt | 2 +- .../survey/service/SurveyManagementService.kt | 12 ++--- .../fixture/survey/SurveyFixtureFactory.kt | 8 +-- .../sulmun2yong/survey/domain/SurveyTest.kt | 24 ++++----- .../survey/domain/reward/RewardInfoTest.kt | 50 ------------------- .../survey/domain/reward/RewardSettingTest.kt | 50 +++++++++++++++++++ 16 files changed, 109 insertions(+), 103 deletions(-) rename src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/{ByUserRewardInfo.kt => ByUserRewardSetting.kt} (83%) rename src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/{ImmediateDrawRewardInfo.kt => ImmediateDrawRewardSetting.kt} (69%) rename src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/{RewardInfo.kt => RewardSetting.kt} (62%) rename src/main/kotlin/com/sbl/sulmun2yong/survey/exception/{InvalidRewardInfoException.kt => InvalidRewardSettingException.kt} (63%) delete mode 100644 src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardInfoTest.kt create mode 100644 src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index fd78febf..bbde6915 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -28,7 +28,7 @@ enum class ErrorCode( INVALID_UPDATE_SURVEY(HttpStatus.BAD_REQUEST, "SV0015", "설문 정보 갱신에 실패했습니다."), INVALID_SURVEY_ACCESS(HttpStatus.FORBIDDEN, "SV0016", "설문 접근 권한이 없습니다."), INVALID_SURVEY_START(HttpStatus.BAD_REQUEST, "SV0018", "설문 시작에 실패했습니다."), - INVALID_REWARD_INFO(HttpStatus.BAD_REQUEST, "SV0019", "유효하지 않은 리워드 정보입니다."), + INVALID_REWARD_SETTING(HttpStatus.BAD_REQUEST, "SV0019", "유효하지 않은 리워드 정보입니다."), INVALID_RESULT_DETAILS(HttpStatus.BAD_REQUEST, "SV0020", "유효하지 않은 설문 결과입니다."), INVALID_QUESTION_FILTER(HttpStatus.BAD_REQUEST, "SV0021", "유효하지 않은 질문 필터입니다."), diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index 30cb31fe..f174e97b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -2,8 +2,8 @@ package com.sbl.sulmun2yong.survey.domain import com.sbl.sulmun2yong.global.util.DateUtil import com.sbl.sulmun2yong.survey.domain.response.SurveyResponse -import com.sbl.sulmun2yong.survey.domain.reward.ByUserRewardInfo -import com.sbl.sulmun2yong.survey.domain.reward.RewardInfo +import com.sbl.sulmun2yong.survey.domain.reward.ByUserRewardSetting +import com.sbl.sulmun2yong.survey.domain.reward.RewardSetting import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId import com.sbl.sulmun2yong.survey.domain.section.SectionIds @@ -25,7 +25,7 @@ data class Survey( val finishedAt: Date, val status: SurveyStatus, val finishMessage: String, - val rewardInfo: RewardInfo, + val rewardSetting: RewardSetting, /** 해당 설문의 설문이용 노출 여부(false면 메인 페이지 노출 X, 링크를 통해서만 접근 가능) */ val isVisible: Boolean, val makerId: UUID, @@ -56,7 +56,7 @@ data class Survey( finishedAt = getDefaultFinishedAt(), status = SurveyStatus.NOT_STARTED, finishMessage = DEFAULT_FINISH_MESSAGE, - rewardInfo = ByUserRewardInfo(listOf()), + rewardSetting = ByUserRewardSetting(listOf()), isVisible = true, makerId = makerId, sections = listOf(Section.create()), @@ -98,7 +98,7 @@ data class Survey( thumbnail: String?, finishedAt: Date, finishMessage: String, - rewardInfo: RewardInfo, + rewardSetting: RewardSetting, isVisible: Boolean, sections: List
, ): Survey { @@ -106,7 +106,7 @@ data class Survey( require( status == SurveyStatus.NOT_STARTED || status == SurveyStatus.IN_MODIFICATION && - rewardInfo == this.rewardInfo, + rewardSetting == this.rewardSetting, ) { throw InvalidUpdateSurveyException() } @@ -116,7 +116,7 @@ data class Survey( thumbnail = thumbnail, finishedAt = finishedAt, finishMessage = finishMessage, - rewardInfo = rewardInfo, + rewardSetting = rewardSetting, isVisible = isVisible, sections = sections, ) @@ -129,7 +129,7 @@ data class Survey( return copy(status = SurveyStatus.IN_PROGRESS, publishedAt = DateUtil.getCurrentDate()) } - fun isImmediateDraw() = rewardInfo.isImmediateDraw + fun isImmediateDraw() = rewardSetting.isImmediateDraw private fun isSectionsUnique() = sections.size == sections.distinctBy { it.id }.size diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ByUserRewardInfo.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ByUserRewardSetting.kt similarity index 83% rename from src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ByUserRewardInfo.kt rename to src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ByUserRewardSetting.kt index 2a026da7..86a216e4 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ByUserRewardInfo.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ByUserRewardSetting.kt @@ -1,9 +1,9 @@ package com.sbl.sulmun2yong.survey.domain.reward /** 사용자 설정(사용자가 직접 추첨 or 추첨을 진행하지 않음) */ -data class ByUserRewardInfo( +data class ByUserRewardSetting( override val rewards: List, -) : RewardInfo { +) : RewardSetting { override val targetParticipantCount = null override val isImmediateDraw = false } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawRewardInfo.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawRewardSetting.kt similarity index 69% rename from src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawRewardInfo.kt rename to src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawRewardSetting.kt index 6b7f774c..8531f6e6 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawRewardInfo.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawRewardSetting.kt @@ -1,18 +1,18 @@ package com.sbl.sulmun2yong.survey.domain.reward -import com.sbl.sulmun2yong.survey.exception.InvalidRewardInfoException +import com.sbl.sulmun2yong.survey.exception.InvalidRewardSettingException /** 즉시 추첨(설문 참여 후 추첨 보드를 통해 즉시 추첨 진행) */ -data class ImmediateDrawRewardInfo( +data class ImmediateDrawRewardSetting( override val rewards: List, override val targetParticipantCount: Int, -) : RewardInfo { +) : RewardSetting { override val isImmediateDraw = true init { - require(rewards.isNotEmpty()) { throw InvalidRewardInfoException() } + require(rewards.isNotEmpty()) { throw InvalidRewardSettingException() } // 즉시 추첨은 리워드 개수의 총합이 목표 참여자 수보다 적어야함 - require(isTargetParticipantValid()) { throw InvalidRewardInfoException() } + require(isTargetParticipantValid()) { throw InvalidRewardSettingException() } } private fun isTargetParticipantValid() = targetParticipantCount >= getRewardCount() diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardInfo.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt similarity index 62% rename from src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardInfo.kt rename to src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt index a733461a..b7f4688a 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardInfo.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt @@ -1,7 +1,7 @@ package com.sbl.sulmun2yong.survey.domain.reward /** 설문의 리워드와 관련된 정보를 담고있는 클래스 */ -interface RewardInfo { +interface RewardSetting { val rewards: List val targetParticipantCount: Int? val isImmediateDraw: Boolean @@ -10,7 +10,13 @@ interface RewardInfo { fun of( rewards: List, targetParticipantCount: Int?, - ) = if (targetParticipantCount != null) ImmediateDrawRewardInfo(rewards, targetParticipantCount) else ByUserRewardInfo(rewards) + ) = if (targetParticipantCount != + null + ) { + ImmediateDrawRewardSetting(rewards, targetParticipantCount) + } else { + ByUserRewardSetting(rewards) + } } fun getRewardCount() = rewards.sumOf { it.count } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt index 5e6a6548..a7a0a99c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt @@ -26,8 +26,8 @@ data class SurveyInfoResponse( status = survey.status, finishedAt = survey.finishedAt, currentParticipants = currentParticipants, - targetParticipants = survey.rewardInfo.targetParticipantCount, - rewards = survey.rewardInfo.rewards.map { it.toResponse() }, + targetParticipants = survey.rewardSetting.targetParticipantCount, + rewards = survey.rewardSetting.rewards.map { it.toResponse() }, thumbnail = survey.thumbnail ?: Survey.DEFAULT_THUMBNAIL_URL, ) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt index b6065e24..bf0ccd5c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt @@ -23,10 +23,10 @@ data class SurveyListResponse( thumbnail = it.thumbnail ?: Survey.DEFAULT_THUMBNAIL_URL, title = it.title, description = it.description, - targetParticipants = it.rewardInfo.targetParticipantCount, + targetParticipants = it.rewardSetting.targetParticipantCount, finishedAt = it.finishedAt, - rewardCount = it.rewardInfo.getRewardCount(), - rewards = it.rewardInfo.rewards.toRewardInfoResponses(), + rewardCount = it.rewardSetting.getRewardCount(), + rewards = it.rewardSetting.rewards.toRewardInfoResponses(), ) }, ) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt index 121e3a91..5d410548 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt @@ -33,8 +33,8 @@ data class SurveyMakeInfoResponse( finishedAt = survey.finishedAt, status = survey.status, finishMessage = survey.finishMessage, - targetParticipantCount = survey.rewardInfo.targetParticipantCount, - rewards = survey.rewardInfo.rewards.map { RewardMakeInfoResponse(it.name, it.category, it.count) }, + targetParticipantCount = survey.rewardSetting.targetParticipantCount, + rewards = survey.rewardSetting.rewards.map { RewardMakeInfoResponse(it.name, it.category, it.count) }, isVisible = survey.isVisible, sections = survey.sections.map { SectionMakeInfoResponse.from(it) }, ) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt index 0447bce2..ad533150 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt @@ -11,7 +11,7 @@ import com.sbl.sulmun2yong.survey.domain.question.impl.StandardMultipleChoiceQue import com.sbl.sulmun2yong.survey.domain.question.impl.StandardSingleChoiceQuestion import com.sbl.sulmun2yong.survey.domain.question.impl.StandardTextQuestion import com.sbl.sulmun2yong.survey.domain.reward.Reward -import com.sbl.sulmun2yong.survey.domain.reward.RewardInfo +import com.sbl.sulmun2yong.survey.domain.reward.RewardSetting import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.routing.RoutingType import com.sbl.sulmun2yong.survey.domain.section.Section @@ -50,9 +50,9 @@ data class SurveyDocument( finishedAt = survey.finishedAt, status = survey.status, finishMessage = survey.finishMessage, - targetParticipantCount = survey.rewardInfo.targetParticipantCount, + targetParticipantCount = survey.rewardSetting.targetParticipantCount, makerId = survey.makerId, - rewards = survey.rewardInfo.rewards.map { it.toDocument() }, + rewards = survey.rewardSetting.rewards.map { it.toDocument() }, isVisible = survey.isVisible, sections = survey.sections.map { it.toDocument() }, ) @@ -159,7 +159,7 @@ data class SurveyDocument( publishedAt = this.publishedAt, status = this.status, finishMessage = this.finishMessage, - rewardInfo = RewardInfo.of(this.rewards.map { it.toDomain() }, this.targetParticipantCount), + rewardSetting = RewardSetting.of(this.rewards.map { it.toDomain() }, this.targetParticipantCount), isVisible = this.isVisible, makerId = this.makerId, sections = this.sections.map { it.toDomain(sectionIds) }, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidRewardInfoException.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidRewardSettingException.kt similarity index 63% rename from src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidRewardInfoException.kt rename to src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidRewardSettingException.kt index 998ffe9a..34b02bf3 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidRewardInfoException.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidRewardSettingException.kt @@ -3,4 +3,4 @@ package com.sbl.sulmun2yong.survey.exception import com.sbl.sulmun2yong.global.error.BusinessException import com.sbl.sulmun2yong.global.error.ErrorCode -class InvalidRewardInfoException : BusinessException(ErrorCode.INVALID_REWARD_INFO) +class InvalidRewardSettingException : BusinessException(ErrorCode.INVALID_REWARD_SETTING) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt index 52e334d4..45025c64 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt @@ -36,7 +36,7 @@ class SurveyInfoService( val survey = surveyAdapter.getSurvey(surveyId) if (survey.status == SurveyStatus.NOT_STARTED) throw InvalidSurveyAccessException() val selectedTicketCount = - if (survey.rewardInfo.isImmediateDraw) drawingBoardAdapter.getBySurveyId(surveyId).selectedTicketCount else null + if (survey.rewardSetting.isImmediateDraw) drawingBoardAdapter.getBySurveyId(surveyId).selectedTicketCount else null return SurveyInfoResponse.of(survey, selectedTicketCount) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index 9c4fdb6f..1859cbbc 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -4,9 +4,9 @@ import com.sbl.sulmun2yong.drawing.adapter.DrawingBoardAdapter import com.sbl.sulmun2yong.drawing.domain.DrawingBoard import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter import com.sbl.sulmun2yong.survey.domain.Survey -import com.sbl.sulmun2yong.survey.domain.reward.ImmediateDrawRewardInfo +import com.sbl.sulmun2yong.survey.domain.reward.ImmediateDrawRewardSetting import com.sbl.sulmun2yong.survey.domain.reward.Reward -import com.sbl.sulmun2yong.survey.domain.reward.RewardInfo +import com.sbl.sulmun2yong.survey.domain.reward.RewardSetting import com.sbl.sulmun2yong.survey.domain.section.SectionId import com.sbl.sulmun2yong.survey.domain.section.SectionIds import com.sbl.sulmun2yong.survey.dto.request.SurveySaveRequest @@ -46,7 +46,7 @@ class SurveyManagementService( thumbnail = this.thumbnail, finishedAt = this.finishedAt, finishMessage = this.finishMessage, - rewardInfo = RewardInfo.of(rewards, this.targetParticipantCount), + rewardSetting = RewardSetting.of(rewards, this.targetParticipantCount), isVisible = this.isVisible, sections = this.sections.map { it.toDomain(sectionIds) }, ) @@ -73,12 +73,12 @@ class SurveyManagementService( // 현재 유저와 설문 제작자가 다를 경우 예외 발생 if (survey.makerId != makerId) throw InvalidSurveyAccessException() surveyAdapter.save(survey.start()) - if (survey.rewardInfo is ImmediateDrawRewardInfo) { + if (survey.rewardSetting is ImmediateDrawRewardSetting) { val drawingBoard = DrawingBoard.create( surveyId = survey.id, - boardSize = survey.rewardInfo.targetParticipantCount, - rewards = survey.rewardInfo.rewards, + boardSize = survey.rewardSetting.targetParticipantCount, + rewards = survey.rewardSetting.rewards, ) drawingBoardAdapter.save(drawingBoard) } diff --git a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt index 8e36c6f7..d37a850c 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt @@ -3,7 +3,7 @@ package com.sbl.sulmun2yong.fixture.survey import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.domain.reward.Reward -import com.sbl.sulmun2yong.survey.domain.reward.RewardInfo +import com.sbl.sulmun2yong.survey.domain.reward.RewardSetting import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId @@ -67,13 +67,13 @@ object SurveyFixtureFactory { status = status, finishMessage = finishMessage + id, makerId = makerId, - rewardInfo = createRewardInfo(rewards, targetParticipantCount), + rewardSetting = createRewardSetting(rewards, targetParticipantCount), isVisible = isVisible, sections = sections, ) - fun createRewardInfo( + fun createRewardSetting( rewards: List = REWARDS, targetParticipantCount: Int? = TARGET_PARTICIPANT_COUNT, - ) = RewardInfo.of(rewards, targetParticipantCount) + ) = RewardSetting.of(rewards, targetParticipantCount) } diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt index 81926c55..66806b6c 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt @@ -9,14 +9,14 @@ import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.SECTIONS import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.SURVEY_STATUS import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.THUMBNAIL import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.TITLE -import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.createRewardInfo +import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.createRewardSetting import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.createSurvey import com.sbl.sulmun2yong.global.util.DateUtil import com.sbl.sulmun2yong.survey.domain.response.SectionResponse import com.sbl.sulmun2yong.survey.domain.response.SurveyResponse -import com.sbl.sulmun2yong.survey.domain.reward.ByUserRewardInfo +import com.sbl.sulmun2yong.survey.domain.reward.ByUserRewardSetting import com.sbl.sulmun2yong.survey.domain.reward.Reward -import com.sbl.sulmun2yong.survey.domain.reward.RewardInfo +import com.sbl.sulmun2yong.survey.domain.reward.RewardSetting import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId @@ -82,7 +82,7 @@ class SurveyTest { assertEquals(PUBLISHED_AT, this.publishedAt) assertEquals(SURVEY_STATUS, this.status) assertEquals(FINISH_MESSAGE + id, this.finishMessage) - assertEquals(createRewardInfo(), this.rewardInfo) + assertEquals(createRewardSetting(), this.rewardSetting) assertEquals(true, this.isVisible) assertEquals(makerId, this.makerId) assertEquals(SECTIONS, this.sections) @@ -107,7 +107,7 @@ class SurveyTest { assertEquals(null, this.publishedAt) assertEquals(SurveyStatus.NOT_STARTED, this.status) assertEquals(Survey.DEFAULT_FINISH_MESSAGE, this.finishMessage) - assertEquals(ByUserRewardInfo(listOf()), this.rewardInfo) + assertEquals(ByUserRewardSetting(listOf()), this.rewardSetting) assertEquals(true, this.isVisible) assertEquals(makerId, this.makerId) assertEquals(listOf(this.sections.first()), this.sections) @@ -244,7 +244,7 @@ class SurveyTest { val newDescription = "new description" val newThumbnail = "new thumbnail" val newFinishMessage = "new finish message" - val newRewardInfo = RewardInfo.of(listOf(Reward("new reward", "new category", 1)), 10) + val newRewardSetting = RewardSetting.of(listOf(Reward("new reward", "new category", 1)), 10) val newIsVisible = false val sectionId = SectionId.Standard(UUID.randomUUID()) val newSections = @@ -268,7 +268,7 @@ class SurveyTest { thumbnail = newThumbnail, finishedAt = survey.finishedAt, finishMessage = newFinishMessage, - rewardInfo = newRewardInfo, + rewardSetting = newRewardSetting, isVisible = newIsVisible, sections = listOf( @@ -290,7 +290,7 @@ class SurveyTest { assertEquals(newThumbnail, this.thumbnail) assertEquals(survey.finishedAt, this.finishedAt) assertEquals(newFinishMessage, this.finishMessage) - assertEquals(newRewardInfo, this.rewardInfo) + assertEquals(newRewardSetting, this.rewardSetting) assertEquals(isVisible, this.isVisible) assertEquals(newSections, this.sections) } @@ -312,7 +312,7 @@ class SurveyTest { thumbnail = survey1.thumbnail, finishedAt = survey1.finishedAt, finishMessage = survey1.finishMessage, - rewardInfo = survey1.rewardInfo, + rewardSetting = survey1.rewardSetting, isVisible = survey1.isVisible, sections = survey1.sections, ) @@ -325,7 +325,7 @@ class SurveyTest { thumbnail = survey2.thumbnail, finishedAt = survey2.finishedAt, finishMessage = survey2.finishMessage, - rewardInfo = survey2.rewardInfo, + rewardSetting = survey2.rewardSetting, isVisible = survey2.isVisible, sections = survey2.sections, ) @@ -338,7 +338,7 @@ class SurveyTest { thumbnail = survey3.thumbnail, finishedAt = survey3.finishedAt, finishMessage = survey3.finishMessage, - rewardInfo = survey3.rewardInfo, + rewardSetting = survey3.rewardSetting, isVisible = survey3.isVisible, sections = survey3.sections, ) @@ -351,7 +351,7 @@ class SurveyTest { thumbnail = survey3.thumbnail, finishedAt = survey3.finishedAt, finishMessage = survey3.finishMessage, - rewardInfo = ByUserRewardInfo(listOf()), + rewardSetting = ByUserRewardSetting(listOf()), isVisible = survey3.isVisible, sections = survey3.sections, ) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardInfoTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardInfoTest.kt deleted file mode 100644 index a4d8662d..00000000 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardInfoTest.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.sbl.sulmun2yong.survey.domain.reward - -import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory -import com.sbl.sulmun2yong.survey.exception.InvalidRewardInfoException -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertEquals - -class RewardInfoTest { - @Test - fun `리워드 정보를 생성하면 정보가 올바르게 설정된다`() { - // given - val rewards = SurveyFixtureFactory.REWARDS - val targetParticipantCount = SurveyFixtureFactory.TARGET_PARTICIPANT_COUNT - - // when - val immediateRewardInfo1 = ImmediateDrawRewardInfo(rewards, targetParticipantCount) - val immediateRewardInfo2 = RewardInfo.of(rewards, targetParticipantCount) - val freeRewardInfo1 = ByUserRewardInfo(listOf()) - val freeRewardInfo2 = RewardInfo.of(listOf(), null) - - // then - assertEquals(rewards, immediateRewardInfo1.rewards) - assertEquals(targetParticipantCount, immediateRewardInfo1.targetParticipantCount) - assertEquals(true, immediateRewardInfo1.isImmediateDraw) - assertEquals(rewards, immediateRewardInfo2.rewards) - assertEquals(targetParticipantCount, immediateRewardInfo2.targetParticipantCount) - assertEquals(true, immediateRewardInfo2.isImmediateDraw) - assertEquals(emptyList(), freeRewardInfo1.rewards) - assertEquals(null, freeRewardInfo1.targetParticipantCount) - assertEquals(false, freeRewardInfo1.isImmediateDraw) - assertEquals(emptyList(), freeRewardInfo2.rewards) - assertEquals(null, freeRewardInfo2.targetParticipantCount) - assertEquals(false, freeRewardInfo2.isImmediateDraw) - } - - @Test - fun `즉시 추첨은 리워드가 하나 이상 존재해야한다`() { - assertThrows { - ImmediateDrawRewardInfo(listOf(), 0) - } - } - - @Test - fun `즉시 추첨은 리워드 개수의 총합이 목표 참여자 수보다 적어야한다`() { - assertThrows { - ImmediateDrawRewardInfo(SurveyFixtureFactory.REWARDS, 1) - } - } -} diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt new file mode 100644 index 00000000..0b7ba408 --- /dev/null +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt @@ -0,0 +1,50 @@ +package com.sbl.sulmun2yong.survey.domain.reward + +import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory +import com.sbl.sulmun2yong.survey.exception.InvalidRewardSettingException +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertEquals + +class RewardSettingTest { + @Test + fun `리워드 정보를 생성하면 정보가 올바르게 설정된다`() { + // given + val rewards = SurveyFixtureFactory.REWARDS + val targetParticipantCount = SurveyFixtureFactory.TARGET_PARTICIPANT_COUNT + + // when + val immediateRewardSetting1 = ImmediateDrawRewardSetting(rewards, targetParticipantCount) + val immediateRewardSetting2 = RewardSetting.of(rewards, targetParticipantCount) + val freeRewardSetting1 = ByUserRewardSetting(listOf()) + val freeRewardSetting2 = RewardSetting.of(listOf(), null) + + // then + assertEquals(rewards, immediateRewardSetting1.rewards) + assertEquals(targetParticipantCount, immediateRewardSetting1.targetParticipantCount) + assertEquals(true, immediateRewardSetting1.isImmediateDraw) + assertEquals(rewards, immediateRewardSetting2.rewards) + assertEquals(targetParticipantCount, immediateRewardSetting2.targetParticipantCount) + assertEquals(true, immediateRewardSetting2.isImmediateDraw) + assertEquals(emptyList(), freeRewardSetting1.rewards) + assertEquals(null, freeRewardSetting1.targetParticipantCount) + assertEquals(false, freeRewardSetting1.isImmediateDraw) + assertEquals(emptyList(), freeRewardSetting2.rewards) + assertEquals(null, freeRewardSetting2.targetParticipantCount) + assertEquals(false, freeRewardSetting2.isImmediateDraw) + } + + @Test + fun `즉시 추첨은 리워드가 하나 이상 존재해야한다`() { + assertThrows { + ImmediateDrawRewardSetting(listOf(), 0) + } + } + + @Test + fun `즉시 추첨은 리워드 개수의 총합이 목표 참여자 수보다 적어야한다`() { + assertThrows { + ImmediateDrawRewardSetting(SurveyFixtureFactory.REWARDS, 1) + } + } +} From d4826087bcbd33a3224ab1d004207cfea43a003e Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 12 Sep 2024 23:54:32 +0900 Subject: [PATCH 060/201] =?UTF-8?q?[SBL-137]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=A2=85=EB=A3=8C=EC=9D=BC=EC=9D=84=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/global/error/ErrorCode.kt | 1 + .../survey/domain/reward/FinishedAt.kt | 20 +++++++++++++++++++ .../exception/InvalidFinishedAtException.kt | 6 ++++++ 3 files changed, 27 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/FinishedAt.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidFinishedAtException.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index bbde6915..b9742762 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -31,6 +31,7 @@ enum class ErrorCode( INVALID_REWARD_SETTING(HttpStatus.BAD_REQUEST, "SV0019", "유효하지 않은 리워드 정보입니다."), INVALID_RESULT_DETAILS(HttpStatus.BAD_REQUEST, "SV0020", "유효하지 않은 설문 결과입니다."), INVALID_QUESTION_FILTER(HttpStatus.BAD_REQUEST, "SV0021", "유효하지 않은 질문 필터입니다."), + INVALID_FINISHED_AT(HttpStatus.BAD_REQUEST, "SV0022", "유효하지 않은 마감일입니다."), // Drawing (DR) INVALID_DRAWING_BOARD(HttpStatus.BAD_REQUEST, "DR0001", "유효하지 않은 추첨 보드입니다."), diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/FinishedAt.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/FinishedAt.kt new file mode 100644 index 00000000..d4a1ed51 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/FinishedAt.kt @@ -0,0 +1,20 @@ +package com.sbl.sulmun2yong.survey.domain.reward + +import com.sbl.sulmun2yong.survey.exception.InvalidFinishedAtException +import java.util.Calendar +import java.util.Date + +/** 설문 종료일, 1시간 단위(분 단위 이하 0) */ +data class FinishedAt( + val value: Date, +) { + init { + require(isMinuteAndBelowZero()) { throw InvalidFinishedAtException() } + } + + private fun isMinuteAndBelowZero(): Boolean { + val calendar = Calendar.getInstance() + calendar.time = value + return calendar.get(Calendar.MINUTE) == 0 && calendar.get(Calendar.SECOND) == 0 && calendar.get(Calendar.MILLISECOND) == 0 + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidFinishedAtException.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidFinishedAtException.kt new file mode 100644 index 00000000..d21401fd --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidFinishedAtException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.survey.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class InvalidFinishedAtException : BusinessException(ErrorCode.INVALID_FINISHED_AT) From 8dd47298ddb6848ef0292238360525d0804c035b Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 12 Sep 2024 23:57:32 +0900 Subject: [PATCH 061/201] =?UTF-8?q?[SBL-137]=20=EB=A6=AC=EC=9B=8C=EB=93=9C?= =?UTF-8?q?=20=EC=A7=80=EA=B8=89=20=EC=84=A4=EC=A0=95=EC=9D=98=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=EC=9D=84=20=EB=8B=B4=EC=9D=80=20Enum=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/survey/domain/reward/RewardSettingType.kt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingType.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingType.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingType.kt new file mode 100644 index 00000000..46898ff1 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingType.kt @@ -0,0 +1,7 @@ +package com.sbl.sulmun2yong.survey.domain.reward + +enum class RewardSettingType { + IMMEDIATE_DRAW, + SELF_MANAGEMENT, + NO_REWARD, +} From 43b1cc2625f24f6ee4c953bd33a4f3cb1524d225 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 01:02:11 +0900 Subject: [PATCH 062/201] =?UTF-8?q?[SBL-137]=20=EB=A6=AC=EC=9B=8C=EB=93=9C?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EA=B4=80=EB=A0=A8=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index b9742762..1ffa9f89 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -28,7 +28,7 @@ enum class ErrorCode( INVALID_UPDATE_SURVEY(HttpStatus.BAD_REQUEST, "SV0015", "설문 정보 갱신에 실패했습니다."), INVALID_SURVEY_ACCESS(HttpStatus.FORBIDDEN, "SV0016", "설문 접근 권한이 없습니다."), INVALID_SURVEY_START(HttpStatus.BAD_REQUEST, "SV0018", "설문 시작에 실패했습니다."), - INVALID_REWARD_SETTING(HttpStatus.BAD_REQUEST, "SV0019", "유효하지 않은 리워드 정보입니다."), + INVALID_REWARD_SETTING(HttpStatus.BAD_REQUEST, "SV0019", "유효하지 않은 리워드 지급 설정입니다."), INVALID_RESULT_DETAILS(HttpStatus.BAD_REQUEST, "SV0020", "유효하지 않은 설문 결과입니다."), INVALID_QUESTION_FILTER(HttpStatus.BAD_REQUEST, "SV0021", "유효하지 않은 질문 필터입니다."), INVALID_FINISHED_AT(HttpStatus.BAD_REQUEST, "SV0022", "유효하지 않은 마감일입니다."), From 73f7892b3013fd9ecb45834475b44b4d2d5235a8 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 01:03:37 +0900 Subject: [PATCH 063/201] =?UTF-8?q?[SBL-137]=20RewardSetting=EC=97=90=20fi?= =?UTF-8?q?nishedAt=EA=B3=BC=20type=20=EC=B6=94=EA=B0=80,=20=EB=A6=AC?= =?UTF-8?q?=EC=9B=8C=EB=93=9C=20=EB=AF=B8=20=EC=A7=80=EA=B8=89=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80,=20=EB=82=98=EB=A8=B8=EC=A7=80?= =?UTF-8?q?=EB=8F=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/reward/ByUserRewardSetting.kt | 9 -------- ...wardSetting.kt => ImmediateDrawSetting.kt} | 5 +++-- .../survey/domain/reward/NoRewardSetting.kt | 9 ++++++++ .../survey/domain/reward/RewardSetting.kt | 21 +++++++++++++------ .../domain/reward/SelfManagementSetting.kt | 16 ++++++++++++++ 5 files changed, 43 insertions(+), 17 deletions(-) delete mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ByUserRewardSetting.kt rename src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/{ImmediateDrawRewardSetting.kt => ImmediateDrawSetting.kt} (84%) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/NoRewardSetting.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/SelfManagementSetting.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ByUserRewardSetting.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ByUserRewardSetting.kt deleted file mode 100644 index 86a216e4..00000000 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ByUserRewardSetting.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.sbl.sulmun2yong.survey.domain.reward - -/** 사용자 설정(사용자가 직접 추첨 or 추첨을 진행하지 않음) */ -data class ByUserRewardSetting( - override val rewards: List, -) : RewardSetting { - override val targetParticipantCount = null - override val isImmediateDraw = false -} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawRewardSetting.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawSetting.kt similarity index 84% rename from src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawRewardSetting.kt rename to src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawSetting.kt index 8531f6e6..80aec321 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawRewardSetting.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/ImmediateDrawSetting.kt @@ -3,11 +3,12 @@ package com.sbl.sulmun2yong.survey.domain.reward import com.sbl.sulmun2yong.survey.exception.InvalidRewardSettingException /** 즉시 추첨(설문 참여 후 추첨 보드를 통해 즉시 추첨 진행) */ -data class ImmediateDrawRewardSetting( +data class ImmediateDrawSetting( override val rewards: List, override val targetParticipantCount: Int, + override val finishedAt: FinishedAt, ) : RewardSetting { - override val isImmediateDraw = true + override val type = RewardSettingType.IMMEDIATE_DRAW init { require(rewards.isNotEmpty()) { throw InvalidRewardSettingException() } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/NoRewardSetting.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/NoRewardSetting.kt new file mode 100644 index 00000000..d9b2df93 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/NoRewardSetting.kt @@ -0,0 +1,9 @@ +package com.sbl.sulmun2yong.survey.domain.reward + +/** 직접 관리(사용자가 직접 리워드 지급) */ +object NoRewardSetting : RewardSetting { + override val rewards = emptyList() + override val finishedAt = null + override val type = RewardSettingType.NO_REWARD + override val targetParticipantCount = null +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt index b7f4688a..508962e7 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt @@ -1,21 +1,30 @@ package com.sbl.sulmun2yong.survey.domain.reward -/** 설문의 리워드와 관련된 정보를 담고있는 클래스 */ +import com.sbl.sulmun2yong.survey.exception.InvalidRewardSettingException +import java.util.Date + +/** 설문의 리워드 지급에 대한 설정을 담고있는 클래스 */ interface RewardSetting { + val type: RewardSettingType val rewards: List val targetParticipantCount: Int? + val finishedAt: FinishedAt? val isImmediateDraw: Boolean + get() = type == RewardSettingType.IMMEDIATE_DRAW companion object { fun of( rewards: List, targetParticipantCount: Int?, - ) = if (targetParticipantCount != - null - ) { - ImmediateDrawRewardSetting(rewards, targetParticipantCount) + finishedAt: Date?, + ) = if (rewards.isEmpty()) { + NoRewardSetting + } else if (finishedAt == null) { + throw InvalidRewardSettingException() + } else if (targetParticipantCount == null) { + SelfManagementSetting(rewards, FinishedAt(finishedAt)) } else { - ByUserRewardSetting(rewards) + ImmediateDrawSetting(rewards, targetParticipantCount, FinishedAt(finishedAt)) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/SelfManagementSetting.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/SelfManagementSetting.kt new file mode 100644 index 00000000..10a977a9 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/SelfManagementSetting.kt @@ -0,0 +1,16 @@ +package com.sbl.sulmun2yong.survey.domain.reward + +import com.sbl.sulmun2yong.survey.exception.InvalidRewardSettingException + +/** 직접 관리(사용자가 직접 리워드 지급) */ +data class SelfManagementSetting( + override val rewards: List, + override val finishedAt: FinishedAt, +) : RewardSetting { + override val type = RewardSettingType.SELF_MANAGEMENT + override val targetParticipantCount = null + + init { + require(rewards.isNotEmpty()) { throw InvalidRewardSettingException() } + } +} From 36b6cb064e851778c47a52b2fa1abd168a24b77c Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 01:05:34 +0900 Subject: [PATCH 064/201] =?UTF-8?q?[SBL-137]=20Survey=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=97=90=EC=84=9C=20finishedAt=EC=9D=84=20rewardSetti?= =?UTF-8?q?ng=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/domain/Survey.kt | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index f174e97b..4d7f00ae 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -2,8 +2,10 @@ package com.sbl.sulmun2yong.survey.domain import com.sbl.sulmun2yong.global.util.DateUtil import com.sbl.sulmun2yong.survey.domain.response.SurveyResponse -import com.sbl.sulmun2yong.survey.domain.reward.ByUserRewardSetting +import com.sbl.sulmun2yong.survey.domain.reward.ImmediateDrawSetting +import com.sbl.sulmun2yong.survey.domain.reward.NoRewardSetting import com.sbl.sulmun2yong.survey.domain.reward.RewardSetting +import com.sbl.sulmun2yong.survey.domain.reward.SelfManagementSetting import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId import com.sbl.sulmun2yong.survey.domain.section.SectionIds @@ -11,8 +13,6 @@ import com.sbl.sulmun2yong.survey.exception.InvalidSurveyException import com.sbl.sulmun2yong.survey.exception.InvalidSurveyResponseException import com.sbl.sulmun2yong.survey.exception.InvalidSurveyStartException import com.sbl.sulmun2yong.survey.exception.InvalidUpdateSurveyException -import java.time.LocalDateTime -import java.time.ZoneId import java.util.Date import java.util.UUID @@ -22,7 +22,6 @@ data class Survey( val description: String, val thumbnail: String?, val publishedAt: Date?, - val finishedAt: Date, val status: SurveyStatus, val finishMessage: String, val rewardSetting: RewardSetting, @@ -43,7 +42,6 @@ data class Survey( const val DEFAULT_THUMBNAIL_URL = "https://test-oriddle-bucket.s3.ap-northeast-2.amazonaws.com/surveyImage.webp" const val DEFAULT_TITLE = "제목 없는 설문" const val DEFAULT_DESCRIPTION = "" - const val DEFAULT_SURVEY_DURATION = 60L const val DEFAULT_FINISH_MESSAGE = "설문에 참여해주셔서 감사합니다." fun create(makerId: UUID) = @@ -53,26 +51,13 @@ data class Survey( description = DEFAULT_DESCRIPTION, thumbnail = null, publishedAt = null, - finishedAt = getDefaultFinishedAt(), status = SurveyStatus.NOT_STARTED, finishMessage = DEFAULT_FINISH_MESSAGE, - rewardSetting = ByUserRewardSetting(listOf()), + rewardSetting = NoRewardSetting, isVisible = true, makerId = makerId, sections = listOf(Section.create()), ) - - /** 설문 종료일 기본 값은 현재 날짜 기준 60일 뒤, 초 단위 이하 제거 */ - private fun getDefaultFinishedAt() = - Date.from( - LocalDateTime - .now() - .plusDays(DEFAULT_SURVEY_DURATION) - .withSecond(0) - .withNano(0) - .atZone(ZoneId.systemDefault()) - .toInstant(), - ) } /** 설문의 응답 순서가 유효한지, 응답이 각 섹션에 유효한지 확인하는 메서드 */ @@ -96,7 +81,6 @@ data class Survey( title: String, description: String, thumbnail: String?, - finishedAt: Date, finishMessage: String, rewardSetting: RewardSetting, isVisible: Boolean, @@ -114,7 +98,6 @@ data class Survey( title = title, description = description, thumbnail = thumbnail, - finishedAt = finishedAt, finishMessage = finishMessage, rewardSetting = rewardSetting, isVisible = isVisible, @@ -135,7 +118,12 @@ data class Survey( private fun isSurveyStatusValid() = publishedAt != null || status == SurveyStatus.NOT_STARTED - private fun isFinishedAtAfterPublishedAt() = publishedAt == null || finishedAt.after(publishedAt) + private fun isFinishedAtAfterPublishedAt(): Boolean { + if (publishedAt == null) return true + if (rewardSetting is SelfManagementSetting) return rewardSetting.finishedAt.value.after(publishedAt) + if (rewardSetting is ImmediateDrawSetting) return rewardSetting.finishedAt.value.after(publishedAt) + return true + } private fun isSectionIdsValid(): Boolean { val sectionIds = SectionIds.from(sections.map { it.id }) From 880d92e5f43e04f49c23a300ac515a3f95b71c03 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 01:06:28 +0900 Subject: [PATCH 065/201] =?UTF-8?q?[SBL-137]=20=EB=A6=AC=EC=9B=8C=EB=93=9C?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95,=20=EC=84=A4=EB=AC=B8=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=EC=9D=98=20=EC=88=98=EC=A0=95=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20Document,=20Service=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt | 7 +++---- .../sulmun2yong/survey/service/SurveyManagementService.kt | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt index ad533150..f8cb7b92 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt @@ -30,7 +30,7 @@ data class SurveyDocument( val description: String, val thumbnail: String?, val publishedAt: Date?, - val finishedAt: Date, + val finishedAt: Date?, val status: SurveyStatus, val finishMessage: String, val targetParticipantCount: Int?, @@ -47,7 +47,7 @@ data class SurveyDocument( description = survey.description, thumbnail = survey.thumbnail, publishedAt = survey.publishedAt, - finishedAt = survey.finishedAt, + finishedAt = survey.rewardSetting.finishedAt?.value, status = survey.status, finishMessage = survey.finishMessage, targetParticipantCount = survey.rewardSetting.targetParticipantCount, @@ -155,11 +155,10 @@ data class SurveyDocument( title = this.title, description = this.description, thumbnail = this.thumbnail, - finishedAt = this.finishedAt, publishedAt = this.publishedAt, status = this.status, finishMessage = this.finishMessage, - rewardSetting = RewardSetting.of(this.rewards.map { it.toDomain() }, this.targetParticipantCount), + rewardSetting = RewardSetting.of(this.rewards.map { it.toDomain() }, this.targetParticipantCount, this.finishedAt), isVisible = this.isVisible, makerId = this.makerId, sections = this.sections.map { it.toDomain(sectionIds) }, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index 1859cbbc..79123f6e 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -4,7 +4,7 @@ import com.sbl.sulmun2yong.drawing.adapter.DrawingBoardAdapter import com.sbl.sulmun2yong.drawing.domain.DrawingBoard import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter import com.sbl.sulmun2yong.survey.domain.Survey -import com.sbl.sulmun2yong.survey.domain.reward.ImmediateDrawRewardSetting +import com.sbl.sulmun2yong.survey.domain.reward.ImmediateDrawSetting import com.sbl.sulmun2yong.survey.domain.reward.Reward import com.sbl.sulmun2yong.survey.domain.reward.RewardSetting import com.sbl.sulmun2yong.survey.domain.section.SectionId @@ -44,9 +44,8 @@ class SurveyManagementService( title = this.title, description = this.description, thumbnail = this.thumbnail, - finishedAt = this.finishedAt, finishMessage = this.finishMessage, - rewardSetting = RewardSetting.of(rewards, this.targetParticipantCount), + rewardSetting = RewardSetting.of(rewards, this.targetParticipantCount, this.finishedAt), isVisible = this.isVisible, sections = this.sections.map { it.toDomain(sectionIds) }, ) @@ -73,7 +72,7 @@ class SurveyManagementService( // 현재 유저와 설문 제작자가 다를 경우 예외 발생 if (survey.makerId != makerId) throw InvalidSurveyAccessException() surveyAdapter.save(survey.start()) - if (survey.rewardSetting is ImmediateDrawRewardSetting) { + if (survey.rewardSetting is ImmediateDrawSetting) { val drawingBoard = DrawingBoard.create( surveyId = survey.id, From 3839903bc0c526d4444729e283406bf2d281d8b1 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 01:07:00 +0900 Subject: [PATCH 066/201] =?UTF-8?q?[SBL-137]=20=EB=A6=AC=EC=9B=8C=EB=93=9C?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95,=20=EC=84=A4=EB=AC=B8=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95,=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BB=A4?= =?UTF-8?q?=EB=B2=84=EB=A6=AC=EC=A7=80=20100%=20=EB=8B=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fixture/survey/SurveyFixtureFactory.kt | 13 +- .../sulmun2yong/survey/domain/SurveyTest.kt | 93 ++++++------- .../survey/domain/reward/RewardSettingTest.kt | 125 +++++++++++++++--- 3 files changed, 156 insertions(+), 75 deletions(-) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt index d37a850c..b79abf47 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt @@ -1,5 +1,6 @@ package com.sbl.sulmun2yong.fixture.survey +import com.sbl.sulmun2yong.global.util.DateUtil import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.domain.reward.Reward @@ -8,7 +9,6 @@ import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId import com.sbl.sulmun2yong.survey.domain.section.SectionIds -import java.time.Instant import java.util.Date import java.util.UUID @@ -17,7 +17,7 @@ object SurveyFixtureFactory { const val DESCRIPTION = "설문 설명" const val THUMBNAIL = "설문 썸네일" val SURVEY_STATUS = SurveyStatus.IN_PROGRESS - val FINISHED_AT = Date.from(Instant.now())!! + val FINISHED_AT = DateUtil.getCurrentDate(noMin = true) val PUBLISHED_AT = Date(FINISHED_AT.time - 24 * 60 * 60 * 10000) const val FINISH_MESSAGE = "설문이 종료되었습니다." const val TARGET_PARTICIPANT_COUNT = 100 @@ -41,7 +41,6 @@ object SurveyFixtureFactory { ), ) } - const val REWARD_COUNT = 9 fun createSurvey( id: UUID = UUID.randomUUID(), @@ -49,7 +48,7 @@ object SurveyFixtureFactory { description: String = DESCRIPTION, thumbnail: String = THUMBNAIL, publishedAt: Date? = PUBLISHED_AT, - finishedAt: Date = FINISHED_AT, + finishedAt: Date? = FINISHED_AT, status: SurveyStatus = SURVEY_STATUS, finishMessage: String = FINISH_MESSAGE, targetParticipantCount: Int? = TARGET_PARTICIPANT_COUNT, @@ -63,11 +62,10 @@ object SurveyFixtureFactory { description = description + id, thumbnail = thumbnail + id, publishedAt = publishedAt, - finishedAt = finishedAt, status = status, finishMessage = finishMessage + id, makerId = makerId, - rewardSetting = createRewardSetting(rewards, targetParticipantCount), + rewardSetting = createRewardSetting(rewards, targetParticipantCount, finishedAt), isVisible = isVisible, sections = sections, ) @@ -75,5 +73,6 @@ object SurveyFixtureFactory { fun createRewardSetting( rewards: List = REWARDS, targetParticipantCount: Int? = TARGET_PARTICIPANT_COUNT, - ) = RewardSetting.of(rewards, targetParticipantCount) + finishedAt: Date? = FINISHED_AT, + ) = RewardSetting.of(rewards, targetParticipantCount, finishedAt) } diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt index 66806b6c..06303a57 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt @@ -14,7 +14,7 @@ import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.createSurvey import com.sbl.sulmun2yong.global.util.DateUtil import com.sbl.sulmun2yong.survey.domain.response.SectionResponse import com.sbl.sulmun2yong.survey.domain.response.SurveyResponse -import com.sbl.sulmun2yong.survey.domain.reward.ByUserRewardSetting +import com.sbl.sulmun2yong.survey.domain.reward.NoRewardSetting import com.sbl.sulmun2yong.survey.domain.reward.Reward import com.sbl.sulmun2yong.survey.domain.reward.RewardSetting import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy @@ -28,9 +28,6 @@ import com.sbl.sulmun2yong.survey.exception.InvalidUpdateSurveyException import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows -import java.time.LocalDateTime -import java.time.ZoneId -import java.util.Date import java.util.UUID import kotlin.test.assertEquals @@ -78,7 +75,6 @@ class SurveyTest { assertEquals(TITLE + id, this.title) assertEquals(DESCRIPTION + id, this.description) assertEquals(THUMBNAIL + id, this.thumbnail) - assertEquals(FINISHED_AT, this.finishedAt) assertEquals(PUBLISHED_AT, this.publishedAt) assertEquals(SURVEY_STATUS, this.status) assertEquals(FINISH_MESSAGE + id, this.finishMessage) @@ -88,26 +84,14 @@ class SurveyTest { assertEquals(SECTIONS, this.sections) } - val finishDate = - Date.from( - LocalDateTime - .now() - .plusDays(Survey.DEFAULT_SURVEY_DURATION) - .withSecond(0) - .withNano(0) - .atZone(ZoneId.systemDefault()) - .toInstant(), - ) - with(defaultSurvey) { assertEquals(Survey.DEFAULT_TITLE, this.title) assertEquals(Survey.DEFAULT_DESCRIPTION, this.description) assertEquals(null, this.thumbnail) - assertEquals(finishDate, this.finishedAt) assertEquals(null, this.publishedAt) assertEquals(SurveyStatus.NOT_STARTED, this.status) assertEquals(Survey.DEFAULT_FINISH_MESSAGE, this.finishMessage) - assertEquals(ByUserRewardSetting(listOf()), this.rewardSetting) + assertEquals(NoRewardSetting, this.rewardSetting) assertEquals(true, this.isVisible) assertEquals(makerId, this.makerId) assertEquals(listOf(this.sections.first()), this.sections) @@ -139,10 +123,27 @@ class SurveyTest { @Test fun `설문의 시작일이 마감일 이후면 예외가 발생한다`() { // given - val publishedAt = Date(FINISHED_AT.time + 24 * 60 * 60 * 1000) + val publishedAtAfterFinishedAt = DateUtil.getDateAfterDay(FINISHED_AT) // when, then - assertThrows { createSurvey(publishedAt = publishedAt) } + // 아직 시작하지 않은 경우 예외가 발생하지 않는다. + assertDoesNotThrow { + createSurvey( + publishedAt = null, + status = SurveyStatus.NOT_STARTED, + targetParticipantCount = null, + finishedAt = null, + rewards = emptyList(), + ) + } + // 리워드 설정이 즉시 추첨인 설문은 시작일이 마감일 이후면 예외가 발생한다. + assertThrows { createSurvey(publishedAt = publishedAtAfterFinishedAt) } + // 리워드 설정이 직접 관리인 설문은 시작일이 마감일 이후면 예외가 발생한다. + assertThrows { createSurvey(publishedAt = publishedAtAfterFinishedAt, targetParticipantCount = null) } + // 리워드 미 지급 설문은 마감일이 존재하지 않으므로 예외가 발생하지 않는다. + assertDoesNotThrow { + createSurvey(publishedAt = publishedAtAfterFinishedAt, targetParticipantCount = null, finishedAt = null, rewards = emptyList()) + } } @Test @@ -244,7 +245,12 @@ class SurveyTest { val newDescription = "new description" val newThumbnail = "new thumbnail" val newFinishMessage = "new finish message" - val newRewardSetting = RewardSetting.of(listOf(Reward("new reward", "new category", 1)), 10) + val newRewardSetting = + RewardSetting.of( + listOf(Reward("new reward", "new category", 1)), + 10, + DateUtil.getCurrentDate(noMin = true), + ) val newIsVisible = false val sectionId = SectionId.Standard(UUID.randomUUID()) val newSections = @@ -266,7 +272,6 @@ class SurveyTest { title = newTitle, description = newDescription, thumbnail = newThumbnail, - finishedAt = survey.finishedAt, finishMessage = newFinishMessage, rewardSetting = newRewardSetting, isVisible = newIsVisible, @@ -288,7 +293,6 @@ class SurveyTest { assertEquals(newTitle, this.title) assertEquals(newDescription, this.description) assertEquals(newThumbnail, this.thumbnail) - assertEquals(survey.finishedAt, this.finishedAt) assertEquals(newFinishMessage, this.finishMessage) assertEquals(newRewardSetting, this.rewardSetting) assertEquals(isVisible, this.isVisible) @@ -310,7 +314,6 @@ class SurveyTest { title = survey1.title, description = survey1.description, thumbnail = survey1.thumbnail, - finishedAt = survey1.finishedAt, finishMessage = survey1.finishMessage, rewardSetting = survey1.rewardSetting, isVisible = survey1.isVisible, @@ -323,7 +326,6 @@ class SurveyTest { title = survey2.title, description = survey2.description, thumbnail = survey2.thumbnail, - finishedAt = survey2.finishedAt, finishMessage = survey2.finishMessage, rewardSetting = survey2.rewardSetting, isVisible = survey2.isVisible, @@ -336,7 +338,6 @@ class SurveyTest { title = survey3.title, description = survey3.description, thumbnail = survey3.thumbnail, - finishedAt = survey3.finishedAt, finishMessage = survey3.finishMessage, rewardSetting = survey3.rewardSetting, isVisible = survey3.isVisible, @@ -349,9 +350,8 @@ class SurveyTest { title = survey3.title, description = survey3.description, thumbnail = survey3.thumbnail, - finishedAt = survey3.finishedAt, finishMessage = survey3.finishMessage, - rewardSetting = ByUserRewardSetting(listOf()), + rewardSetting = NoRewardSetting, isVisible = survey3.isVisible, sections = survey3.sections, ) @@ -361,7 +361,12 @@ class SurveyTest { @Test fun `설문을 시작하면, 설문의 시작일과 상태가 업데이트된다`() { // given - val survey = createSurvey(finishedAt = DateUtil.getDateAfterDay(), publishedAt = null, status = SurveyStatus.NOT_STARTED) + val survey = + createSurvey( + finishedAt = DateUtil.getDateAfterDay(date = DateUtil.getCurrentDate(noMin = true)), + publishedAt = null, + status = SurveyStatus.NOT_STARTED, + ) // when val startedSurvey = survey.start() @@ -374,24 +379,9 @@ class SurveyTest { @Test fun `설문이 시작 전 상태가 아니면 시작할 수 없다`() { // given - val survey1 = - createSurvey( - finishedAt = DateUtil.getDateAfterDay(), - publishedAt = DateUtil.getCurrentDate(), - status = SurveyStatus.IN_PROGRESS, - ) - val survey2 = - createSurvey( - finishedAt = DateUtil.getDateAfterDay(), - publishedAt = DateUtil.getCurrentDate(), - status = SurveyStatus.IN_MODIFICATION, - ) - val survey3 = - createSurvey( - finishedAt = DateUtil.getDateAfterDay(), - publishedAt = DateUtil.getCurrentDate(), - status = SurveyStatus.CLOSED, - ) + val survey1 = createSurvey(status = SurveyStatus.IN_PROGRESS) + val survey2 = createSurvey(status = SurveyStatus.IN_MODIFICATION) + val survey3 = createSurvey(status = SurveyStatus.CLOSED) // when, then assertThrows { survey1.start() } @@ -402,10 +392,15 @@ class SurveyTest { @Test fun `설문은 설문의 추첨 방식이 즉시 추첨 방식인지 확인할 수 있다`() { // given - val survey1 = createSurvey(finishedAt = DateUtil.getDateAfterDay(), publishedAt = null, status = SurveyStatus.NOT_STARTED) + val survey1 = + createSurvey( + finishedAt = DateUtil.getDateAfterDay(date = DateUtil.getCurrentDate(noMin = true)), + publishedAt = null, + status = SurveyStatus.NOT_STARTED, + ) val survey2 = createSurvey( - finishedAt = DateUtil.getDateAfterDay(), + finishedAt = DateUtil.getDateAfterDay(date = DateUtil.getCurrentDate(noMin = true)), publishedAt = null, status = SurveyStatus.NOT_STARTED, targetParticipantCount = null, diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt index 0b7ba408..270adb8b 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt @@ -1,50 +1,137 @@ package com.sbl.sulmun2yong.survey.domain.reward import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory +import com.sbl.sulmun2yong.global.util.DateUtil +import com.sbl.sulmun2yong.survey.exception.InvalidFinishedAtException import com.sbl.sulmun2yong.survey.exception.InvalidRewardSettingException import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import java.util.Calendar import kotlin.test.assertEquals class RewardSettingTest { @Test - fun `리워드 정보를 생성하면 정보가 올바르게 설정된다`() { + fun `리워드 설정을 생성하면 정보가 올바르게 설정된다`() { // given val rewards = SurveyFixtureFactory.REWARDS val targetParticipantCount = SurveyFixtureFactory.TARGET_PARTICIPANT_COUNT + val finishedAt = SurveyFixtureFactory.FINISHED_AT // when - val immediateRewardSetting1 = ImmediateDrawRewardSetting(rewards, targetParticipantCount) - val immediateRewardSetting2 = RewardSetting.of(rewards, targetParticipantCount) - val freeRewardSetting1 = ByUserRewardSetting(listOf()) - val freeRewardSetting2 = RewardSetting.of(listOf(), null) + val immediateRewardSetting1 = ImmediateDrawSetting(rewards, targetParticipantCount, FinishedAt(finishedAt)) + val immediateRewardSetting2 = RewardSetting.of(rewards, targetParticipantCount, finishedAt) + val selfManagementSetting1 = SelfManagementSetting(rewards, FinishedAt(finishedAt)) + val selfManagementSetting2 = RewardSetting.of(rewards, null, finishedAt) + val noRewardSetting1 = NoRewardSetting + val noRewardSetting2 = RewardSetting.of(listOf(), null, null) // then - assertEquals(rewards, immediateRewardSetting1.rewards) - assertEquals(targetParticipantCount, immediateRewardSetting1.targetParticipantCount) - assertEquals(true, immediateRewardSetting1.isImmediateDraw) - assertEquals(rewards, immediateRewardSetting2.rewards) - assertEquals(targetParticipantCount, immediateRewardSetting2.targetParticipantCount) - assertEquals(true, immediateRewardSetting2.isImmediateDraw) - assertEquals(emptyList(), freeRewardSetting1.rewards) - assertEquals(null, freeRewardSetting1.targetParticipantCount) - assertEquals(false, freeRewardSetting1.isImmediateDraw) - assertEquals(emptyList(), freeRewardSetting2.rewards) - assertEquals(null, freeRewardSetting2.targetParticipantCount) - assertEquals(false, freeRewardSetting2.isImmediateDraw) + // 즉시 추첨 + with(immediateRewardSetting1) { + assertEquals(RewardSettingType.IMMEDIATE_DRAW, this.type) + assertEquals(rewards, this.rewards) + assertEquals(targetParticipantCount, this.targetParticipantCount) + assertEquals(FinishedAt(finishedAt), this.finishedAt) + assertEquals(true, this.isImmediateDraw) + } + with(immediateRewardSetting2) { + assertEquals(RewardSettingType.IMMEDIATE_DRAW, this.type) + assertEquals(rewards, this.rewards) + assertEquals(targetParticipantCount, this.targetParticipantCount) + assertEquals(FinishedAt(finishedAt), this.finishedAt) + assertEquals(true, this.isImmediateDraw) + } + // 직접 추첨 + with(selfManagementSetting1) { + assertEquals(RewardSettingType.SELF_MANAGEMENT, this.type) + assertEquals(rewards, this.rewards) + assertEquals(null, this.targetParticipantCount) + assertEquals(FinishedAt(finishedAt), this.finishedAt) + assertEquals(false, this.isImmediateDraw) + } + with(selfManagementSetting2) { + assertEquals(RewardSettingType.SELF_MANAGEMENT, this.type) + assertEquals(rewards, this.rewards) + assertEquals(null, this.targetParticipantCount) + assertEquals(FinishedAt(finishedAt), this.finishedAt) + assertEquals(false, this.isImmediateDraw) + } + // 리워드 미 지급 + with(noRewardSetting1) { + assertEquals(RewardSettingType.NO_REWARD, this.type) + assertEquals(emptyList(), this.rewards) + assertEquals(null, this.targetParticipantCount) + assertEquals(null, this.finishedAt) + assertEquals(false, this.isImmediateDraw) + } + with(noRewardSetting2) { + assertEquals(RewardSettingType.NO_REWARD, this.type) + assertEquals(emptyList(), this.rewards) + assertEquals(null, this.targetParticipantCount) + assertEquals(null, this.finishedAt) + assertEquals(false, this.isImmediateDraw) + } + } + + @Test + fun `리워드 설정를 잘못 생성하면 예외가 발생한다`() { + assertThrows { + RewardSetting.of(SurveyFixtureFactory.REWARDS, null, null) + } } @Test fun `즉시 추첨은 리워드가 하나 이상 존재해야한다`() { assertThrows { - ImmediateDrawRewardSetting(listOf(), 0) + ImmediateDrawSetting(listOf(), 10, FinishedAt(SurveyFixtureFactory.FINISHED_AT)) } } @Test fun `즉시 추첨은 리워드 개수의 총합이 목표 참여자 수보다 적어야한다`() { assertThrows { - ImmediateDrawRewardSetting(SurveyFixtureFactory.REWARDS, 1) + ImmediateDrawSetting(SurveyFixtureFactory.REWARDS, 1, FinishedAt(SurveyFixtureFactory.FINISHED_AT)) } } + + @Test + fun `직접 지급은 리워드가 하나 이상 존재해야한다`() { + assertThrows { + SelfManagementSetting(listOf(), FinishedAt(SurveyFixtureFactory.FINISHED_AT)) + } + } + + @Test + fun `설문 종료일을 생성하면 정보가 올바르게 설정된다`() { + // given + val date = SurveyFixtureFactory.FINISHED_AT + + // when + val finishedAt = FinishedAt(date) + + // then + assertEquals(date, finishedAt.value) + } + + @Test + fun `설문 종료일은 분 단위 이하가 0이여야 한다`() { + // given + val calendar = Calendar.getInstance() + calendar.time = DateUtil.getCurrentDate() + calendar.set(Calendar.MINUTE, 1) + calendar.set(Calendar.SECOND, 0) + calendar.set(Calendar.MILLISECOND, 0) + val date1 = calendar.time + calendar.set(Calendar.MINUTE, 0) + calendar.set(Calendar.SECOND, 1) + val date2 = calendar.time + calendar.set(Calendar.SECOND, 0) + calendar.set(Calendar.MILLISECOND, 1) + val date3 = calendar.time + + // when, then + assertThrows { FinishedAt(date1) } + assertThrows { FinishedAt(date2) } + assertThrows { FinishedAt(date3) } + } } From db00c50e36153b39df17ca2f2e2d4c9cfea060f7 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 01:25:34 +0900 Subject: [PATCH 067/201] =?UTF-8?q?[SBL-137]=20=EB=A6=AC=EC=9B=8C=EB=93=9C?= =?UTF-8?q?=20=EC=A7=80=EA=B8=89=20=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20Service=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/service/SurveyManagementService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index 79123f6e..c8efc9b3 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -36,7 +36,7 @@ class SurveyManagementService( val survey = surveyAdapter.getSurvey(surveyId) // 현재 유저와 설문 제작자가 다를 경우 예외 발생 if (survey.makerId != makerId) throw InvalidSurveyAccessException() - val rewards = surveySaveRequest.rewards.map { Reward(name = it.name, category = it.category, count = it.count) } + val rewards = surveySaveRequest.rewardSetting.rewards.map { Reward(name = it.name, category = it.category, count = it.count) } val newSurvey = with(surveySaveRequest) { val sectionIds = SectionIds.from(surveySaveRequest.sections.map { SectionId.Standard(it.sectionId) }) @@ -45,7 +45,7 @@ class SurveyManagementService( description = this.description, thumbnail = this.thumbnail, finishMessage = this.finishMessage, - rewardSetting = RewardSetting.of(rewards, this.targetParticipantCount, this.finishedAt), + rewardSetting = RewardSetting.of(rewards, this.rewardSetting.targetParticipantCount, this.rewardSetting.finishedAt), isVisible = this.isVisible, sections = this.sections.map { it.toDomain(sectionIds) }, ) From cf0664dc360e156fc5654562da7ebd5c48f027e6 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 01:41:25 +0900 Subject: [PATCH 068/201] =?UTF-8?q?[SBL-137]=20=EB=A6=AC=EC=9B=8C=EB=93=9C?= =?UTF-8?q?=EA=B0=80=20=EC=97=86=EC=9D=84=20=EB=95=8C=20=EC=A2=85=EB=A3=8C?= =?UTF-8?q?=EC=9D=BC=EC=9D=B4=20=EC=A1=B4=EC=9E=AC=ED=95=98=EA=B1=B0?= =?UTF-8?q?=EB=82=98=20=EB=AA=A9=ED=91=9C=20=EC=B0=B8=EC=97=AC=EC=9E=90=20?= =?UTF-8?q?=EC=88=98=EA=B0=80=20=EC=A1=B4=EC=9E=AC=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/survey/domain/reward/RewardSetting.kt | 6 +++++- .../survey/domain/reward/RewardSettingTest.kt | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt index 508962e7..837c1852 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt @@ -18,7 +18,11 @@ interface RewardSetting { targetParticipantCount: Int?, finishedAt: Date?, ) = if (rewards.isEmpty()) { - NoRewardSetting + if (finishedAt == null && targetParticipantCount == null) { + NoRewardSetting + } else { + throw InvalidRewardSettingException() + } } else if (finishedAt == null) { throw InvalidRewardSettingException() } else if (targetParticipantCount == null) { diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt index 270adb8b..2341ef83 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt @@ -75,9 +75,18 @@ class RewardSettingTest { @Test fun `리워드 설정를 잘못 생성하면 예외가 발생한다`() { + // 리워드는 존재하는데 종료일이 없는 경우 assertThrows { RewardSetting.of(SurveyFixtureFactory.REWARDS, null, null) } + // 리워드가 존재하지 않는데 종료일이 있는 경우 + assertThrows { + RewardSetting.of(emptyList(), null, SurveyFixtureFactory.FINISHED_AT) + } + // 리워드가 존재하지 않는데 목표 참여자 수가 있는 경우 + assertThrows { + RewardSetting.of(emptyList(), 100, null) + } } @Test From abdae584378e993236a18e259a10b93b56bff284 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 01:50:00 +0900 Subject: [PATCH 069/201] =?UTF-8?q?[SBL-137]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=A0=9C=EC=9E=91=20=EC=A0=95=EB=B3=B4=20API=EC=9D=98=20Respon?= =?UTF-8?q?seBody=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/SurveyMakeInfoResponse.kt | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt index 5d410548..bd25d43b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt @@ -4,6 +4,7 @@ import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.domain.question.Question import com.sbl.sulmun2yong.survey.domain.question.QuestionType +import com.sbl.sulmun2yong.survey.domain.reward.RewardSettingType import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.routing.RoutingType import com.sbl.sulmun2yong.survey.domain.section.Section @@ -15,11 +16,9 @@ data class SurveyMakeInfoResponse( val description: String, val thumbnail: String?, val publishedAt: Date?, - val finishedAt: Date, + val rewardSetting: RewardSettingResponse, val status: SurveyStatus, val finishMessage: String, - val targetParticipantCount: Int?, - val rewards: List, val isVisible: Boolean, val sections: List, ) { @@ -30,16 +29,27 @@ data class SurveyMakeInfoResponse( description = survey.description, thumbnail = survey.thumbnail, publishedAt = survey.publishedAt, - finishedAt = survey.finishedAt, + rewardSetting = + RewardSettingResponse( + type = survey.rewardSetting.type, + rewards = survey.rewardSetting.rewards.map { RewardMakeInfoResponse(it.name, it.category, it.count) }, + targetParticipantCount = survey.rewardSetting.targetParticipantCount, + finishedAt = survey.rewardSetting.finishedAt?.value, + ), status = survey.status, finishMessage = survey.finishMessage, - targetParticipantCount = survey.rewardSetting.targetParticipantCount, - rewards = survey.rewardSetting.rewards.map { RewardMakeInfoResponse(it.name, it.category, it.count) }, isVisible = survey.isVisible, sections = survey.sections.map { SectionMakeInfoResponse.from(it) }, ) } + data class RewardSettingResponse( + val type: RewardSettingType, + val rewards: List, + val targetParticipantCount: Int?, + val finishedAt: Date?, + ) + data class RewardMakeInfoResponse( val name: String, val category: String, From f7653dc033c69ede990ad9e783de3768df680dc8 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 01:50:30 +0900 Subject: [PATCH 070/201] =?UTF-8?q?[SBL-137]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A0=95=EB=B3=B4=20API=EC=9D=98=20Respon?= =?UTF-8?q?seBody=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/dto/response/SurveyInfoResponse.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt index a7a0a99c..6fc877fa 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt @@ -3,18 +3,19 @@ package com.sbl.sulmun2yong.survey.dto.response import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.domain.reward.Reward +import com.sbl.sulmun2yong.survey.domain.reward.RewardSettingType import java.util.Date -// TODO: 설문 제작자 정보도 추가하기 data class SurveyInfoResponse( val title: String, val description: String, val status: SurveyStatus, - val finishedAt: Date, + val type: RewardSettingType, + val finishedAt: Date?, val currentParticipants: Int?, val targetParticipants: Int?, - val thumbnail: String, val rewards: List, + val thumbnail: String, ) { companion object { fun of( @@ -24,7 +25,8 @@ data class SurveyInfoResponse( title = survey.title, description = survey.description, status = survey.status, - finishedAt = survey.finishedAt, + type = survey.rewardSetting.type, + finishedAt = survey.rewardSetting.finishedAt?.value, currentParticipants = currentParticipants, targetParticipants = survey.rewardSetting.targetParticipantCount, rewards = survey.rewardSetting.rewards.map { it.toResponse() }, From a168a470c84620c3b93b9d04025b09ce0960c9ac Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 01:50:41 +0900 Subject: [PATCH 071/201] =?UTF-8?q?[SBL-137]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20API=EC=9D=98=20ResponseBody=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/survey/dto/response/SurveyListResponse.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt index bf0ccd5c..380d8f8d 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt @@ -2,6 +2,7 @@ package com.sbl.sulmun2yong.survey.dto.response import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.reward.Reward +import com.sbl.sulmun2yong.survey.domain.reward.RewardSettingType import java.util.Date import java.util.UUID @@ -24,8 +25,9 @@ data class SurveyListResponse( title = it.title, description = it.description, targetParticipants = it.rewardSetting.targetParticipantCount, - finishedAt = it.finishedAt, + finishedAt = it.rewardSetting.finishedAt?.value, rewardCount = it.rewardSetting.getRewardCount(), + rewardSettingType = it.rewardSetting.type, rewards = it.rewardSetting.rewards.toRewardInfoResponses(), ) }, @@ -39,7 +41,8 @@ data class SurveyListResponse( val description: String, val targetParticipants: Int?, val rewardCount: Int, - val finishedAt: Date, + val finishedAt: Date?, + val rewardSettingType: RewardSettingType, val rewards: List, ) From 860c3de9e663143827f5b1e5e478dd5a6adbbc2e Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 01:51:01 +0900 Subject: [PATCH 072/201] =?UTF-8?q?[SBL-137]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20API=EC=9D=98=20RequestBody=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/dto/request/SurveySaveRequest.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt index f0fb6f26..6d1ea117 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt @@ -6,6 +6,7 @@ import com.sbl.sulmun2yong.survey.domain.question.choice.Choices import com.sbl.sulmun2yong.survey.domain.question.impl.StandardMultipleChoiceQuestion import com.sbl.sulmun2yong.survey.domain.question.impl.StandardSingleChoiceQuestion import com.sbl.sulmun2yong.survey.domain.question.impl.StandardTextQuestion +import com.sbl.sulmun2yong.survey.domain.reward.RewardSettingType import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.routing.RoutingType import com.sbl.sulmun2yong.survey.domain.section.Section @@ -19,13 +20,18 @@ data class SurveySaveRequest( val description: String, // TODO: 섬네일의 URL이 우리 서비스의 S3 URL인지 확인하기 val thumbnail: String?, - val finishedAt: Date, val finishMessage: String, - val targetParticipantCount: Int?, - val rewards: List, val isVisible: Boolean, + val rewardSetting: RewardSettingResponse, val sections: List, ) { + data class RewardSettingResponse( + val type: RewardSettingType, + val rewards: List, + val targetParticipantCount: Int?, + val finishedAt: Date?, + ) + data class RewardCreateRequest( val name: String, val category: String, From 1338c304e7b7583a31fea313c6af24e7a245aa30 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Fri, 13 Sep 2024 14:58:31 +0900 Subject: [PATCH 073/201] =?UTF-8?q?[SBL-133]=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/NoExtensionExistException.kt | 6 ++ .../sbl/sulmun2yong/aws/service/S3Service.kt | 46 ++----------- .../sbl/sulmun2yong/global/config/S3Config.kt | 21 ++++++ .../sbl/sulmun2yong/global/error/ErrorCode.kt | 3 +- .../sulmun2yong/global/util/FileValidator.kt | 65 +++++++++++++++++++ 5 files changed, 100 insertions(+), 41 deletions(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/aws/exception/NoExtensionExistException.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/NoExtensionExistException.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/NoExtensionExistException.kt new file mode 100644 index 00000000..c65e95ea --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/NoExtensionExistException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.aws.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class NoExtensionExistException : BusinessException(ErrorCode.NO_EXTENSION_EXIST) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt index bc34e1ec..7d276856 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt @@ -1,34 +1,27 @@ package com.sbl.sulmun2yong.aws.service import com.sbl.sulmun2yong.aws.dto.response.S3UploadResponse -import com.sbl.sulmun2yong.aws.exception.FileNameTooLongException -import com.sbl.sulmun2yong.aws.exception.FileNameTooShortException -import com.sbl.sulmun2yong.aws.exception.InvalidExtensionException -import com.sbl.sulmun2yong.aws.exception.NoFileExistException -import com.sbl.sulmun2yong.aws.exception.OutOfFileSizeException +import com.sbl.sulmun2yong.global.util.FileValidator import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service import org.springframework.web.multipart.MultipartFile +import software.amazon.awssdk.core.sync.RequestBody import software.amazon.awssdk.services.s3.S3Client import software.amazon.awssdk.services.s3.model.PutObjectRequest -import java.nio.file.Files -import java.nio.file.Path import java.time.LocalDateTime import java.time.format.DateTimeFormatter @Service class S3Service( + private val s3Client: S3Client, @Value("\${aws.s3.bucket-name}") private val bucketName: String, @Value("\${cloudfront.base-url}") private val cloudFrontUrl: String, - private val s3Client: S3Client, + private val fileValidator: FileValidator, ) { fun uploadFile(receivedFile: MultipartFile): S3UploadResponse { - validateReceivedFile(receivedFile) - - val tempFilePath: Path = Files.createTempFile(receivedFile.originalFilename, null) - receivedFile.transferTo(tempFilePath) + fileValidator.validateFileOf(receivedFile) val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")) val keyName = "${receivedFile.originalFilename}_$timestamp" @@ -40,35 +33,8 @@ class S3Service( .key(keyName) .build() - s3Client.putObject(putObjectRequest, tempFilePath) - Files.deleteIfExists(tempFilePath) + s3Client.putObject(putObjectRequest, RequestBody.fromInputStream(receivedFile.inputStream, receivedFile.size)) return S3UploadResponse("$cloudFrontUrl/$keyName") } } - -private fun validateReceivedFile(receivedFile: MultipartFile) { - val maxFileSize = 5 * 1024 * 1024 - val maxFileLength = 100 - val allowedExtension = mutableListOf("application/pdf", "image/jpeg", "image/png") - - val fileSize = receivedFile.size - val fileName: String? = receivedFile.originalFilename - val contentType = receivedFile.contentType - - if (receivedFile.isEmpty) { - throw NoFileExistException() - } - if (fileSize > maxFileSize) { - throw OutOfFileSizeException() - } - if (fileName.isNullOrBlank()) { - throw FileNameTooShortException() - } - if (fileName.length > maxFileLength) { - throw FileNameTooLongException() - } - if (!allowedExtension.contains(contentType)) { - throw InvalidExtensionException() - } -} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt index de1ef412..e1936899 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt @@ -1,8 +1,10 @@ package com.sbl.sulmun2yong.global.config +import com.sbl.sulmun2yong.global.util.FileValidator import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.util.unit.DataSize import software.amazon.awssdk.auth.credentials.AwsBasicCredentials import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider import software.amazon.awssdk.regions.Region @@ -10,10 +12,20 @@ import software.amazon.awssdk.services.s3.S3Client @Configuration class S3Config( + // S3 클라이언트 관련 @Value("\${aws.s3.access-key}") private val accessKey: String, @Value("\${aws.s3.secret-key}") private val secretKey: String, + // 파일 예외처리 관련 + @Value("\${aws.s3.max-file-size}") + private val maxFileSize: DataSize, + @Value("\${aws.s3.max-file-name-length}") + private val maxFileNameLength: Int, + @Value("\${aws.s3.allowed-extensions}") + private val allowedExtensions: String, + @Value("\${aws.s3.allowed-content-types}") + private val allowedContentTypes: String, ) { @Bean fun s3Client(): S3Client { @@ -25,4 +37,13 @@ class S3Config( .credentialsProvider(StaticCredentialsProvider.create(awsCredentials)) .build() } + + @Bean + fun fileValidateValues(): FileValidator = + FileValidator.from( + maxFileSize = maxFileSize, + maxFileNameLength = maxFileNameLength, + allowedExtensions = allowedExtensions, + allowedContentTypes = allowedContentTypes, + ) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index 58def915..d7129ab3 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -52,9 +52,10 @@ enum class ErrorCode( INVALID_PHONE_NUMBER(HttpStatus.BAD_REQUEST, "DT0001", "유효하지 않은 전화번호입니다."), // AWS S3 (S3) - INVALID_EXTENSION(HttpStatus.BAD_REQUEST, "S30001", "지원하지 않는 확장자입니다."), + INVALID_EXTENSION(HttpStatus.BAD_REQUEST, "S30001", "허용하지 않는 확장자입니다."), OUT_OF_FILE_SIZE(HttpStatus.BAD_REQUEST, "S30002", "파일 크기가 너무 큽니다."), FILE_NAME_TOO_SHORT(HttpStatus.BAD_REQUEST, "S30003", "파일 이름이 너무 짧습니다."), FILE_NAME_TOO_LONG(HttpStatus.BAD_REQUEST, "S30004", "파일 이름이 너무 깁니다."), NO_FILE_EXIST(HttpStatus.BAD_REQUEST, "S30005", "파일이 존재하지 않습니다."), + NO_EXTENSION_EXIST(HttpStatus.BAD_REQUEST, "S30006", "파일 확장자가 존재하지 않습니다."), } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt new file mode 100644 index 00000000..d4cfb63b --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt @@ -0,0 +1,65 @@ +package com.sbl.sulmun2yong.global.util + +import com.sbl.sulmun2yong.aws.exception.FileNameTooLongException +import com.sbl.sulmun2yong.aws.exception.FileNameTooShortException +import com.sbl.sulmun2yong.aws.exception.InvalidExtensionException +import com.sbl.sulmun2yong.aws.exception.NoExtensionExistException +import com.sbl.sulmun2yong.aws.exception.NoFileExistException +import com.sbl.sulmun2yong.aws.exception.OutOfFileSizeException +import org.springframework.util.unit.DataSize +import org.springframework.web.multipart.MultipartFile + +class FileValidator( + private val maxFileSize: Long, + private val maxFileNameLength: Int, + private val allowedExtensions: MutableList, + private val allowedContentTypes: MutableList, +) { + companion object { + fun from( + maxFileSize: DataSize, + maxFileNameLength: Int, + allowedExtensions: String, + allowedContentTypes: String, + ): FileValidator { + val fileSize = maxFileSize.toBytes() + val extensions = allowedExtensions.split(",").toMutableList() + val contentTypes = allowedContentTypes.split(",").toMutableList() + + return FileValidator( + fileSize, + maxFileNameLength, + extensions, + contentTypes, + ) + } + } + + fun validateFileOf(file: MultipartFile) { + fun checkIsAllowedExtensionOrType(contentType: String): Boolean = + allowedExtensions.contains(contentType) || allowedContentTypes.any { contentType.startsWith(it) } + + val fileSize = file.size + val fileName: String? = file.originalFilename + val contentType = file.contentType + + if (file.isEmpty) { + throw NoFileExistException() + } + if (contentType.isNullOrBlank()) { + throw NoExtensionExistException() + } + if (fileSize > maxFileSize) { + throw OutOfFileSizeException() + } + if (fileName.isNullOrBlank()) { + throw FileNameTooShortException() + } + if (fileName.length > maxFileNameLength) { + throw FileNameTooLongException() + } + if (!checkIsAllowedExtensionOrType(contentType)) { + throw InvalidExtensionException() + } + } +} From b2e214d9c9019d3a7d89ca6199719607b02caf11 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Fri, 13 Sep 2024 15:39:00 +0900 Subject: [PATCH 074/201] =?UTF-8?q?[SBL-133]=20=ED=83=80=EC=9E=84=EC=8A=A4?= =?UTF-8?q?=ED=83=AC=ED=94=84=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt index 7d276856..7de2598b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt @@ -24,7 +24,7 @@ class S3Service( fileValidator.validateFileOf(receivedFile) val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")) - val keyName = "${receivedFile.originalFilename}_$timestamp" + val keyName = "${timestamp}_${receivedFile.originalFilename}" val putObjectRequest = PutObjectRequest From 0d1699d8121031f4d83f78138c9c7202e7e8b6de Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 17:22:19 +0900 Subject: [PATCH 075/201] =?UTF-8?q?[SBL-137]=20RewardSetting=EC=9D=98=20of?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=A7=A4=EA=B0=9C=EB=B3=80?= =?UTF-8?q?=EC=88=98=EC=97=90=20type=20=EC=B6=94=EA=B0=80,=20Document?= =?UTF-8?q?=EC=97=90=20rewardSettingType=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/domain/reward/RewardSetting.kt | 39 +++++++++++++------ .../survey/entity/SurveyDocument.kt | 11 +++++- .../survey/service/SurveyManagementService.kt | 8 +++- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt index 837c1852..35fce235 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt @@ -14,21 +14,38 @@ interface RewardSetting { companion object { fun of( + type: RewardSettingType, rewards: List, targetParticipantCount: Int?, finishedAt: Date?, - ) = if (rewards.isEmpty()) { - if (finishedAt == null && targetParticipantCount == null) { - NoRewardSetting - } else { - throw InvalidRewardSettingException() + ) = when (type) { + RewardSettingType.NO_REWARD -> { + if (rewards.isEmpty() && targetParticipantCount == null && finishedAt == null) { + NoRewardSetting + } else { + throw InvalidRewardSettingException() + } + } + RewardSettingType.SELF_MANAGEMENT -> { + if (rewards.isNotEmpty() && + targetParticipantCount == null && + finishedAt != null + ) { + SelfManagementSetting(rewards, FinishedAt(finishedAt)) + } else { + throw InvalidRewardSettingException() + } + } + RewardSettingType.IMMEDIATE_DRAW -> { + if (rewards.isNotEmpty() && + targetParticipantCount != null && + finishedAt != null + ) { + ImmediateDrawSetting(rewards, targetParticipantCount, FinishedAt(finishedAt)) + } else { + throw InvalidRewardSettingException() + } } - } else if (finishedAt == null) { - throw InvalidRewardSettingException() - } else if (targetParticipantCount == null) { - SelfManagementSetting(rewards, FinishedAt(finishedAt)) - } else { - ImmediateDrawSetting(rewards, targetParticipantCount, FinishedAt(finishedAt)) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt index f8cb7b92..66cf65fe 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt @@ -12,6 +12,7 @@ import com.sbl.sulmun2yong.survey.domain.question.impl.StandardSingleChoiceQuest import com.sbl.sulmun2yong.survey.domain.question.impl.StandardTextQuestion import com.sbl.sulmun2yong.survey.domain.reward.Reward import com.sbl.sulmun2yong.survey.domain.reward.RewardSetting +import com.sbl.sulmun2yong.survey.domain.reward.RewardSettingType import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.routing.RoutingType import com.sbl.sulmun2yong.survey.domain.section.Section @@ -34,6 +35,7 @@ data class SurveyDocument( val status: SurveyStatus, val finishMessage: String, val targetParticipantCount: Int?, + val rewardSettingType: RewardSettingType, val isVisible: Boolean, val makerId: UUID, val rewards: List, @@ -53,6 +55,7 @@ data class SurveyDocument( targetParticipantCount = survey.rewardSetting.targetParticipantCount, makerId = survey.makerId, rewards = survey.rewardSetting.rewards.map { it.toDocument() }, + rewardSettingType = survey.rewardSetting.type, isVisible = survey.isVisible, sections = survey.sections.map { it.toDocument() }, ) @@ -158,7 +161,13 @@ data class SurveyDocument( publishedAt = this.publishedAt, status = this.status, finishMessage = this.finishMessage, - rewardSetting = RewardSetting.of(this.rewards.map { it.toDomain() }, this.targetParticipantCount, this.finishedAt), + rewardSetting = + RewardSetting.of( + this.rewardSettingType, + this.rewards.map { it.toDomain() }, + this.targetParticipantCount, + this.finishedAt, + ), isVisible = this.isVisible, makerId = this.makerId, sections = this.sections.map { it.toDomain(sectionIds) }, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index c8efc9b3..048ecba2 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -45,7 +45,13 @@ class SurveyManagementService( description = this.description, thumbnail = this.thumbnail, finishMessage = this.finishMessage, - rewardSetting = RewardSetting.of(rewards, this.rewardSetting.targetParticipantCount, this.rewardSetting.finishedAt), + rewardSetting = + RewardSetting.of( + this.rewardSetting.type, + rewards, + this.rewardSetting.targetParticipantCount, + this.rewardSetting.finishedAt, + ), isVisible = this.isVisible, sections = this.sections.map { it.toDomain(sectionIds) }, ) From 402cd00b0edb1850f96cefae17650419fbd06f60 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 17:24:49 +0900 Subject: [PATCH 076/201] =?UTF-8?q?[SBL-137]=20RewardSetting=EC=9D=98=20of?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=88=98=EC=A0=95=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fixture/survey/SurveyFixtureFactory.kt | 7 ++- .../sulmun2yong/survey/domain/SurveyTest.kt | 17 +++++- .../survey/domain/reward/RewardSettingTest.kt | 57 +++++++++++++++---- 3 files changed, 64 insertions(+), 17 deletions(-) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt index b79abf47..6c8bf345 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt @@ -5,6 +5,7 @@ import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.domain.reward.Reward import com.sbl.sulmun2yong.survey.domain.reward.RewardSetting +import com.sbl.sulmun2yong.survey.domain.reward.RewardSettingType import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId @@ -52,6 +53,7 @@ object SurveyFixtureFactory { status: SurveyStatus = SURVEY_STATUS, finishMessage: String = FINISH_MESSAGE, targetParticipantCount: Int? = TARGET_PARTICIPANT_COUNT, + type: RewardSettingType = RewardSettingType.IMMEDIATE_DRAW, makerId: UUID = UUID.randomUUID(), rewards: List = REWARDS, isVisible: Boolean = true, @@ -65,14 +67,15 @@ object SurveyFixtureFactory { status = status, finishMessage = finishMessage + id, makerId = makerId, - rewardSetting = createRewardSetting(rewards, targetParticipantCount, finishedAt), + rewardSetting = createRewardSetting(type, rewards, targetParticipantCount, finishedAt), isVisible = isVisible, sections = sections, ) fun createRewardSetting( + type: RewardSettingType = RewardSettingType.IMMEDIATE_DRAW, rewards: List = REWARDS, targetParticipantCount: Int? = TARGET_PARTICIPANT_COUNT, finishedAt: Date? = FINISHED_AT, - ) = RewardSetting.of(rewards, targetParticipantCount, finishedAt) + ) = RewardSetting.of(type, rewards, targetParticipantCount, finishedAt) } diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt index 06303a57..a449af8c 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt @@ -17,6 +17,7 @@ import com.sbl.sulmun2yong.survey.domain.response.SurveyResponse import com.sbl.sulmun2yong.survey.domain.reward.NoRewardSetting import com.sbl.sulmun2yong.survey.domain.reward.Reward import com.sbl.sulmun2yong.survey.domain.reward.RewardSetting +import com.sbl.sulmun2yong.survey.domain.reward.RewardSettingType import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId @@ -134,15 +135,24 @@ class SurveyTest { targetParticipantCount = null, finishedAt = null, rewards = emptyList(), + type = RewardSettingType.NO_REWARD, ) } // 리워드 설정이 즉시 추첨인 설문은 시작일이 마감일 이후면 예외가 발생한다. assertThrows { createSurvey(publishedAt = publishedAtAfterFinishedAt) } // 리워드 설정이 직접 관리인 설문은 시작일이 마감일 이후면 예외가 발생한다. - assertThrows { createSurvey(publishedAt = publishedAtAfterFinishedAt, targetParticipantCount = null) } + assertThrows { + createSurvey(type = RewardSettingType.SELF_MANAGEMENT, publishedAt = publishedAtAfterFinishedAt, targetParticipantCount = null) + } // 리워드 미 지급 설문은 마감일이 존재하지 않으므로 예외가 발생하지 않는다. assertDoesNotThrow { - createSurvey(publishedAt = publishedAtAfterFinishedAt, targetParticipantCount = null, finishedAt = null, rewards = emptyList()) + createSurvey( + type = RewardSettingType.NO_REWARD, + publishedAt = publishedAtAfterFinishedAt, + targetParticipantCount = null, + finishedAt = null, + rewards = emptyList(), + ) } } @@ -247,6 +257,7 @@ class SurveyTest { val newFinishMessage = "new finish message" val newRewardSetting = RewardSetting.of( + type = RewardSettingType.IMMEDIATE_DRAW, listOf(Reward("new reward", "new category", 1)), 10, DateUtil.getCurrentDate(noMin = true), @@ -394,7 +405,6 @@ class SurveyTest { // given val survey1 = createSurvey( - finishedAt = DateUtil.getDateAfterDay(date = DateUtil.getCurrentDate(noMin = true)), publishedAt = null, status = SurveyStatus.NOT_STARTED, ) @@ -404,6 +414,7 @@ class SurveyTest { publishedAt = null, status = SurveyStatus.NOT_STARTED, targetParticipantCount = null, + type = RewardSettingType.SELF_MANAGEMENT, ) // when diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt index 2341ef83..d8d85c3a 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt @@ -19,11 +19,11 @@ class RewardSettingTest { // when val immediateRewardSetting1 = ImmediateDrawSetting(rewards, targetParticipantCount, FinishedAt(finishedAt)) - val immediateRewardSetting2 = RewardSetting.of(rewards, targetParticipantCount, finishedAt) + val immediateRewardSetting2 = RewardSetting.of(RewardSettingType.IMMEDIATE_DRAW, rewards, targetParticipantCount, finishedAt) val selfManagementSetting1 = SelfManagementSetting(rewards, FinishedAt(finishedAt)) - val selfManagementSetting2 = RewardSetting.of(rewards, null, finishedAt) + val selfManagementSetting2 = RewardSetting.of(RewardSettingType.SELF_MANAGEMENT, rewards, null, finishedAt) val noRewardSetting1 = NoRewardSetting - val noRewardSetting2 = RewardSetting.of(listOf(), null, null) + val noRewardSetting2 = RewardSetting.of(RewardSettingType.NO_REWARD, listOf(), null, null) // then // 즉시 추첨 @@ -75,17 +75,50 @@ class RewardSettingTest { @Test fun `리워드 설정를 잘못 생성하면 예외가 발생한다`() { - // 리워드는 존재하는데 종료일이 없는 경우 - assertThrows { - RewardSetting.of(SurveyFixtureFactory.REWARDS, null, null) + // 리워드 미지급 + with(RewardSettingType.NO_REWARD) { + // 리워드가 존재하는 경우 예외 발생 + assertThrows { + RewardSetting.of(this, SurveyFixtureFactory.REWARDS, null, null) + } + // targetParticipant가 존재하는 경우 예외 발생 + assertThrows { + RewardSetting.of(this, emptyList(), 100, null) + } + // finishedAt이 존재하는 경우 예외 발생 + assertThrows { + RewardSetting.of(this, emptyList(), null, SurveyFixtureFactory.FINISHED_AT) + } } - // 리워드가 존재하지 않는데 종료일이 있는 경우 - assertThrows { - RewardSetting.of(emptyList(), null, SurveyFixtureFactory.FINISHED_AT) + // 직접 지급 + with(RewardSettingType.SELF_MANAGEMENT) { + // 리워드가 없는 경우 예외 발생 + assertThrows { + RewardSetting.of(this, emptyList(), 100, SurveyFixtureFactory.FINISHED_AT) + } + // targetParticipant가 존재하는 경우 예외 발생 + assertThrows { + RewardSetting.of(this, SurveyFixtureFactory.REWARDS, 100, SurveyFixtureFactory.FINISHED_AT) + } + // finishedAt이 없는 경우 예외 발생 + assertThrows { + RewardSetting.of(this, SurveyFixtureFactory.REWARDS, null, null) + } } - // 리워드가 존재하지 않는데 목표 참여자 수가 있는 경우 - assertThrows { - RewardSetting.of(emptyList(), 100, null) + // 즉시 추첨 + with(RewardSettingType.IMMEDIATE_DRAW) { + // 리워드가 없는 경우 예외 발생 + assertThrows { + RewardSetting.of(this, emptyList(), 100, SurveyFixtureFactory.FINISHED_AT) + } + // targetParticipant가 존재하지 않는 경우 예외 발생 + assertThrows { + RewardSetting.of(this, SurveyFixtureFactory.REWARDS, null, SurveyFixtureFactory.FINISHED_AT) + } + // finishedAt이 없는 경우 예외 발생 + assertThrows { + RewardSetting.of(this, SurveyFixtureFactory.REWARDS, 100, null) + } } } From 089c46f28967690e55bd55d36a404f19b52475c1 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Fri, 13 Sep 2024 18:30:19 +0900 Subject: [PATCH 077/201] =?UTF-8?q?[SBL-133]=20=ED=83=80=EC=9E=84=EC=8A=A4?= =?UTF-8?q?=ED=83=AC=ED=94=84=20ms=EA=B9=8C=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt | 2 +- .../kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt index 7de2598b..8a4a4b83 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt @@ -23,7 +23,7 @@ class S3Service( fun uploadFile(receivedFile: MultipartFile): S3UploadResponse { fileValidator.validateFileOf(receivedFile) - val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")) + val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmssSSS")) val keyName = "${timestamp}_${receivedFile.originalFilename}" val putObjectRequest = diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt index 5fc1d407..ce87a418 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt @@ -123,6 +123,7 @@ class SecurityConfig( authorize("/api/v1/admin/**", hasRole("ADMIN")) authorize("/api/v1/user/**", authenticated) authorize("/api/v1/surveys/results/**", authenticated) +// authorize("/api/v1/s3/**", authenticated) // TODO: 추후에 AUTHENTICATED_USER 로 수정 authorize("/api/v1/surveys/workbench/**", hasRole("ADMIN")) authorize("/**", permitAll) From d31968b986d715a482879425d55407629b762316 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Fri, 13 Sep 2024 18:30:42 +0900 Subject: [PATCH 078/201] =?UTF-8?q?[SBL-133]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=20=EC=82=AC=EC=9A=A9=EC=9E=90=EB=A7=8C=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=20=ED=95=A0=20=EC=88=98=20=EC=9E=88=EA=B2=8C=20?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt index ce87a418..e388fe68 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt @@ -123,7 +123,7 @@ class SecurityConfig( authorize("/api/v1/admin/**", hasRole("ADMIN")) authorize("/api/v1/user/**", authenticated) authorize("/api/v1/surveys/results/**", authenticated) -// authorize("/api/v1/s3/**", authenticated) + authorize("/api/v1/s3/**", authenticated) // TODO: 추후에 AUTHENTICATED_USER 로 수정 authorize("/api/v1/surveys/workbench/**", hasRole("ADMIN")) authorize("/**", permitAll) From 20480eca5d203366540da0b8aff0f72b4a7e2437 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 13:13:17 +0900 Subject: [PATCH 079/201] =?UTF-8?q?[SBL-136]=20=EC=84=A4=EB=AC=B8=EC=9D=84?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=EC=83=81=ED=83=9C=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=ED=95=A0=20=EC=88=98=20=EC=97=86=EC=9D=84=20=EB=95=8C?= =?UTF-8?q?=20=EB=B0=9C=EC=83=9D=EC=8B=9C=ED=82=AC=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt | 1 + .../sulmun2yong/survey/domain/InvalidSurveyEditException.kt | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/InvalidSurveyEditException.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index b8456dc1..1e8cece8 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -32,6 +32,7 @@ enum class ErrorCode( INVALID_RESULT_DETAILS(HttpStatus.BAD_REQUEST, "SV0020", "유효하지 않은 설문 결과입니다."), INVALID_QUESTION_FILTER(HttpStatus.BAD_REQUEST, "SV0021", "유효하지 않은 질문 필터입니다."), INVALID_FINISHED_AT(HttpStatus.BAD_REQUEST, "SV0022", "유효하지 않은 마감일입니다."), + INVALID_SURVEY_EDIT(HttpStatus.BAD_REQUEST, "SV0023", "설문 수정 상태 변경에 실패했습니다."), // Drawing (DR) INVALID_DRAWING_BOARD(HttpStatus.BAD_REQUEST, "DR0001", "유효하지 않은 추첨 보드입니다."), diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/InvalidSurveyEditException.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/InvalidSurveyEditException.kt new file mode 100644 index 00000000..f1a2c30e --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/InvalidSurveyEditException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.survey.domain + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class InvalidSurveyEditException : BusinessException(ErrorCode.INVALID_SURVEY_EDIT) From d660d61481913b525cab9131af10af92076ebafa Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 13:20:06 +0900 Subject: [PATCH 080/201] =?UTF-8?q?[SBL-136]=20=EC=84=A4=EB=AC=B8=EC=9D=84?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20=EC=83=81=ED=83=9C=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=ED=95=98=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index 4d7f00ae..f8210a81 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -112,6 +112,11 @@ data class Survey( return copy(status = SurveyStatus.IN_PROGRESS, publishedAt = DateUtil.getCurrentDate()) } + fun edit(): Survey { + require(status == SurveyStatus.IN_PROGRESS) { throw InvalidSurveyEditException() } + return copy(status = SurveyStatus.IN_MODIFICATION) + } + fun isImmediateDraw() = rewardSetting.isImmediateDraw private fun isSectionsUnique() = sections.size == sections.distinctBy { it.id }.size From 443598b1927b20f50458cd9692e0b7cf6d188c29 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 13:20:44 +0900 Subject: [PATCH 081/201] =?UTF-8?q?[SBL-136]=20=EC=84=A4=EB=AC=B8=EC=9D=84?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/domain/SurveyTest.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt index a449af8c..7bf90b97 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt @@ -248,6 +248,21 @@ class SurveyTest { assertEquals(SurveyStatus.CLOSED, finishedSurvey.status) } + @Test + fun `진행 중인 설문은 수정 중인 상태로 변경할 수 있다`() { + // given + val inProgressSurvey = createSurvey(status = SurveyStatus.IN_PROGRESS, targetParticipantCount = null) + val notStartedSurvey = createSurvey(status = SurveyStatus.NOT_STARTED, targetParticipantCount = null) + val inModificationSurvey = createSurvey(status = SurveyStatus.IN_MODIFICATION, targetParticipantCount = null) + val closedSurvey = createSurvey(status = SurveyStatus.CLOSED, targetParticipantCount = null) + + // when, then + assertEquals(SurveyStatus.IN_MODIFICATION, inProgressSurvey.edit().status) + assertThrows { notStartedSurvey.edit() } + assertThrows { inModificationSurvey.edit() } + assertThrows { closedSurvey.edit() } + } + @Test fun `설문의 내용를 업데이트할 수 있다`() { // given From 3a0e630765e3a76dbf9125bf4433c7cdc056e2c1 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 13:28:27 +0900 Subject: [PATCH 082/201] =?UTF-8?q?[SBL-136]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/controller/SurveyManagementController.kt | 8 ++++++-- .../survey/controller/doc/SurveyManagementApiDoc.kt | 7 +++++++ .../survey/service/SurveyManagementService.kt | 10 ++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyManagementController.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyManagementController.kt index 1719a4f0..7c212ca4 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyManagementController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyManagementController.kt @@ -1,6 +1,5 @@ package com.sbl.sulmun2yong.survey.controller -import com.sbl.sulmun2yong.drawing.adapter.DrawingBoardAdapter import com.sbl.sulmun2yong.global.annotation.LoginUser import com.sbl.sulmun2yong.survey.controller.doc.SurveyManagementApiDoc import com.sbl.sulmun2yong.survey.dto.request.SurveySaveRequest @@ -20,7 +19,6 @@ import java.util.UUID @RequestMapping("/api/v1/surveys/workbench") class SurveyManagementController( private val surveyManagementService: SurveyManagementService, - private val drawingBoardAdapter: DrawingBoardAdapter, ) : SurveyManagementApiDoc { @PostMapping("/create") override fun createSurvey( @@ -51,4 +49,10 @@ class SurveyManagementController( @PathVariable("surveyId") surveyId: UUID, @LoginUser id: UUID, ) = ResponseEntity.ok(surveyManagementService.startSurvey(surveyId = surveyId, makerId = id)) + + @PatchMapping("/edit/{surveyId}") + override fun editSurvey( + @PathVariable("surveyId") surveyId: UUID, + @LoginUser id: UUID, + ) = ResponseEntity.ok(surveyManagementService.editSurvey(surveyId = surveyId, makerId = id)) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyManagementApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyManagementApiDoc.kt index d2454975..5ebe569f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyManagementApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyManagementApiDoc.kt @@ -44,4 +44,11 @@ interface SurveyManagementApiDoc { @PathVariable("surveyId") surveyId: UUID, @LoginUser id: UUID, ): ResponseEntity + + @Operation(summary = "설문 수정 API") + @PatchMapping("/edit/{surveyId}") + fun editSurvey( + @PathVariable("surveyId") surveyId: UUID, + @LoginUser id: UUID, + ): ResponseEntity } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index 048ecba2..a7235257 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -88,4 +88,14 @@ class SurveyManagementService( drawingBoardAdapter.save(drawingBoard) } } + + fun editSurvey( + surveyId: UUID, + makerId: UUID, + ) { + val survey = surveyAdapter.getSurvey(surveyId) + // 현재 유저와 설문 제작자가 다를 경우 예외 발생 + if (survey.makerId != makerId) throw InvalidSurveyAccessException() + surveyAdapter.save(survey.edit()) + } } From 4e10d7b4fc5bf7a852a03a83bd29c79e5a6bf5af Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 13:40:21 +0900 Subject: [PATCH 083/201] =?UTF-8?q?[SBL-136]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=8B=9C=EC=9E=91=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=A4=91=EC=9D=B8=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EC=97=90=EC=84=9C=EB=8F=84=20=ED=98=B8=EC=B6=9C=20=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95,?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/domain/Survey.kt | 12 +++++--- .../sulmun2yong/survey/domain/SurveyTest.kt | 30 ++++++++++++------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index f8210a81..6e16e7ab 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -107,10 +107,14 @@ data class Survey( fun finish() = copy(status = SurveyStatus.CLOSED) - fun start(): Survey { - require(status == SurveyStatus.NOT_STARTED) { throw InvalidSurveyStartException() } - return copy(status = SurveyStatus.IN_PROGRESS, publishedAt = DateUtil.getCurrentDate()) - } + /** 설문을 IN_PROGRESS 상태로 변경하는 메서드. 설문이 시작 전이거나 수정 중인 경우만 가능하다. */ + fun start() = + when (status) { + SurveyStatus.NOT_STARTED -> copy(status = SurveyStatus.IN_PROGRESS, publishedAt = DateUtil.getCurrentDate()) + SurveyStatus.IN_MODIFICATION -> copy(status = SurveyStatus.IN_PROGRESS) + SurveyStatus.IN_PROGRESS -> throw InvalidSurveyStartException() + SurveyStatus.CLOSED -> throw InvalidSurveyStartException() + } fun edit(): Survey { require(status == SurveyStatus.IN_PROGRESS) { throw InvalidSurveyEditException() } diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt index 7bf90b97..1e1a19fb 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt @@ -387,32 +387,40 @@ class SurveyTest { @Test fun `설문을 시작하면, 설문의 시작일과 상태가 업데이트된다`() { // given - val survey = + val notStartedSurvey = createSurvey( finishedAt = DateUtil.getDateAfterDay(date = DateUtil.getCurrentDate(noMin = true)), publishedAt = null, + targetParticipantCount = null, status = SurveyStatus.NOT_STARTED, ) + val inModificationSurvey = + createSurvey( + finishedAt = DateUtil.getDateAfterDay(date = DateUtil.getCurrentDate(noMin = true)), + targetParticipantCount = null, + status = SurveyStatus.IN_MODIFICATION, + ) // when - val startedSurvey = survey.start() + val startedSurvey1 = notStartedSurvey.start() + val startedSurvey2 = inModificationSurvey.start() // then - assertEquals(DateUtil.getCurrentDate(), startedSurvey.publishedAt) - assertEquals(SurveyStatus.IN_PROGRESS, startedSurvey.status) + assertEquals(DateUtil.getCurrentDate(), startedSurvey1.publishedAt) + assertEquals(SurveyStatus.IN_PROGRESS, startedSurvey1.status) + assertEquals(inModificationSurvey.publishedAt, startedSurvey2.publishedAt) + assertEquals(SurveyStatus.IN_PROGRESS, startedSurvey2.status) } @Test - fun `설문이 시작 전 상태가 아니면 시작할 수 없다`() { + fun `설문이 시작 전 상태나 수정 중인 상태가 아니면 시작할 수 없다`() { // given - val survey1 = createSurvey(status = SurveyStatus.IN_PROGRESS) - val survey2 = createSurvey(status = SurveyStatus.IN_MODIFICATION) - val survey3 = createSurvey(status = SurveyStatus.CLOSED) + val inProgressSurvey = createSurvey(status = SurveyStatus.IN_PROGRESS) + val inModificationSurvey = createSurvey(status = SurveyStatus.CLOSED) // when, then - assertThrows { survey1.start() } - assertThrows { survey2.start() } - assertThrows { survey3.start() } + assertThrows { inProgressSurvey.start() } + assertThrows { inModificationSurvey.start() } } @Test From 73124dc17bfc686ef1f5386ff68c052723cf683e Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 13 Sep 2024 18:18:16 +0900 Subject: [PATCH 084/201] =?UTF-8?q?[SBL-136]=20RewardSetting=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EC=82=AC=ED=95=AD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/domain/SurveyTest.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt index 1e1a19fb..100c87a4 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt @@ -251,10 +251,14 @@ class SurveyTest { @Test fun `진행 중인 설문은 수정 중인 상태로 변경할 수 있다`() { // given - val inProgressSurvey = createSurvey(status = SurveyStatus.IN_PROGRESS, targetParticipantCount = null) - val notStartedSurvey = createSurvey(status = SurveyStatus.NOT_STARTED, targetParticipantCount = null) - val inModificationSurvey = createSurvey(status = SurveyStatus.IN_MODIFICATION, targetParticipantCount = null) - val closedSurvey = createSurvey(status = SurveyStatus.CLOSED, targetParticipantCount = null) + val inProgressSurvey = + createSurvey(type = RewardSettingType.SELF_MANAGEMENT, status = SurveyStatus.IN_PROGRESS, targetParticipantCount = null) + val notStartedSurvey = + createSurvey(type = RewardSettingType.SELF_MANAGEMENT, status = SurveyStatus.NOT_STARTED, targetParticipantCount = null) + val inModificationSurvey = + createSurvey(type = RewardSettingType.SELF_MANAGEMENT, status = SurveyStatus.IN_MODIFICATION, targetParticipantCount = null) + val closedSurvey = + createSurvey(type = RewardSettingType.SELF_MANAGEMENT, status = SurveyStatus.CLOSED, targetParticipantCount = null) // when, then assertEquals(SurveyStatus.IN_MODIFICATION, inProgressSurvey.edit().status) @@ -389,6 +393,7 @@ class SurveyTest { // given val notStartedSurvey = createSurvey( + type = RewardSettingType.SELF_MANAGEMENT, finishedAt = DateUtil.getDateAfterDay(date = DateUtil.getCurrentDate(noMin = true)), publishedAt = null, targetParticipantCount = null, @@ -396,6 +401,7 @@ class SurveyTest { ) val inModificationSurvey = createSurvey( + type = RewardSettingType.SELF_MANAGEMENT, finishedAt = DateUtil.getDateAfterDay(date = DateUtil.getCurrentDate(noMin = true)), targetParticipantCount = null, status = SurveyStatus.IN_MODIFICATION, From 999c901e7d2dc386690eb9576d25deee249d364a Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 14 Sep 2024 03:17:59 +0900 Subject: [PATCH 085/201] =?UTF-8?q?[SBL-136]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=A2=85=EB=A3=8C=EC=9D=BC=EC=9D=B4=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=8B=9C=EC=9E=91=EC=9D=BC=20=EB=B3=B4=EB=8B=A4=20=EA=B3=BC?= =?UTF-8?q?=EA=B1=B0=EC=9D=BC=20=EB=95=8C=20=EB=B0=9C=EC=83=9D=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EC=98=88=EC=99=B8=EB=A5=BC=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=EC=84=A4=EB=AC=B8=20=EC=98=88=EC=99=B8=EC=99=80=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC,=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt | 1 + src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt | 3 ++- .../survey/exception/InvalidPublishedAtException.kt | 6 ++++++ .../kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt | 5 +++-- 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidPublishedAtException.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index 1e8cece8..907fd11b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -33,6 +33,7 @@ enum class ErrorCode( INVALID_QUESTION_FILTER(HttpStatus.BAD_REQUEST, "SV0021", "유효하지 않은 질문 필터입니다."), INVALID_FINISHED_AT(HttpStatus.BAD_REQUEST, "SV0022", "유효하지 않은 마감일입니다."), INVALID_SURVEY_EDIT(HttpStatus.BAD_REQUEST, "SV0023", "설문 수정 상태 변경에 실패했습니다."), + INVALID_PUBLISHED_AT(HttpStatus.BAD_REQUEST, "SV0024", "설문 마감일이 설문 공개일 보다 빠릅니다."), // Drawing (DR) INVALID_DRAWING_BOARD(HttpStatus.BAD_REQUEST, "DR0001", "유효하지 않은 추첨 보드입니다."), diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index 6e16e7ab..acb66549 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -9,6 +9,7 @@ import com.sbl.sulmun2yong.survey.domain.reward.SelfManagementSetting import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId import com.sbl.sulmun2yong.survey.domain.section.SectionIds +import com.sbl.sulmun2yong.survey.exception.InvalidPublishedAtException import com.sbl.sulmun2yong.survey.exception.InvalidSurveyException import com.sbl.sulmun2yong.survey.exception.InvalidSurveyResponseException import com.sbl.sulmun2yong.survey.exception.InvalidSurveyStartException @@ -34,7 +35,7 @@ data class Survey( require(sections.isNotEmpty()) { throw InvalidSurveyException() } require(isSectionsUnique()) { throw InvalidSurveyException() } require(isSurveyStatusValid()) { throw InvalidSurveyException() } - require(isFinishedAtAfterPublishedAt()) { throw InvalidSurveyException() } + require(isFinishedAtAfterPublishedAt()) { throw InvalidPublishedAtException() } require(isSectionIdsValid()) { throw InvalidSurveyException() } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidPublishedAtException.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidPublishedAtException.kt new file mode 100644 index 00000000..331e89c7 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidPublishedAtException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.survey.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class InvalidPublishedAtException : BusinessException(ErrorCode.INVALID_PUBLISHED_AT) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt index 100c87a4..cacecdc1 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt @@ -22,6 +22,7 @@ import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId import com.sbl.sulmun2yong.survey.domain.section.SectionIds +import com.sbl.sulmun2yong.survey.exception.InvalidPublishedAtException import com.sbl.sulmun2yong.survey.exception.InvalidSurveyException import com.sbl.sulmun2yong.survey.exception.InvalidSurveyResponseException import com.sbl.sulmun2yong.survey.exception.InvalidSurveyStartException @@ -139,9 +140,9 @@ class SurveyTest { ) } // 리워드 설정이 즉시 추첨인 설문은 시작일이 마감일 이후면 예외가 발생한다. - assertThrows { createSurvey(publishedAt = publishedAtAfterFinishedAt) } + assertThrows { createSurvey(publishedAt = publishedAtAfterFinishedAt) } // 리워드 설정이 직접 관리인 설문은 시작일이 마감일 이후면 예외가 발생한다. - assertThrows { + assertThrows { createSurvey(type = RewardSettingType.SELF_MANAGEMENT, publishedAt = publishedAtAfterFinishedAt, targetParticipantCount = null) } // 리워드 미 지급 설문은 마감일이 존재하지 않으므로 예외가 발생하지 않는다. From f644a76759bc6629f186dafe5e00eacf113bc918 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 11 Sep 2024 03:15:25 +0900 Subject: [PATCH 086/201] =?UTF-8?q?[SBL-119]=20=EB=82=B4=20=EC=84=A4?= =?UTF-8?q?=EB=AC=B8=EC=9D=84=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20Ada?= =?UTF-8?q?pter=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt | 2 ++ .../com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt index bb213494..3fc9f6fe 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt @@ -56,4 +56,6 @@ class SurveyAdapter( surveyId: UUID, userId: UUID, ) = surveyRepository.existsByIdAndMakerId(surveyId, userId) + + fun getMyPageSurveys(makerId: UUID) = surveyRepository.findByMakerId(makerId).map { it.toDomain() } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt index a9cb9d65..a6783154 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt @@ -19,4 +19,6 @@ interface SurveyRepository : MongoRepository { surveyId: UUID, userId: UUID, ): Boolean + + fun findByMakerId(makerId: UUID): List } From 4e8696e6890b7c129887b536ba53412870c72019 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 11 Sep 2024 03:16:00 +0900 Subject: [PATCH 087/201] =?UTF-8?q?[SBL-119]=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=84=A4=EB=AC=B8=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=EC=9D=98=20ResponseBody=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/MyPageSurveysResponse.kt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveysResponse.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveysResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveysResponse.kt new file mode 100644 index 00000000..77ffbc54 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveysResponse.kt @@ -0,0 +1,23 @@ +package com.sbl.sulmun2yong.survey.dto.response + +import com.sbl.sulmun2yong.survey.domain.Survey +import com.sbl.sulmun2yong.survey.domain.SurveyStatus +import java.util.UUID + +data class MyPageSurveysResponse( + val surveys: List, +) { + data class MyPageSurveyInfoResponse( + val surveyId: UUID, + val title: String, + val status: SurveyStatus, + ) { + companion object { + fun from(survey: Survey) = MyPageSurveyInfoResponse(survey.id, survey.title, survey.status) + } + } + + companion object { + fun from(surveys: List) = MyPageSurveysResponse(surveys.map { MyPageSurveyInfoResponse.from(it) }) + } +} From 4ffb228a54077eb35d58f3488cb7e910a6983399 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 11 Sep 2024 03:16:23 +0900 Subject: [PATCH 088/201] =?UTF-8?q?[SBL-119]=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=84=A4=EB=AC=B8=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/global/config/SecurityConfig.kt | 1 + .../survey/controller/SurveyInfoController.kt | 11 ++++++++++- .../survey/controller/doc/SurveyInfoApiDoc.kt | 8 ++++++++ .../sulmun2yong/survey/service/SurveyInfoService.kt | 6 ++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt index e388fe68..8cf57a19 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt @@ -124,6 +124,7 @@ class SecurityConfig( authorize("/api/v1/user/**", authenticated) authorize("/api/v1/surveys/results/**", authenticated) authorize("/api/v1/s3/**", authenticated) + authorize("/api/v1/surveys/my-page", authenticated) // TODO: 추후에 AUTHENTICATED_USER 로 수정 authorize("/api/v1/surveys/workbench/**", hasRole("ADMIN")) authorize("/**", permitAll) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyInfoController.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyInfoController.kt index 4c849699..ca3d7dea 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyInfoController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyInfoController.kt @@ -1,7 +1,9 @@ package com.sbl.sulmun2yong.survey.controller +import com.sbl.sulmun2yong.global.annotation.LoginUser import com.sbl.sulmun2yong.survey.controller.doc.SurveyInfoApiDoc import com.sbl.sulmun2yong.survey.dto.request.SurveySortType +import com.sbl.sulmun2yong.survey.dto.response.MyPageSurveysResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyInfoResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyListResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyProgressInfoResponse @@ -16,7 +18,9 @@ import java.util.UUID @RestController @RequestMapping("/api/v1/surveys") -class SurveyInfoController(private val surveyInfoService: SurveyInfoService) : SurveyInfoApiDoc { +class SurveyInfoController( + private val surveyInfoService: SurveyInfoService, +) : SurveyInfoApiDoc { @GetMapping("/list") override fun getSurveysWithPagination( @RequestParam(defaultValue = "10") size: Int, @@ -37,4 +41,9 @@ class SurveyInfoController(private val surveyInfoService: SurveyInfoService) : S override fun getSurveyProgressInfo( @PathVariable("survey-id") surveyId: UUID, ): ResponseEntity = ResponseEntity.ok(surveyInfoService.getSurveyProgressInfo(surveyId)) + + @GetMapping("/my-page") + override fun getMyPageSurveys( + @LoginUser userId: UUID, + ): ResponseEntity = ResponseEntity.ok(surveyInfoService.getMyPageSurveys(userId)) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyInfoApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyInfoApiDoc.kt index cf24096c..00e1af5c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyInfoApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyInfoApiDoc.kt @@ -1,6 +1,8 @@ package com.sbl.sulmun2yong.survey.controller.doc +import com.sbl.sulmun2yong.global.annotation.LoginUser import com.sbl.sulmun2yong.survey.dto.request.SurveySortType +import com.sbl.sulmun2yong.survey.dto.response.MyPageSurveysResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyInfoResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyListResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyProgressInfoResponse @@ -34,4 +36,10 @@ interface SurveyInfoApiDoc { fun getSurveyProgressInfo( @PathVariable("survey-id") surveyId: UUID, ): ResponseEntity + + @Operation(summary = "마이페이지 설문 목록 조회") + @GetMapping("/my-page") + fun getMyPageSurveys( + @LoginUser userId: UUID, + ): ResponseEntity } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt index 45025c64..85a9343d 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt @@ -4,6 +4,7 @@ import com.sbl.sulmun2yong.drawing.adapter.DrawingBoardAdapter import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.dto.request.SurveySortType +import com.sbl.sulmun2yong.survey.dto.response.MyPageSurveysResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyInfoResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyListResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyProgressInfoResponse @@ -45,4 +46,9 @@ class SurveyInfoService( if (survey.status != SurveyStatus.IN_PROGRESS) throw InvalidSurveyAccessException() return SurveyProgressInfoResponse.of(survey) } + + fun getMyPageSurveys(makerId: UUID): MyPageSurveysResponse { + val surveys = surveyAdapter.getMyPageSurveys(makerId) + return MyPageSurveysResponse.from(surveys) + } } From 5771deb70f04053136a6f54c75219b72863512cd Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 14 Sep 2024 23:15:41 +0900 Subject: [PATCH 089/201] =?UTF-8?q?[SBL-119]=20Survey=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=97=90=20updatedAt=20=EC=B6=94=EA=B0=80,=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt | 2 ++ .../com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt | 1 + .../sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt | 2 ++ .../kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt | 4 +++- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index acb66549..3e91013d 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -29,6 +29,7 @@ data class Survey( /** 해당 설문의 설문이용 노출 여부(false면 메인 페이지 노출 X, 링크를 통해서만 접근 가능) */ val isVisible: Boolean, val makerId: UUID, + val updatedAt: Date, val sections: List
, ) { init { @@ -57,6 +58,7 @@ data class Survey( rewardSetting = NoRewardSetting, isVisible = true, makerId = makerId, + updatedAt = DateUtil.getCurrentDate(noSec = false, noMilliSecond = false), sections = listOf(Section.create()), ) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt index 66cf65fe..ce98670c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt @@ -170,6 +170,7 @@ data class SurveyDocument( ), isVisible = this.isVisible, makerId = this.makerId, + updatedAt = this.updatedAt, sections = this.sections.map { it.toDomain(sectionIds) }, ) } diff --git a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt index 6c8bf345..0e209d65 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt @@ -57,6 +57,7 @@ object SurveyFixtureFactory { makerId: UUID = UUID.randomUUID(), rewards: List = REWARDS, isVisible: Boolean = true, + updatedAt: Date = DateUtil.getCurrentDate(noSec = false, noMilliSecond = false), sections: List
= SECTIONS, ) = Survey( id = id, @@ -69,6 +70,7 @@ object SurveyFixtureFactory { makerId = makerId, rewardSetting = createRewardSetting(type, rewards, targetParticipantCount, finishedAt), isVisible = isVisible, + updatedAt = updatedAt, sections = sections, ) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt index cacecdc1..c82ae534 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt @@ -68,7 +68,8 @@ class SurveyTest { fun `설문을 생성하면 설문의 정보들이 설정된다`() { // given, when val makerId: UUID = UUID.randomUUID() - val survey = createSurvey(id = id, makerId = makerId) + val updatedAt = DateUtil.getCurrentDate(noSec = false, noMilliSecond = false) + val survey = createSurvey(id = id, makerId = makerId, updatedAt = updatedAt) val defaultSurvey = Survey.create(makerId) // then @@ -83,6 +84,7 @@ class SurveyTest { assertEquals(createRewardSetting(), this.rewardSetting) assertEquals(true, this.isVisible) assertEquals(makerId, this.makerId) + assertEquals(updatedAt, this.updatedAt) assertEquals(SECTIONS, this.sections) } From 7aec64c3c68e5c2324b482058464dfc2cd79eb4e Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 14 Sep 2024 23:22:54 +0900 Subject: [PATCH 090/201] =?UTF-8?q?[SBL-119]=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=84=A4=EB=AC=B8=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20DTO=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/MyPageSurveyInfoResponse.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveyInfoResponse.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveyInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveyInfoResponse.kt new file mode 100644 index 00000000..0135da9b --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveyInfoResponse.kt @@ -0,0 +1,16 @@ +package com.sbl.sulmun2yong.survey.dto.response + +import com.sbl.sulmun2yong.survey.domain.SurveyStatus +import java.util.Date +import java.util.UUID + +data class MyPageSurveyInfoResponse( + val id: UUID, + val title: String, + val thumbnail: String?, + val updatedAt: Date, + val status: SurveyStatus, + val publishedAt: Date?, + val finishedAt: Date?, + val responseCount: Int, +) From 3ce552e0bc45d53de5c82a2bcff01f853facafe7 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 14 Sep 2024 23:24:02 +0900 Subject: [PATCH 091/201] =?UTF-8?q?[SBL-119]=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EC=9D=98=20=EC=84=A4=EB=AC=B8=EC=9D=84=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=ED=95=98=EB=8A=94=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=EC=9D=84=20=EB=8B=B4=EC=9D=80=20enum=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/survey/dto/request/MySurveySortType.kt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/MySurveySortType.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/MySurveySortType.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/MySurveySortType.kt new file mode 100644 index 00000000..d28321d4 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/MySurveySortType.kt @@ -0,0 +1,8 @@ +package com.sbl.sulmun2yong.survey.dto.request + +enum class MySurveySortType { + LAST_MODIFIED, + OLD_MODIFIED, + TITLE_ASC, + TITLE_DESC, +} From 4da9f839cb4691b185c210bdffbbc2cad05dc1b4 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 14 Sep 2024 23:27:41 +0900 Subject: [PATCH 092/201] =?UTF-8?q?[SBL-119]=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EC=97=90=20=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=EC=84=A4=EB=AC=B8=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=EB=8A=94=20CustomRepository=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/SurveyCustomRepository.kt | 14 ++++++ .../repository/SurveyCustomRepositoryImpl.kt | 50 +++++++++++++++++++ .../survey/repository/SurveyRepository.kt | 4 +- 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepository.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepository.kt new file mode 100644 index 00000000..cd71d094 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepository.kt @@ -0,0 +1,14 @@ +package com.sbl.sulmun2yong.survey.repository + +import com.sbl.sulmun2yong.survey.domain.SurveyStatus +import com.sbl.sulmun2yong.survey.dto.request.MySurveySortType +import com.sbl.sulmun2yong.survey.dto.response.MyPageSurveyInfoResponse +import java.util.UUID + +interface SurveyCustomRepository { + fun findSurveysWithResponseCount( + makerId: UUID, + status: SurveyStatus?, + sortType: MySurveySortType, + ): List +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt new file mode 100644 index 00000000..96c276cc --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt @@ -0,0 +1,50 @@ +package com.sbl.sulmun2yong.survey.repository + +import com.sbl.sulmun2yong.survey.domain.SurveyStatus +import com.sbl.sulmun2yong.survey.dto.request.MySurveySortType +import com.sbl.sulmun2yong.survey.dto.response.MyPageSurveyInfoResponse +import org.springframework.data.domain.Sort +import org.springframework.data.mongodb.core.MongoTemplate +import org.springframework.data.mongodb.core.aggregation.Aggregation +import org.springframework.data.mongodb.core.query.Criteria +import java.util.UUID + +class SurveyCustomRepositoryImpl( + private val mongoTemplate: MongoTemplate, +) : SurveyCustomRepository { + override fun findSurveysWithResponseCount( + makerId: UUID, + status: SurveyStatus?, + sortType: MySurveySortType, + ): List { + val matchStage = + Aggregation.match( + Criteria.where("makerId").`is`(makerId).apply { + status?.let { and("status").`is`(it) } + }, + ) + + val lookupStage = Aggregation.lookup("participants", "_id", "surveyId", "participantDocs") + + val projectStage = + Aggregation + .project("_id", "title", "thumbnail", "updatedAt", "status", "publishedAt", "finishedAt") + .andExpression("size(participantDocs)") + .`as`("responseCount") + + val sortStage = Aggregation.sort(sortType.toSort()) + + val aggregation = Aggregation.newAggregation(matchStage, lookupStage, projectStage, sortStage) + + val results = mongoTemplate.aggregate(aggregation, "surveys", MyPageSurveyInfoResponse::class.java) + return results.mappedResults + } + + private fun MySurveySortType.toSort() = + when (this) { + MySurveySortType.LAST_MODIFIED -> Sort.by(Sort.Direction.DESC, "updatedAt") + MySurveySortType.OLD_MODIFIED -> Sort.by(Sort.Direction.ASC, "updatedAt") + MySurveySortType.TITLE_ASC -> Sort.by(Sort.Direction.ASC, "title") + MySurveySortType.TITLE_DESC -> Sort.by(Sort.Direction.DESC, "title") + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt index a6783154..493cca57 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt @@ -9,7 +9,9 @@ import org.springframework.stereotype.Repository import java.util.UUID @Repository -interface SurveyRepository : MongoRepository { +interface SurveyRepository : + MongoRepository, + SurveyCustomRepository { fun findByStatusAndIsVisibleTrue( status: SurveyStatus, pageable: Pageable, From 7f9df6f318897659ed4b3c0e9fee330d39ff7653 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 14 Sep 2024 23:28:51 +0900 Subject: [PATCH 093/201] =?UTF-8?q?[SBL-119]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EC=99=80=20=EC=A0=95=EB=A0=AC=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=EA=B9=8C=EC=A7=80=20=EB=B0=9B=EC=95=84=EC=84=9C=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EC=84=A4=EB=AC=B8=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EB=A5=BC=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20ada?= =?UTF-8?q?pter=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt | 7 ++++++- .../sbl/sulmun2yong/survey/repository/SurveyRepository.kt | 2 -- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt index 3fc9f6fe..c7eaa5db 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt @@ -2,6 +2,7 @@ package com.sbl.sulmun2yong.survey.adapter import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.SurveyStatus +import com.sbl.sulmun2yong.survey.dto.request.MySurveySortType import com.sbl.sulmun2yong.survey.dto.request.SurveySortType import com.sbl.sulmun2yong.survey.entity.SurveyDocument import com.sbl.sulmun2yong.survey.exception.SurveyNotFoundException @@ -57,5 +58,9 @@ class SurveyAdapter( userId: UUID, ) = surveyRepository.existsByIdAndMakerId(surveyId, userId) - fun getMyPageSurveys(makerId: UUID) = surveyRepository.findByMakerId(makerId).map { it.toDomain() } + fun getMyPageSurveysInfo( + makerId: UUID, + status: SurveyStatus?, + sortType: MySurveySortType, + ) = surveyRepository.findSurveysWithResponseCount(makerId, status, sortType) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt index 493cca57..eddfc10d 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt @@ -21,6 +21,4 @@ interface SurveyRepository : surveyId: UUID, userId: UUID, ): Boolean - - fun findByMakerId(makerId: UUID): List } From d46334338b8964cb4582569d45caefe2687f9eb9 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 14 Sep 2024 23:29:21 +0900 Subject: [PATCH 094/201] =?UTF-8?q?[SBL-119]=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=84=A4=EB=AC=B8=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?API=EC=97=90=20=EC=A0=95=EB=A0=AC=20=EB=B0=8F=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=EB=A7=81=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80,?= =?UTF-8?q?=20=EC=9D=91=EB=8B=B5=20=EC=88=98=20=EB=93=B1=EC=9D=98=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/controller/SurveyInfoController.kt | 6 +++++- .../survey/controller/doc/SurveyInfoApiDoc.kt | 4 ++++ .../dto/response/MyPageSurveysResponse.kt | 20 +------------------ .../survey/service/SurveyInfoService.kt | 11 +++++++--- 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyInfoController.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyInfoController.kt index ca3d7dea..4253d99a 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyInfoController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyInfoController.kt @@ -2,6 +2,8 @@ package com.sbl.sulmun2yong.survey.controller import com.sbl.sulmun2yong.global.annotation.LoginUser import com.sbl.sulmun2yong.survey.controller.doc.SurveyInfoApiDoc +import com.sbl.sulmun2yong.survey.domain.SurveyStatus +import com.sbl.sulmun2yong.survey.dto.request.MySurveySortType import com.sbl.sulmun2yong.survey.dto.request.SurveySortType import com.sbl.sulmun2yong.survey.dto.response.MyPageSurveysResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyInfoResponse @@ -45,5 +47,7 @@ class SurveyInfoController( @GetMapping("/my-page") override fun getMyPageSurveys( @LoginUser userId: UUID, - ): ResponseEntity = ResponseEntity.ok(surveyInfoService.getMyPageSurveys(userId)) + @RequestParam status: SurveyStatus?, + @RequestParam(defaultValue = "LAST_MODIFIED") sortType: MySurveySortType, + ): ResponseEntity = ResponseEntity.ok(surveyInfoService.getMyPageSurveys(userId, status, sortType)) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyInfoApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyInfoApiDoc.kt index 00e1af5c..6dedcdcd 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyInfoApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyInfoApiDoc.kt @@ -1,6 +1,8 @@ package com.sbl.sulmun2yong.survey.controller.doc import com.sbl.sulmun2yong.global.annotation.LoginUser +import com.sbl.sulmun2yong.survey.domain.SurveyStatus +import com.sbl.sulmun2yong.survey.dto.request.MySurveySortType import com.sbl.sulmun2yong.survey.dto.request.SurveySortType import com.sbl.sulmun2yong.survey.dto.response.MyPageSurveysResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyInfoResponse @@ -41,5 +43,7 @@ interface SurveyInfoApiDoc { @GetMapping("/my-page") fun getMyPageSurveys( @LoginUser userId: UUID, + @RequestParam status: SurveyStatus?, + @RequestParam(defaultValue = "LAST_MODIFIED") sortType: MySurveySortType, ): ResponseEntity } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveysResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveysResponse.kt index 77ffbc54..1e75ea8c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveysResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveysResponse.kt @@ -1,23 +1,5 @@ package com.sbl.sulmun2yong.survey.dto.response -import com.sbl.sulmun2yong.survey.domain.Survey -import com.sbl.sulmun2yong.survey.domain.SurveyStatus -import java.util.UUID - data class MyPageSurveysResponse( val surveys: List, -) { - data class MyPageSurveyInfoResponse( - val surveyId: UUID, - val title: String, - val status: SurveyStatus, - ) { - companion object { - fun from(survey: Survey) = MyPageSurveyInfoResponse(survey.id, survey.title, survey.status) - } - } - - companion object { - fun from(surveys: List) = MyPageSurveysResponse(surveys.map { MyPageSurveyInfoResponse.from(it) }) - } -} +) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt index 85a9343d..d2d6f72c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyInfoService.kt @@ -3,6 +3,7 @@ package com.sbl.sulmun2yong.survey.service import com.sbl.sulmun2yong.drawing.adapter.DrawingBoardAdapter import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter import com.sbl.sulmun2yong.survey.domain.SurveyStatus +import com.sbl.sulmun2yong.survey.dto.request.MySurveySortType import com.sbl.sulmun2yong.survey.dto.request.SurveySortType import com.sbl.sulmun2yong.survey.dto.response.MyPageSurveysResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyInfoResponse @@ -47,8 +48,12 @@ class SurveyInfoService( return SurveyProgressInfoResponse.of(survey) } - fun getMyPageSurveys(makerId: UUID): MyPageSurveysResponse { - val surveys = surveyAdapter.getMyPageSurveys(makerId) - return MyPageSurveysResponse.from(surveys) + fun getMyPageSurveys( + makerId: UUID, + status: SurveyStatus?, + sortType: MySurveySortType, + ): MyPageSurveysResponse { + val myPageSurveysInfoResponse = surveyAdapter.getMyPageSurveysInfo(makerId, status, sortType) + return MyPageSurveysResponse(myPageSurveysInfoResponse) } } From f0f6f712a004f2f92fc1f5cf074185b454902e9c Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 14 Sep 2024 23:40:30 +0900 Subject: [PATCH 095/201] =?UTF-8?q?[SBL-119]=20Survey=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=97=90=20updatedAt=20=EC=B6=94=EA=B0=80=ED=96=88?= =?UTF-8?q?=EB=8D=98=20=EB=B6=80=EB=B6=84=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt | 2 -- .../com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt | 1 - .../sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt | 2 -- .../kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt | 4 +--- 4 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index 3e91013d..acb66549 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -29,7 +29,6 @@ data class Survey( /** 해당 설문의 설문이용 노출 여부(false면 메인 페이지 노출 X, 링크를 통해서만 접근 가능) */ val isVisible: Boolean, val makerId: UUID, - val updatedAt: Date, val sections: List
, ) { init { @@ -58,7 +57,6 @@ data class Survey( rewardSetting = NoRewardSetting, isVisible = true, makerId = makerId, - updatedAt = DateUtil.getCurrentDate(noSec = false, noMilliSecond = false), sections = listOf(Section.create()), ) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt index ce98670c..66cf65fe 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt @@ -170,7 +170,6 @@ data class SurveyDocument( ), isVisible = this.isVisible, makerId = this.makerId, - updatedAt = this.updatedAt, sections = this.sections.map { it.toDomain(sectionIds) }, ) } diff --git a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt index 0e209d65..6c8bf345 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt @@ -57,7 +57,6 @@ object SurveyFixtureFactory { makerId: UUID = UUID.randomUUID(), rewards: List = REWARDS, isVisible: Boolean = true, - updatedAt: Date = DateUtil.getCurrentDate(noSec = false, noMilliSecond = false), sections: List
= SECTIONS, ) = Survey( id = id, @@ -70,7 +69,6 @@ object SurveyFixtureFactory { makerId = makerId, rewardSetting = createRewardSetting(type, rewards, targetParticipantCount, finishedAt), isVisible = isVisible, - updatedAt = updatedAt, sections = sections, ) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt index c82ae534..cacecdc1 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt @@ -68,8 +68,7 @@ class SurveyTest { fun `설문을 생성하면 설문의 정보들이 설정된다`() { // given, when val makerId: UUID = UUID.randomUUID() - val updatedAt = DateUtil.getCurrentDate(noSec = false, noMilliSecond = false) - val survey = createSurvey(id = id, makerId = makerId, updatedAt = updatedAt) + val survey = createSurvey(id = id, makerId = makerId) val defaultSurvey = Survey.create(makerId) // then @@ -84,7 +83,6 @@ class SurveyTest { assertEquals(createRewardSetting(), this.rewardSetting) assertEquals(true, this.isVisible) assertEquals(makerId, this.makerId) - assertEquals(updatedAt, this.updatedAt) assertEquals(SECTIONS, this.sections) } From a98a193fbc433311f96e3aa9090fa47bb7726c28 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sun, 15 Sep 2024 02:55:45 +0900 Subject: [PATCH 096/201] =?UTF-8?q?[SBL-119]=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=84=A4=EB=AC=B8=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?API=EC=97=90=EC=84=9C=20=EC=8B=9C=EC=9E=91=20=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/survey/dto/response/MyPageSurveyInfoResponse.kt | 1 - .../sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveyInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveyInfoResponse.kt index 0135da9b..84e81131 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveyInfoResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/MyPageSurveyInfoResponse.kt @@ -10,7 +10,6 @@ data class MyPageSurveyInfoResponse( val thumbnail: String?, val updatedAt: Date, val status: SurveyStatus, - val publishedAt: Date?, val finishedAt: Date?, val responseCount: Int, ) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt index 96c276cc..9d57b825 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt @@ -28,7 +28,7 @@ class SurveyCustomRepositoryImpl( val projectStage = Aggregation - .project("_id", "title", "thumbnail", "updatedAt", "status", "publishedAt", "finishedAt") + .project("_id", "title", "thumbnail", "updatedAt", "status", "finishedAt") .andExpression("size(participantDocs)") .`as`("responseCount") From d0d7e7ab505fb7095c5f2c94b0fa048c724f0a82 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 16 Sep 2024 17:35:45 +0900 Subject: [PATCH 097/201] =?UTF-8?q?[SBL-138]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=EC=97=90=EC=84=9C=20=EC=A7=88=EB=AC=B8=20ID?= =?UTF-8?q?=EC=97=90=20=ED=95=B4=EB=8B=B9=ED=95=98=EB=8A=94=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EB=A7=8C=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/domain/result/SurveyResult.kt | 2 ++ .../survey/domain/result/SurveyResultTest.kt | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt index 63f36914..b951fcfb 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt @@ -16,6 +16,8 @@ data class SurveyResult( return filteredSurveyResult } + fun findResultDetailsByQuestionId(questionId: UUID) = resultDetails.filter { it.questionId == questionId } + private fun filterByQuestionFilter(questionFilter: QuestionFilter): SurveyResult { val participantSet = getMatchedParticipants(questionFilter) return if (questionFilter.isPositive) { diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResultTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResultTest.kt index bcb5b15f..b193aa90 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResultTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResultTest.kt @@ -1,6 +1,7 @@ package com.sbl.sulmun2yong.survey.domain.result import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.EXCEPT_STUDENT_FILTER +import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.JOB_QUESTION_ID import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.K_J_FOOD_FILTER import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.MAN_FILTER import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.PARTICIPANT_RESULT_DETAILS_1 @@ -158,4 +159,21 @@ class SurveyResultTest { assertTrue { this.containsAll(PARTICIPANT_RESULT_DETAILS_2) } } } + + @Test + fun `설문 결과는 질문 ID를 받으면 해당 질문의 응답들만 반환한다`() { + // given + val surveyResult = SURVEY_RESULT + + // when + val jobQuestionResponses = surveyResult.findResultDetailsByQuestionId(JOB_QUESTION_ID) + + // then + assertEquals(3, jobQuestionResponses.size) + with(jobQuestionResponses.map { it.contents }.flatten()) { + assertEquals(true, containsAll(PARTICIPANT_RESULT_DETAILS_1[0].contents)) + assertEquals(true, containsAll(PARTICIPANT_RESULT_DETAILS_2[0].contents)) + assertEquals(true, containsAll(PARTICIPANT_RESULT_DETAILS_3[0].contents)) + } + } } From 6ea85e79319ec87579a6e3ce2c456f92641a403e Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 16 Sep 2024 17:44:38 +0900 Subject: [PATCH 098/201] =?UTF-8?q?[SBL-138]=20surveyId=EC=99=80=20makerId?= =?UTF-8?q?=EB=A1=9C=20=EC=84=A4=EB=AC=B8=EC=9D=84=20=EA=B0=80=EC=A0=B8?= =?UTF-8?q?=EC=98=A4=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt | 6 +++--- .../sulmun2yong/survey/repository/SurveyRepository.kt | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt index c7eaa5db..6a40345c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt @@ -53,10 +53,10 @@ class SurveyAdapter( surveyRepository.save(surveyDocument) } - fun existsByIdAndMakerId( + fun getByIdAndMakerId( surveyId: UUID, - userId: UUID, - ) = surveyRepository.existsByIdAndMakerId(surveyId, userId) + makerId: UUID, + ) = surveyRepository.findByIdAndMakerId(surveyId, makerId).orElseThrow { SurveyNotFoundException() }.toDomain() fun getMyPageSurveysInfo( makerId: UUID, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt index eddfc10d..83f8adcb 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt @@ -6,6 +6,7 @@ import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.data.mongodb.repository.MongoRepository import org.springframework.stereotype.Repository +import java.util.Optional import java.util.UUID @Repository @@ -17,8 +18,8 @@ interface SurveyRepository : pageable: Pageable, ): Page - fun existsByIdAndMakerId( - surveyId: UUID, - userId: UUID, - ): Boolean + fun findByIdAndMakerId( + id: UUID, + makerId: UUID, + ): Optional } From 02274f453984c7851e1d0e1c21fe44ca0494397b Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 16 Sep 2024 17:45:20 +0900 Subject: [PATCH 099/201] =?UTF-8?q?[SBL-138]=20=EC=84=A4=EB=AC=B8=20ID?= =?UTF-8?q?=EC=99=80=20=EC=B0=B8=EC=97=AC=EC=9E=90=20ID=EB=A1=9C=20?= =?UTF-8?q?=ED=95=B4=EB=8B=B9=ED=95=98=EB=8A=94=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=EC=9D=84=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/survey/adapter/ResponseAdapter.kt | 12 ++++++++++-- .../survey/repository/ResponseRepository.kt | 5 +++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt index 1aaec7d0..0c29a4e0 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt @@ -34,8 +34,16 @@ class ResponseAdapter( } } - fun getSurveyResult(surveyId: UUID): SurveyResult { - val responses = responseRepository.findBySurveyId(surveyId) + fun getSurveyResult( + surveyId: UUID, + participantId: UUID?, + ): SurveyResult { + val responses = + if (participantId != null) { + responseRepository.findBySurveyIdAndParticipantId(surveyId, participantId) + } else { + responseRepository.findBySurveyId(surveyId) + } // TODO: 추후 DB Level에서 처리하도록 변경 + 필터링을 동적쿼리로 하도록 변경 val groupingResponses = responses.groupBy { "${it.questionId}|${it.participantId}" }.values groupingResponses.map { it.toDomain() } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ResponseRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ResponseRepository.kt index 81fef8bd..5480bbc8 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ResponseRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ResponseRepository.kt @@ -8,4 +8,9 @@ import java.util.UUID @Repository interface ResponseRepository : MongoRepository { fun findBySurveyId(surveyId: UUID): List + + fun findBySurveyIdAndParticipantId( + surveyId: UUID, + participantId: UUID, + ): List } From d34455d25653d3844464eb125d0f083a10ff71b9 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 16 Sep 2024 17:46:13 +0900 Subject: [PATCH 100/201] =?UTF-8?q?[SBL-138]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20ResponseBody=EC=97=90=20=EC=84=B9=EC=85=98?= =?UTF-8?q?=20=EB=B0=8F=20=EC=A7=88=EB=AC=B8=EC=9D=98=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=EB=A5=BC=20=ED=8F=AC=ED=95=A8=EC=8B=9C=ED=82=A4=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/SurveyResultResponse.kt | 76 ++++++++++++++----- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt index 15d2ac46..c245b9f1 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt @@ -1,38 +1,76 @@ package com.sbl.sulmun2yong.survey.dto.response +import com.sbl.sulmun2yong.survey.domain.Survey +import com.sbl.sulmun2yong.survey.domain.question.Question +import com.sbl.sulmun2yong.survey.domain.question.QuestionType import com.sbl.sulmun2yong.survey.domain.result.ResultDetails import com.sbl.sulmun2yong.survey.domain.result.SurveyResult +import com.sbl.sulmun2yong.survey.domain.section.Section import java.util.UUID data class SurveyResultResponse( - val results: List, + val sectionResults: List, ) { companion object { - fun of(surveyResult: SurveyResult) = - SurveyResultResponse( - results = - surveyResult.resultDetails.groupBy { it.questionId }.map { - Result.from(it.value) - }, - ) + fun of( + surveyResult: SurveyResult, + survey: Survey, + ) = SurveyResultResponse(survey.sections.map { SectionResultResponse.of(surveyResult, it) }) } - data class Result( + data class SectionResultResponse( + val sectionId: UUID, + val title: String, + val questionResults: List, + ) { + companion object { + fun of( + surveyResult: SurveyResult, + section: Section, + ): SectionResultResponse = + SectionResultResponse( + sectionId = section.id.value, + title = section.title, + questionResults = + section.questions.map { + QuestionResultResponse.of( + question = it, + responses = surveyResult.findResultDetailsByQuestionId(it.id), + ) + }, + ) + } + } + + data class QuestionResultResponse( val questionId: UUID, + val title: String, + val type: QuestionType, + val participantCount: Int, val responses: List, ) { companion object { - fun from(responses: List): Result = - Result( - questionId = responses.first().questionId, - responses = - responses - .map { it.contents } - .flatten() - .groupingBy { it } - .eachCount() - .map { Response(it.key, it.value) }, + fun of( + question: Question, + responses: List, + ): QuestionResultResponse { + val contentCountMap = + responses + .map { it.contents } + .flatten() + .groupingBy { it } + .eachCount() + .toMutableMap() + val contents = question.choices?.standardChoices?.map { it.content } ?: emptyList() + contents.forEach { contentCountMap.putIfAbsent(it, 0) } + return QuestionResultResponse( + questionId = question.id, + title = question.title, + type = question.questionType, + participantCount = responses.size, + responses = contentCountMap.map { Response(it.key, it.value) }, ) + } } data class Response( From 9618826c5083461f9d55badf99d3b446a641a89f Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 16 Sep 2024 17:46:55 +0900 Subject: [PATCH 101/201] =?UTF-8?q?[SBL-138]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20API=EA=B0=80=20=EC=B0=B8=EA=B0=80=EC=9E=90?= =?UTF-8?q?=20ID=EB=A1=9C=20=ED=95=84=ED=84=B0=EB=A7=81=EC=9D=84=20?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EA=B3=A0,=20=EC=84=A4?= =?UTF-8?q?=EB=AC=B8=EC=9D=98=20=EC=A0=95=EB=B3=B4=EB=8F=84=20=ED=8F=AC?= =?UTF-8?q?=ED=95=A8=20=EC=8B=9C=ED=82=A4=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/controller/SurveyResultController.kt | 4 +++- .../survey/controller/doc/SurveyResultApiDoc.kt | 2 ++ .../survey/service/SurveyResultService.kt | 12 +++++------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResultController.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResultController.kt index c755cab6..e1382b47 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResultController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResultController.kt @@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.PathVariable 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 import java.util.UUID @@ -22,5 +23,6 @@ class SurveyResultController( @PathVariable("survey-id") surveyId: UUID, @LoginUser id: UUID, @RequestBody surveyResultRequest: SurveyResultRequest, - ) = ResponseEntity.ok(surveyResultService.getSurveyResult(surveyId, id, surveyResultRequest)) + @RequestParam participantId: UUID?, + ) = ResponseEntity.ok(surveyResultService.getSurveyResult(surveyId, id, surveyResultRequest, participantId)) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResultApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResultApiDoc.kt index 1b24efea..ae1f098e 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResultApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResultApiDoc.kt @@ -9,6 +9,7 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestParam import java.util.UUID @Tag(name = "SurveyResult", description = "설문 결과 관련 API") @@ -19,5 +20,6 @@ interface SurveyResultApiDoc { @PathVariable("survey-id") surveyId: UUID, @LoginUser id: UUID, @RequestBody surveyResultRequest: SurveyResultRequest, + @RequestParam participantId: UUID?, ): ResponseEntity } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt index 27107373..7a40bab4 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt @@ -4,7 +4,6 @@ import com.sbl.sulmun2yong.survey.adapter.ResponseAdapter import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter import com.sbl.sulmun2yong.survey.dto.request.SurveyResultRequest import com.sbl.sulmun2yong.survey.dto.response.SurveyResultResponse -import com.sbl.sulmun2yong.survey.exception.SurveyNotFoundException import org.springframework.stereotype.Service import java.util.UUID @@ -15,19 +14,18 @@ class SurveyResultService( ) { fun getSurveyResult( surveyId: UUID, - userId: UUID, + makerId: UUID, surveyResultRequest: SurveyResultRequest, + participantId: UUID?, ): SurveyResultResponse { - val isSurveyExists = surveyAdapter.existsByIdAndMakerId(surveyId, userId) - // 본인이 만든 설문이 아닌 경우 예외 발생 - if (!isSurveyExists) throw SurveyNotFoundException() + val survey = surveyAdapter.getByIdAndMakerId(surveyId, makerId) // DB에서 설문 결과 조회 - val surveyResult = responseAdapter.getSurveyResult(surveyId) + val surveyResult = responseAdapter.getSurveyResult(surveyId, participantId) // 요청에 따라 설문 결과 필터링 val resultFilter = surveyResultRequest.toDomain() val filteredSurveyResult = surveyResult.getFilteredResult(resultFilter) - return SurveyResultResponse.of(filteredSurveyResult) + return SurveyResultResponse.of(filteredSurveyResult, survey) } } From b4d825427903fea7727d513135b284a4f6a0aedb Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 18 Sep 2024 16:23:19 +0900 Subject: [PATCH 102/201] =?UTF-8?q?[SBL-139]=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EA=B5=AC=EC=A1=B0=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/controller/GenerateController.kt | 23 +++++++++++ .../ai/controller/doc/GenerateAPIDoc.kt | 18 +++++++++ .../ai/dto/request/SurveyGenerateRequest.kt | 7 ++++ .../ai/dto/response/SurveyGenerateResponse.kt | 8 ++++ .../ai/exception/InvalidFileUrlException.kt | 6 +++ .../sulmun2yong/ai/service/GenerateService.kt | 39 +++++++++++++++++++ .../sbl/sulmun2yong/global/config/S3Config.kt | 3 ++ .../sbl/sulmun2yong/global/error/ErrorCode.kt | 3 ++ .../sulmun2yong/global/util/FileValidator.kt | 10 +++++ 9 files changed, 117 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/dto/request/SurveyGenerateRequest.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/exception/InvalidFileUrlException.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt new file mode 100644 index 00000000..3da00653 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt @@ -0,0 +1,23 @@ +package com.sbl.sulmun2yong.ai.controller + +import com.sbl.sulmun2yong.ai.controller.doc.GenerateAPIDoc +import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerateRequest +import com.sbl.sulmun2yong.ai.dto.response.SurveyGenerateResponse +import com.sbl.sulmun2yong.ai.service.GenerateService +import org.springframework.http.ResponseEntity +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.RestController + +@RestController +@RequestMapping("/api/v1/ai/generate") +class GenerateController( + private val generateService: GenerateService, +) : GenerateAPIDoc { + @PostMapping("/survey") + override fun generateSurvey( + @RequestBody request: SurveyGenerateRequest, + ): ResponseEntity = + ResponseEntity.ok(generateService.generateSurvey(request.job, request.groupName, request.fileUrl)) +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt new file mode 100644 index 00000000..7d124bed --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt @@ -0,0 +1,18 @@ +package com.sbl.sulmun2yong.ai.controller.doc + +import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerateRequest +import com.sbl.sulmun2yong.ai.dto.response.SurveyGenerateResponse +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody + +@Tag(name = "AI 생성 기능", description = "AI 생성 기능 관련 API") +interface GenerateAPIDoc { + @Operation(summary = "설문 생성") + @PostMapping("/survey") + fun generateSurvey( + @RequestBody request: SurveyGenerateRequest, + ): ResponseEntity +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/request/SurveyGenerateRequest.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/request/SurveyGenerateRequest.kt new file mode 100644 index 00000000..887e2eaf --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/request/SurveyGenerateRequest.kt @@ -0,0 +1,7 @@ +package com.sbl.sulmun2yong.ai.dto.request + +data class SurveyGenerateRequest( + val job: String, + val groupName: String, + val fileUrl: String, +) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt new file mode 100644 index 00000000..44b19d8e --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt @@ -0,0 +1,8 @@ +package com.sbl.sulmun2yong.ai.dto.response + +data class SurveyGenerateResponse( + private val title: String, + private val description: String, + private val finishMessage: String, + private val sections: MutableList, +) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/InvalidFileUrlException.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/InvalidFileUrlException.kt new file mode 100644 index 00000000..b2d67f10 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/InvalidFileUrlException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.ai.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class InvalidFileUrlException : BusinessException(ErrorCode.INVALID_FILE_URL) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt new file mode 100644 index 00000000..f1a8c4a1 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -0,0 +1,39 @@ +package com.sbl.sulmun2yong.ai.service + +import com.sbl.sulmun2yong.ai.dto.response.SurveyGenerateResponse +import com.sbl.sulmun2yong.global.util.FileValidator +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Service +import org.springframework.web.client.RestTemplate + +@Service +class GenerateService( + private val fileValidator: FileValidator, +) { + fun generateSurvey( + job: String, + groupName: String, + fileUrl: String, + ): SurveyGenerateResponse { + fileValidator.validateFileUrlOf(fileUrl) + + val restTemplate = RestTemplate() + val url = "http://localhost:8000/generate/survey" + val requestBody = + mapOf( + "job" to job, + "group_name" to groupName, + "file_url" to fileUrl, + ) + val response: ResponseEntity = + restTemplate.postForEntity( + url, + requestBody, + SurveyGenerateResponse::class.java, + ) + + val responseBody = response.body ?: throw RuntimeException("Failed to generate survey") + println(responseBody) + return responseBody + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt index e1936899..5dd81604 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt @@ -26,6 +26,8 @@ class S3Config( private val allowedExtensions: String, @Value("\${aws.s3.allowed-content-types}") private val allowedContentTypes: String, + @Value("\${cloudfront.base-url}") + private val cloudFrontBaseUrl: String, ) { @Bean fun s3Client(): S3Client { @@ -45,5 +47,6 @@ class S3Config( maxFileNameLength = maxFileNameLength, allowedExtensions = allowedExtensions, allowedContentTypes = allowedContentTypes, + cloudFrontBaseUrl = cloudFrontBaseUrl, ) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index 907fd11b..41789459 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -63,4 +63,7 @@ enum class ErrorCode( FILE_NAME_TOO_LONG(HttpStatus.BAD_REQUEST, "S30004", "파일 이름이 너무 깁니다."), NO_FILE_EXIST(HttpStatus.BAD_REQUEST, "S30005", "파일이 존재하지 않습니다."), NO_EXTENSION_EXIST(HttpStatus.BAD_REQUEST, "S30006", "파일 확장자가 존재하지 않습니다."), + + // File (FL) + INVALID_FILE_URL(HttpStatus.BAD_REQUEST, "FL0001", "유효하지 않은 파일 주소입니다."), } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt index d4cfb63b..a610631c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt @@ -1,5 +1,6 @@ package com.sbl.sulmun2yong.global.util +import com.sbl.sulmun2yong.ai.exception.InvalidFileUrlException import com.sbl.sulmun2yong.aws.exception.FileNameTooLongException import com.sbl.sulmun2yong.aws.exception.FileNameTooShortException import com.sbl.sulmun2yong.aws.exception.InvalidExtensionException @@ -14,6 +15,7 @@ class FileValidator( private val maxFileNameLength: Int, private val allowedExtensions: MutableList, private val allowedContentTypes: MutableList, + private val cloudFrontBaseUrl: String, ) { companion object { fun from( @@ -21,6 +23,7 @@ class FileValidator( maxFileNameLength: Int, allowedExtensions: String, allowedContentTypes: String, + cloudFrontBaseUrl: String, ): FileValidator { val fileSize = maxFileSize.toBytes() val extensions = allowedExtensions.split(",").toMutableList() @@ -31,6 +34,7 @@ class FileValidator( maxFileNameLength, extensions, contentTypes, + cloudFrontBaseUrl, ) } } @@ -62,4 +66,10 @@ class FileValidator( throw InvalidExtensionException() } } + + fun validateFileUrlOf(fileUrl: String) { + if (fileUrl.startsWith(cloudFrontBaseUrl).not()) { + throw InvalidFileUrlException() + } + } } From 1716544f4dfbcf13c82be826f7a6f886443451da Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 18 Sep 2024 16:33:52 +0900 Subject: [PATCH 103/201] =?UTF-8?q?[SBL-139]=20reponse=20dto=20=EA=B5=AC?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/ai/controller/GenerateController.kt | 4 +--- .../sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt | 2 +- .../sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt | 8 ++++---- .../com/sbl/sulmun2yong/ai/service/GenerateService.kt | 1 - 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt index 3da00653..9b854202 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt @@ -2,7 +2,6 @@ package com.sbl.sulmun2yong.ai.controller import com.sbl.sulmun2yong.ai.controller.doc.GenerateAPIDoc import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerateRequest -import com.sbl.sulmun2yong.ai.dto.response.SurveyGenerateResponse import com.sbl.sulmun2yong.ai.service.GenerateService import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping @@ -18,6 +17,5 @@ class GenerateController( @PostMapping("/survey") override fun generateSurvey( @RequestBody request: SurveyGenerateRequest, - ): ResponseEntity = - ResponseEntity.ok(generateService.generateSurvey(request.job, request.groupName, request.fileUrl)) + ) = ResponseEntity.ok(generateService.generateSurvey(request.job, request.groupName, request.fileUrl)) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt index 7d124bed..63264c6f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt @@ -8,7 +8,7 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody -@Tag(name = "AI 생성 기능", description = "AI 생성 기능 관련 API") +@Tag(name = "AI", description = "AI 생성 기능 관련 API") interface GenerateAPIDoc { @Operation(summary = "설문 생성") @PostMapping("/survey") diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt index 44b19d8e..08a410eb 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt @@ -1,8 +1,8 @@ package com.sbl.sulmun2yong.ai.dto.response data class SurveyGenerateResponse( - private val title: String, - private val description: String, - private val finishMessage: String, - private val sections: MutableList, + val title: String, + val description: String, + val finishMessage: String, + val sections: MutableList, ) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index f1a8c4a1..c68536ce 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -33,7 +33,6 @@ class GenerateService( ) val responseBody = response.body ?: throw RuntimeException("Failed to generate survey") - println(responseBody) return responseBody } } From 631db3706d96d3afc76d3ec8f44c3ffa3b302e8d Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 18 Sep 2024 16:36:18 +0900 Subject: [PATCH 104/201] =?UTF-8?q?[SBL-139]=20ai-server=20=EC=A3=BC?= =?UTF-8?q?=EC=86=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c25834f3..feb63a35 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -36,5 +36,8 @@ frontend: backend: base-url: http://localhost:8080 +ai-sever: + base-url: http://localhost:8000 + cloudfront: base-url: https://files.sulmoon.io From f6d3ed112c53d072284c278163e24fadfe48d614 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 18 Sep 2024 16:38:09 +0900 Subject: [PATCH 105/201] =?UTF-8?q?[SBL-139]=20ai-server=20=EC=A3=BC?= =?UTF-8?q?=EC=86=8C=EB=A5=BC=20=EC=B0=B8=EC=A1=B0=EB=8F=84=ED=95=98?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/ai/service/GenerateService.kt | 6 +++++- src/main/resources/application.yml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index c68536ce..8fbc40df 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -2,12 +2,15 @@ package com.sbl.sulmun2yong.ai.service import com.sbl.sulmun2yong.ai.dto.response.SurveyGenerateResponse import com.sbl.sulmun2yong.global.util.FileValidator +import org.springframework.beans.factory.annotation.Value import org.springframework.http.ResponseEntity import org.springframework.stereotype.Service import org.springframework.web.client.RestTemplate @Service class GenerateService( + @Value("\${ai-server.base-url}") + private val aiServerBaseUrl: String, private val fileValidator: FileValidator, ) { fun generateSurvey( @@ -18,13 +21,14 @@ class GenerateService( fileValidator.validateFileUrlOf(fileUrl) val restTemplate = RestTemplate() - val url = "http://localhost:8000/generate/survey" + val url = "$aiServerBaseUrl/generate/survey" val requestBody = mapOf( "job" to job, "group_name" to groupName, "file_url" to fileUrl, ) + val response: ResponseEntity = restTemplate.postForEntity( url, diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index feb63a35..46f31d26 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -36,7 +36,7 @@ frontend: backend: base-url: http://localhost:8080 -ai-sever: +ai-server: base-url: http://localhost:8000 cloudfront: From 55437a54306d840a5cb5414eed2213c3d8b79cd0 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 19 Sep 2024 00:40:27 +0900 Subject: [PATCH 106/201] =?UTF-8?q?[SBL-139]=20AI=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=A0=9C=EC=9E=91=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=EC=9D=B4=20=20=EC=84=A4=EB=AC=B8=20=EC=A0=9C=EC=9E=91?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20DTO=EB=A5=BC=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B4=EB=8F=84=EB=A1=9D=20=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/controller/doc/GenerateAPIDoc.kt | 4 +- .../ai/domain/QuestionGeneratedByAI.kt | 11 +++++ .../ai/domain/SectionGeneratedByAI.kt | 7 ++++ .../ai/domain/SurveyGeneratedByAI.kt | 8 ++++ .../ai/dto/response/SurveyGenerateResponse.kt | 8 +--- .../sulmun2yong/ai/service/GenerateService.kt | 22 +++++----- .../dto/response/SurveyMakeInfoResponse.kt | 42 +++++++++++++++++++ 7 files changed, 83 insertions(+), 19 deletions(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/domain/QuestionGeneratedByAI.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/domain/SectionGeneratedByAI.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/domain/SurveyGeneratedByAI.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt index 63264c6f..141b96f7 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt @@ -1,7 +1,7 @@ package com.sbl.sulmun2yong.ai.controller.doc import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerateRequest -import com.sbl.sulmun2yong.ai.dto.response.SurveyGenerateResponse +import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity @@ -14,5 +14,5 @@ interface GenerateAPIDoc { @PostMapping("/survey") fun generateSurvey( @RequestBody request: SurveyGenerateRequest, - ): ResponseEntity + ): ResponseEntity } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/domain/QuestionGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/domain/QuestionGeneratedByAI.kt new file mode 100644 index 00000000..c5ee4e64 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/domain/QuestionGeneratedByAI.kt @@ -0,0 +1,11 @@ +package com.sbl.sulmun2yong.ai.domain + +import com.sbl.sulmun2yong.survey.domain.question.QuestionType + +class QuestionGeneratedByAI( + val questionType: QuestionType, + val title: String, + val isRequired: Boolean, + val choices: List, + val isAllowOther: Boolean, +) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/domain/SectionGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/domain/SectionGeneratedByAI.kt new file mode 100644 index 00000000..9323f627 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/domain/SectionGeneratedByAI.kt @@ -0,0 +1,7 @@ +package com.sbl.sulmun2yong.ai.domain + +class SectionGeneratedByAI( + val title: String, + val description: String, + val questions: List, +) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/domain/SurveyGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/domain/SurveyGeneratedByAI.kt new file mode 100644 index 00000000..141d34aa --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/domain/SurveyGeneratedByAI.kt @@ -0,0 +1,8 @@ +package com.sbl.sulmun2yong.ai.domain + +class SurveyGeneratedByAI( + val title: String, + val description: String, + val finishMessage: String, + val sections: List, +) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt index 08a410eb..97c29472 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt @@ -1,8 +1,4 @@ package com.sbl.sulmun2yong.ai.dto.response -data class SurveyGenerateResponse( - val title: String, - val description: String, - val finishMessage: String, - val sections: MutableList, -) +class SurveyGenerateResponse { +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index 8fbc40df..abe21011 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -1,9 +1,9 @@ package com.sbl.sulmun2yong.ai.service -import com.sbl.sulmun2yong.ai.dto.response.SurveyGenerateResponse +import com.sbl.sulmun2yong.ai.domain.SurveyGeneratedByAI import com.sbl.sulmun2yong.global.util.FileValidator +import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse import org.springframework.beans.factory.annotation.Value -import org.springframework.http.ResponseEntity import org.springframework.stereotype.Service import org.springframework.web.client.RestTemplate @@ -17,7 +17,7 @@ class GenerateService( job: String, groupName: String, fileUrl: String, - ): SurveyGenerateResponse { + ): SurveyMakeInfoResponse { fileValidator.validateFileUrlOf(fileUrl) val restTemplate = RestTemplate() @@ -29,14 +29,14 @@ class GenerateService( "file_url" to fileUrl, ) - val response: ResponseEntity = - restTemplate.postForEntity( - url, - requestBody, - SurveyGenerateResponse::class.java, - ) + val response = + restTemplate + .postForEntity( + url, + requestBody, + SurveyGeneratedByAI::class.java, + ).body ?: throw Exception("AI 서버에서 설문 생성에 실패했습니다.") - val responseBody = response.body ?: throw RuntimeException("Failed to generate survey") - return responseBody + return SurveyMakeInfoResponse.of(response) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt index bd25d43b..7d8f8ced 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt @@ -1,5 +1,8 @@ package com.sbl.sulmun2yong.survey.dto.response +import com.sbl.sulmun2yong.ai.domain.QuestionGeneratedByAI +import com.sbl.sulmun2yong.ai.domain.SectionGeneratedByAI +import com.sbl.sulmun2yong.ai.domain.SurveyGeneratedByAI import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.domain.question.Question @@ -41,6 +44,25 @@ data class SurveyMakeInfoResponse( isVisible = survey.isVisible, sections = survey.sections.map { SectionMakeInfoResponse.from(it) }, ) + + fun of(surveyGeneratedByAI: SurveyGeneratedByAI) = + SurveyMakeInfoResponse( + title = surveyGeneratedByAI.title, + description = surveyGeneratedByAI.description, + thumbnail = null, + publishedAt = null, + rewardSetting = + RewardSettingResponse( + type = RewardSettingType.NO_REWARD, + rewards = emptyList(), + targetParticipantCount = null, + finishedAt = null, + ), + status = SurveyStatus.NOT_STARTED, + finishMessage = "", + isVisible = true, + sections = surveyGeneratedByAI.sections.map { SectionMakeInfoResponse.from(it) }, + ) } data class RewardSettingResponse( @@ -72,6 +94,15 @@ data class SurveyMakeInfoResponse( questions = section.questions.map { QuestionMakeInfoResponse.from(it) }, routeDetails = RouteDetailsMakeInfoResponse.from(section.routingStrategy), ) + + fun from(sectionGeneratedByAI: SectionGeneratedByAI) = + SectionMakeInfoResponse( + sectionId = UUID.randomUUID(), + title = sectionGeneratedByAI.title, + description = sectionGeneratedByAI.description, + questions = sectionGeneratedByAI.questions.map { QuestionMakeInfoResponse.of(it) }, + routeDetails = RouteDetailsMakeInfoResponse.from(RoutingStrategy.NumericalOrder), + ) } } @@ -127,6 +158,17 @@ data class SurveyMakeInfoResponse( isAllowOther = question.choices?.isAllowOther ?: false, choices = question.choices?.standardChoices?.map { it.content }, ) + + fun of(questionGeneratedByAI: QuestionGeneratedByAI) = + QuestionMakeInfoResponse( + questionId = UUID.randomUUID(), + type = questionGeneratedByAI.questionType, + title = questionGeneratedByAI.title, + description = "", + isRequired = questionGeneratedByAI.isRequired, + isAllowOther = questionGeneratedByAI.isAllowOther, + choices = questionGeneratedByAI.choices.ifEmpty { null }, + ) } } } From 010030b8d4c0e4dc3dca11b50ccb836caa39273a Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 19 Sep 2024 11:33:57 +0900 Subject: [PATCH 107/201] =?UTF-8?q?[SBL-139]=20request=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/dto/response/SurveyGenerateResponse.kt | 4 ---- .../sulmun2yong/ai/service/GenerateService.kt | 18 ++++++++++++++---- .../sulmun2yong/global/util/FileValidator.kt | 14 +++++++------- .../exception/FileNameTooLongException.kt | 2 +- .../exception/FileNameTooShortException.kt | 2 +- .../exception/InvalidExtensionException.kt | 2 +- .../util}/exception/InvalidFileUrlException.kt | 2 +- .../exception/NoExtensionExistException.kt | 2 +- .../util}/exception/NoFileExistException.kt | 2 +- .../util}/exception/OutOfFileSizeException.kt | 2 +- 10 files changed, 28 insertions(+), 22 deletions(-) delete mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt rename src/main/kotlin/com/sbl/sulmun2yong/{aws => global/util}/exception/FileNameTooLongException.kt (79%) rename src/main/kotlin/com/sbl/sulmun2yong/{aws => global/util}/exception/FileNameTooShortException.kt (79%) rename src/main/kotlin/com/sbl/sulmun2yong/{aws => global/util}/exception/InvalidExtensionException.kt (79%) rename src/main/kotlin/com/sbl/sulmun2yong/{ai => global/util}/exception/InvalidFileUrlException.kt (78%) rename src/main/kotlin/com/sbl/sulmun2yong/{aws => global/util}/exception/NoExtensionExistException.kt (79%) rename src/main/kotlin/com/sbl/sulmun2yong/{aws => global/util}/exception/NoFileExistException.kt (78%) rename src/main/kotlin/com/sbl/sulmun2yong/{aws => global/util}/exception/OutOfFileSizeException.kt (78%) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt deleted file mode 100644 index 97c29472..00000000 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerateResponse.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.sbl.sulmun2yong.ai.dto.response - -class SurveyGenerateResponse { -} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index abe21011..8a0ce65a 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -17,10 +17,19 @@ class GenerateService( job: String, groupName: String, fileUrl: String, - ): SurveyMakeInfoResponse { + ): SurveyMakeInfoResponse { fileValidator.validateFileUrlOf(fileUrl) - val restTemplate = RestTemplate() + val response = postRequestToAIServer(job, groupName, fileUrl) + + return SurveyMakeInfoResponse.of(response) + } + + private fun postRequestToAIServer( + job: String, + groupName: String, + fileUrl: String, + ): SurveyGeneratedByAI { val url = "$aiServerBaseUrl/generate/survey" val requestBody = mapOf( @@ -29,7 +38,8 @@ class GenerateService( "file_url" to fileUrl, ) - val response = + val restTemplate = RestTemplate() + val surveyGeneratedByAI = restTemplate .postForEntity( url, @@ -37,6 +47,6 @@ class GenerateService( SurveyGeneratedByAI::class.java, ).body ?: throw Exception("AI 서버에서 설문 생성에 실패했습니다.") - return SurveyMakeInfoResponse.of(response) + return surveyGeneratedByAI } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt index a610631c..6ff30ac0 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt @@ -1,12 +1,12 @@ package com.sbl.sulmun2yong.global.util -import com.sbl.sulmun2yong.ai.exception.InvalidFileUrlException -import com.sbl.sulmun2yong.aws.exception.FileNameTooLongException -import com.sbl.sulmun2yong.aws.exception.FileNameTooShortException -import com.sbl.sulmun2yong.aws.exception.InvalidExtensionException -import com.sbl.sulmun2yong.aws.exception.NoExtensionExistException -import com.sbl.sulmun2yong.aws.exception.NoFileExistException -import com.sbl.sulmun2yong.aws.exception.OutOfFileSizeException +import com.sbl.sulmun2yong.global.util.exception.FileNameTooLongException +import com.sbl.sulmun2yong.global.util.exception.FileNameTooShortException +import com.sbl.sulmun2yong.global.util.exception.InvalidExtensionException +import com.sbl.sulmun2yong.global.util.exception.InvalidFileUrlException +import com.sbl.sulmun2yong.global.util.exception.NoExtensionExistException +import com.sbl.sulmun2yong.global.util.exception.NoFileExistException +import com.sbl.sulmun2yong.global.util.exception.OutOfFileSizeException import org.springframework.util.unit.DataSize import org.springframework.web.multipart.MultipartFile diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/FileNameTooLongException.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/FileNameTooLongException.kt similarity index 79% rename from src/main/kotlin/com/sbl/sulmun2yong/aws/exception/FileNameTooLongException.kt rename to src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/FileNameTooLongException.kt index f1265f0c..215b9e0f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/FileNameTooLongException.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/FileNameTooLongException.kt @@ -1,4 +1,4 @@ -package com.sbl.sulmun2yong.aws.exception +package com.sbl.sulmun2yong.global.util.exception import com.sbl.sulmun2yong.global.error.BusinessException import com.sbl.sulmun2yong.global.error.ErrorCode diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/FileNameTooShortException.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/FileNameTooShortException.kt similarity index 79% rename from src/main/kotlin/com/sbl/sulmun2yong/aws/exception/FileNameTooShortException.kt rename to src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/FileNameTooShortException.kt index e1b726e7..5ae05456 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/FileNameTooShortException.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/FileNameTooShortException.kt @@ -1,4 +1,4 @@ -package com.sbl.sulmun2yong.aws.exception +package com.sbl.sulmun2yong.global.util.exception import com.sbl.sulmun2yong.global.error.BusinessException import com.sbl.sulmun2yong.global.error.ErrorCode diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/InvalidExtensionException.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/InvalidExtensionException.kt similarity index 79% rename from src/main/kotlin/com/sbl/sulmun2yong/aws/exception/InvalidExtensionException.kt rename to src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/InvalidExtensionException.kt index 7aaf7235..c33496e9 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/InvalidExtensionException.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/InvalidExtensionException.kt @@ -1,4 +1,4 @@ -package com.sbl.sulmun2yong.aws.exception +package com.sbl.sulmun2yong.global.util.exception import com.sbl.sulmun2yong.global.error.BusinessException import com.sbl.sulmun2yong.global.error.ErrorCode diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/InvalidFileUrlException.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/InvalidFileUrlException.kt similarity index 78% rename from src/main/kotlin/com/sbl/sulmun2yong/ai/exception/InvalidFileUrlException.kt rename to src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/InvalidFileUrlException.kt index b2d67f10..00abd470 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/InvalidFileUrlException.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/InvalidFileUrlException.kt @@ -1,4 +1,4 @@ -package com.sbl.sulmun2yong.ai.exception +package com.sbl.sulmun2yong.global.util.exception import com.sbl.sulmun2yong.global.error.BusinessException import com.sbl.sulmun2yong.global.error.ErrorCode diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/NoExtensionExistException.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/NoExtensionExistException.kt similarity index 79% rename from src/main/kotlin/com/sbl/sulmun2yong/aws/exception/NoExtensionExistException.kt rename to src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/NoExtensionExistException.kt index c65e95ea..393630c1 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/NoExtensionExistException.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/NoExtensionExistException.kt @@ -1,4 +1,4 @@ -package com.sbl.sulmun2yong.aws.exception +package com.sbl.sulmun2yong.global.util.exception import com.sbl.sulmun2yong.global.error.BusinessException import com.sbl.sulmun2yong.global.error.ErrorCode diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/NoFileExistException.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/NoFileExistException.kt similarity index 78% rename from src/main/kotlin/com/sbl/sulmun2yong/aws/exception/NoFileExistException.kt rename to src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/NoFileExistException.kt index b1ce6ae5..3d18ba8e 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/NoFileExistException.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/NoFileExistException.kt @@ -1,4 +1,4 @@ -package com.sbl.sulmun2yong.aws.exception +package com.sbl.sulmun2yong.global.util.exception import com.sbl.sulmun2yong.global.error.BusinessException import com.sbl.sulmun2yong.global.error.ErrorCode diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/OutOfFileSizeException.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/OutOfFileSizeException.kt similarity index 78% rename from src/main/kotlin/com/sbl/sulmun2yong/aws/exception/OutOfFileSizeException.kt rename to src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/OutOfFileSizeException.kt index e214c0e1..b5688e4b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/aws/exception/OutOfFileSizeException.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/OutOfFileSizeException.kt @@ -1,4 +1,4 @@ -package com.sbl.sulmun2yong.aws.exception +package com.sbl.sulmun2yong.global.util.exception import com.sbl.sulmun2yong.global.error.BusinessException import com.sbl.sulmun2yong.global.error.ErrorCode From 6d8a5302bf58a3e8a12f6d1a9dc8cd67cdf4265d Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 19 Sep 2024 11:37:16 +0900 Subject: [PATCH 108/201] =?UTF-8?q?[SBL-139]=20=ED=97=88=EB=9D=BD=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=ED=99=95=EC=9E=A5=EC=9E=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/ai/service/GenerateService.kt | 3 ++- .../com/sbl/sulmun2yong/global/util/FileValidator.kt | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index 8a0ce65a..eb1fd41c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -18,7 +18,8 @@ class GenerateService( groupName: String, fileUrl: String, ): SurveyMakeInfoResponse { - fileValidator.validateFileUrlOf(fileUrl) + val allowedExtensions = mutableListOf(".txt", ".pdf") + fileValidator.validateFileUrlOf(fileUrl, allowedExtensions) val response = postRequestToAIServer(job, groupName, fileUrl) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt index 6ff30ac0..a691fe76 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt @@ -67,9 +67,15 @@ class FileValidator( } } - fun validateFileUrlOf(fileUrl: String) { + fun validateFileUrlOf( + fileUrl: String, + allowedExtensions: MutableList, + ) { if (fileUrl.startsWith(cloudFrontBaseUrl).not()) { throw InvalidFileUrlException() } + if (allowedExtensions.any { fileUrl.endsWith(it).not() }) { + throw InvalidExtensionException() + } } } From af6e542b9ad7772fa1ba5d95eec08ce25c9a86c7 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 19 Sep 2024 11:41:28 +0900 Subject: [PATCH 109/201] =?UTF-8?q?[SBL-139]=20=EC=97=90=EB=9F=AC=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/ai/exception/GenerationByAIFailed.kt | 6 ++++++ .../com/sbl/sulmun2yong/ai/service/GenerateService.kt | 4 ++-- .../kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt | 5 +++-- 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/exception/GenerationByAIFailed.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/GenerationByAIFailed.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/GenerationByAIFailed.kt new file mode 100644 index 00000000..cbe92996 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/GenerationByAIFailed.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.ai.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class GenerationByAIFailed : BusinessException(ErrorCode.GENERATION_BY_AI_FAILED) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index eb1fd41c..21a973c3 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -1,6 +1,7 @@ package com.sbl.sulmun2yong.ai.service import com.sbl.sulmun2yong.ai.domain.SurveyGeneratedByAI +import com.sbl.sulmun2yong.ai.exception.GenerationByAIFailed import com.sbl.sulmun2yong.global.util.FileValidator import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse import org.springframework.beans.factory.annotation.Value @@ -22,7 +23,6 @@ class GenerateService( fileValidator.validateFileUrlOf(fileUrl, allowedExtensions) val response = postRequestToAIServer(job, groupName, fileUrl) - return SurveyMakeInfoResponse.of(response) } @@ -46,7 +46,7 @@ class GenerateService( url, requestBody, SurveyGeneratedByAI::class.java, - ).body ?: throw Exception("AI 서버에서 설문 생성에 실패했습니다.") + ).body ?: throw GenerationByAIFailed() return surveyGeneratedByAI } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index 41789459..6845cb2f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -63,7 +63,8 @@ enum class ErrorCode( FILE_NAME_TOO_LONG(HttpStatus.BAD_REQUEST, "S30004", "파일 이름이 너무 깁니다."), NO_FILE_EXIST(HttpStatus.BAD_REQUEST, "S30005", "파일이 존재하지 않습니다."), NO_EXTENSION_EXIST(HttpStatus.BAD_REQUEST, "S30006", "파일 확장자가 존재하지 않습니다."), + INVALID_FILE_URL(HttpStatus.BAD_REQUEST, "S30001", "유효하지 않은 파일 주소입니다."), - // File (FL) - INVALID_FILE_URL(HttpStatus.BAD_REQUEST, "FL0001", "유효하지 않은 파일 주소입니다."), + // AI (AI) + GENERATION_BY_AI_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "AI0001", "AI를 활용한 생성에 실패했습니다."), } From 19f4bb08db52665812be3575824f55b72b28739d Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 19 Sep 2024 11:49:31 +0900 Subject: [PATCH 110/201] =?UTF-8?q?[SBL-139]=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt index a691fe76..5bcca339 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt @@ -74,7 +74,7 @@ class FileValidator( if (fileUrl.startsWith(cloudFrontBaseUrl).not()) { throw InvalidFileUrlException() } - if (allowedExtensions.any { fileUrl.endsWith(it).not() }) { + if (allowedExtensions.none { fileUrl.endsWith(it) }) { throw InvalidExtensionException() } } From fce7b1c93d74e9d8248d7b14dca588dd339f8971 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 19 Sep 2024 18:14:39 +0900 Subject: [PATCH 111/201] =?UTF-8?q?[SBL-135]=20=EC=9D=91=EB=8B=B5=20status?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=B6=84=EA=B8=B0=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=E3=85=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/ai/service/GenerateService.kt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index 21a973c3..47487ecb 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -5,6 +5,7 @@ import com.sbl.sulmun2yong.ai.exception.GenerationByAIFailed import com.sbl.sulmun2yong.global.util.FileValidator import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse import org.springframework.beans.factory.annotation.Value +import org.springframework.http.HttpStatus import org.springframework.stereotype.Service import org.springframework.web.client.RestTemplate @@ -40,14 +41,22 @@ class GenerateService( ) val restTemplate = RestTemplate() - val surveyGeneratedByAI = + val response = restTemplate .postForEntity( url, requestBody, SurveyGeneratedByAI::class.java, - ).body ?: throw GenerationByAIFailed() + ) - return surveyGeneratedByAI + val responseBody = response.body + + if (response.statusCode != HttpStatus.OK || + responseBody == null + ) { + throw GenerationByAIFailed() + } + + return responseBody } } From 43d12c83c98804b9fd5d4e8e4bac532e11ae0d88 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 19 Sep 2024 18:17:18 +0900 Subject: [PATCH 112/201] =?UTF-8?q?[SBL-135]=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index 47487ecb..5e4f5cae 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -40,9 +40,8 @@ class GenerateService( "file_url" to fileUrl, ) - val restTemplate = RestTemplate() val response = - restTemplate + RestTemplate() .postForEntity( url, requestBody, From 249065015e58c44113cfb60720b1fd54dce641c0 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 19 Sep 2024 18:40:10 +0900 Subject: [PATCH 113/201] =?UTF-8?q?[SBL-139]=20swagger=20=EC=84=A4?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt index 141b96f7..5c19fa11 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt @@ -8,9 +8,9 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody -@Tag(name = "AI", description = "AI 생성 기능 관련 API") +@Tag(name = "AI", description = "AI 기능 관련 API") interface GenerateAPIDoc { - @Operation(summary = "설문 생성") + @Operation(summary = "AI 설문 생성") @PostMapping("/survey") fun generateSurvey( @RequestBody request: SurveyGenerateRequest, From 17884966a78b54a2f490463b5509738c5a5b4c2c Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 19 Sep 2024 20:25:50 +0900 Subject: [PATCH 114/201] =?UTF-8?q?[SBL-139]=20=ED=8C=8C=EC=9D=B4=EC=8D=AC?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=98=A4=EB=8A=94=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FileExtensionNotSupportedException.kt | 6 ++++ .../SurveyGenerationByAIFailedException.kt | 6 ++++ ...nByAIFailed.kt => TextTooLongException.kt} | 2 +- .../sulmun2yong/ai/service/GenerateService.kt | 31 ++++++++---------- .../sbl/sulmun2yong/global/error/ErrorCode.kt | 6 ++-- .../error/PythonServerExceptionHandler.kt | 32 +++++++++++++++++++ 6 files changed, 63 insertions(+), 20 deletions(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/exception/FileExtensionNotSupportedException.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/exception/SurveyGenerationByAIFailedException.kt rename src/main/kotlin/com/sbl/sulmun2yong/ai/exception/{GenerationByAIFailed.kt => TextTooLongException.kt} (64%) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionHandler.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/FileExtensionNotSupportedException.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/FileExtensionNotSupportedException.kt new file mode 100644 index 00000000..4cdb491c --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/FileExtensionNotSupportedException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.ai.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class FileExtensionNotSupportedException : BusinessException(ErrorCode.FILE_EXTENSION_NOT_SUPPORTED) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/SurveyGenerationByAIFailedException.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/SurveyGenerationByAIFailedException.kt new file mode 100644 index 00000000..4c0f3731 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/SurveyGenerationByAIFailedException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.ai.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class SurveyGenerationByAIFailedException : BusinessException(ErrorCode.SURVEY_GENERATION_BY_AI_FAILED) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/GenerationByAIFailed.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/TextTooLongException.kt similarity index 64% rename from src/main/kotlin/com/sbl/sulmun2yong/ai/exception/GenerationByAIFailed.kt rename to src/main/kotlin/com/sbl/sulmun2yong/ai/exception/TextTooLongException.kt index cbe92996..fcf2652a 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/GenerationByAIFailed.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/TextTooLongException.kt @@ -3,4 +3,4 @@ package com.sbl.sulmun2yong.ai.exception import com.sbl.sulmun2yong.global.error.BusinessException import com.sbl.sulmun2yong.global.error.ErrorCode -class GenerationByAIFailed : BusinessException(ErrorCode.GENERATION_BY_AI_FAILED) +class TextTooLongException : BusinessException(ErrorCode.TEXT_TOO_LONG) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index 5e4f5cae..a1088ce5 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -1,12 +1,13 @@ package com.sbl.sulmun2yong.ai.service import com.sbl.sulmun2yong.ai.domain.SurveyGeneratedByAI -import com.sbl.sulmun2yong.ai.exception.GenerationByAIFailed +import com.sbl.sulmun2yong.ai.exception.SurveyGenerationByAIFailedException +import com.sbl.sulmun2yong.global.error.PythonServerExceptionHandler import com.sbl.sulmun2yong.global.util.FileValidator import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse import org.springframework.beans.factory.annotation.Value -import org.springframework.http.HttpStatus import org.springframework.stereotype.Service +import org.springframework.web.client.HttpClientErrorException import org.springframework.web.client.RestTemplate @Service @@ -40,21 +41,17 @@ class GenerateService( "file_url" to fileUrl, ) - val response = - RestTemplate() - .postForEntity( - url, - requestBody, - SurveyGeneratedByAI::class.java, - ) - - val responseBody = response.body - - if (response.statusCode != HttpStatus.OK || - responseBody == null - ) { - throw GenerationByAIFailed() - } + val responseBody = + try { + RestTemplate() + .postForEntity( + url, + requestBody, + SurveyGeneratedByAI::class.java, + ).body ?: throw SurveyGenerationByAIFailedException() + } catch (e: HttpClientErrorException) { + throw PythonServerExceptionHandler.handlerException(e) + } return responseBody } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index 6845cb2f..c4caa920 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -65,6 +65,8 @@ enum class ErrorCode( NO_EXTENSION_EXIST(HttpStatus.BAD_REQUEST, "S30006", "파일 확장자가 존재하지 않습니다."), INVALID_FILE_URL(HttpStatus.BAD_REQUEST, "S30001", "유효하지 않은 파일 주소입니다."), - // AI (AI) - GENERATION_BY_AI_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "AI0001", "AI를 활용한 생성에 실패했습니다."), + // Python Server (PY) + SURVEY_GENERATION_BY_AI_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "PY0001", "AI를 활용한 설문 생성에 실패했습니다."), + TEXT_TOO_LONG(HttpStatus.BAD_REQUEST, "PY0002", "텍스트 길이는 1만 2천자 이하여야 합니다"), + FILE_EXTENSION_NOT_SUPPORTED(HttpStatus.BAD_REQUEST, "PY0003", "지원하지 않는 파일 확장자입니다."), } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionHandler.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionHandler.kt new file mode 100644 index 00000000..5c44ff24 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionHandler.kt @@ -0,0 +1,32 @@ +package com.sbl.sulmun2yong.global.error + +import com.fasterxml.jackson.databind.ObjectMapper +import com.sbl.sulmun2yong.ai.exception.FileExtensionNotSupportedException +import com.sbl.sulmun2yong.ai.exception.SurveyGenerationByAIFailedException +import com.sbl.sulmun2yong.ai.exception.TextTooLongException +import org.springframework.web.client.HttpClientErrorException + +class PythonServerExceptionHandler { + companion object { + private val objectMapper = ObjectMapper() + + data class ErrorDetail( + val code: String = "", + val message: String = "", + ) + + data class PythonServerException( + val detail: ErrorDetail = ErrorDetail(), + ) + + fun handlerException(e: HttpClientErrorException): BusinessException { + val exception = objectMapper.readValue(e.responseBodyAsString, PythonServerException::class.java) + when (exception.detail.code) { + "PY0001" -> throw SurveyGenerationByAIFailedException() + "PY0002" -> throw TextTooLongException() + "PY0003" -> throw FileExtensionNotSupportedException() + else -> throw e + } + } + } +} From 66c8dc955868389757784c922cff0e5819cbcb1a Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Fri, 20 Sep 2024 23:57:48 +0900 Subject: [PATCH 115/201] =?UTF-8?q?[SBL-144]=20=EC=8B=9C=EC=9E=91=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=A0=84=EC=9D=98=20=EC=95=84=EB=AC=B4=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D=EB=8F=84=20=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=A6=AC=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/domain/reward/NotStartedDrawSetting.kt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/NotStartedDrawSetting.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/NotStartedDrawSetting.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/NotStartedDrawSetting.kt new file mode 100644 index 00000000..6989b5a1 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/NotStartedDrawSetting.kt @@ -0,0 +1,9 @@ +package com.sbl.sulmun2yong.survey.domain.reward + +/** 시작 전 상태의 불완전한 리워드 지급 설정 */ +data class NotStartedDrawSetting( + override val type: RewardSettingType, + override val rewards: List, + override val targetParticipantCount: Int?, + override val finishedAt: FinishedAt?, +) : RewardSetting From 59baff19f09ea96d7573aff8b4d7acdac918dad3 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 21 Sep 2024 01:15:38 +0900 Subject: [PATCH 116/201] =?UTF-8?q?[SBL-144]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EC=8B=9C=20=EC=84=A4=EB=AC=B8=EC=9D=B4=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=20=EC=A4=91=EC=9D=B8=EC=A7=80=20=EC=B2=B4=ED=81=AC?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=B6=80=EB=B6=84=EC=9D=84=20Survey=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/survey/domain/Survey.kt | 2 ++ .../survey/service/SurveyResponseService.kt | 5 ----- .../sbl/sulmun2yong/survey/domain/SurveyTest.kt | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index acb66549..7c900210 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -63,6 +63,8 @@ data class Survey( /** 설문의 응답 순서가 유효한지, 응답이 각 섹션에 유효한지 확인하는 메서드 */ fun validateResponse(surveyResponse: SurveyResponse) { + // 진행 중인 설문이 아니면 응답이 유효한지 확인할 수 없다. + require(status == SurveyStatus.IN_PROGRESS) { throw SurveyClosedException() } // 확인할 응답의 예상 섹션 ID, 첫 응답의 섹션 ID는 첫 섹션의 ID var expectedSectionId: SectionId = sections.first().id for (sectionResponse in surveyResponse) { diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt index 1302ab3a..53d1c7c8 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt @@ -4,10 +4,8 @@ import com.sbl.sulmun2yong.survey.adapter.ParticipantAdapter import com.sbl.sulmun2yong.survey.adapter.ResponseAdapter import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter import com.sbl.sulmun2yong.survey.domain.Participant -import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.dto.request.SurveyResponseRequest import com.sbl.sulmun2yong.survey.dto.response.SurveyParticipantResponse -import com.sbl.sulmun2yong.survey.exception.SurveyClosedException import org.springframework.stereotype.Service import java.util.UUID @@ -23,9 +21,6 @@ class SurveyResponseService( surveyResponseRequest: SurveyResponseRequest, ): SurveyParticipantResponse { val survey = surveyAdapter.getSurvey(surveyId) - if (survey.status != SurveyStatus.IN_PROGRESS) { - throw SurveyClosedException() - } val surveyResponse = surveyResponseRequest.toDomain(surveyId) survey.validateResponse(surveyResponse) // TODO: 참가자 객체의 UserId에 실제 유저 값 넣기 diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt index cacecdc1..a88f0b06 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt @@ -27,6 +27,7 @@ import com.sbl.sulmun2yong.survey.exception.InvalidSurveyException import com.sbl.sulmun2yong.survey.exception.InvalidSurveyResponseException import com.sbl.sulmun2yong.survey.exception.InvalidSurveyStartException import com.sbl.sulmun2yong.survey.exception.InvalidUpdateSurveyException +import com.sbl.sulmun2yong.survey.exception.SurveyClosedException import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows @@ -179,6 +180,17 @@ class SurveyTest { val id = UUID.randomUUID() val survey = createSurvey(id = id, sections = listOf(section1, section2, section3)) + val notStartedSurvey = + createSurvey( + id = id, + sections = listOf(section1, section2, section3), + status = SurveyStatus.NOT_STARTED, + rewards = emptyList(), + targetParticipantCount = null, + finishedAt = null, + publishedAt = null, + type = RewardSettingType.NO_REWARD, + ) val surveyResponse1 = SurveyResponse( @@ -215,6 +227,8 @@ class SurveyTest { assertThrows { survey.validateResponse(surveyResponse2) } // 마지막 섹션을 응답하지 않은 경우 assertThrows { survey.validateResponse(surveyResponse3) } + // 설문이 시작되지 않은 경우 예외 발생 + assertThrows { notStartedSurvey.validateResponse(surveyResponse1) } } @Test From 04c9dcc9f04476d0e6226efbd14f7556e94a5823 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 21 Sep 2024 01:21:38 +0900 Subject: [PATCH 117/201] =?UTF-8?q?[SBL-144]=20Choices=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=EC=97=90=EB=8A=94=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=EC=A7=80=20=EB=82=B4=EC=9A=A9=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D?= =?UTF-8?q?=EC=9D=84=20=EC=A7=84=ED=96=89=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EA=B3=A0,=20=EC=A4=91=EB=B3=B5=20=EC=97=AC=EB=B6=80=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/domain/question/choice/Choices.kt | 3 ++- .../survey/domain/question/ChoicesTest.kt | 15 +++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/question/choice/Choices.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/question/choice/Choices.kt index bef8b5bd..56258824 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/question/choice/Choices.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/question/choice/Choices.kt @@ -11,7 +11,6 @@ data class Choices( ) { init { if (standardChoices.isEmpty()) throw InvalidChoiceException() - if (standardChoices.size != standardChoices.distinct().size) throw InvalidChoiceException() } /** 응답이 선택지에 포함되는지 확인 */ @@ -22,4 +21,6 @@ data class Choices( /** 선택지 기반 라우팅의 선택지와 같은지 비교하기 위해 선택지 집합을 얻는다. */ fun getChoiceSet() = if (isAllowOther) standardChoices.toSet() + Choice.Other else standardChoices.toSet() + + fun isUnique() = standardChoices.size == standardChoices.distinct().size } diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/question/ChoicesTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/question/ChoicesTest.kt index 00fab906..d580b3c6 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/question/ChoicesTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/question/ChoicesTest.kt @@ -39,14 +39,21 @@ class ChoicesTest { } @Test - fun `선택지 목록의 내용은 중복될 수 없다`() { + fun `선택지 목록에 중복된 내용이 있는지 확인할 수 있다`() { // given + val uniqueContents = listOf("1", "2", "3") val duplicatedContents1 = listOf("1", "2", "2") val duplicatedContents2 = listOf("3", "3") - // when, then - assertThrows { createChoices(duplicatedContents1, true) } - assertThrows { createChoices(duplicatedContents2, false) } + // when + val uniqueChoices = createChoices(uniqueContents, true) + val duplicatedChoices1 = createChoices(duplicatedContents1, true) + val duplicatedChoices2 = createChoices(duplicatedContents2, false) + + // then + assertEquals(true, uniqueChoices.isUnique()) + assertEquals(false, duplicatedChoices1.isUnique()) + assertEquals(false, duplicatedChoices2.isUnique()) } @Test From 01ed62eac7e0275f9288e225f6803f04e517845b Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 21 Sep 2024 01:29:56 +0900 Subject: [PATCH 118/201] =?UTF-8?q?[SBL-144]=20=EB=88=84=EB=9D=BD=EB=90=9C?= =?UTF-8?q?=20import=20=EC=B6=94=EA=B0=80,=20=EB=A6=AC=EC=9B=8C=EB=93=9C?= =?UTF-8?q?=20=EC=84=B8=ED=8C=85=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20?= =?UTF-8?q?=EC=84=A4=EB=AC=B8=EC=9D=B4=20=EC=8B=9C=EC=9E=91=20=EC=A0=84?= =?UTF-8?q?=EC=9D=B4=EB=A9=B4=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=EC=9D=84=20=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/domain/Survey.kt | 15 ++++++- .../survey/domain/reward/RewardSetting.kt | 38 ++++++++-------- .../survey/dto/request/SurveySaveRequest.kt | 43 +++++++++++++------ .../survey/entity/SurveyDocument.kt | 19 +++++++- .../survey/service/SurveyManagementService.kt | 16 +------ 5 files changed, 81 insertions(+), 50 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index 7c900210..111059f5 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -14,6 +14,7 @@ import com.sbl.sulmun2yong.survey.exception.InvalidSurveyException import com.sbl.sulmun2yong.survey.exception.InvalidSurveyResponseException import com.sbl.sulmun2yong.survey.exception.InvalidSurveyStartException import com.sbl.sulmun2yong.survey.exception.InvalidUpdateSurveyException +import com.sbl.sulmun2yong.survey.exception.SurveyClosedException import java.util.Date import java.util.UUID @@ -113,7 +114,19 @@ data class Survey( /** 설문을 IN_PROGRESS 상태로 변경하는 메서드. 설문이 시작 전이거나 수정 중인 경우만 가능하다. */ fun start() = when (status) { - SurveyStatus.NOT_STARTED -> copy(status = SurveyStatus.IN_PROGRESS, publishedAt = DateUtil.getCurrentDate()) + SurveyStatus.NOT_STARTED -> + copy( + status = SurveyStatus.IN_PROGRESS, + publishedAt = DateUtil.getCurrentDate(), + rewardSetting = + RewardSetting.of( + type = rewardSetting.type, + rewards = rewardSetting.rewards, + targetParticipantCount = rewardSetting.targetParticipantCount, + finishedAt = rewardSetting.finishedAt?.value, + surveyStatus = SurveyStatus.IN_PROGRESS, + ), + ) SurveyStatus.IN_MODIFICATION -> copy(status = SurveyStatus.IN_PROGRESS) SurveyStatus.IN_PROGRESS -> throw InvalidSurveyStartException() SurveyStatus.CLOSED -> throw InvalidSurveyStartException() diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt index 35fce235..d294130d 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSetting.kt @@ -1,5 +1,6 @@ package com.sbl.sulmun2yong.survey.domain.reward +import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.exception.InvalidRewardSettingException import java.util.Date @@ -18,31 +19,26 @@ interface RewardSetting { rewards: List, targetParticipantCount: Int?, finishedAt: Date?, - ) = when (type) { - RewardSettingType.NO_REWARD -> { - if (rewards.isEmpty() && targetParticipantCount == null && finishedAt == null) { - NoRewardSetting - } else { + surveyStatus: SurveyStatus = SurveyStatus.IN_PROGRESS, + ): RewardSetting { + if (surveyStatus == SurveyStatus.NOT_STARTED) { + val finishedAtValue = finishedAt?.let { FinishedAt(it) } + return NotStartedDrawSetting(type, rewards, targetParticipantCount, finishedAtValue) + } + when (type) { + RewardSettingType.NO_REWARD -> { + val isNoReward = rewards.isEmpty() && targetParticipantCount == null && finishedAt == null + if (isNoReward) return NoRewardSetting throw InvalidRewardSettingException() } - } - RewardSettingType.SELF_MANAGEMENT -> { - if (rewards.isNotEmpty() && - targetParticipantCount == null && - finishedAt != null - ) { - SelfManagementSetting(rewards, FinishedAt(finishedAt)) - } else { + RewardSettingType.SELF_MANAGEMENT -> { + val isSelfManagement = rewards.isNotEmpty() && targetParticipantCount == null && finishedAt != null + if (isSelfManagement) return SelfManagementSetting(rewards, FinishedAt(finishedAt!!)) throw InvalidRewardSettingException() } - } - RewardSettingType.IMMEDIATE_DRAW -> { - if (rewards.isNotEmpty() && - targetParticipantCount != null && - finishedAt != null - ) { - ImmediateDrawSetting(rewards, targetParticipantCount, FinishedAt(finishedAt)) - } else { + RewardSettingType.IMMEDIATE_DRAW -> { + val isImmediateDraw = rewards.isNotEmpty() && targetParticipantCount != null && finishedAt != null + if (isImmediateDraw) return ImmediateDrawSetting(rewards, targetParticipantCount!!, FinishedAt(finishedAt!!)) throw InvalidRewardSettingException() } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt index 6d1ea117..3649d1e6 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveySaveRequest.kt @@ -1,11 +1,14 @@ package com.sbl.sulmun2yong.survey.dto.request +import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.domain.question.QuestionType import com.sbl.sulmun2yong.survey.domain.question.choice.Choice import com.sbl.sulmun2yong.survey.domain.question.choice.Choices import com.sbl.sulmun2yong.survey.domain.question.impl.StandardMultipleChoiceQuestion import com.sbl.sulmun2yong.survey.domain.question.impl.StandardSingleChoiceQuestion import com.sbl.sulmun2yong.survey.domain.question.impl.StandardTextQuestion +import com.sbl.sulmun2yong.survey.domain.reward.Reward +import com.sbl.sulmun2yong.survey.domain.reward.RewardSetting import com.sbl.sulmun2yong.survey.domain.reward.RewardSettingType import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.routing.RoutingType @@ -25,12 +28,38 @@ data class SurveySaveRequest( val rewardSetting: RewardSettingResponse, val sections: List, ) { + fun List.toDomain() = + if (isEmpty()) { + listOf() + } else { + val sectionIds = SectionIds.from(this.map { SectionId.Standard(it.sectionId) }) + this.map { + Section( + id = SectionId.Standard(it.sectionId), + title = it.title, + description = it.description, + routingStrategy = it.getRoutingStrategy(), + questions = it.questions.map { question -> question.toDomain() }, + sectionIds = sectionIds, + ) + } + } + data class RewardSettingResponse( val type: RewardSettingType, val rewards: List, val targetParticipantCount: Int?, val finishedAt: Date?, - ) + ) { + fun toDomain(surveyStatus: SurveyStatus) = + RewardSetting.of( + type, + rewards.map { Reward(it.name, it.category, it.count) }, + targetParticipantCount, + finishedAt, + surveyStatus, + ) + } data class RewardCreateRequest( val name: String, @@ -45,17 +74,7 @@ data class SurveySaveRequest( val questions: List, val routeDetails: RouteDetailsCreateRequest, ) { - fun toDomain(sectionIds: SectionIds) = - Section( - id = SectionId.Standard(sectionId), - title = title, - description = description, - routingStrategy = getRoutingStrategy(), - questions = questions.map { it.toDomain() }, - sectionIds = sectionIds, - ) - - private fun getRoutingStrategy() = + fun getRoutingStrategy() = when (routeDetails.type) { RoutingType.NUMERICAL_ORDER -> RoutingStrategy.NumericalOrder RoutingType.SET_BY_USER -> RoutingStrategy.SetByUser(SectionId.from(routeDetails.nextSectionId)) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt index 66cf65fe..0637c920 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt @@ -152,7 +152,13 @@ data class SurveyDocument( ) fun toDomain(): Survey { - val sectionIds = SectionIds.from(this.sections.map { SectionId.Standard(it.sectionId) }) + val sections = + if (this.sections.isEmpty()) { + listOf() + } else { + val sectionIds = SectionIds.from(this.sections.map { SectionId.Standard(it.sectionId) }) + this.sections.map { it.toDomain(sectionIds) } + } return Survey( id = this.id, title = this.title, @@ -167,13 +173,22 @@ data class SurveyDocument( this.rewards.map { it.toDomain() }, this.targetParticipantCount, this.finishedAt, + this.status, ), isVisible = this.isVisible, makerId = this.makerId, - sections = this.sections.map { it.toDomain(sectionIds) }, + sections = this.sections.toDomain(), ) } + private fun List.toDomain() = + if (this.isEmpty()) { + listOf() + } else { + val sectionIds = SectionIds.from(this.map { SectionId.Standard(it.sectionId) }) + this.map { it.toDomain(sectionIds) } + } + private fun RewardSubDocument.toDomain() = Reward( name = this.name, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index a7235257..bbd363f4 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -5,10 +5,6 @@ import com.sbl.sulmun2yong.drawing.domain.DrawingBoard import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.reward.ImmediateDrawSetting -import com.sbl.sulmun2yong.survey.domain.reward.Reward -import com.sbl.sulmun2yong.survey.domain.reward.RewardSetting -import com.sbl.sulmun2yong.survey.domain.section.SectionId -import com.sbl.sulmun2yong.survey.domain.section.SectionIds import com.sbl.sulmun2yong.survey.dto.request.SurveySaveRequest import com.sbl.sulmun2yong.survey.dto.response.SurveyCreateResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse @@ -36,24 +32,16 @@ class SurveyManagementService( val survey = surveyAdapter.getSurvey(surveyId) // 현재 유저와 설문 제작자가 다를 경우 예외 발생 if (survey.makerId != makerId) throw InvalidSurveyAccessException() - val rewards = surveySaveRequest.rewardSetting.rewards.map { Reward(name = it.name, category = it.category, count = it.count) } val newSurvey = with(surveySaveRequest) { - val sectionIds = SectionIds.from(surveySaveRequest.sections.map { SectionId.Standard(it.sectionId) }) survey.updateContent( title = this.title, description = this.description, thumbnail = this.thumbnail, finishMessage = this.finishMessage, - rewardSetting = - RewardSetting.of( - this.rewardSetting.type, - rewards, - this.rewardSetting.targetParticipantCount, - this.rewardSetting.finishedAt, - ), + rewardSetting = this.rewardSetting.toDomain(survey.status), isVisible = this.isVisible, - sections = this.sections.map { it.toDomain(sectionIds) }, + sections = this.sections.toDomain(), ) } surveyAdapter.save(newSurvey) From 78591842d272b9cfd82f5f0448165fe4d578189e Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 21 Sep 2024 01:36:58 +0900 Subject: [PATCH 119/201] =?UTF-8?q?[SBL-144]=20=EC=84=A4=EB=AC=B8=EC=9D=B4?= =?UTF-8?q?=20=EC=A7=84=ED=96=89=20=EC=A4=91=EC=9D=B8=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=EB=A7=8C=20=EC=84=B9=EC=85=98=EC=9D=B4=20=EB=B9=84=EC=96=B4?= =?UTF-8?q?=EC=9E=88=EB=8A=94=EC=A7=80,=20=EC=84=B9=EC=85=98=20ID=EA=B0=80?= =?UTF-8?q?=20=EC=9C=A0=ED=9A=A8=ED=95=9C=EC=A7=80,=20=EC=84=A0=ED=83=9D?= =?UTF-8?q?=EC=A7=80=20=EC=A4=91=EB=B3=B5=EC=9D=B4=20=EC=97=86=EB=8A=94?= =?UTF-8?q?=EC=A7=80=20=ED=99=95=EC=9D=B8=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/survey/domain/Survey.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index 111059f5..3806ef83 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -33,14 +33,19 @@ data class Survey( val sections: List
, ) { init { - require(sections.isNotEmpty()) { throw InvalidSurveyException() } require(isSectionsUnique()) { throw InvalidSurveyException() } require(isSurveyStatusValid()) { throw InvalidSurveyException() } require(isFinishedAtAfterPublishedAt()) { throw InvalidPublishedAtException() } - require(isSectionIdsValid()) { throw InvalidSurveyException() } + // 설문이 진행 중인 경우만 섹션이 비었는지, 섹션 ID가 유효한지, 선택지가 중복되는지 확인 + if (status == SurveyStatus.IN_PROGRESS) { + require(sections.isNotEmpty()) { throw InvalidSurveyException() } + require(isSectionIdsValid()) { throw InvalidSurveyException() } + require(isAllChoicesUnique()) { throw InvalidSurveyException() } + } } companion object { + // TODO: 기본 섬네일 URL은 프론트에서 처리하도록 변경 const val DEFAULT_THUMBNAIL_URL = "https://test-oriddle-bucket.s3.ap-northeast-2.amazonaws.com/surveyImage.webp" const val DEFAULT_TITLE = "제목 없는 설문" const val DEFAULT_DESCRIPTION = "" @@ -154,4 +159,6 @@ data class Survey( val sectionIds = SectionIds.from(sections.map { it.id }) return sections.all { it.sectionIds == sectionIds } } + + private fun isAllChoicesUnique() = sections.all { section -> section.questions.all { it.choices?.isUnique() ?: true } } } From f0b50f3fe1f94a2459dfdcfdf29464d7681ed08f Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 21 Sep 2024 02:09:25 +0900 Subject: [PATCH 120/201] =?UTF-8?q?[SBL-144]=20=EC=84=B9=EC=85=98=20ID=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=20=EC=8B=9C=20=EC=84=B9=EC=85=98=EC=9D=B4=20?= =?UTF-8?q?=EB=B9=88=20=EA=B2=BD=EC=9A=B0=20true=EB=A5=BC=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=ED=95=98=EA=B3=A0,=20=EC=A7=84=ED=96=89=20=EC=A4=91?= =?UTF-8?q?=EC=9D=B4=20=EC=95=84=EB=8B=8C=20=EA=B2=BD=EC=9A=B0=EB=8F=84=20?= =?UTF-8?q?=EC=84=B9=EC=85=98=20ID=EB=A5=BC=20=EC=B2=B4=ED=81=AC=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index 3806ef83..39970792 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -36,10 +36,10 @@ data class Survey( require(isSectionsUnique()) { throw InvalidSurveyException() } require(isSurveyStatusValid()) { throw InvalidSurveyException() } require(isFinishedAtAfterPublishedAt()) { throw InvalidPublishedAtException() } - // 설문이 진행 중인 경우만 섹션이 비었는지, 섹션 ID가 유효한지, 선택지가 중복되는지 확인 + require(isSectionIdsValid()) { throw InvalidSurveyException() } + // 설문이 진행 중인 경우만 섹션이 비었는지, 선택지가 중복되는지 확인 if (status == SurveyStatus.IN_PROGRESS) { require(sections.isNotEmpty()) { throw InvalidSurveyException() } - require(isSectionIdsValid()) { throw InvalidSurveyException() } require(isAllChoicesUnique()) { throw InvalidSurveyException() } } } @@ -156,6 +156,7 @@ data class Survey( } private fun isSectionIdsValid(): Boolean { + if (sections.isEmpty()) return true val sectionIds = SectionIds.from(sections.map { it.id }) return sections.all { it.sectionIds == sectionIds } } From cbc90a758bc98d4849d1774767b9eb9d88a24c0b Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 21 Sep 2024 02:09:55 +0900 Subject: [PATCH 121/201] =?UTF-8?q?[SBL-144]=20Survey=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EB=B3=80=EA=B2=BD=EB=90=9C=20=EC=82=AC=ED=95=AD?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/survey/domain/SurveyTest.kt | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt index a88f0b06..3c940168 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/SurveyTest.kt @@ -1,6 +1,8 @@ package com.sbl.sulmun2yong.survey.domain +import com.sbl.sulmun2yong.fixture.survey.QuestionFixtureFactory import com.sbl.sulmun2yong.fixture.survey.SectionFixtureFactory.createMockSection +import com.sbl.sulmun2yong.fixture.survey.SectionFixtureFactory.createSection import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.DESCRIPTION import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.FINISHED_AT import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.FINISH_MESSAGE @@ -14,6 +16,7 @@ import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory.createSurvey import com.sbl.sulmun2yong.global.util.DateUtil import com.sbl.sulmun2yong.survey.domain.response.SectionResponse import com.sbl.sulmun2yong.survey.domain.response.SurveyResponse +import com.sbl.sulmun2yong.survey.domain.reward.FinishedAt import com.sbl.sulmun2yong.survey.domain.reward.NoRewardSetting import com.sbl.sulmun2yong.survey.domain.reward.Reward import com.sbl.sulmun2yong.survey.domain.reward.RewardSetting @@ -102,10 +105,21 @@ class SurveyTest { } @Test - fun `설문을 생성할 때 섹션이 1개 이상 없으면 예외가 발생한다`() { + fun `진행 중인 설문을 생성할 때 섹션이 1개 이상 없으면 예외가 발생한다`() { assertThrows { createSurvey(sections = listOf()) } } + @Test + fun `진행 중인 설문을 생성할 때 중복되는 선택지가 있으면 예외가 발생한다`() { + // given + val question1 = QuestionFixtureFactory.createTextResponseQuestion() + val question2 = QuestionFixtureFactory.createSingleChoiceQuestion(contents = listOf("1", "1")) + val section = createSection(questions = listOf(question1, question2)) + + // when, then + assertThrows { createSurvey(sections = listOf(section)) } + } + @Test fun `설문을 생성할 때 섹션들의 ID가 중복되면 예외가 발생한다`() { // given @@ -247,6 +261,7 @@ class SurveyTest { // when, then assertDoesNotThrow { createSurvey(sections = listOf(section1, section2, section3)) } + assertDoesNotThrow { createSurvey(sections = listOf(), status = SurveyStatus.NOT_STARTED, publishedAt = null) } assertThrows { createSurvey(sections = listOf(section1, section2, section4)) } } @@ -406,31 +421,50 @@ class SurveyTest { @Test fun `설문을 시작하면, 설문의 시작일과 상태가 업데이트된다`() { // given - val notStartedSurvey = + val finishedAt = DateUtil.getDateAfterDay(date = DateUtil.getCurrentDate(noMin = true)) + val notStartedSurvey1 = createSurvey( type = RewardSettingType.SELF_MANAGEMENT, - finishedAt = DateUtil.getDateAfterDay(date = DateUtil.getCurrentDate(noMin = true)), + finishedAt = finishedAt, publishedAt = null, targetParticipantCount = null, status = SurveyStatus.NOT_STARTED, ) + val notStartedSurvey2 = + createSurvey( + type = RewardSettingType.NO_REWARD, + finishedAt = null, + targetParticipantCount = null, + rewards = emptyList(), + publishedAt = null, + status = SurveyStatus.NOT_STARTED, + ) val inModificationSurvey = createSurvey( - type = RewardSettingType.SELF_MANAGEMENT, - finishedAt = DateUtil.getDateAfterDay(date = DateUtil.getCurrentDate(noMin = true)), + type = RewardSettingType.NO_REWARD, + finishedAt = null, targetParticipantCount = null, status = SurveyStatus.IN_MODIFICATION, + rewards = emptyList(), ) // when - val startedSurvey1 = notStartedSurvey.start() - val startedSurvey2 = inModificationSurvey.start() + val startedSurvey1 = notStartedSurvey1.start() + val startedSurvey2 = notStartedSurvey2.start() + val startedSurvey3 = inModificationSurvey.start() // then assertEquals(DateUtil.getCurrentDate(), startedSurvey1.publishedAt) assertEquals(SurveyStatus.IN_PROGRESS, startedSurvey1.status) - assertEquals(inModificationSurvey.publishedAt, startedSurvey2.publishedAt) + assertEquals(FinishedAt(finishedAt), startedSurvey1.rewardSetting.finishedAt) + + assertEquals(DateUtil.getCurrentDate(), startedSurvey2.publishedAt) assertEquals(SurveyStatus.IN_PROGRESS, startedSurvey2.status) + assertEquals(null, startedSurvey2.rewardSetting.finishedAt) + + assertEquals(inModificationSurvey.publishedAt, startedSurvey3.publishedAt) + assertEquals(SurveyStatus.IN_PROGRESS, startedSurvey3.status) + assertEquals(null, startedSurvey3.rewardSetting.finishedAt) } @Test From 8128e9a53ccf2e44aafda2ac7b37556916932809 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sat, 21 Sep 2024 02:26:13 +0900 Subject: [PATCH 122/201] =?UTF-8?q?[SBL-144]=20NotStartedDrawSetting?= =?UTF-8?q?=EC=9D=98=20=EC=B6=94=EA=B0=80=EC=99=80=20RewardSetting?= =?UTF-8?q?=EC=9D=98=20of=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fixture/survey/SurveyFixtureFactory.kt | 5 +++-- .../survey/domain/reward/RewardSettingTest.kt | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt index 6c8bf345..27a81419 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyFixtureFactory.kt @@ -67,7 +67,7 @@ object SurveyFixtureFactory { status = status, finishMessage = finishMessage + id, makerId = makerId, - rewardSetting = createRewardSetting(type, rewards, targetParticipantCount, finishedAt), + rewardSetting = createRewardSetting(type, rewards, targetParticipantCount, finishedAt, status), isVisible = isVisible, sections = sections, ) @@ -77,5 +77,6 @@ object SurveyFixtureFactory { rewards: List = REWARDS, targetParticipantCount: Int? = TARGET_PARTICIPANT_COUNT, finishedAt: Date? = FINISHED_AT, - ) = RewardSetting.of(type, rewards, targetParticipantCount, finishedAt) + status: SurveyStatus = SURVEY_STATUS, + ) = RewardSetting.of(type, rewards, targetParticipantCount, finishedAt, status) } diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt index d8d85c3a..2308cf3d 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/reward/RewardSettingTest.kt @@ -2,6 +2,7 @@ package com.sbl.sulmun2yong.survey.domain.reward import com.sbl.sulmun2yong.fixture.survey.SurveyFixtureFactory import com.sbl.sulmun2yong.global.util.DateUtil +import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.exception.InvalidFinishedAtException import com.sbl.sulmun2yong.survey.exception.InvalidRewardSettingException import org.junit.jupiter.api.Test @@ -24,6 +25,10 @@ class RewardSettingTest { val selfManagementSetting2 = RewardSetting.of(RewardSettingType.SELF_MANAGEMENT, rewards, null, finishedAt) val noRewardSetting1 = NoRewardSetting val noRewardSetting2 = RewardSetting.of(RewardSettingType.NO_REWARD, listOf(), null, null) + val notStartedRewardSetting1 = + RewardSetting.of(RewardSettingType.IMMEDIATE_DRAW, listOf(), targetParticipantCount, null, SurveyStatus.NOT_STARTED) + val notStartedRewardSetting2 = + RewardSetting.of(RewardSettingType.NO_REWARD, rewards, null, finishedAt, SurveyStatus.NOT_STARTED) // then // 즉시 추첨 @@ -71,6 +76,21 @@ class RewardSettingTest { assertEquals(null, this.finishedAt) assertEquals(false, this.isImmediateDraw) } + // 시작 전 상태의 불완전한 리워드 지급 설정 + with(notStartedRewardSetting1) { + assertEquals(RewardSettingType.IMMEDIATE_DRAW, this.type) + assertEquals(emptyList(), this.rewards) + assertEquals(targetParticipantCount, this.targetParticipantCount) + assertEquals(null, this.finishedAt) + assertEquals(true, this.isImmediateDraw) + } + with(notStartedRewardSetting2) { + assertEquals(RewardSettingType.NO_REWARD, this.type) + assertEquals(rewards, this.rewards) + assertEquals(null, this.targetParticipantCount) + assertEquals(FinishedAt(finishedAt), this.finishedAt) + assertEquals(false, this.isImmediateDraw) + } } @Test From 164d9158e9aa8a54c8bf6d4c592d21fea6ee8f1d Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Mon, 23 Sep 2024 13:51:22 +0900 Subject: [PATCH 123/201] =?UTF-8?q?[SBL-139]=20adapter=EB=A1=9C=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/ai/adapter/GenerateAdapter.kt | 47 +++++++++++++++++++ .../{domain => dto}/QuestionGeneratedByAI.kt | 2 +- .../{domain => dto}/SectionGeneratedByAI.kt | 2 +- .../ai/{domain => dto}/SurveyGeneratedByAI.kt | 2 +- .../sulmun2yong/ai/service/GenerateService.kt | 41 ++-------------- ...dler.kt => PythonServerExceptionMapper.kt} | 4 +- .../dto/response/SurveyMakeInfoResponse.kt | 6 +-- 7 files changed, 58 insertions(+), 46 deletions(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt rename src/main/kotlin/com/sbl/sulmun2yong/ai/{domain => dto}/QuestionGeneratedByAI.kt (86%) rename src/main/kotlin/com/sbl/sulmun2yong/ai/{domain => dto}/SectionGeneratedByAI.kt (77%) rename src/main/kotlin/com/sbl/sulmun2yong/ai/{domain => dto}/SurveyGeneratedByAI.kt (80%) rename src/main/kotlin/com/sbl/sulmun2yong/global/error/{PythonServerExceptionHandler.kt => PythonServerExceptionMapper.kt} (90%) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt new file mode 100644 index 00000000..776239cb --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt @@ -0,0 +1,47 @@ +package com.sbl.sulmun2yong.ai.adapter + +import com.sbl.sulmun2yong.ai.dto.SurveyGeneratedByAI +import com.sbl.sulmun2yong.ai.exception.SurveyGenerationByAIFailedException +import com.sbl.sulmun2yong.global.error.PythonServerExceptionMapper +import com.sbl.sulmun2yong.survey.domain.Survey +import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Component +import org.springframework.web.client.HttpClientErrorException +import org.springframework.web.client.RestTemplate + +@Component +class GenerateAdapter( + @Value("\${ai-server.base-url}") + private val aiServerBaseUrl: String, +) { + private val url = "$aiServerBaseUrl/generate/survey" + + fun postRequestWithFileUrl( + job: String, + groupName: String, + fileUrl: String, + ): SurveyGeneratedByAI { + val requestBody = + mapOf( + "job" to job, + "group_name" to groupName, + "file_url" to fileUrl, + ) + + val responseBody = + try { + RestTemplate() + .postForEntity( + url, + requestBody, + SurveyGeneratedByAI::class.java, + ).body ?: throw SurveyGenerationByAIFailedException() + } catch (e: HttpClientErrorException) { + throw PythonServerExceptionMapper.mapException(e) + } + + val survey = Survey.of(responseBody) + return SurveyMakeInfoResponse.of(survey) + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/domain/QuestionGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt similarity index 86% rename from src/main/kotlin/com/sbl/sulmun2yong/ai/domain/QuestionGeneratedByAI.kt rename to src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt index c5ee4e64..ac08544a 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/domain/QuestionGeneratedByAI.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt @@ -1,4 +1,4 @@ -package com.sbl.sulmun2yong.ai.domain +package com.sbl.sulmun2yong.ai.dto import com.sbl.sulmun2yong.survey.domain.question.QuestionType diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/domain/SectionGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt similarity index 77% rename from src/main/kotlin/com/sbl/sulmun2yong/ai/domain/SectionGeneratedByAI.kt rename to src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt index 9323f627..9711f031 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/domain/SectionGeneratedByAI.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt @@ -1,4 +1,4 @@ -package com.sbl.sulmun2yong.ai.domain +package com.sbl.sulmun2yong.ai.dto class SectionGeneratedByAI( val title: String, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/domain/SurveyGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SurveyGeneratedByAI.kt similarity index 80% rename from src/main/kotlin/com/sbl/sulmun2yong/ai/domain/SurveyGeneratedByAI.kt rename to src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SurveyGeneratedByAI.kt index 141d34aa..66737ca8 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/domain/SurveyGeneratedByAI.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SurveyGeneratedByAI.kt @@ -1,4 +1,4 @@ -package com.sbl.sulmun2yong.ai.domain +package com.sbl.sulmun2yong.ai.dto class SurveyGeneratedByAI( val title: String, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index a1088ce5..17509167 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -1,20 +1,14 @@ package com.sbl.sulmun2yong.ai.service -import com.sbl.sulmun2yong.ai.domain.SurveyGeneratedByAI -import com.sbl.sulmun2yong.ai.exception.SurveyGenerationByAIFailedException -import com.sbl.sulmun2yong.global.error.PythonServerExceptionHandler +import com.sbl.sulmun2yong.ai.adapter.GenerateAdapter import com.sbl.sulmun2yong.global.util.FileValidator import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse -import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service -import org.springframework.web.client.HttpClientErrorException -import org.springframework.web.client.RestTemplate @Service class GenerateService( - @Value("\${ai-server.base-url}") - private val aiServerBaseUrl: String, private val fileValidator: FileValidator, + private val generateAdapter: GenerateAdapter, ) { fun generateSurvey( job: String, @@ -24,35 +18,6 @@ class GenerateService( val allowedExtensions = mutableListOf(".txt", ".pdf") fileValidator.validateFileUrlOf(fileUrl, allowedExtensions) - val response = postRequestToAIServer(job, groupName, fileUrl) - return SurveyMakeInfoResponse.of(response) - } - - private fun postRequestToAIServer( - job: String, - groupName: String, - fileUrl: String, - ): SurveyGeneratedByAI { - val url = "$aiServerBaseUrl/generate/survey" - val requestBody = - mapOf( - "job" to job, - "group_name" to groupName, - "file_url" to fileUrl, - ) - - val responseBody = - try { - RestTemplate() - .postForEntity( - url, - requestBody, - SurveyGeneratedByAI::class.java, - ).body ?: throw SurveyGenerationByAIFailedException() - } catch (e: HttpClientErrorException) { - throw PythonServerExceptionHandler.handlerException(e) - } - - return responseBody + val response = generateAdapter.postRequestWithFileUrl(job, groupName, fileUrl) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionHandler.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionMapper.kt similarity index 90% rename from src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionHandler.kt rename to src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionMapper.kt index 5c44ff24..946d4e0f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionHandler.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionMapper.kt @@ -6,7 +6,7 @@ import com.sbl.sulmun2yong.ai.exception.SurveyGenerationByAIFailedException import com.sbl.sulmun2yong.ai.exception.TextTooLongException import org.springframework.web.client.HttpClientErrorException -class PythonServerExceptionHandler { +class PythonServerExceptionMapper { companion object { private val objectMapper = ObjectMapper() @@ -19,7 +19,7 @@ class PythonServerExceptionHandler { val detail: ErrorDetail = ErrorDetail(), ) - fun handlerException(e: HttpClientErrorException): BusinessException { + fun mapException(e: HttpClientErrorException): BusinessException { val exception = objectMapper.readValue(e.responseBodyAsString, PythonServerException::class.java) when (exception.detail.code) { "PY0001" -> throw SurveyGenerationByAIFailedException() diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt index 7d8f8ced..b7b2b33d 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt @@ -1,8 +1,8 @@ package com.sbl.sulmun2yong.survey.dto.response -import com.sbl.sulmun2yong.ai.domain.QuestionGeneratedByAI -import com.sbl.sulmun2yong.ai.domain.SectionGeneratedByAI -import com.sbl.sulmun2yong.ai.domain.SurveyGeneratedByAI +import com.sbl.sulmun2yong.ai.dto.QuestionGeneratedByAI +import com.sbl.sulmun2yong.ai.dto.SectionGeneratedByAI +import com.sbl.sulmun2yong.ai.dto.SurveyGeneratedByAI import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.domain.question.Question From fda9dfb07b72e4a43fcee8ee014651b2cc162f49 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Mon, 23 Sep 2024 13:52:38 +0900 Subject: [PATCH 124/201] =?UTF-8?q?[SBL-139]=20SurveyMakeInfoResponse=20?= =?UTF-8?q?=EB=B3=B5=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/SurveyMakeInfoResponse.kt | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt index b7b2b33d..bd25d43b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyMakeInfoResponse.kt @@ -1,8 +1,5 @@ package com.sbl.sulmun2yong.survey.dto.response -import com.sbl.sulmun2yong.ai.dto.QuestionGeneratedByAI -import com.sbl.sulmun2yong.ai.dto.SectionGeneratedByAI -import com.sbl.sulmun2yong.ai.dto.SurveyGeneratedByAI import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.domain.question.Question @@ -44,25 +41,6 @@ data class SurveyMakeInfoResponse( isVisible = survey.isVisible, sections = survey.sections.map { SectionMakeInfoResponse.from(it) }, ) - - fun of(surveyGeneratedByAI: SurveyGeneratedByAI) = - SurveyMakeInfoResponse( - title = surveyGeneratedByAI.title, - description = surveyGeneratedByAI.description, - thumbnail = null, - publishedAt = null, - rewardSetting = - RewardSettingResponse( - type = RewardSettingType.NO_REWARD, - rewards = emptyList(), - targetParticipantCount = null, - finishedAt = null, - ), - status = SurveyStatus.NOT_STARTED, - finishMessage = "", - isVisible = true, - sections = surveyGeneratedByAI.sections.map { SectionMakeInfoResponse.from(it) }, - ) } data class RewardSettingResponse( @@ -94,15 +72,6 @@ data class SurveyMakeInfoResponse( questions = section.questions.map { QuestionMakeInfoResponse.from(it) }, routeDetails = RouteDetailsMakeInfoResponse.from(section.routingStrategy), ) - - fun from(sectionGeneratedByAI: SectionGeneratedByAI) = - SectionMakeInfoResponse( - sectionId = UUID.randomUUID(), - title = sectionGeneratedByAI.title, - description = sectionGeneratedByAI.description, - questions = sectionGeneratedByAI.questions.map { QuestionMakeInfoResponse.of(it) }, - routeDetails = RouteDetailsMakeInfoResponse.from(RoutingStrategy.NumericalOrder), - ) } } @@ -158,17 +127,6 @@ data class SurveyMakeInfoResponse( isAllowOther = question.choices?.isAllowOther ?: false, choices = question.choices?.standardChoices?.map { it.content }, ) - - fun of(questionGeneratedByAI: QuestionGeneratedByAI) = - QuestionMakeInfoResponse( - questionId = UUID.randomUUID(), - type = questionGeneratedByAI.questionType, - title = questionGeneratedByAI.title, - description = "", - isRequired = questionGeneratedByAI.isRequired, - isAllowOther = questionGeneratedByAI.isAllowOther, - choices = questionGeneratedByAI.choices.ifEmpty { null }, - ) } } } From 150521f39516631cad6066ca4cd2e4113120a42a Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Mon, 23 Sep 2024 14:10:30 +0900 Subject: [PATCH 125/201] =?UTF-8?q?[SBL-139]=20toDomain=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/ai/adapter/GenerateAdapter.kt | 7 +++-- .../ai/dto/QuestionGeneratedByAI.kt | 14 +++++++++- .../ai/dto/SectionGeneratedByAI.kt | 16 ++++++++++- .../sulmun2yong/ai/dto/SurveyGeneratedByAI.kt | 27 +++++++++++++++---- .../sulmun2yong/ai/service/GenerateService.kt | 2 +- 5 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt index 776239cb..25692721 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt @@ -3,7 +3,6 @@ package com.sbl.sulmun2yong.ai.adapter import com.sbl.sulmun2yong.ai.dto.SurveyGeneratedByAI import com.sbl.sulmun2yong.ai.exception.SurveyGenerationByAIFailedException import com.sbl.sulmun2yong.global.error.PythonServerExceptionMapper -import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component @@ -21,7 +20,7 @@ class GenerateAdapter( job: String, groupName: String, fileUrl: String, - ): SurveyGeneratedByAI { + ): SurveyMakeInfoResponse { val requestBody = mapOf( "job" to job, @@ -29,7 +28,7 @@ class GenerateAdapter( "file_url" to fileUrl, ) - val responseBody = + val surveyGeneratedByAI = try { RestTemplate() .postForEntity( @@ -41,7 +40,7 @@ class GenerateAdapter( throw PythonServerExceptionMapper.mapException(e) } - val survey = Survey.of(responseBody) + val survey = surveyGeneratedByAI.toDomain() return SurveyMakeInfoResponse.of(survey) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt index ac08544a..ec555ec4 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt @@ -1,6 +1,8 @@ package com.sbl.sulmun2yong.ai.dto +import com.sbl.sulmun2yong.survey.domain.question.Question import com.sbl.sulmun2yong.survey.domain.question.QuestionType +import java.util.UUID class QuestionGeneratedByAI( val questionType: QuestionType, @@ -8,4 +10,14 @@ class QuestionGeneratedByAI( val isRequired: Boolean, val choices: List, val isAllowOther: Boolean, -) +) { + fun toDomain() = + Question( + id = UUID.randomUUID(), + title = title, + questionType = questionType, + isRequired = isRequired, + choices = choices, + isAllowOther = isAllowOther, + ) +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt index 9711f031..6061ebb5 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt @@ -1,7 +1,21 @@ package com.sbl.sulmun2yong.ai.dto +import com.sbl.sulmun2yong.survey.domain.section.Section +import com.sbl.sulmun2yong.survey.domain.section.SectionId +import java.util.UUID + class SectionGeneratedByAI( val title: String, val description: String, val questions: List, -) +) { + fun toDomain(): Section = + Section( + id = SectionId.Standard(UUID.randomUUID()), + title = title, + description = description, + routingStrategy = null, + questions = questions.map { it.toDomain() }, + sectionIds = null, + ) +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SurveyGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SurveyGeneratedByAI.kt index 66737ca8..60780465 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SurveyGeneratedByAI.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SurveyGeneratedByAI.kt @@ -1,8 +1,25 @@ package com.sbl.sulmun2yong.ai.dto +import com.sbl.sulmun2yong.survey.domain.Survey +import java.util.UUID + class SurveyGeneratedByAI( - val title: String, - val description: String, - val finishMessage: String, - val sections: List, -) + private val title: String, + private val description: String, + private val finishMessage: String, + private val sections: List, +) { + fun toDomain(): Survey { + val survey = Survey.create(UUID.randomUUID()) + survey.updateContent( + title = title, + description = description, + thumbnail = survey.thumbnail, + finishMessage = finishMessage, + rewardSetting = survey.rewardSetting, + isVisible = false, + sections = sections.map { it.toDomain() }, + ) + return survey + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index 17509167..bc144a13 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -18,6 +18,6 @@ class GenerateService( val allowedExtensions = mutableListOf(".txt", ".pdf") fileValidator.validateFileUrlOf(fileUrl, allowedExtensions) - val response = generateAdapter.postRequestWithFileUrl(job, groupName, fileUrl) + return generateAdapter.postRequestWithFileUrl(job, groupName, fileUrl) } } From 8cad46bcafcd0d87dec9182f6e845671d5918350 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Mon, 23 Sep 2024 18:38:57 +0900 Subject: [PATCH 126/201] =?UTF-8?q?[SBL-139]=20dto=EB=A1=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=84=B0=EB=A7=81=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/ai/adapter/GenerateAdapter.kt | 4 +- .../ai/dto/QuestionGeneratedByAI.kt | 23 --------- .../sulmun2yong/ai/dto/SurveyGeneratedByAI.kt | 25 ---------- .../ai/entity/QuestionGeneratedByAI.kt | 50 +++++++++++++++++++ .../{dto => entity}/SectionGeneratedByAI.kt | 16 +++--- .../ai/entity/SurveyGeneratedByAI.kt | 37 ++++++++++++++ .../sulmun2yong/ai/service/GenerateService.kt | 2 +- 7 files changed, 100 insertions(+), 57 deletions(-) delete mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt delete mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SurveyGeneratedByAI.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/entity/QuestionGeneratedByAI.kt rename src/main/kotlin/com/sbl/sulmun2yong/ai/{dto => entity}/SectionGeneratedByAI.kt (50%) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/entity/SurveyGeneratedByAI.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt index 25692721..66ffda14 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt @@ -1,6 +1,6 @@ package com.sbl.sulmun2yong.ai.adapter -import com.sbl.sulmun2yong.ai.dto.SurveyGeneratedByAI +import com.sbl.sulmun2yong.ai.entity.SurveyGeneratedByAI import com.sbl.sulmun2yong.ai.exception.SurveyGenerationByAIFailedException import com.sbl.sulmun2yong.global.error.PythonServerExceptionMapper import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse @@ -16,7 +16,7 @@ class GenerateAdapter( ) { private val url = "$aiServerBaseUrl/generate/survey" - fun postRequestWithFileUrl( + fun requestSurveyGenerationWithFileUrl( job: String, groupName: String, fileUrl: String, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt deleted file mode 100644 index ec555ec4..00000000 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.sbl.sulmun2yong.ai.dto - -import com.sbl.sulmun2yong.survey.domain.question.Question -import com.sbl.sulmun2yong.survey.domain.question.QuestionType -import java.util.UUID - -class QuestionGeneratedByAI( - val questionType: QuestionType, - val title: String, - val isRequired: Boolean, - val choices: List, - val isAllowOther: Boolean, -) { - fun toDomain() = - Question( - id = UUID.randomUUID(), - title = title, - questionType = questionType, - isRequired = isRequired, - choices = choices, - isAllowOther = isAllowOther, - ) -} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SurveyGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SurveyGeneratedByAI.kt deleted file mode 100644 index 60780465..00000000 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SurveyGeneratedByAI.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.sbl.sulmun2yong.ai.dto - -import com.sbl.sulmun2yong.survey.domain.Survey -import java.util.UUID - -class SurveyGeneratedByAI( - private val title: String, - private val description: String, - private val finishMessage: String, - private val sections: List, -) { - fun toDomain(): Survey { - val survey = Survey.create(UUID.randomUUID()) - survey.updateContent( - title = title, - description = description, - thumbnail = survey.thumbnail, - finishMessage = finishMessage, - rewardSetting = survey.rewardSetting, - isVisible = false, - sections = sections.map { it.toDomain() }, - ) - return survey - } -} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/entity/QuestionGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/entity/QuestionGeneratedByAI.kt new file mode 100644 index 00000000..5ff47419 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/entity/QuestionGeneratedByAI.kt @@ -0,0 +1,50 @@ +package com.sbl.sulmun2yong.ai.entity + +import com.sbl.sulmun2yong.survey.domain.question.QuestionType +import com.sbl.sulmun2yong.survey.domain.question.choice.Choice +import com.sbl.sulmun2yong.survey.domain.question.choice.Choices +import com.sbl.sulmun2yong.survey.domain.question.impl.StandardMultipleChoiceQuestion +import com.sbl.sulmun2yong.survey.domain.question.impl.StandardSingleChoiceQuestion +import com.sbl.sulmun2yong.survey.domain.question.impl.StandardTextQuestion +import java.util.UUID + +class QuestionGeneratedByAI( + private val questionType: QuestionType, + private val title: String, + private val isRequired: Boolean, + private val choices: List, + private val isAllowOther: Boolean, +) { + fun toDomain() = + when (questionType) { + QuestionType.SINGLE_CHOICE -> + StandardSingleChoiceQuestion( + id = UUID.randomUUID(), + title = this.title, + description = DEFAULT_DESCRIPTION, + isRequired = this.isRequired, + // TODO: Document를 Domain클래스로 변환 중에 생긴 에러는 여기서 직접 반환하도록 수정 + choices = Choices(this.choices.map { Choice.Standard(it) }, isAllowOther), + ) + QuestionType.MULTIPLE_CHOICE -> + StandardMultipleChoiceQuestion( + id = UUID.randomUUID(), + title = this.title, + description = DEFAULT_DESCRIPTION, + isRequired = this.isRequired, + // TODO: Document를 Domain클래스로 변환 중에 생긴 에러는 여기서 직접 반환하도록 수정 + choices = Choices(this.choices.map { Choice.Standard(it) }, isAllowOther), + ) + QuestionType.TEXT_RESPONSE -> + StandardTextQuestion( + id = UUID.randomUUID(), + title = this.title, + description = DEFAULT_DESCRIPTION, + isRequired = this.isRequired, + ) + } + + companion object { + private const val DEFAULT_DESCRIPTION = "" + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/entity/SectionGeneratedByAI.kt similarity index 50% rename from src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt rename to src/main/kotlin/com/sbl/sulmun2yong/ai/entity/SectionGeneratedByAI.kt index 6061ebb5..93ac8939 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/entity/SectionGeneratedByAI.kt @@ -1,21 +1,25 @@ -package com.sbl.sulmun2yong.ai.dto +package com.sbl.sulmun2yong.ai.entity +import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.section.Section import com.sbl.sulmun2yong.survey.domain.section.SectionId -import java.util.UUID +import com.sbl.sulmun2yong.survey.domain.section.SectionIds class SectionGeneratedByAI( val title: String, val description: String, val questions: List, ) { - fun toDomain(): Section = + fun toDomain( + sectionId: SectionId.Standard, + sectionIds: SectionIds, + ): Section = Section( - id = SectionId.Standard(UUID.randomUUID()), + id = sectionId, title = title, description = description, - routingStrategy = null, + routingStrategy = RoutingStrategy.NumericalOrder, questions = questions.map { it.toDomain() }, - sectionIds = null, + sectionIds = sectionIds, ) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/entity/SurveyGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/entity/SurveyGeneratedByAI.kt new file mode 100644 index 00000000..62b8da76 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/entity/SurveyGeneratedByAI.kt @@ -0,0 +1,37 @@ +package com.sbl.sulmun2yong.ai.entity + +import com.sbl.sulmun2yong.survey.domain.Survey +import com.sbl.sulmun2yong.survey.domain.section.SectionId +import com.sbl.sulmun2yong.survey.domain.section.SectionIds +import java.util.UUID + +class SurveyGeneratedByAI( + private val title: String, + private val description: String, + private val finishMessage: String, + private val sections: List, +) { + fun toDomain(): Survey { + val sectionIds = List(sections.size) { SectionId.Standard(UUID.randomUUID()) } + val sectionIdsManger = SectionIds.from(sectionIds) + + val sections = + sections.mapIndexed { index, sectionGeneratedByAI -> + sectionGeneratedByAI.toDomain( + sectionIds[index], + sectionIdsManger, + ) + } + + val survey = Survey.create(UUID.randomUUID()) + return survey.updateContent( + title = title, + description = description, + thumbnail = survey.thumbnail, + finishMessage = finishMessage, + rewardSetting = survey.rewardSetting, + isVisible = false, + sections = sections, + ) + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index bc144a13..3538560c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -18,6 +18,6 @@ class GenerateService( val allowedExtensions = mutableListOf(".txt", ".pdf") fileValidator.validateFileUrlOf(fileUrl, allowedExtensions) - return generateAdapter.postRequestWithFileUrl(job, groupName, fileUrl) + return generateAdapter.requestSurveyGenerationWithFileUrl(job, groupName, fileUrl) } } From 5e79ae1a03ecda75fae858b44bcca44864ec4cfe Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Tue, 24 Sep 2024 13:02:24 +0900 Subject: [PATCH 127/201] =?UTF-8?q?[SBL-139]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/ai/adapter/GenerateAdapter.kt | 5 +-- .../{entity => dto}/QuestionGeneratedByAI.kt | 2 +- .../{entity => dto}/SectionGeneratedByAI.kt | 2 +- .../ai/{entity => dto}/SurveyGeneratedByAI.kt | 2 +- .../sulmun2yong/ai/service/GenerateService.kt | 6 ++-- .../sbl/sulmun2yong/aws/service/S3Service.kt | 6 ++-- .../global/config/RestTemplateConfig.kt | 11 ++++++ ...3Config.kt => UserFileManagementConfig.kt} | 15 ++++---- .../sbl/sulmun2yong/global/error/ErrorCode.kt | 16 ++++----- .../error/PythonServerExceptionMapper.kt | 34 +++++++++---------- .../global/util/RandomNicknameGenerator.kt | 2 +- .../FileUploadValidator.kt} | 24 +++---------- .../global/util/validator/FileUrlValidator.kt | 24 +++++++++++++ 13 files changed, 85 insertions(+), 64 deletions(-) rename src/main/kotlin/com/sbl/sulmun2yong/ai/{entity => dto}/QuestionGeneratedByAI.kt (98%) rename src/main/kotlin/com/sbl/sulmun2yong/ai/{entity => dto}/SectionGeneratedByAI.kt (95%) rename src/main/kotlin/com/sbl/sulmun2yong/ai/{entity => dto}/SurveyGeneratedByAI.kt (96%) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/config/RestTemplateConfig.kt rename src/main/kotlin/com/sbl/sulmun2yong/global/config/{S3Config.kt => UserFileManagementConfig.kt} (80%) rename src/main/kotlin/com/sbl/sulmun2yong/global/util/{FileValidator.kt => validator/FileUploadValidator.kt} (77%) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/util/validator/FileUrlValidator.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt index 66ffda14..4807bc76 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt @@ -1,6 +1,6 @@ package com.sbl.sulmun2yong.ai.adapter -import com.sbl.sulmun2yong.ai.entity.SurveyGeneratedByAI +import com.sbl.sulmun2yong.ai.dto.SurveyGeneratedByAI import com.sbl.sulmun2yong.ai.exception.SurveyGenerationByAIFailedException import com.sbl.sulmun2yong.global.error.PythonServerExceptionMapper import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse @@ -13,6 +13,7 @@ import org.springframework.web.client.RestTemplate class GenerateAdapter( @Value("\${ai-server.base-url}") private val aiServerBaseUrl: String, + private val restTemplate: RestTemplate, ) { private val url = "$aiServerBaseUrl/generate/survey" @@ -30,7 +31,7 @@ class GenerateAdapter( val surveyGeneratedByAI = try { - RestTemplate() + restTemplate .postForEntity( url, requestBody, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/entity/QuestionGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt similarity index 98% rename from src/main/kotlin/com/sbl/sulmun2yong/ai/entity/QuestionGeneratedByAI.kt rename to src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt index 5ff47419..346540b5 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/entity/QuestionGeneratedByAI.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt @@ -1,4 +1,4 @@ -package com.sbl.sulmun2yong.ai.entity +package com.sbl.sulmun2yong.ai.dto import com.sbl.sulmun2yong.survey.domain.question.QuestionType import com.sbl.sulmun2yong.survey.domain.question.choice.Choice diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/entity/SectionGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt similarity index 95% rename from src/main/kotlin/com/sbl/sulmun2yong/ai/entity/SectionGeneratedByAI.kt rename to src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt index 93ac8939..2a67f73f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/entity/SectionGeneratedByAI.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt @@ -1,4 +1,4 @@ -package com.sbl.sulmun2yong.ai.entity +package com.sbl.sulmun2yong.ai.dto import com.sbl.sulmun2yong.survey.domain.routing.RoutingStrategy import com.sbl.sulmun2yong.survey.domain.section.Section diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/entity/SurveyGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SurveyGeneratedByAI.kt similarity index 96% rename from src/main/kotlin/com/sbl/sulmun2yong/ai/entity/SurveyGeneratedByAI.kt rename to src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SurveyGeneratedByAI.kt index 62b8da76..861a06cc 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/entity/SurveyGeneratedByAI.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SurveyGeneratedByAI.kt @@ -1,4 +1,4 @@ -package com.sbl.sulmun2yong.ai.entity +package com.sbl.sulmun2yong.ai.dto import com.sbl.sulmun2yong.survey.domain.Survey import com.sbl.sulmun2yong.survey.domain.section.SectionId diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index 3538560c..a256c30a 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -1,13 +1,13 @@ package com.sbl.sulmun2yong.ai.service import com.sbl.sulmun2yong.ai.adapter.GenerateAdapter -import com.sbl.sulmun2yong.global.util.FileValidator +import com.sbl.sulmun2yong.global.util.validator.FileUrlValidator import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse import org.springframework.stereotype.Service @Service class GenerateService( - private val fileValidator: FileValidator, + private val fileUrlValidator: FileUrlValidator, private val generateAdapter: GenerateAdapter, ) { fun generateSurvey( @@ -16,7 +16,7 @@ class GenerateService( fileUrl: String, ): SurveyMakeInfoResponse { val allowedExtensions = mutableListOf(".txt", ".pdf") - fileValidator.validateFileUrlOf(fileUrl, allowedExtensions) + fileUrlValidator.validateFileUrlOf(fileUrl, allowedExtensions) return generateAdapter.requestSurveyGenerationWithFileUrl(job, groupName, fileUrl) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt b/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt index 8a4a4b83..16d3a9c0 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/aws/service/S3Service.kt @@ -1,7 +1,7 @@ package com.sbl.sulmun2yong.aws.service import com.sbl.sulmun2yong.aws.dto.response.S3UploadResponse -import com.sbl.sulmun2yong.global.util.FileValidator +import com.sbl.sulmun2yong.global.util.validator.FileUploadValidator import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service import org.springframework.web.multipart.MultipartFile @@ -18,10 +18,10 @@ class S3Service( private val bucketName: String, @Value("\${cloudfront.base-url}") private val cloudFrontUrl: String, - private val fileValidator: FileValidator, + private val fileUploadValidator: FileUploadValidator, ) { fun uploadFile(receivedFile: MultipartFile): S3UploadResponse { - fileValidator.validateFileOf(receivedFile) + fileUploadValidator.validateFileOf(receivedFile) val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmssSSS")) val keyName = "${timestamp}_${receivedFile.originalFilename}" diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/RestTemplateConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/RestTemplateConfig.kt new file mode 100644 index 00000000..92b39a37 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/RestTemplateConfig.kt @@ -0,0 +1,11 @@ +package com.sbl.sulmun2yong.global.config + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.web.client.RestTemplate + +@Configuration +class RestTemplateConfig { + @Bean + fun createRestTemplate(): RestTemplate = RestTemplate() +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/UserFileManagementConfig.kt similarity index 80% rename from src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt rename to src/main/kotlin/com/sbl/sulmun2yong/global/config/UserFileManagementConfig.kt index 5dd81604..f344cf7a 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/S3Config.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/UserFileManagementConfig.kt @@ -1,6 +1,7 @@ package com.sbl.sulmun2yong.global.config -import com.sbl.sulmun2yong.global.util.FileValidator +import com.sbl.sulmun2yong.global.util.validator.FileUploadValidator +import com.sbl.sulmun2yong.global.util.validator.FileUrlValidator import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -11,7 +12,7 @@ import software.amazon.awssdk.regions.Region import software.amazon.awssdk.services.s3.S3Client @Configuration -class S3Config( +class UserFileManagementConfig( // S3 클라이언트 관련 @Value("\${aws.s3.access-key}") private val accessKey: String, @@ -30,7 +31,7 @@ class S3Config( private val cloudFrontBaseUrl: String, ) { @Bean - fun s3Client(): S3Client { + fun createS3Client(): S3Client { val awsCredentials = AwsBasicCredentials.create(accessKey, secretKey) return S3Client @@ -41,12 +42,14 @@ class S3Config( } @Bean - fun fileValidateValues(): FileValidator = - FileValidator.from( + fun createFileUploadValidator(): FileUploadValidator = + FileUploadValidator.from( maxFileSize = maxFileSize, maxFileNameLength = maxFileNameLength, allowedExtensions = allowedExtensions, allowedContentTypes = allowedContentTypes, - cloudFrontBaseUrl = cloudFrontBaseUrl, ) + + @Bean + fun createFileUrlValidator(): FileUrlValidator = FileUrlValidator.of(cloudFrontBaseUrl) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index c4caa920..fd19becb 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -56,14 +56,14 @@ enum class ErrorCode( // Data (DT) INVALID_PHONE_NUMBER(HttpStatus.BAD_REQUEST, "DT0001", "유효하지 않은 전화번호입니다."), - // AWS S3 (S3) - INVALID_EXTENSION(HttpStatus.BAD_REQUEST, "S30001", "허용하지 않는 확장자입니다."), - OUT_OF_FILE_SIZE(HttpStatus.BAD_REQUEST, "S30002", "파일 크기가 너무 큽니다."), - FILE_NAME_TOO_SHORT(HttpStatus.BAD_REQUEST, "S30003", "파일 이름이 너무 짧습니다."), - FILE_NAME_TOO_LONG(HttpStatus.BAD_REQUEST, "S30004", "파일 이름이 너무 깁니다."), - NO_FILE_EXIST(HttpStatus.BAD_REQUEST, "S30005", "파일이 존재하지 않습니다."), - NO_EXTENSION_EXIST(HttpStatus.BAD_REQUEST, "S30006", "파일 확장자가 존재하지 않습니다."), - INVALID_FILE_URL(HttpStatus.BAD_REQUEST, "S30001", "유효하지 않은 파일 주소입니다."), + // File Validator (FV) + INVALID_EXTENSION(HttpStatus.BAD_REQUEST, "FV0001", "허용하지 않는 확장자입니다."), + OUT_OF_FILE_SIZE(HttpStatus.BAD_REQUEST, "FV0002", "파일 크기가 너무 큽니다."), + FILE_NAME_TOO_SHORT(HttpStatus.BAD_REQUEST, "FV0003", "파일 이름이 너무 짧습니다."), + FILE_NAME_TOO_LONG(HttpStatus.BAD_REQUEST, "FV0004", "파일 이름이 너무 깁니다."), + NO_FILE_EXIST(HttpStatus.BAD_REQUEST, "FV0005", "파일이 존재하지 않습니다."), + NO_EXTENSION_EXIST(HttpStatus.BAD_REQUEST, "FV0006", "파일 확장자가 존재하지 않습니다."), + INVALID_FILE_URL(HttpStatus.BAD_REQUEST, "FV0007", "유효하지 않은 파일 주소입니다."), // Python Server (PY) SURVEY_GENERATION_BY_AI_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "PY0001", "AI를 활용한 설문 생성에 실패했습니다."), diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionMapper.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionMapper.kt index 946d4e0f..832494d9 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionMapper.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionMapper.kt @@ -6,27 +6,25 @@ import com.sbl.sulmun2yong.ai.exception.SurveyGenerationByAIFailedException import com.sbl.sulmun2yong.ai.exception.TextTooLongException import org.springframework.web.client.HttpClientErrorException -class PythonServerExceptionMapper { - companion object { - private val objectMapper = ObjectMapper() +object PythonServerExceptionMapper { + private val objectMapper = ObjectMapper() - data class ErrorDetail( - val code: String = "", - val message: String = "", - ) + data class ErrorDetail( + val code: String = "", + val message: String = "", + ) - data class PythonServerException( - val detail: ErrorDetail = ErrorDetail(), - ) + data class PythonServerException( + val detail: ErrorDetail = ErrorDetail(), + ) - fun mapException(e: HttpClientErrorException): BusinessException { - val exception = objectMapper.readValue(e.responseBodyAsString, PythonServerException::class.java) - when (exception.detail.code) { - "PY0001" -> throw SurveyGenerationByAIFailedException() - "PY0002" -> throw TextTooLongException() - "PY0003" -> throw FileExtensionNotSupportedException() - else -> throw e - } + fun mapException(e: HttpClientErrorException): BusinessException { + val exception = objectMapper.readValue(e.responseBodyAsString, PythonServerException::class.java) + when (exception.detail.code) { + "PY0001" -> throw SurveyGenerationByAIFailedException() + "PY0002" -> throw TextTooLongException() + "PY0003" -> throw FileExtensionNotSupportedException() + else -> throw e } } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/RandomNicknameGenerator.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/RandomNicknameGenerator.kt index 45a39b30..6182a150 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/util/RandomNicknameGenerator.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/RandomNicknameGenerator.kt @@ -71,7 +71,7 @@ class RandomNicknameGenerator { "열정있는", ) - val animalNames = + private val animalNames = listOf( "고양이", "강아지", diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/validator/FileUploadValidator.kt similarity index 77% rename from src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt rename to src/main/kotlin/com/sbl/sulmun2yong/global/util/validator/FileUploadValidator.kt index 5bcca339..d91f5bb1 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/util/FileValidator.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/validator/FileUploadValidator.kt @@ -1,21 +1,19 @@ -package com.sbl.sulmun2yong.global.util +package com.sbl.sulmun2yong.global.util.validator import com.sbl.sulmun2yong.global.util.exception.FileNameTooLongException import com.sbl.sulmun2yong.global.util.exception.FileNameTooShortException import com.sbl.sulmun2yong.global.util.exception.InvalidExtensionException -import com.sbl.sulmun2yong.global.util.exception.InvalidFileUrlException import com.sbl.sulmun2yong.global.util.exception.NoExtensionExistException import com.sbl.sulmun2yong.global.util.exception.NoFileExistException import com.sbl.sulmun2yong.global.util.exception.OutOfFileSizeException import org.springframework.util.unit.DataSize import org.springframework.web.multipart.MultipartFile -class FileValidator( +class FileUploadValidator( private val maxFileSize: Long, private val maxFileNameLength: Int, private val allowedExtensions: MutableList, private val allowedContentTypes: MutableList, - private val cloudFrontBaseUrl: String, ) { companion object { fun from( @@ -23,18 +21,16 @@ class FileValidator( maxFileNameLength: Int, allowedExtensions: String, allowedContentTypes: String, - cloudFrontBaseUrl: String, - ): FileValidator { + ): FileUploadValidator { val fileSize = maxFileSize.toBytes() val extensions = allowedExtensions.split(",").toMutableList() val contentTypes = allowedContentTypes.split(",").toMutableList() - return FileValidator( + return FileUploadValidator( fileSize, maxFileNameLength, extensions, contentTypes, - cloudFrontBaseUrl, ) } } @@ -66,16 +62,4 @@ class FileValidator( throw InvalidExtensionException() } } - - fun validateFileUrlOf( - fileUrl: String, - allowedExtensions: MutableList, - ) { - if (fileUrl.startsWith(cloudFrontBaseUrl).not()) { - throw InvalidFileUrlException() - } - if (allowedExtensions.none { fileUrl.endsWith(it) }) { - throw InvalidExtensionException() - } - } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/validator/FileUrlValidator.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/validator/FileUrlValidator.kt new file mode 100644 index 00000000..d21c98e1 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/validator/FileUrlValidator.kt @@ -0,0 +1,24 @@ +package com.sbl.sulmun2yong.global.util.validator + +import com.sbl.sulmun2yong.global.util.exception.InvalidExtensionException +import com.sbl.sulmun2yong.global.util.exception.InvalidFileUrlException + +class FileUrlValidator( + private val cloudFrontBaseUrl: String, +) { + companion object { + fun of(cloudFrontBaseUrl: String): FileUrlValidator = FileUrlValidator(cloudFrontBaseUrl) + } + + fun validateFileUrlOf( + fileUrl: String, + allowedExtensions: MutableList, + ) { + if (fileUrl.startsWith(cloudFrontBaseUrl).not()) { + throw InvalidFileUrlException() + } + if (allowedExtensions.none { fileUrl.endsWith(it) }) { + throw InvalidExtensionException() + } + } +} From 8d2a263cd36709be28cd3f7b13b45277c1a9ed3b Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Tue, 24 Sep 2024 13:16:09 +0900 Subject: [PATCH 128/201] =?UTF-8?q?[SBL-139]=20defaultRoutingStrategy?= =?UTF-8?q?=EB=A1=9C=EC=8D=A8=20=EC=9E=85=EB=A0=A5=20=EB=B0=9B=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt index 2a67f73f..82cb86a7 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/SectionGeneratedByAI.kt @@ -10,6 +10,8 @@ class SectionGeneratedByAI( val description: String, val questions: List, ) { + private val defaultRoutingStrategy = RoutingStrategy.NumericalOrder + fun toDomain( sectionId: SectionId.Standard, sectionIds: SectionIds, @@ -18,7 +20,7 @@ class SectionGeneratedByAI( id = sectionId, title = title, description = description, - routingStrategy = RoutingStrategy.NumericalOrder, + routingStrategy = defaultRoutingStrategy, questions = questions.map { it.toDomain() }, sectionIds = sectionIds, ) From 933a74bd564e48e4366da5e1041ba21ef92c6325 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Tue, 24 Sep 2024 14:45:54 +0900 Subject: [PATCH 129/201] =?UTF-8?q?[SBL-139]=20fileNotFoundException=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/ai/exception/FileNotFoundException.kt | 6 ++++++ .../kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt | 1 + .../sulmun2yong/global/error/PythonServerExceptionMapper.kt | 2 ++ 3 files changed, 9 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/exception/FileNotFoundException.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/FileNotFoundException.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/FileNotFoundException.kt new file mode 100644 index 00000000..f531f599 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/exception/FileNotFoundException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.ai.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class FileNotFoundException : BusinessException(ErrorCode.FILE_NOT_FOUND) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index fd19becb..59c8e7e2 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -69,4 +69,5 @@ enum class ErrorCode( SURVEY_GENERATION_BY_AI_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "PY0001", "AI를 활용한 설문 생성에 실패했습니다."), TEXT_TOO_LONG(HttpStatus.BAD_REQUEST, "PY0002", "텍스트 길이는 1만 2천자 이하여야 합니다"), FILE_EXTENSION_NOT_SUPPORTED(HttpStatus.BAD_REQUEST, "PY0003", "지원하지 않는 파일 확장자입니다."), + FILE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PY0004", "파일을 찾을 수 없습니다."), } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionMapper.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionMapper.kt index 832494d9..a62af938 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionMapper.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/PythonServerExceptionMapper.kt @@ -2,6 +2,7 @@ package com.sbl.sulmun2yong.global.error import com.fasterxml.jackson.databind.ObjectMapper import com.sbl.sulmun2yong.ai.exception.FileExtensionNotSupportedException +import com.sbl.sulmun2yong.ai.exception.FileNotFoundException import com.sbl.sulmun2yong.ai.exception.SurveyGenerationByAIFailedException import com.sbl.sulmun2yong.ai.exception.TextTooLongException import org.springframework.web.client.HttpClientErrorException @@ -24,6 +25,7 @@ object PythonServerExceptionMapper { "PY0001" -> throw SurveyGenerationByAIFailedException() "PY0002" -> throw TextTooLongException() "PY0003" -> throw FileExtensionNotSupportedException() + "PY0004" -> throw FileNotFoundException() else -> throw e } } From 9210709a65c27d3435aee3aedce72cfb0f75f9ba Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Tue, 24 Sep 2024 14:47:00 +0900 Subject: [PATCH 130/201] =?UTF-8?q?[SBL-139]=20TODO=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/global/config/RestTemplateConfig.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/RestTemplateConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/RestTemplateConfig.kt index 92b39a37..bc0a5325 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/RestTemplateConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/RestTemplateConfig.kt @@ -4,6 +4,7 @@ import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.web.client.RestTemplate +// TODO : WebClient 시용 고려 @Configuration class RestTemplateConfig { @Bean From 4dbff42e7c0053333075850d05a4d34bebcd438a Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 24 Sep 2024 02:19:30 +0900 Subject: [PATCH 131/201] =?UTF-8?q?[SBL-148]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=EB=A5=BC=20=EC=A7=88=EB=AC=B8=20=EB=B3=84?= =?UTF-8?q?=EB=A1=9C=20=EA=B4=80=EB=A6=AC=ED=95=98=EA=B8=B0=20=EC=9C=84?= =?UTF-8?q?=ED=95=B4=20QuestionResult=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/domain/result/QuestionResult.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/QuestionResult.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/QuestionResult.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/QuestionResult.kt new file mode 100644 index 00000000..77436e7d --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/QuestionResult.kt @@ -0,0 +1,14 @@ +package com.sbl.sulmun2yong.survey.domain.result + +import java.util.SortedSet +import java.util.UUID + +data class QuestionResult( + val questionId: UUID, + val resultDetails: List, + /** 해당 질문의 모든 응답 집합 */ + val contents: SortedSet, +) { + fun getMatchedParticipants(questionFilter: QuestionFilter): Set = + resultDetails.mapNotNull { if (it.isMatched(questionFilter)) it.participantId else null }.toSet() +} From 61341ca9a6a18e619e1bee59f5bc995cbf8dfd65 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 24 Sep 2024 02:20:35 +0900 Subject: [PATCH 132/201] =?UTF-8?q?[SBL-148]=20QuestionResult=EB=A5=BC=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20SurvetResult?= =?UTF-8?q?=EC=99=80=20ResultDetails=EB=A5=BC=20=EC=88=98=EC=A0=95,=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=EC=9E=90=20=EC=88=98=EB=A5=BC=20=EA=B5=AC?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/domain/result/ResultDetails.kt | 6 +-- .../survey/domain/result/SurveyResult.kt | 52 ++++++++++++------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultDetails.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultDetails.kt index 626bee43..0f2bbddf 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultDetails.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultDetails.kt @@ -4,7 +4,6 @@ import com.sbl.sulmun2yong.survey.exception.InvalidResultDetailsException import java.util.UUID data class ResultDetails( - val questionId: UUID, val participantId: UUID, val contents: List, ) { @@ -12,8 +11,5 @@ data class ResultDetails( require(contents.isNotEmpty()) { throw InvalidResultDetailsException() } } - fun isMatched(questionFilter: QuestionFilter): Boolean { - if (questionFilter.questionId != questionId) return false - return contents.any { questionFilter.contents.contains(it) } - } + fun isMatched(questionFilter: QuestionFilter) = contents.any { questionFilter.contents.contains(it) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt index b951fcfb..b3ebb045 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResult.kt @@ -3,35 +3,47 @@ package com.sbl.sulmun2yong.survey.domain.result import java.util.UUID data class SurveyResult( - val resultDetails: List, + val questionResults: List, ) { fun getFilteredResult(resultFilter: ResultFilter): SurveyResult { val questionFilters = resultFilter.questionFilters - if (questionFilters.isEmpty()) return this var filteredSurveyResult = copy() for (questionFilter in questionFilters) { - filteredSurveyResult = filteredSurveyResult.filterByQuestionFilter(questionFilter) - if (filteredSurveyResult.resultDetails.isEmpty()) return filteredSurveyResult + val targetQuestionResult = findQuestionResult(questionFilter.questionId) ?: continue + val participantSet = targetQuestionResult.getMatchedParticipants(questionFilter) + filteredSurveyResult = filteredSurveyResult.filterByQuestionFilter(participantSet, questionFilter) } return filteredSurveyResult } - fun findResultDetailsByQuestionId(questionId: UUID) = resultDetails.filter { it.questionId == questionId } + fun findQuestionResult(questionId: UUID) = questionResults.find { it.questionId == questionId } - private fun filterByQuestionFilter(questionFilter: QuestionFilter): SurveyResult { - val participantSet = getMatchedParticipants(questionFilter) - return if (questionFilter.isPositive) { - // isPositive가 true이면 해당 참가자들을 포함 - SurveyResult(resultDetails.filter { participantSet.contains(it.participantId) }) - } else { - // isPositive가 false이면 해당 참가자들을 제외 - SurveyResult(resultDetails.filter { !participantSet.contains(it.participantId) }) - } - } + fun getParticipantCount() = + questionResults + .map { it.resultDetails.map { resultDetail -> resultDetail.participantId } } + .flatten() + .toSet() + .size - private fun getMatchedParticipants(questionFilter: QuestionFilter): Set = - resultDetails - .mapNotNull { response -> - if (response.isMatched(questionFilter)) response.participantId else null - }.toSet() + private fun filterByQuestionFilter( + participantSet: Set, + questionFilter: QuestionFilter, + ): SurveyResult = + SurveyResult( + questionResults.map { questionResult -> + val responseDetails = + if (questionFilter.isPositive) { + questionResult.resultDetails.filter { + participantSet.contains(it.participantId) + } + } else { + questionResult.resultDetails.filter { !participantSet.contains(it.participantId) } + } + QuestionResult( + questionResult.questionId, + responseDetails, + questionResult.contents, + ) + }, + ) } From 74b1500bab84a32783eb0b7ccc17d3b724d1c0f8 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 24 Sep 2024 02:21:15 +0900 Subject: [PATCH 133/201] =?UTF-8?q?[SBL-148]=20=EB=B3=80=EA=B2=BD=EB=90=9C?= =?UTF-8?q?=20SurveyResult=EC=97=90=20=EB=A7=9E=EA=B2=8C=20ResponseAdapter?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/adapter/ResponseAdapter.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt index 0c29a4e0..edda470b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ResponseAdapter.kt @@ -1,6 +1,7 @@ package com.sbl.sulmun2yong.survey.adapter import com.sbl.sulmun2yong.survey.domain.response.SurveyResponse +import com.sbl.sulmun2yong.survey.domain.result.QuestionResult import com.sbl.sulmun2yong.survey.domain.result.ResultDetails import com.sbl.sulmun2yong.survey.domain.result.SurveyResult import com.sbl.sulmun2yong.survey.entity.ResponseDocument @@ -45,15 +46,20 @@ class ResponseAdapter( responseRepository.findBySurveyId(surveyId) } // TODO: 추후 DB Level에서 처리하도록 변경 + 필터링을 동적쿼리로 하도록 변경 - val groupingResponses = responses.groupBy { "${it.questionId}|${it.participantId}" }.values - groupingResponses.map { it.toDomain() } - return SurveyResult(resultDetails = groupingResponses.map { it.toDomain() }) + val groupingResponses = responses.groupBy { it.questionId }.values + return SurveyResult(questionResults = groupingResponses.map { it.toDomain() }) } private fun List.toDomain() = - ResultDetails( + QuestionResult( questionId = first().questionId, - participantId = first().participantId, - contents = map { responseDocument -> responseDocument.content }, + resultDetails = + this.groupBy { it.participantId }.map { + ResultDetails( + participantId = it.key, + contents = it.value.map { responseDocument -> responseDocument.content }, + ) + }, + contents = this.map { it.content }.toSortedSet(), ) } From 96d70bf22b43f586784719437246b742f6e1f017 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 24 Sep 2024 02:21:43 +0900 Subject: [PATCH 134/201] =?UTF-8?q?[SBL-148]=20=EB=B3=80=EA=B2=BD=EB=90=9C?= =?UTF-8?q?=20SurveyResult=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/SurveyResultConstFactory.kt | 44 ++++++---- .../survey/domain/result/SurveyResultTest.kt | 83 +++++++++++-------- 2 files changed, 79 insertions(+), 48 deletions(-) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyResultConstFactory.kt b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyResultConstFactory.kt index 84810032..659ef63a 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyResultConstFactory.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/fixture/survey/SurveyResultConstFactory.kt @@ -1,6 +1,7 @@ package com.sbl.sulmun2yong.fixture.survey import com.sbl.sulmun2yong.survey.domain.result.QuestionFilter +import com.sbl.sulmun2yong.survey.domain.result.QuestionResult import com.sbl.sulmun2yong.survey.domain.result.ResultDetails import com.sbl.sulmun2yong.survey.domain.result.SurveyResult import java.util.UUID @@ -24,22 +25,18 @@ object SurveyResultConstFactory { val PARTICIPANT_RESULT_DETAILS_1 = listOf( ResultDetails( - questionId = JOB_QUESTION_ID, participantId = PARTICIPANT_ID_1, contents = listOf(JOB_QUESTION_CONTENTS[0]), ), ResultDetails( - questionId = GENDER_QUESTION_ID, participantId = PARTICIPANT_ID_1, contents = listOf(GENDER_QUESTION_CONTENTS[0]), ), ResultDetails( - questionId = FOOD_MULTIPLE_CHOICE_QUESTION_ID, participantId = PARTICIPANT_ID_1, contents = listOf(FOOD_MULTIPLE_CHOICE_QUESTION_CONTENTS[0], FOOD_MULTIPLE_CHOICE_QUESTION_CONTENTS[1]), ), ResultDetails( - questionId = FOOD_TEXT_RESPONSE_QUESTION_ID, participantId = PARTICIPANT_ID_1, contents = listOf(FOOD_TEXT_RESPONSE_QUESTION_CONTENTS[0]), ), @@ -51,17 +48,14 @@ object SurveyResultConstFactory { val PARTICIPANT_RESULT_DETAILS_2 = listOf( ResultDetails( - questionId = JOB_QUESTION_ID, participantId = PARTICIPANT_ID_2, contents = listOf(JOB_QUESTION_CONTENTS[1]), ), ResultDetails( - questionId = GENDER_QUESTION_ID, participantId = PARTICIPANT_ID_2, contents = listOf(GENDER_QUESTION_CONTENTS[1]), ), ResultDetails( - questionId = FOOD_MULTIPLE_CHOICE_QUESTION_ID, participantId = PARTICIPANT_ID_2, contents = listOf( @@ -71,7 +65,6 @@ object SurveyResultConstFactory { ), ), ResultDetails( - questionId = FOOD_TEXT_RESPONSE_QUESTION_ID, participantId = PARTICIPANT_ID_2, contents = listOf(FOOD_TEXT_RESPONSE_QUESTION_CONTENTS[1]), ), @@ -83,32 +76,53 @@ object SurveyResultConstFactory { val PARTICIPANT_RESULT_DETAILS_3 = listOf( ResultDetails( - questionId = JOB_QUESTION_ID, participantId = PARTICIPANT_ID_3, contents = listOf(JOB_QUESTION_CONTENTS[0]), ), ResultDetails( - questionId = GENDER_QUESTION_ID, participantId = PARTICIPANT_ID_3, contents = listOf(GENDER_QUESTION_CONTENTS[1]), ), ResultDetails( - questionId = FOOD_MULTIPLE_CHOICE_QUESTION_ID, participantId = PARTICIPANT_ID_3, contents = listOf(FOOD_MULTIPLE_CHOICE_QUESTION_CONTENTS[2], FOOD_MULTIPLE_CHOICE_QUESTION_CONTENTS[4]), ), ResultDetails( - questionId = FOOD_TEXT_RESPONSE_QUESTION_ID, participantId = PARTICIPANT_ID_3, contents = listOf(FOOD_TEXT_RESPONSE_QUESTION_CONTENTS[2]), ), ) - val SURVEY_RESULT = - SurveyResult( - resultDetails = PARTICIPANT_RESULT_DETAILS_1 + PARTICIPANT_RESULT_DETAILS_2 + PARTICIPANT_RESULT_DETAILS_3, + val QUESTION_RESULT_1 = + QuestionResult( + questionId = JOB_QUESTION_ID, + resultDetails = listOf(PARTICIPANT_RESULT_DETAILS_1[0], PARTICIPANT_RESULT_DETAILS_2[0], PARTICIPANT_RESULT_DETAILS_3[0]), + contents = JOB_QUESTION_CONTENTS.toSortedSet(), ) + val QUESTION_RESULT_2 = + QuestionResult( + questionId = GENDER_QUESTION_ID, + resultDetails = listOf(PARTICIPANT_RESULT_DETAILS_1[1], PARTICIPANT_RESULT_DETAILS_2[1], PARTICIPANT_RESULT_DETAILS_3[1]), + contents = GENDER_QUESTION_CONTENTS.toSortedSet(), + ) + + val QUESTION_RESULT_3 = + QuestionResult( + questionId = FOOD_MULTIPLE_CHOICE_QUESTION_ID, + resultDetails = listOf(PARTICIPANT_RESULT_DETAILS_1[2], PARTICIPANT_RESULT_DETAILS_2[2], PARTICIPANT_RESULT_DETAILS_3[2]), + contents = FOOD_MULTIPLE_CHOICE_QUESTION_CONTENTS.toSortedSet(), + ) + + val QUESTION_RESULT_4 = + QuestionResult( + questionId = FOOD_TEXT_RESPONSE_QUESTION_ID, + resultDetails = listOf(PARTICIPANT_RESULT_DETAILS_1[3], PARTICIPANT_RESULT_DETAILS_2[3], PARTICIPANT_RESULT_DETAILS_3[3]), + contents = FOOD_TEXT_RESPONSE_QUESTION_CONTENTS.toSortedSet(), + ) + + val SURVEY_RESULT = SurveyResult(questionResults = listOf(QUESTION_RESULT_1, QUESTION_RESULT_2, QUESTION_RESULT_3, QUESTION_RESULT_4)) + val EXCEPT_STUDENT_FILTER = QuestionFilter(JOB_QUESTION_ID, listOf(JOB_QUESTION_CONTENTS[0]), false) val MAN_FILTER = QuestionFilter(GENDER_QUESTION_ID, listOf(GENDER_QUESTION_CONTENTS[0]), true) val K_J_FOOD_FILTER = diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResultTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResultTest.kt index b193aa90..acd6ffc4 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResultTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/SurveyResultTest.kt @@ -29,54 +29,64 @@ class SurveyResultTest { val response1 = ResultDetails( - questionId = questionId1, participantId = participantId1, contents = contents1, ) val response2 = ResultDetails( - questionId = questionId2, participantId = participantId1, contents = contents2, ) val response3 = ResultDetails( - questionId = questionId1, participantId = participantId2, contents = contents3, ) + val questionContents1 = sortedSetOf(contents1.first(), contents3.first()) + val questionResult1 = + QuestionResult( + questionId = questionId1, + resultDetails = listOf(response1, response3), + contents = questionContents1, + ) + val questionContents2 = sortedSetOf(contents2.first()) + val questionResult2 = + QuestionResult( + questionId = questionId2, + resultDetails = listOf(response2), + contents = sortedSetOf(contents2.first()), + ) + // when - val surveyResult = SurveyResult(listOf(response1, response2, response3)) + val surveyResult = SurveyResult(listOf(questionResult1, questionResult2)) // then - with(surveyResult.resultDetails[0]) { + with(surveyResult.questionResults[0]) { assertEquals(questionId1, questionId) - assertEquals(participantId1, participantId) - assertEquals(contents1, contents) + assertEquals(questionContents1, contents) + assertEquals(contents1, resultDetails[0].contents) + assertEquals(participantId1, resultDetails[0].participantId) + assertEquals(contents3, resultDetails[1].contents) + assertEquals(participantId2, resultDetails[1].participantId) } - with(surveyResult.resultDetails[1]) { + with(surveyResult.questionResults[1]) { assertEquals(questionId2, questionId) - assertEquals(participantId1, participantId) - assertEquals(contents2, contents) - } - with(surveyResult.resultDetails[2]) { - assertEquals(questionId1, questionId) - assertEquals(participantId2, participantId) - assertEquals(contents3, contents) + assertEquals(questionContents2, contents) + assertEquals(contents2, resultDetails[0].contents) + assertEquals(participantId1, resultDetails[0].participantId) } } @Test fun `설문 결과 상세의 contents는 비어있을 수 없다`() { // given - val questionId = UUID.randomUUID() val participantId = UUID.randomUUID() val contents = emptyList() // when, then assertThrows { - ResultDetails(questionId, participantId, contents) + ResultDetails(participantId, contents) } } @@ -110,9 +120,6 @@ class SurveyResultTest { val isMan3 = genderQuestionDetails3.isMatched(MAN_FILTER) val isKOrJFood3 = foodQuestionDetails3.isMatched(K_J_FOOD_FILTER) - // 다른 질문인 경우 - val differentQuestion = foodQuestionDetails3.isMatched(MAN_FILTER) - // then // 1번 참가자의 응답(학생, 남자, 한식 & 일식, 맛있어요!) assertEquals(true, isStudent1) @@ -128,9 +135,6 @@ class SurveyResultTest { assertEquals(true, isStudent3) assertEquals(false, isMan3) assertEquals(false, isKOrJFood3) - - // 다른 질문인 경우 false - assertEquals(false, differentQuestion) } @Test @@ -139,24 +143,25 @@ class SurveyResultTest { val surveyResult = SURVEY_RESULT val exceptStudentAndKOrJFoodFilter = ResultFilter(listOf(EXCEPT_STUDENT_FILTER, K_J_FOOD_FILTER)) val exceptStudentAndManAndKOrJFoodFilter = ResultFilter(listOf(EXCEPT_STUDENT_FILTER, MAN_FILTER, K_J_FOOD_FILTER)) - val emptyFilter = ResultFilter(listOf()) + val invalidFilter = ResultFilter(listOf(QuestionFilter(UUID.randomUUID(), listOf("invalid_content"), true))) // when val filteredResult1 = surveyResult.getFilteredResult(exceptStudentAndKOrJFoodFilter) val filteredResult2 = surveyResult.getFilteredResult(exceptStudentAndManAndKOrJFoodFilter) - val filteredResult3 = surveyResult.getFilteredResult(emptyFilter) + val filteredResult3 = surveyResult.getFilteredResult(invalidFilter) // then - with(filteredResult1.resultDetails) { - assertEquals(4, size) + with(filteredResult1.questionResults.map { it.resultDetails }.flatten()) { assertTrue { this.containsAll(PARTICIPANT_RESULT_DETAILS_2) } } - with(filteredResult2.resultDetails) { + with(filteredResult2.questionResults.map { it.resultDetails }.flatten()) { assertEquals(0, size) } - with(filteredResult3.resultDetails) { - assertEquals(12, size) + // 이상한 필터는 무시한다. + with(filteredResult3.questionResults.map { it.resultDetails }.flatten()) { + assertTrue { this.containsAll(PARTICIPANT_RESULT_DETAILS_1) } assertTrue { this.containsAll(PARTICIPANT_RESULT_DETAILS_2) } + assertTrue { this.containsAll(PARTICIPANT_RESULT_DETAILS_3) } } } @@ -166,14 +171,26 @@ class SurveyResultTest { val surveyResult = SURVEY_RESULT // when - val jobQuestionResponses = surveyResult.findResultDetailsByQuestionId(JOB_QUESTION_ID) + val jobQuestionResponses = surveyResult.findQuestionResult(JOB_QUESTION_ID) // then - assertEquals(3, jobQuestionResponses.size) - with(jobQuestionResponses.map { it.contents }.flatten()) { + assertEquals(3, jobQuestionResponses?.resultDetails?.size) + with(jobQuestionResponses!!.resultDetails.map { it.contents }.flatten()) { assertEquals(true, containsAll(PARTICIPANT_RESULT_DETAILS_1[0].contents)) assertEquals(true, containsAll(PARTICIPANT_RESULT_DETAILS_2[0].contents)) assertEquals(true, containsAll(PARTICIPANT_RESULT_DETAILS_3[0].contents)) } } + + @Test + fun `설문 결과는 해당 설문의 참여자 수를 가져올 수 있다`() { + // given + val surveyResult = SURVEY_RESULT + + // when + val participantCount = surveyResult.getParticipantCount() + + // then + assertEquals(3, participantCount) + } } From 8f1a7db02e04b3e5db5fc4796ff0d9e6359878f7 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 24 Sep 2024 02:22:43 +0900 Subject: [PATCH 135/201] =?UTF-8?q?[SBL-148]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20Response=20DTO=EC=97=90=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20=EC=9D=91=EB=8B=B5=EC=9E=90=20=EC=88=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20=EC=A3=BC=EA=B4=80=EC=8B=9D=EC=9D=80=20groupBy?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20content=20=EB=AA=A9=EB=A1=9D=EC=9D=B4=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=EC=97=90=20=EC=98=81=ED=96=A5=20=EB=B0=9B=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/SurveyResultResponse.kt | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt index c245b9f1..77ce5ce3 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt @@ -1,21 +1,23 @@ package com.sbl.sulmun2yong.survey.dto.response import com.sbl.sulmun2yong.survey.domain.Survey +import com.sbl.sulmun2yong.survey.domain.question.ChoiceQuestion import com.sbl.sulmun2yong.survey.domain.question.Question import com.sbl.sulmun2yong.survey.domain.question.QuestionType -import com.sbl.sulmun2yong.survey.domain.result.ResultDetails +import com.sbl.sulmun2yong.survey.domain.result.QuestionResult import com.sbl.sulmun2yong.survey.domain.result.SurveyResult import com.sbl.sulmun2yong.survey.domain.section.Section import java.util.UUID data class SurveyResultResponse( val sectionResults: List, + val participantCount: Int, ) { companion object { fun of( surveyResult: SurveyResult, survey: Survey, - ) = SurveyResultResponse(survey.sections.map { SectionResultResponse.of(surveyResult, it) }) + ) = SurveyResultResponse(survey.sections.map { SectionResultResponse.of(surveyResult, it) }, surveyResult.getParticipantCount()) } data class SectionResultResponse( @@ -32,11 +34,9 @@ data class SurveyResultResponse( sectionId = section.id.value, title = section.title, questionResults = - section.questions.map { - QuestionResultResponse.of( - question = it, - responses = surveyResult.findResultDetailsByQuestionId(it.id), - ) + section.questions.mapNotNull { question -> + val questionResult = surveyResult.findQuestionResult(question.id) + questionResult?.let { QuestionResultResponse.of(question, it) } }, ) } @@ -48,27 +48,39 @@ data class SurveyResultResponse( val type: QuestionType, val participantCount: Int, val responses: List, + val responseContents: List, ) { companion object { fun of( question: Question, - responses: List, + questionResult: QuestionResult, ): QuestionResultResponse { - val contentCountMap = - responses - .map { it.contents } - .flatten() - .groupingBy { it } - .eachCount() - .toMutableMap() - val contents = question.choices?.standardChoices?.map { it.content } ?: emptyList() - contents.forEach { contentCountMap.putIfAbsent(it, 0) } + val allContents = + if (question is ChoiceQuestion) { + val tempContents = question.choices.standardChoices.map { it.content } + tempContents + questionResult.contents.filter { !tempContents.contains(it) } + } else { + questionResult.contents.toList() + } + + val responses = + if (question is ChoiceQuestion) { + val contentCountMap = + questionResult.resultDetails + .flatMap { it.contents } + .groupingBy { it } + .eachCount() + allContents.map { Response(it, contentCountMap[it] ?: 0) } + } else { + questionResult.resultDetails.map { Response(it.contents.first(), 1) } + } return QuestionResultResponse( questionId = question.id, title = question.title, type = question.questionType, - participantCount = responses.size, - responses = contentCountMap.map { Response(it.key, it.value) }, + participantCount = questionResult.resultDetails.size, + responses = responses, + responseContents = allContents, ) } } From c1052880971f57987f58ec54c3b870a4d93d6e27 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 24 Sep 2024 03:59:27 +0900 Subject: [PATCH 136/201] =?UTF-8?q?[SBL-148]=20=ED=95=84=ED=84=B0=EB=8A=94?= =?UTF-8?q?=20=EC=B5=9C=EB=8C=80=2020=EA=B0=9C=20=EA=B9=8C=EC=A7=80?= =?UTF-8?q?=EB=A7=8C=20=EC=A0=81=EC=9A=A9=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/global/error/ErrorCode.kt | 1 + .../sulmun2yong/survey/domain/result/ResultFilter.kt | 12 +++++++++++- .../survey/exception/InvalidResultFilterException.kt | 6 ++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidResultFilterException.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index 59c8e7e2..d65513ad 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -34,6 +34,7 @@ enum class ErrorCode( INVALID_FINISHED_AT(HttpStatus.BAD_REQUEST, "SV0022", "유효하지 않은 마감일입니다."), INVALID_SURVEY_EDIT(HttpStatus.BAD_REQUEST, "SV0023", "설문 수정 상태 변경에 실패했습니다."), INVALID_PUBLISHED_AT(HttpStatus.BAD_REQUEST, "SV0024", "설문 마감일이 설문 공개일 보다 빠릅니다."), + INVALID_RESULT_FILTER(HttpStatus.BAD_REQUEST, "SV0025", "필터는 최대 20개 까지만 적용 가능합니다."), // Drawing (DR) INVALID_DRAWING_BOARD(HttpStatus.BAD_REQUEST, "DR0001", "유효하지 않은 추첨 보드입니다."), diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilter.kt index 52be0ac7..d1cbc5cd 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilter.kt @@ -1,5 +1,15 @@ package com.sbl.sulmun2yong.survey.domain.result +import com.sbl.sulmun2yong.survey.exception.InvalidResultFilterException + data class ResultFilter( val questionFilters: List, -) +) { + companion object { + const val MAX_SIZE = 20 + } + + init { + require(questionFilters.size <= MAX_SIZE) { throw InvalidResultFilterException() } + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidResultFilterException.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidResultFilterException.kt new file mode 100644 index 00000000..cbb6c3b8 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/InvalidResultFilterException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.survey.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class InvalidResultFilterException : BusinessException(ErrorCode.INVALID_RESULT_FILTER) From 476919a69c3b23fcbc0e4fd39c80488032e01009 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 24 Sep 2024 04:00:39 +0900 Subject: [PATCH 137/201] =?UTF-8?q?[SBL-148]=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/survey/domain/result/ResultFilterTest.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilterTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilterTest.kt index 611fd414..76dffbca 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilterTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/result/ResultFilterTest.kt @@ -3,6 +3,7 @@ package com.sbl.sulmun2yong.survey.domain.result import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.EXCEPT_STUDENT_FILTER import com.sbl.sulmun2yong.fixture.survey.SurveyResultConstFactory.MAN_FILTER import com.sbl.sulmun2yong.survey.exception.InvalidQuestionFilterException +import com.sbl.sulmun2yong.survey.exception.InvalidResultFilterException import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import java.util.UUID @@ -43,4 +44,10 @@ class ResultFilterTest { // then assertEquals(questionFilters, resultFilter.questionFilters) } + + @Test + fun `필터는 최대 20개 까지만 적용할 수 있다`() { + val questionFilters = List(ResultFilter.MAX_SIZE + 1) { QuestionFilter(UUID.randomUUID(), listOf("content"), true) } + assertThrows { ResultFilter(questionFilters) } + } } From c656a04337f7a08ede46e178b287b0cbeb109697 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 24 Sep 2024 04:01:14 +0900 Subject: [PATCH 138/201] =?UTF-8?q?[SBL-148]=20=EC=84=A0=ED=83=9D=EC=A7=80?= =?UTF-8?q?=EB=8A=94=20=EC=B5=9C=EB=8C=80=2020=EA=B0=9C=20=EA=B9=8C?= =?UTF-8?q?=EC=A7=80=EB=A7=8C=20=EC=B6=94=EA=B0=80=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/domain/question/choice/Choices.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/question/choice/Choices.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/question/choice/Choices.kt index 56258824..dbc3a5c3 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/question/choice/Choices.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/question/choice/Choices.kt @@ -9,8 +9,13 @@ data class Choices( /** 기타 선택지 허용 여부 */ val isAllowOther: Boolean, ) { + companion object { + const val MAX_SIZE = 20 + } + init { if (standardChoices.isEmpty()) throw InvalidChoiceException() + if (standardChoices.size > MAX_SIZE) throw InvalidChoiceException() } /** 응답이 선택지에 포함되는지 확인 */ From 153a4e69f971efbf8cfa3c347e28f834ff7e4e39 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 24 Sep 2024 04:01:24 +0900 Subject: [PATCH 139/201] =?UTF-8?q?[SBL-148]=20=EC=84=A0=ED=83=9D=EC=A7=80?= =?UTF-8?q?=20=EA=B0=9C=EC=88=98=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/domain/question/ChoicesTest.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/question/ChoicesTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/question/ChoicesTest.kt index d580b3c6..32d237ed 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/question/ChoicesTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/question/ChoicesTest.kt @@ -38,6 +38,13 @@ class ChoicesTest { assertThrows { Choices(listOf(), false) } } + @Test + fun `선택지는 최대 20개 까지만 추가할 수 있다`() { + val standardChoices = List(Choices.MAX_SIZE + 1) { Choice.Standard(it.toString()) } + assertThrows { Choices(standardChoices, true) } + assertThrows { Choices(standardChoices, false) } + } + @Test fun `선택지 목록에 중복된 내용이 있는지 확인할 수 있다`() { // given From 176dedfefae8e2152038cf079e154030f3d4f035 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 25 Sep 2024 03:20:59 +0900 Subject: [PATCH 140/201] =?UTF-8?q?[SBL-146]=20Participant=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EA=B0=9D=EC=B2=B4=EC=97=90=20createdAt=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/survey/domain/Participant.kt | 4 +++- .../com/sbl/sulmun2yong/survey/entity/ParticipantDocument.kt | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Participant.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Participant.kt index 6189dba3..6a61ff7a 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Participant.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Participant.kt @@ -1,16 +1,18 @@ package com.sbl.sulmun2yong.survey.domain +import java.util.Date import java.util.UUID data class Participant( val id: UUID, val surveyId: UUID, val userId: UUID?, + val createdAt: Date, ) { companion object { fun create( surveyId: UUID, userId: UUID?, - ) = Participant(UUID.randomUUID(), surveyId, userId) + ) = Participant(UUID.randomUUID(), surveyId, userId, Date()) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ParticipantDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ParticipantDocument.kt index f2cd7d90..6e07d2d4 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ParticipantDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ParticipantDocument.kt @@ -27,5 +27,6 @@ data class ParticipantDocument( id = this.id, surveyId = this.surveyId, userId = this.userId, + createdAt = this.createdAt, ) } From 5577957d915edbba9f9f6e796a44cfdbfe117380 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 25 Sep 2024 03:21:51 +0900 Subject: [PATCH 141/201] =?UTF-8?q?[SBL-146]=20Participant=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EA=B0=9D=EC=B2=B4=EC=97=90=20createdAt=20?= =?UTF-8?q?=EC=86=8D=EC=84=B1=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=A8?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=9D=BC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt index e6d66700..b76f6669 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt @@ -2,6 +2,7 @@ package com.sbl.sulmun2yong.survey.domain import org.junit.jupiter.api.Test import org.mockito.Mockito +import java.util.Date import java.util.UUID import kotlin.test.assertEquals @@ -12,6 +13,7 @@ class ParticipantTest { val participantId = UUID.randomUUID() val surveyId = UUID.randomUUID() val userId = UUID.randomUUID() + val createdAt = Date() Mockito.mockStatic(UUID::class.java).use { mockedUUID -> mockedUUID.`when` { UUID.randomUUID() }.thenReturn(participantId) @@ -26,5 +28,7 @@ class ParticipantTest { assertEquals(userId, this.userId) } } + + assertEquals(createdAt, Participant(participantId, surveyId, userId, createdAt).createdAt) } } From f082e07bc925a40c8aafc0854883b580d3828a68 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 25 Sep 2024 03:22:41 +0900 Subject: [PATCH 142/201] =?UTF-8?q?[SBL-146]=20=EC=84=A4=EB=AC=B8=20ID?= =?UTF-8?q?=EB=A1=9C=20=EC=B0=B8=EA=B0=80=EC=9E=90=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=EC=9D=84=20=EB=B6=88=EB=9F=AC=EC=98=A4=EB=8A=94=20Adapter=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt | 3 +-- .../sbl/sulmun2yong/survey/repository/ParticipantRepository.kt | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt index 6621bc75..b2c9b4ce 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt @@ -21,9 +21,8 @@ class ParticipantAdapter( .orElseThrow { InvalidParticipantException() } .toDomain() - fun findBySurveyId(surveyId: UUID): Participant? = + fun findBySurveyId(surveyId: UUID): List = participantRepository .findBySurveyId(surveyId) .map { it.toDomain() } - .orElse(null) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ParticipantRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ParticipantRepository.kt index 79e02c5d..0e4f46c2 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ParticipantRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ParticipantRepository.kt @@ -3,10 +3,9 @@ package com.sbl.sulmun2yong.survey.repository import com.sbl.sulmun2yong.survey.entity.ParticipantDocument import org.springframework.data.mongodb.repository.MongoRepository import org.springframework.stereotype.Repository -import java.util.Optional import java.util.UUID @Repository interface ParticipantRepository : MongoRepository { - fun findBySurveyId(surveyId: UUID): Optional + fun findBySurveyId(surveyId: UUID): List } From f0184281802a34c5c48810fde3659406a38e56fe Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 25 Sep 2024 03:23:03 +0900 Subject: [PATCH 143/201] =?UTF-8?q?[SBL-146]=20=EC=B0=B8=EA=B0=80=EC=9E=90?= =?UTF-8?q?=20=EB=AA=A9=EB=A1=9D=20ResponseDTO=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/ParticipantInfoListResponse.kt | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/ParticipantInfoListResponse.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/ParticipantInfoListResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/ParticipantInfoListResponse.kt new file mode 100644 index 00000000..da952de9 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/ParticipantInfoListResponse.kt @@ -0,0 +1,88 @@ +package com.sbl.sulmun2yong.survey.dto.response + +import com.sbl.sulmun2yong.drawing.domain.DrawingHistory +import com.sbl.sulmun2yong.drawing.domain.DrawingHistoryGroup +import com.sbl.sulmun2yong.drawing.domain.ticket.Ticket +import com.sbl.sulmun2yong.survey.domain.Participant +import java.util.Date +import java.util.UUID + +data class ParticipantInfoListResponse( + val participants: List, +) { + data class ParticipantInfoResponse( + val participantId: UUID, + val participatedAt: Date, + val drawInfo: DrawInfoResponse?, + ) + + data class DrawInfoResponse( + val drawResult: DrawResult, + val reward: String?, + val phoneNumber: String?, + ) { + companion object { + fun from(drawingHistory: DrawingHistory?): DrawInfoResponse { + if (drawingHistory == null) { + return DrawInfoResponse( + drawResult = DrawResult.BEFORE_DRAW, + reward = null, + phoneNumber = null, + ) + } + + if (drawingHistory.ticket is Ticket.Winning) { + return DrawInfoResponse( + drawResult = DrawResult.WIN, + reward = drawingHistory.ticket.rewardName, + phoneNumber = drawingHistory.phoneNumber.value, + ) + } + + return DrawInfoResponse( + drawResult = DrawResult.LOSE, + reward = null, + phoneNumber = null, + ) + } + } + } + + enum class DrawResult { + BEFORE_DRAW, + WIN, + LOSE, + } + + companion object { + fun of( + participants: List, + drawingHistories: DrawingHistoryGroup?, + ): ParticipantInfoListResponse { + if (drawingHistories == null) { + return ParticipantInfoListResponse( + participants.map { + ParticipantInfoResponse( + participantId = it.id, + participatedAt = it.createdAt, + drawInfo = null, + ) + }, + ) + } + + val drawingHistoryMap = drawingHistories.histories.associateBy { it.participantId } + + return ParticipantInfoListResponse( + participants.map { + val drawingHistory = drawingHistoryMap[it.id] + ParticipantInfoResponse( + participantId = it.id, + participatedAt = it.createdAt, + drawInfo = DrawInfoResponse.from(drawingHistory), + ) + }, + ) + } + } +} From 7e3980949919c8e350eccc321a747333270d28b9 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 25 Sep 2024 03:23:15 +0900 Subject: [PATCH 144/201] =?UTF-8?q?[SBL-146]=20=EC=B0=B8=EA=B0=80=EC=9E=90?= =?UTF-8?q?=20=EB=AA=A9=EB=A1=9D=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SurveyParticipantController.kt | 23 +++++++++++++++++ .../controller/doc/SurveyParticipantApiDoc.kt | 20 +++++++++++++++ .../service/SurveyParticipantService.kt | 25 +++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyParticipantController.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyParticipantApiDoc.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyParticipantService.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyParticipantController.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyParticipantController.kt new file mode 100644 index 00000000..ee843c92 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyParticipantController.kt @@ -0,0 +1,23 @@ +package com.sbl.sulmun2yong.survey.controller + +import com.sbl.sulmun2yong.global.annotation.LoginUser +import com.sbl.sulmun2yong.survey.controller.doc.SurveyParticipantApiDoc +import com.sbl.sulmun2yong.survey.service.SurveyParticipantService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import java.util.UUID + +@RestController +@RequestMapping("/api/v1/surveys/participants") +class SurveyParticipantController( + private val surveyParticipantService: SurveyParticipantService, +) : SurveyParticipantApiDoc { + @GetMapping("/{survey-id}") + override fun getSurveyParticipants( + @PathVariable("survey-id") surveyId: UUID, + @LoginUser userId: UUID, + ) = ResponseEntity.ok(surveyParticipantService.getSurveyParticipants(surveyId, userId)) +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyParticipantApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyParticipantApiDoc.kt new file mode 100644 index 00000000..c3a472e7 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyParticipantApiDoc.kt @@ -0,0 +1,20 @@ +package com.sbl.sulmun2yong.survey.controller.doc + +import com.sbl.sulmun2yong.global.annotation.LoginUser +import com.sbl.sulmun2yong.survey.dto.response.ParticipantInfoListResponse +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import java.util.UUID + +@Tag(name = "SurveyParticipant", description = "설문 참가자 관련 API") +interface SurveyParticipantApiDoc { + @Operation(summary = "참가자 목록") + @PostMapping + fun getSurveyParticipants( + @PathVariable("survey-id") surveyId: UUID, + @LoginUser id: UUID, + ): ResponseEntity +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyParticipantService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyParticipantService.kt new file mode 100644 index 00000000..24c9324f --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyParticipantService.kt @@ -0,0 +1,25 @@ +package com.sbl.sulmun2yong.survey.service + +import com.sbl.sulmun2yong.drawing.adapter.DrawingHistoryAdapter +import com.sbl.sulmun2yong.survey.adapter.ParticipantAdapter +import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter +import com.sbl.sulmun2yong.survey.dto.response.ParticipantInfoListResponse +import org.springframework.stereotype.Service +import java.util.UUID + +@Service +class SurveyParticipantService( + private val surveyAdapter: SurveyAdapter, + private val participantAdapter: ParticipantAdapter, + private val drawingHistoryAdapter: DrawingHistoryAdapter, +) { + fun getSurveyParticipants( + surveyId: UUID, + makerId: UUID, + ): ParticipantInfoListResponse { + val survey = surveyAdapter.getByIdAndMakerId(surveyId, makerId) + val participants = participantAdapter.findBySurveyId(surveyId) + val drawingHistories = if (survey.isImmediateDraw()) drawingHistoryAdapter.getBySurveyId(surveyId, false) else null + return ParticipantInfoListResponse.of(participants, drawingHistories) + } +} From 646ecc0f471e2aedaecbe6eaa67329fe7a6ed8c9 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 25 Sep 2024 03:30:47 +0900 Subject: [PATCH 145/201] =?UTF-8?q?[SBL-146]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EB=90=9C=20=EC=82=AC=EC=9A=A9=EC=9E=90=EB=A7=8C=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?Security=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt index 8cf57a19..ddc76ecc 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt @@ -125,6 +125,7 @@ class SecurityConfig( authorize("/api/v1/surveys/results/**", authenticated) authorize("/api/v1/s3/**", authenticated) authorize("/api/v1/surveys/my-page", authenticated) + authorize("/api/v1/surveys/participants/**", authenticated) // TODO: 추후에 AUTHENTICATED_USER 로 수정 authorize("/api/v1/surveys/workbench/**", hasRole("ADMIN")) authorize("/**", permitAll) From 37be23273ddb54bf000232bbb26ef5c0325bf877 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 25 Sep 2024 20:58:41 +0900 Subject: [PATCH 146/201] =?UTF-8?q?[SBL-146]=20DTO=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EC=9D=84=20=EB=AA=85=ED=99=95=ED=95=98=EA=B2=8C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD(=EB=B3=B5=EC=88=98=EB=A1=9C),=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=EC=97=90=20=EC=A6=89=EC=8B=9C=20=EC=B6=94=EC=B2=A8=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/doc/SurveyParticipantApiDoc.kt | 4 +- ...nse.kt => ParticipantsInfoListResponse.kt} | 41 +++++++++++-------- .../service/SurveyParticipantService.kt | 6 +-- 3 files changed, 28 insertions(+), 23 deletions(-) rename src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/{ParticipantInfoListResponse.kt => ParticipantsInfoListResponse.kt} (73%) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyParticipantApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyParticipantApiDoc.kt index c3a472e7..9a56ae57 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyParticipantApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyParticipantApiDoc.kt @@ -1,7 +1,7 @@ package com.sbl.sulmun2yong.survey.controller.doc import com.sbl.sulmun2yong.global.annotation.LoginUser -import com.sbl.sulmun2yong.survey.dto.response.ParticipantInfoListResponse +import com.sbl.sulmun2yong.survey.dto.response.ParticipantsInfoListResponse import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity @@ -16,5 +16,5 @@ interface SurveyParticipantApiDoc { fun getSurveyParticipants( @PathVariable("survey-id") surveyId: UUID, @LoginUser id: UUID, - ): ResponseEntity + ): ResponseEntity } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/ParticipantInfoListResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/ParticipantsInfoListResponse.kt similarity index 73% rename from src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/ParticipantInfoListResponse.kt rename to src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/ParticipantsInfoListResponse.kt index da952de9..00d46862 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/ParticipantInfoListResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/ParticipantsInfoListResponse.kt @@ -7,8 +7,9 @@ import com.sbl.sulmun2yong.survey.domain.Participant import java.util.Date import java.util.UUID -data class ParticipantInfoListResponse( +data class ParticipantsInfoListResponse( val participants: List, + val isImmediateDraw: Boolean, ) { data class ParticipantInfoResponse( val participantId: UUID, @@ -58,30 +59,34 @@ data class ParticipantInfoListResponse( fun of( participants: List, drawingHistories: DrawingHistoryGroup?, - ): ParticipantInfoListResponse { + ): ParticipantsInfoListResponse { if (drawingHistories == null) { - return ParticipantInfoListResponse( + return ParticipantsInfoListResponse( + participants = + participants.map { + ParticipantInfoResponse( + participantId = it.id, + participatedAt = it.createdAt, + drawInfo = null, + ) + }, + isImmediateDraw = false, + ) + } + + val drawingHistoryMap = drawingHistories.histories.associateBy { it.participantId } + + return ParticipantsInfoListResponse( + participants = participants.map { + val drawingHistory = drawingHistoryMap[it.id] ParticipantInfoResponse( participantId = it.id, participatedAt = it.createdAt, - drawInfo = null, + drawInfo = DrawInfoResponse.from(drawingHistory), ) }, - ) - } - - val drawingHistoryMap = drawingHistories.histories.associateBy { it.participantId } - - return ParticipantInfoListResponse( - participants.map { - val drawingHistory = drawingHistoryMap[it.id] - ParticipantInfoResponse( - participantId = it.id, - participatedAt = it.createdAt, - drawInfo = DrawInfoResponse.from(drawingHistory), - ) - }, + isImmediateDraw = true, ) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyParticipantService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyParticipantService.kt index 24c9324f..3afac4eb 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyParticipantService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyParticipantService.kt @@ -3,7 +3,7 @@ package com.sbl.sulmun2yong.survey.service import com.sbl.sulmun2yong.drawing.adapter.DrawingHistoryAdapter import com.sbl.sulmun2yong.survey.adapter.ParticipantAdapter import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter -import com.sbl.sulmun2yong.survey.dto.response.ParticipantInfoListResponse +import com.sbl.sulmun2yong.survey.dto.response.ParticipantsInfoListResponse import org.springframework.stereotype.Service import java.util.UUID @@ -16,10 +16,10 @@ class SurveyParticipantService( fun getSurveyParticipants( surveyId: UUID, makerId: UUID, - ): ParticipantInfoListResponse { + ): ParticipantsInfoListResponse { val survey = surveyAdapter.getByIdAndMakerId(surveyId, makerId) val participants = participantAdapter.findBySurveyId(surveyId) val drawingHistories = if (survey.isImmediateDraw()) drawingHistoryAdapter.getBySurveyId(surveyId, false) else null - return ParticipantInfoListResponse.of(participants, drawingHistories) + return ParticipantsInfoListResponse.of(participants, drawingHistories) } } From da81af907d51f02e706b55796c4a5e0a10f47ae5 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 25 Sep 2024 21:48:50 +0900 Subject: [PATCH 147/201] =?UTF-8?q?[SBL-146]=20=EC=B0=B8=EA=B0=80=EC=9E=90?= =?UTF-8?q?=20=EB=AA=A9=EB=A1=9D=20API=20=EC=9D=91=EB=8B=B5=EC=97=90=20?= =?UTF-8?q?=EB=AA=A9=ED=91=9C=20=EC=B0=B8=EC=97=AC=EC=9E=90=20=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/dto/response/ParticipantsInfoListResponse.kt | 7 ++++--- .../sulmun2yong/survey/service/SurveyParticipantService.kt | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/ParticipantsInfoListResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/ParticipantsInfoListResponse.kt index 00d46862..7d5e09c4 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/ParticipantsInfoListResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/ParticipantsInfoListResponse.kt @@ -9,7 +9,7 @@ import java.util.UUID data class ParticipantsInfoListResponse( val participants: List, - val isImmediateDraw: Boolean, + val targetParticipant: Int?, ) { data class ParticipantInfoResponse( val participantId: UUID, @@ -59,6 +59,7 @@ data class ParticipantsInfoListResponse( fun of( participants: List, drawingHistories: DrawingHistoryGroup?, + targetParticipant: Int?, ): ParticipantsInfoListResponse { if (drawingHistories == null) { return ParticipantsInfoListResponse( @@ -70,7 +71,7 @@ data class ParticipantsInfoListResponse( drawInfo = null, ) }, - isImmediateDraw = false, + targetParticipant = targetParticipant, ) } @@ -86,7 +87,7 @@ data class ParticipantsInfoListResponse( drawInfo = DrawInfoResponse.from(drawingHistory), ) }, - isImmediateDraw = true, + targetParticipant = targetParticipant, ) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyParticipantService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyParticipantService.kt index 3afac4eb..70f198df 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyParticipantService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyParticipantService.kt @@ -20,6 +20,6 @@ class SurveyParticipantService( val survey = surveyAdapter.getByIdAndMakerId(surveyId, makerId) val participants = participantAdapter.findBySurveyId(surveyId) val drawingHistories = if (survey.isImmediateDraw()) drawingHistoryAdapter.getBySurveyId(surveyId, false) else null - return ParticipantsInfoListResponse.of(participants, drawingHistories) + return ParticipantsInfoListResponse.of(participants, drawingHistories, survey.rewardSetting.targetParticipantCount) } } From df949e5373ad6848d51d224fc62a72345f9802fe Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Tue, 24 Sep 2024 15:32:02 +0900 Subject: [PATCH 148/201] =?UTF-8?q?[SBL-150]=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/util/RandomNicknameGenerator.kt | 248 +++++++++--------- .../sulmun2yong/global/util/ResetSession.kt | 34 ++- .../global/util/SessionRegistryCleaner.kt | 22 +- 3 files changed, 149 insertions(+), 155 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/RandomNicknameGenerator.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/RandomNicknameGenerator.kt index 6182a150..17c41da1 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/util/RandomNicknameGenerator.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/RandomNicknameGenerator.kt @@ -2,133 +2,131 @@ package com.sbl.sulmun2yong.global.util import kotlin.random.Random -class RandomNicknameGenerator { - companion object { - fun generate(): String { - val positiveAdjective = getRandomPositiveAdjective() - val animalName = getRandomAnimalName() - return "$positiveAdjective $animalName" - } +object RandomNicknameGenerator { + fun generate(): String { + val positiveAdjective = getRandomPositiveAdjective() + val animalName = getRandomAnimalName() + return "$positiveAdjective $animalName" + } - private fun getRandomPositiveAdjective(): String { - val randomIndex = Random.nextInt(positiveAdjectives.size) - return positiveAdjectives[randomIndex] - } + private fun getRandomPositiveAdjective(): String { + val randomIndex = Random.nextInt(positiveAdjectives.size) + return positiveAdjectives[randomIndex] + } - private fun getRandomAnimalName(): String { - val randomIndex = Random.nextInt(animalNames.size) - return animalNames[randomIndex] - } + private fun getRandomAnimalName(): String { + val randomIndex = Random.nextInt(animalNames.size) + return animalNames[randomIndex] + } - private val positiveAdjectives = - listOf( - "멋진", - "아름다운", - "사랑스러운", - "기분 좋은", - "활기찬", - "행복한", - "용감한", - "현명한", - "똑똑한", - "친절한", - "따뜻한", - "차분한", - "건강한", - "잘생긴", - "매력적인", - "유쾌한", - "긍정적인", - "발랄한", - "자유로운", - "훌륭한", - "빛나는", - "깨끗한", - "상쾌한", - "풍부한", - "고운", - "순수한", - "평온한", - "친절한", - "재치 있는", - "유머러스한", - "사려 깊은", - "활발한", - "열정적인", - "끈기 있는", - "적극적인", - "신나는", - "흥미로운", - "놀라운", - "기대되는", - "즐거운", - "기쁜", - "자랑스러운", - "감동적인", - "성실한", - "충실한", - "헌신적인", - "열정있는", - ) + private val positiveAdjectives = + listOf( + "멋진", + "아름다운", + "사랑스러운", + "기분 좋은", + "활기찬", + "행복한", + "용감한", + "현명한", + "똑똑한", + "친절한", + "따뜻한", + "차분한", + "건강한", + "잘생긴", + "매력적인", + "유쾌한", + "긍정적인", + "발랄한", + "자유로운", + "훌륭한", + "빛나는", + "깨끗한", + "상쾌한", + "풍부한", + "고운", + "순수한", + "평온한", + "친절한", + "재치 있는", + "유머러스한", + "사려 깊은", + "활발한", + "열정적인", + "끈기 있는", + "적극적인", + "신나는", + "흥미로운", + "놀라운", + "기대되는", + "즐거운", + "기쁜", + "자랑스러운", + "감동적인", + "성실한", + "충실한", + "헌신적인", + "열정있는", + ) - private val animalNames = - listOf( - "고양이", - "강아지", - "코끼리", - "기린", - "사자", - "호랑이", - "곰", - "늑대", - "여우", - "토끼", - "사슴", - "원숭이", - "판다", - "코알라", - "캥거루", - "말", - "소", - "양", - "염소", - "닭", - "오리", - "거위", - "공작", - "앵무새", - "독수리", - "매", - "올빼미", - "참새", - "비둘기", - "까마귀", - "까치", - "두루미", - "황새", - "치타", - "하마", - "악어", - "두더지", - "수달", - "다람쥐", - "너구리", - "사막여우", - "라쿤", - "뱀", - "도마뱀", - "거북이", - "펭귄", - "돌고래", - "용", - "표범", - "고래", - "부엉이", - "타조", - "코뿔소", - "물개", - "고슴도치", - "카멜레온", - ) - } + private val animalNames = + listOf( + "고양이", + "강아지", + "코끼리", + "기린", + "사자", + "호랑이", + "곰", + "늑대", + "여우", + "토끼", + "사슴", + "원숭이", + "판다", + "코알라", + "캥거루", + "말", + "소", + "양", + "염소", + "닭", + "오리", + "거위", + "공작", + "앵무새", + "독수리", + "매", + "올빼미", + "참새", + "비둘기", + "까마귀", + "까치", + "두루미", + "황새", + "치타", + "하마", + "악어", + "두더지", + "수달", + "다람쥐", + "너구리", + "사막여우", + "라쿤", + "뱀", + "도마뱀", + "거북이", + "펭귄", + "돌고래", + "용", + "표범", + "고래", + "부엉이", + "타조", + "코뿔소", + "물개", + "고슴도치", + "카멜레온", + ) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/ResetSession.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/ResetSession.kt index 064dd2e7..f21d49db 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/util/ResetSession.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/ResetSession.kt @@ -4,25 +4,23 @@ import jakarta.servlet.http.Cookie import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse -class ResetSession { - companion object { - fun reset( - request: HttpServletRequest, - response: HttpServletResponse, - ) { - // 세션 무효화 - request.session.invalidate() +object ResetSession { + fun reset( + request: HttpServletRequest, + response: HttpServletResponse, + ) { + // 세션 무효화 + request.session.invalidate() - // 쿠키 삭제 - val expiredJsessionIdCookie = Cookie("JSESSIONID", null) - expiredJsessionIdCookie.path = "/" - expiredJsessionIdCookie.maxAge = 0 - response.addCookie(expiredJsessionIdCookie) + // 쿠키 삭제 + val expiredJsessionIdCookie = Cookie("JSESSIONID", null) + expiredJsessionIdCookie.path = "/" + expiredJsessionIdCookie.maxAge = 0 + response.addCookie(expiredJsessionIdCookie) - val expiredUserProfileCookie = Cookie("user-profile", null) - expiredUserProfileCookie.path = "/" - expiredUserProfileCookie.maxAge = 0 - response.addCookie(expiredUserProfileCookie) - } + val expiredUserProfileCookie = Cookie("user-profile", null) + expiredUserProfileCookie.path = "/" + expiredUserProfileCookie.maxAge = 0 + response.addCookie(expiredUserProfileCookie) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/SessionRegistryCleaner.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/SessionRegistryCleaner.kt index a2f5860f..dd30c789 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/util/SessionRegistryCleaner.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/SessionRegistryCleaner.kt @@ -3,17 +3,15 @@ package com.sbl.sulmun2yong.global.util import org.springframework.security.core.Authentication import org.springframework.security.core.session.SessionRegistry -class SessionRegistryCleaner { - companion object { - fun removeSessionByAuthentication( - sessionRegistry: SessionRegistry, - authentication: Authentication, - ) { - sessionRegistry - .getAllSessions(authentication.principal, false) - .forEach { sessionInformation -> - sessionRegistry.removeSessionInformation(sessionInformation.sessionId) - } - } +object SessionRegistryCleaner { + fun removeSessionByAuthentication( + sessionRegistry: SessionRegistry, + authentication: Authentication, + ) { + sessionRegistry + .getAllSessions(authentication.principal, false) + .forEach { sessionInformation -> + sessionRegistry.removeSessionInformation(sessionInformation.sessionId) + } } } From b1545ca3156f1dc5171815c9bb7231b617844706 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 26 Sep 2024 01:14:42 +0900 Subject: [PATCH 149/201] =?UTF-8?q?[SBL-150]=20=EC=8B=9C=ED=81=90=EB=A6=AC?= =?UTF-8?q?=ED=8B=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/global/config/SecurityConfig.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt index ddc76ecc..439fe5d6 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt @@ -122,10 +122,10 @@ class SecurityConfig( authorizeHttpRequests { authorize("/api/v1/admin/**", hasRole("ADMIN")) authorize("/api/v1/user/**", authenticated) + authorize("/api/v1/surveys/my-page", authenticated) authorize("/api/v1/surveys/results/**", authenticated) authorize("/api/v1/s3/**", authenticated) - authorize("/api/v1/surveys/my-page", authenticated) - authorize("/api/v1/surveys/participants/**", authenticated) + authorize("/api/v1/ai/*", authenticated) // TODO: 추후에 AUTHENTICATED_USER 로 수정 authorize("/api/v1/surveys/workbench/**", hasRole("ADMIN")) authorize("/**", permitAll) From 7fd4aedff23d987ba3a3805fabdf34aad4efea5f Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 26 Sep 2024 01:33:56 +0900 Subject: [PATCH 150/201] =?UTF-8?q?[SBL-150]=20**=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt index 439fe5d6..97ad5a28 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt @@ -125,7 +125,7 @@ class SecurityConfig( authorize("/api/v1/surveys/my-page", authenticated) authorize("/api/v1/surveys/results/**", authenticated) authorize("/api/v1/s3/**", authenticated) - authorize("/api/v1/ai/*", authenticated) + authorize("/api/v1/ai/**", authenticated) // TODO: 추후에 AUTHENTICATED_USER 로 수정 authorize("/api/v1/surveys/workbench/**", hasRole("ADMIN")) authorize("/**", permitAll) From 66119c62cb297711cde76eb856f597f3f11bbf51 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 26 Sep 2024 13:34:08 +0900 Subject: [PATCH 151/201] =?UTF-8?q?[SBL-150]=20AI=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 46f31d26..b182d725 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -37,7 +37,7 @@ backend: base-url: http://localhost:8080 ai-server: - base-url: http://localhost:8000 + base-url: http://ai.sulmoon.io cloudfront: base-url: https://files.sulmoon.io From 165bde6903caa16d04277a8993c7421e12b21bdd Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 26 Sep 2024 14:03:41 +0900 Subject: [PATCH 152/201] =?UTF-8?q?HOTFIX:=20=EC=A6=89=EC=8B=9C=20?= =?UTF-8?q?=EC=B6=94=EC=B2=A8=20=EC=84=A4=EB=AC=B8=20=EC=8B=9C=EC=9E=91=20?= =?UTF-8?q?=EC=8B=9C=20=EC=B6=94=EC=B2=A8=20=EB=B3=B4=EB=93=9C=EA=B0=80=20?= =?UTF-8?q?=EB=A7=8C=EB=93=A4=EC=96=B4=EC=A7=80=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/service/SurveyManagementService.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index bbd363f4..75301e34 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -65,13 +65,14 @@ class SurveyManagementService( val survey = surveyAdapter.getSurvey(surveyId) // 현재 유저와 설문 제작자가 다를 경우 예외 발생 if (survey.makerId != makerId) throw InvalidSurveyAccessException() - surveyAdapter.save(survey.start()) - if (survey.rewardSetting is ImmediateDrawSetting) { + val startedSurvey = survey.start() + surveyAdapter.save(startedSurvey) + if (startedSurvey.rewardSetting is ImmediateDrawSetting) { val drawingBoard = DrawingBoard.create( - surveyId = survey.id, - boardSize = survey.rewardSetting.targetParticipantCount, - rewards = survey.rewardSetting.rewards, + surveyId = startedSurvey.id, + boardSize = startedSurvey.rewardSetting.targetParticipantCount, + rewards = startedSurvey.rewardSetting.rewards, ) drawingBoardAdapter.save(drawingBoard) } From 46b74860b686c502f9429e73e230d2207a777ba2 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 26 Sep 2024 14:52:38 +0900 Subject: [PATCH 153/201] =?UTF-8?q?[SBL-160]=20cicd=EC=97=90=20ai=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EC=A3=BC=EC=86=8C=EB=A5=BC=20=EB=84=A3=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/dev_deploy.yml | 3 ++- src/main/resources/application.yml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dev_deploy.yml b/.github/workflows/dev_deploy.yml index f69f1fd0..d832f35e 100644 --- a/.github/workflows/dev_deploy.yml +++ b/.github/workflows/dev_deploy.yml @@ -79,6 +79,7 @@ jobs: # Spring Boot 관련 FRONTEND_BASE_URL: http://localhost:3000 BACKEND_BASE_URL: https://dev-api.sulmoon.io + AI_SERVER_BASE_URL: http://ai.sulmoon.io:8000 # New Relic 관련 NEW_RELIC_APP_NAME: sulmun2yong-development @@ -99,5 +100,5 @@ jobs: "docker pull ${{ env.DOCKER_ID }}/${{ env.DOCKER_IMAGE_NAME }}", "docker stop ${{ env.CONTAINER_NAME }} || true", "docker rm ${{ env.CONTAINER_NAME }} || true", - "docker run -d --name ${{ env.CONTAINER_NAME }} -p 8080:8080 -e SPRING_DATA_MONGODB_URI=${{ env.MONGODB_URL }} -e SPRING_DATA_MONGODB_DATABASE=${{ env.MONGODB_DATABASE }} -e FRONTEND_BASE-URL=${{ env.FRONTEND_BASE_URL }} -e BACKEND_BASE-URL=${{ env.BACKEND_BASE_URL }} -e NEW_RELIC_APP_NAME=${{ env.NEW_RELIC_APP_NAME }} ${{ env.DOCKER_ID }}/${{ env.DOCKER_IMAGE_NAME }}", + "docker run -d --name ${{ env.CONTAINER_NAME }} -p 8080:8080 -e SPRING_DATA_MONGODB_URI=${{ env.MONGODB_URL }} -e SPRING_DATA_MONGODB_DATABASE=${{ env.MONGODB_DATABASE }} -e FRONTEND_BASE-URL=${{ env.FRONTEND_BASE_URL }} -e BACKEND_BASE-URL=${{ env.BACKEND_BASE_URL }} -e AI-SERVER_BASE-URL=${{ env.AI_SERVER_BASE_URL }} -e NEW_RELIC_APP_NAME=${{ env.NEW_RELIC_APP_NAME }} ${{ env.DOCKER_ID }}/${{ env.DOCKER_IMAGE_NAME }}", "docker image prune -af"]}' diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b182d725..46f31d26 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -37,7 +37,7 @@ backend: base-url: http://localhost:8080 ai-server: - base-url: http://ai.sulmoon.io + base-url: http://localhost:8000 cloudfront: base-url: https://files.sulmoon.io From 3f48f98bec29e0f21e3660fbadd2d105702c7522 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 26 Sep 2024 15:35:52 +0900 Subject: [PATCH 154/201] =?UTF-8?q?[SBL-161]=20dto=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/ai/adapter/GenerateAdapter.kt | 34 +++++++++++++++++-- .../ai/controller/GenerateController.kt | 18 +++++++--- .../ai/controller/doc/GenerateAPIDoc.kt | 17 +++++++--- .../ai/dto/QuestionGeneratedByAI.kt | 6 ++-- ... => SurveyGenerationWithFileUrlRequest.kt} | 3 +- ...SurveyGenerationWithTextDocumentRequest.kt | 8 +++++ .../sulmun2yong/ai/service/GenerateService.kt | 12 +++++-- 7 files changed, 79 insertions(+), 19 deletions(-) rename src/main/kotlin/com/sbl/sulmun2yong/ai/dto/request/{SurveyGenerateRequest.kt => SurveyGenerationWithFileUrlRequest.kt} (61%) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/dto/request/SurveyGenerationWithTextDocumentRequest.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt index 4807bc76..0d18f28f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt @@ -15,25 +15,53 @@ class GenerateAdapter( private val aiServerBaseUrl: String, private val restTemplate: RestTemplate, ) { - private val url = "$aiServerBaseUrl/generate/survey" - fun requestSurveyGenerationWithFileUrl( job: String, groupName: String, fileUrl: String, + userPrompt: String, ): SurveyMakeInfoResponse { + val requestUrl = "$aiServerBaseUrl/generate/survey/file-url" + val requestBody = mapOf( "job" to job, "group_name" to groupName, "file_url" to fileUrl, + "user_prompt" to userPrompt, ) + return requestToGenerateSurvey(requestUrl, requestBody) + } + + fun requestSurveyGenerationWithTextDocument( + job: String, + groupName: String, + textDocument: String, + userPrompt: String, + ): SurveyMakeInfoResponse { + val requestUrl = "$aiServerBaseUrl/generate/survey/text-document" + + val requestBody = + mapOf( + "job" to job, + "group_name" to groupName, + "text_document" to textDocument, + "user_prompt" to userPrompt, + ) + + return requestToGenerateSurvey(requestUrl, requestBody) + } + + private fun requestToGenerateSurvey( + requestUrl: String, + requestBody: Map, + ): SurveyMakeInfoResponse { val surveyGeneratedByAI = try { restTemplate .postForEntity( - url, + requestUrl, requestBody, SurveyGeneratedByAI::class.java, ).body ?: throw SurveyGenerationByAIFailedException() diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt index 9b854202..53a6e787 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt @@ -1,7 +1,8 @@ package com.sbl.sulmun2yong.ai.controller import com.sbl.sulmun2yong.ai.controller.doc.GenerateAPIDoc -import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerateRequest +import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithFileUrlRequest +import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithTextDocumentRequest import com.sbl.sulmun2yong.ai.service.GenerateService import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping @@ -14,8 +15,15 @@ import org.springframework.web.bind.annotation.RestController class GenerateController( private val generateService: GenerateService, ) : GenerateAPIDoc { - @PostMapping("/survey") - override fun generateSurvey( - @RequestBody request: SurveyGenerateRequest, - ) = ResponseEntity.ok(generateService.generateSurvey(request.job, request.groupName, request.fileUrl)) + @PostMapping("/survey/file-url") + override fun generateSurveyWithFileUrl( + @RequestBody request: SurveyGenerationWithFileUrlRequest, + ) = ResponseEntity.ok(generateService.generateSurveyWithFileUrl(request.job, request.groupName, request.fileUrl, request.userPrompt)) + + @PostMapping("/survey/text-document") + override fun generateSurveyWithTextDocument( + @RequestBody request: SurveyGenerationWithTextDocumentRequest, + ) = ResponseEntity.ok( + generateService.generateSurveyWithTextDocument(request.job, request.groupName, request.textDocument, request.userPrompt), + ) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt index 5c19fa11..dadca8b9 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt @@ -1,6 +1,7 @@ package com.sbl.sulmun2yong.ai.controller.doc -import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerateRequest +import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithFileUrlRequest +import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithTextDocumentRequest import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag @@ -10,9 +11,15 @@ import org.springframework.web.bind.annotation.RequestBody @Tag(name = "AI", description = "AI 기능 관련 API") interface GenerateAPIDoc { - @Operation(summary = "AI 설문 생성") - @PostMapping("/survey") - fun generateSurvey( - @RequestBody request: SurveyGenerateRequest, + @Operation(summary = "파일을 통한 AI 설문 생성") + @PostMapping("/survey/file-url") + fun generateSurveyWithFileUrl( + @RequestBody request: SurveyGenerationWithFileUrlRequest, + ): ResponseEntity + + @Operation(summary = "텍스트 입력을 통한 AI 설문 생성") + @PostMapping("/survey/text-document") + fun generateSurveyWithTextDocument( + @RequestBody request: SurveyGenerationWithTextDocumentRequest, ): ResponseEntity } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt index 346540b5..287feb81 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/QuestionGeneratedByAI.kt @@ -12,7 +12,7 @@ class QuestionGeneratedByAI( private val questionType: QuestionType, private val title: String, private val isRequired: Boolean, - private val choices: List, + private val choices: List?, private val isAllowOther: Boolean, ) { fun toDomain() = @@ -24,7 +24,7 @@ class QuestionGeneratedByAI( description = DEFAULT_DESCRIPTION, isRequired = this.isRequired, // TODO: Document를 Domain클래스로 변환 중에 생긴 에러는 여기서 직접 반환하도록 수정 - choices = Choices(this.choices.map { Choice.Standard(it) }, isAllowOther), + choices = Choices(this.choices?.map { Choice.Standard(it) } ?: listOf(), isAllowOther), ) QuestionType.MULTIPLE_CHOICE -> StandardMultipleChoiceQuestion( @@ -33,7 +33,7 @@ class QuestionGeneratedByAI( description = DEFAULT_DESCRIPTION, isRequired = this.isRequired, // TODO: Document를 Domain클래스로 변환 중에 생긴 에러는 여기서 직접 반환하도록 수정 - choices = Choices(this.choices.map { Choice.Standard(it) }, isAllowOther), + choices = Choices(this.choices?.map { Choice.Standard(it) } ?: listOf(), isAllowOther), ) QuestionType.TEXT_RESPONSE -> StandardTextQuestion( diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/request/SurveyGenerateRequest.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/request/SurveyGenerationWithFileUrlRequest.kt similarity index 61% rename from src/main/kotlin/com/sbl/sulmun2yong/ai/dto/request/SurveyGenerateRequest.kt rename to src/main/kotlin/com/sbl/sulmun2yong/ai/dto/request/SurveyGenerationWithFileUrlRequest.kt index 887e2eaf..e3b0422b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/request/SurveyGenerateRequest.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/request/SurveyGenerationWithFileUrlRequest.kt @@ -1,7 +1,8 @@ package com.sbl.sulmun2yong.ai.dto.request -data class SurveyGenerateRequest( +data class SurveyGenerationWithFileUrlRequest( val job: String, val groupName: String, val fileUrl: String, + val userPrompt: String, ) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/request/SurveyGenerationWithTextDocumentRequest.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/request/SurveyGenerationWithTextDocumentRequest.kt new file mode 100644 index 00000000..57bb2ef4 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/request/SurveyGenerationWithTextDocumentRequest.kt @@ -0,0 +1,8 @@ +package com.sbl.sulmun2yong.ai.dto.request + +data class SurveyGenerationWithTextDocumentRequest( + val job: String, + val groupName: String, + val textDocument: String, + val userPrompt: String, +) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index a256c30a..b981023e 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -10,14 +10,22 @@ class GenerateService( private val fileUrlValidator: FileUrlValidator, private val generateAdapter: GenerateAdapter, ) { - fun generateSurvey( + fun generateSurveyWithFileUrl( job: String, groupName: String, fileUrl: String, + userPrompt: String, ): SurveyMakeInfoResponse { val allowedExtensions = mutableListOf(".txt", ".pdf") fileUrlValidator.validateFileUrlOf(fileUrl, allowedExtensions) - return generateAdapter.requestSurveyGenerationWithFileUrl(job, groupName, fileUrl) + return generateAdapter.requestSurveyGenerationWithFileUrl(job, groupName, fileUrl, userPrompt) } + + fun generateSurveyWithTextDocument( + job: String, + groupName: String, + textDocument: String, + userPrompt: String, + ): SurveyMakeInfoResponse = generateAdapter.requestSurveyGenerationWithTextDocument(job, groupName, textDocument, userPrompt) } From e09672e7549f8329e1af4cb3b77b683bdac25ef9 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 26 Sep 2024 15:39:00 +0900 Subject: [PATCH 155/201] =?UTF-8?q?[SBL-161]=20=ED=85=8D=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EA=B0=80=20=EB=84=88=EB=AC=B4=20=EA=B9=81=EB=8B=88=EB=8B=A4=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index d65513ad..1a24c3c5 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -68,7 +68,7 @@ enum class ErrorCode( // Python Server (PY) SURVEY_GENERATION_BY_AI_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "PY0001", "AI를 활용한 설문 생성에 실패했습니다."), - TEXT_TOO_LONG(HttpStatus.BAD_REQUEST, "PY0002", "텍스트 길이는 1만 2천자 이하여야 합니다"), + TEXT_TOO_LONG(HttpStatus.BAD_REQUEST, "PY0002", "텍스트의 길이가 너무 깁니다"), FILE_EXTENSION_NOT_SUPPORTED(HttpStatus.BAD_REQUEST, "PY0003", "지원하지 않는 파일 확장자입니다."), FILE_NOT_FOUND(HttpStatus.BAD_REQUEST, "PY0004", "파일을 찾을 수 없습니다."), } From 2e80df6acc1c29f242b095463ea48d88c8ea4975 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 26 Sep 2024 15:14:39 +0900 Subject: [PATCH 156/201] =?UTF-8?q?[SBL-157]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=A2=85=EB=A3=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/controller/SurveyManagementController.kt | 6 ++++++ .../survey/controller/doc/SurveyManagementApiDoc.kt | 7 +++++++ .../sulmun2yong/survey/service/SurveyManagementService.kt | 8 ++++++++ 3 files changed, 21 insertions(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyManagementController.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyManagementController.kt index 7c212ca4..dca51d20 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyManagementController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyManagementController.kt @@ -55,4 +55,10 @@ class SurveyManagementController( @PathVariable("surveyId") surveyId: UUID, @LoginUser id: UUID, ) = ResponseEntity.ok(surveyManagementService.editSurvey(surveyId = surveyId, makerId = id)) + + @PatchMapping("/finish/{surveyId}") + override fun finishSurvey( + @PathVariable("surveyId") surveyId: UUID, + @LoginUser id: UUID, + ) = ResponseEntity.ok(surveyManagementService.finishSurvey(surveyId = surveyId, makerId = id)) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyManagementApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyManagementApiDoc.kt index 5ebe569f..68500f06 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyManagementApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyManagementApiDoc.kt @@ -51,4 +51,11 @@ interface SurveyManagementApiDoc { @PathVariable("surveyId") surveyId: UUID, @LoginUser id: UUID, ): ResponseEntity + + @Operation(summary = "설문 종료 API") + @PatchMapping("/finish/{surveyId}") + fun finishSurvey( + @PathVariable("surveyId") surveyId: UUID, + @LoginUser id: UUID, + ): ResponseEntity } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index 75301e34..43c32f09 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -87,4 +87,12 @@ class SurveyManagementService( if (survey.makerId != makerId) throw InvalidSurveyAccessException() surveyAdapter.save(survey.edit()) } + + fun finishSurvey( + surveyId: UUID, + makerId: UUID, + ) { + val survey = surveyAdapter.getByIdAndMakerId(surveyId, makerId) + surveyAdapter.save(survey.finish()) + } } From bf68bbd55f5deff5a354c5f3828dde7cacf52055 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 26 Sep 2024 15:17:40 +0900 Subject: [PATCH 157/201] =?UTF-8?q?[SBL-157]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5,=20=EC=A0=9C=EC=9E=91=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=20=EB=B3=B4=EA=B8=B0,=20=EC=8B=9C=EC=9E=91,=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20api=EC=97=90=EC=84=9C=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=A0=9C=EC=9E=91=EC=9E=90=EB=A5=BC=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/service/SurveyManagementService.kt | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index 43c32f09..2665a87b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -8,7 +8,6 @@ import com.sbl.sulmun2yong.survey.domain.reward.ImmediateDrawSetting import com.sbl.sulmun2yong.survey.dto.request.SurveySaveRequest import com.sbl.sulmun2yong.survey.dto.response.SurveyCreateResponse import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse -import com.sbl.sulmun2yong.survey.exception.InvalidSurveyAccessException import org.springframework.stereotype.Service import java.util.UUID @@ -29,9 +28,7 @@ class SurveyManagementService( surveySaveRequest: SurveySaveRequest, makerId: UUID, ) { - val survey = surveyAdapter.getSurvey(surveyId) - // 현재 유저와 설문 제작자가 다를 경우 예외 발생 - if (survey.makerId != makerId) throw InvalidSurveyAccessException() + val survey = surveyAdapter.getByIdAndMakerId(surveyId, makerId) val newSurvey = with(surveySaveRequest) { survey.updateContent( @@ -51,9 +48,7 @@ class SurveyManagementService( surveyId: UUID, makerId: UUID, ): SurveyMakeInfoResponse { - val survey = surveyAdapter.getSurvey(surveyId) - // 현재 유저와 설문 제작자가 다를 경우 예외 발생 - if (survey.makerId != makerId) throw InvalidSurveyAccessException() + val survey = surveyAdapter.getByIdAndMakerId(surveyId, makerId) return SurveyMakeInfoResponse.of(survey) } @@ -62,9 +57,7 @@ class SurveyManagementService( surveyId: UUID, makerId: UUID, ) { - val survey = surveyAdapter.getSurvey(surveyId) - // 현재 유저와 설문 제작자가 다를 경우 예외 발생 - if (survey.makerId != makerId) throw InvalidSurveyAccessException() + val survey = surveyAdapter.getByIdAndMakerId(surveyId, makerId) val startedSurvey = survey.start() surveyAdapter.save(startedSurvey) if (startedSurvey.rewardSetting is ImmediateDrawSetting) { @@ -82,9 +75,7 @@ class SurveyManagementService( surveyId: UUID, makerId: UUID, ) { - val survey = surveyAdapter.getSurvey(surveyId) - // 현재 유저와 설문 제작자가 다를 경우 예외 발생 - if (survey.makerId != makerId) throw InvalidSurveyAccessException() + val survey = surveyAdapter.getByIdAndMakerId(surveyId, makerId) surveyAdapter.save(survey.edit()) } From ddca5ebffb81cc392d494071b44ccb441f5b39c0 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 26 Sep 2024 15:53:01 +0900 Subject: [PATCH 158/201] =?UTF-8?q?[SBL-157]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=EB=A5=BC=20=ED=95=98=EB=82=98=EB=8F=84=20=EA=B1=B8?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EA=B2=BD=EC=9A=B0=20Participa?= =?UTF-8?q?nt=20Document=EC=97=90=EC=84=9C=20=EC=B0=B8=EA=B0=80=EC=9E=90?= =?UTF-8?q?=20=EC=88=98=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/dto/response/SurveyResultResponse.kt | 3 ++- .../survey/service/SurveyResultService.kt | 14 +++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt index 77ce5ce3..c3452cfe 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt @@ -17,7 +17,8 @@ data class SurveyResultResponse( fun of( surveyResult: SurveyResult, survey: Survey, - ) = SurveyResultResponse(survey.sections.map { SectionResultResponse.of(surveyResult, it) }, surveyResult.getParticipantCount()) + participantCount: Int, + ) = SurveyResultResponse(survey.sections.map { SectionResultResponse.of(surveyResult, it) }, participantCount) } data class SectionResultResponse( diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt index 7a40bab4..8a9443cf 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResultService.kt @@ -1,5 +1,6 @@ package com.sbl.sulmun2yong.survey.service +import com.sbl.sulmun2yong.survey.adapter.ParticipantAdapter import com.sbl.sulmun2yong.survey.adapter.ResponseAdapter import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter import com.sbl.sulmun2yong.survey.dto.request.SurveyResultRequest @@ -11,6 +12,7 @@ import java.util.UUID class SurveyResultService( private val responseAdapter: ResponseAdapter, private val surveyAdapter: SurveyAdapter, + private val participantAdapter: ParticipantAdapter, ) { fun getSurveyResult( surveyId: UUID, @@ -26,6 +28,16 @@ class SurveyResultService( // 요청에 따라 설문 결과 필터링 val resultFilter = surveyResultRequest.toDomain() val filteredSurveyResult = surveyResult.getFilteredResult(resultFilter) - return SurveyResultResponse.of(filteredSurveyResult, survey) + + val participantCount = + if (resultFilter.questionFilters.isEmpty()) { + // 필터를 걸지 않은 경우는 Participant Document에서 참가자 수 조회 + participantAdapter.findBySurveyId(surveyId).size + } else { + // 필터를 건 경우는 필터링된 결과 수로 참가자 수 조회 + surveyResult.getParticipantCount() + } + + return SurveyResultResponse.of(filteredSurveyResult, survey, participantCount) } } From 1602327a405004dd1edcfc1f9a45bd032f9e482b Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 26 Sep 2024 17:16:24 +0900 Subject: [PATCH 159/201] =?UTF-8?q?[SBL-157]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=EA=B0=80=20=EC=A1=B4=EC=9E=AC=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EA=B2=BD=EC=9A=B0=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/SurveyResultResponse.kt | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt index c3452cfe..20fb2ed4 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyResultResponse.kt @@ -34,11 +34,7 @@ data class SurveyResultResponse( SectionResultResponse( sectionId = section.id.value, title = section.title, - questionResults = - section.questions.mapNotNull { question -> - val questionResult = surveyResult.findQuestionResult(question.id) - questionResult?.let { QuestionResultResponse.of(question, it) } - }, + questionResults = section.questions.map { QuestionResultResponse.of(it, surveyResult.findQuestionResult(it.id)) }, ) } } @@ -54,8 +50,19 @@ data class SurveyResultResponse( companion object { fun of( question: Question, - questionResult: QuestionResult, + questionResult: QuestionResult?, ): QuestionResultResponse { + if (questionResult == null) { + return QuestionResultResponse( + questionId = question.id, + title = question.title, + type = question.questionType, + participantCount = 0, + responses = listOf(), + responseContents = question.choices?.standardChoices?.map { it.content } ?: listOf(), + ) + } + val allContents = if (question is ChoiceQuestion) { val tempContents = question.choices.standardChoices.map { it.content } From e2203ff721fcde4bcdcc7e21b8aeb0c849dae17d Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Sun, 29 Sep 2024 03:09:55 +0900 Subject: [PATCH 160/201] =?UTF-8?q?HOTFIX:=20patch=20method=EB=A5=BC=20COR?= =?UTF-8?q?S=20=EC=84=A4=EC=A0=95=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/global/config/WebMvcConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/WebMvcConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/WebMvcConfig.kt index aec49eb3..f9f8f6ab 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/WebMvcConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/WebMvcConfig.kt @@ -19,7 +19,7 @@ class WebMvcConfig( registry .addMapping("/**") .allowedOrigins(frontendBaseUrl) // 프론트엔드 도메인 - .allowedMethods("GET", "POST", "PUT", "DELETE") + .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH") .allowCredentials(true) .allowedHeaders("*") } From e775e0374b005b02cfbc8035f8d8a8df42c6b099 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 30 Sep 2024 03:22:40 +0900 Subject: [PATCH 161/201] =?UTF-8?q?[SBL-165]=20Mongock=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index dd50cff7..1d977d86 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -48,6 +48,10 @@ dependencies { // mongoDB implementation("org.springframework.boot:spring-boot-starter-data-mongodb") + // mongock + implementation("io.mongock:mongock-springboot:5.4.4") + implementation("io.mongock:mongodb-springdata-v4-driver:5.4.4") + // validation implementation("org.springframework.boot:spring-boot-starter-validation") From c3d1633aacd4ee6c2236f0b19428a5dc4bf7cc47 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 30 Sep 2024 03:23:20 +0900 Subject: [PATCH 162/201] =?UTF-8?q?[SBL-165]=20Mongock=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20Config=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80,?= =?UTF-8?q?=20application.yml=EC=97=90=20=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/global/config/MongockConfig.kt | 8 ++++++++ src/main/resources/application.yml | 4 ++++ 2 files changed, 12 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/config/MongockConfig.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/MongockConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/MongockConfig.kt new file mode 100644 index 00000000..84094222 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/MongockConfig.kt @@ -0,0 +1,8 @@ +package com.sbl.sulmun2yong.global.config + +import io.mongock.runner.springboot.EnableMongock +import org.springframework.context.annotation.Configuration + +@Configuration +@EnableMongock +class MongockConfig diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 46f31d26..ade5f5de 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -41,3 +41,7 @@ ai-server: cloudfront: base-url: https://files.sulmoon.io + +mongock: + enabled: true + migration-scan-package: com.sbl.sulmun2yong.global.migration From 6498f56c9a4223ccaa8c7c291ee8fc6fd312b0cd Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 30 Sep 2024 03:32:39 +0900 Subject: [PATCH 163/201] =?UTF-8?q?[SBL-165]=20=EC=B4=88=EA=B8=B0=20?= =?UTF-8?q?=EC=BB=AC=EB=A0=89=EC=85=98=20=EC=83=9D=EC=84=B1=20ChangeUnit?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/migration/InitialMongoDBSetUp.kt | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt new file mode 100644 index 00000000..69bb417e --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt @@ -0,0 +1,47 @@ +package com.sbl.sulmun2yong.global.migration + +import com.sbl.sulmun2yong.global.error.GlobalExceptionHandler +import io.mongock.api.annotations.ChangeUnit +import io.mongock.api.annotations.Execution +import io.mongock.api.annotations.RollbackExecution +import org.slf4j.LoggerFactory +import org.springframework.data.mongodb.core.MongoTemplate + +@ChangeUnit(id = "InitialMongoDBSetUp", order = "001", author = "hunhui") +class InitialMongoDBSetUp { + companion object { + private const val SURVEY_COLLECTION = "surveys" + private const val DRAWING_BOARD_COLLECTION = "drawingBoards" + private const val DRAWING_HISTORY_COLLECTION = "drawingHistories" + private const val PARTICIPANT_COLLECTION = "participants" + private const val RESPONSE_COLLECTION = "responses" + private const val USER_COLLECTION = "users" + val COLLECTIONS = + listOf( + SURVEY_COLLECTION, + DRAWING_BOARD_COLLECTION, + DRAWING_HISTORY_COLLECTION, + PARTICIPANT_COLLECTION, + RESPONSE_COLLECTION, + USER_COLLECTION, + ) + } + + private val log = LoggerFactory.getLogger(GlobalExceptionHandler::class.java) + + @Execution + fun initialSetup(mongoTemplate: MongoTemplate) { + val collectionNames = mongoTemplate.db.listCollectionNames().toList() + for (collection in COLLECTIONS) { + if (!collectionNames.contains(collection)) { + mongoTemplate.db.createCollection(collection) + } + } + log.info("001-InitialMongoDBSetUp 완료") + } + + @RollbackExecution + fun rollback() { + log.warn("001-InitialMongoDBSetUp 롤백") + } +} From ec07688c7a120670a2e0fb9f188fd73d27d6c134 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 30 Sep 2024 03:51:19 +0900 Subject: [PATCH 164/201] =?UTF-8?q?[SBL-165]=20MigrationClass=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=84=A4=EB=AA=85=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt index 69bb417e..2b9cc619 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt @@ -7,6 +7,7 @@ import io.mongock.api.annotations.RollbackExecution import org.slf4j.LoggerFactory import org.springframework.data.mongodb.core.MongoTemplate +/** 최초에 컬렉션들을 생성하는 Migration Class */ @ChangeUnit(id = "InitialMongoDBSetUp", order = "001", author = "hunhui") class InitialMongoDBSetUp { companion object { From 33621785b6e5398b5bb35308fe6fa26da75ca461 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 1 Oct 2024 05:18:51 +0900 Subject: [PATCH 165/201] =?UTF-8?q?HOTFIX:=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20API,=20=EC=84=A4=EB=AC=B8=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20API=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20=EC=84=AC=EB=84=A4=EC=9D=BC=EC=9D=84=20?= =?UTF-8?q?=EC=A3=BC=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt | 2 -- .../sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt | 4 ++-- .../sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index 39970792..5ccde8fd 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -45,8 +45,6 @@ data class Survey( } companion object { - // TODO: 기본 섬네일 URL은 프론트에서 처리하도록 변경 - const val DEFAULT_THUMBNAIL_URL = "https://test-oriddle-bucket.s3.ap-northeast-2.amazonaws.com/surveyImage.webp" const val DEFAULT_TITLE = "제목 없는 설문" const val DEFAULT_DESCRIPTION = "" const val DEFAULT_FINISH_MESSAGE = "설문에 참여해주셔서 감사합니다." diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt index 6fc877fa..74a85af4 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyInfoResponse.kt @@ -15,7 +15,7 @@ data class SurveyInfoResponse( val currentParticipants: Int?, val targetParticipants: Int?, val rewards: List, - val thumbnail: String, + val thumbnail: String?, ) { companion object { fun of( @@ -30,7 +30,7 @@ data class SurveyInfoResponse( currentParticipants = currentParticipants, targetParticipants = survey.rewardSetting.targetParticipantCount, rewards = survey.rewardSetting.rewards.map { it.toResponse() }, - thumbnail = survey.thumbnail ?: Survey.DEFAULT_THUMBNAIL_URL, + thumbnail = survey.thumbnail, ) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt index 380d8f8d..92e053d1 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/response/SurveyListResponse.kt @@ -21,7 +21,7 @@ data class SurveyListResponse( surveys.map { SurveyInfoResponse( surveyId = it.id, - thumbnail = it.thumbnail ?: Survey.DEFAULT_THUMBNAIL_URL, + thumbnail = it.thumbnail, title = it.title, description = it.description, targetParticipants = it.rewardSetting.targetParticipantCount, @@ -36,7 +36,7 @@ data class SurveyListResponse( data class SurveyInfoResponse( val surveyId: UUID, - val thumbnail: String, + val thumbnail: String?, val title: String, val description: String, val targetParticipants: Int?, From d4d1f6bb5ca7caa59cdc3783266abc7a080ed73a Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 1 Oct 2024 05:19:57 +0900 Subject: [PATCH 166/201] =?UTF-8?q?HOTFIX:=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=A4=91=EC=9D=BC=20=EB=95=8C=20=EC=9E=AC?= =?UTF-8?q?=EA=B0=9C=ED=95=98=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EC=B6=94?= =?UTF-8?q?=EC=B2=A8=20=EB=B3=B4=EB=93=9C=EA=B0=80=20=ED=95=9C=20=EB=B2=88?= =?UTF-8?q?=20=EB=8D=94=20=EC=83=9D=EC=84=B1=EB=90=98=EB=8A=94=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/service/SurveyManagementService.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index 2665a87b..70e6e648 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -4,6 +4,7 @@ import com.sbl.sulmun2yong.drawing.adapter.DrawingBoardAdapter import com.sbl.sulmun2yong.drawing.domain.DrawingBoard import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter import com.sbl.sulmun2yong.survey.domain.Survey +import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.domain.reward.ImmediateDrawSetting import com.sbl.sulmun2yong.survey.dto.request.SurveySaveRequest import com.sbl.sulmun2yong.survey.dto.response.SurveyCreateResponse @@ -60,7 +61,8 @@ class SurveyManagementService( val survey = surveyAdapter.getByIdAndMakerId(surveyId, makerId) val startedSurvey = survey.start() surveyAdapter.save(startedSurvey) - if (startedSurvey.rewardSetting is ImmediateDrawSetting) { + // 즉시 추첨이면서 최초 시작 시 추첨 보드 생성 + if (startedSurvey.rewardSetting is ImmediateDrawSetting && survey.status == SurveyStatus.NOT_STARTED) { val drawingBoard = DrawingBoard.create( surveyId = startedSurvey.id, From 5e9c03295fca5e8b108c8fdc8930fa53d3dfc5ad Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 1 Oct 2024 05:43:28 +0900 Subject: [PATCH 167/201] =?UTF-8?q?HOTFIX:=20=EC=B6=94=EC=B2=A8=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=EC=9D=B4=20=ED=95=98=EB=82=98=EB=8F=84=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=EC=83=81=ED=83=9C=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=B6=94=EC=B2=A8=20=EA=B8=B0=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=8B=9C=20=EC=98=88=EC=99=B8=EA=B0=80=20=EB=B0=9C=EC=83=9D?= =?UTF-8?q?=ED=95=98=EB=8D=98=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/drawing/adapter/DrawingHistoryAdapter.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingHistoryAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingHistoryAdapter.kt index c61219d1..058b71a5 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingHistoryAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingHistoryAdapter.kt @@ -3,7 +3,6 @@ package com.sbl.sulmun2yong.drawing.adapter import com.sbl.sulmun2yong.drawing.domain.DrawingHistory import com.sbl.sulmun2yong.drawing.domain.DrawingHistoryGroup import com.sbl.sulmun2yong.drawing.entity.DrawingHistoryDocument -import com.sbl.sulmun2yong.drawing.exception.InvalidDrawingHistoryException import com.sbl.sulmun2yong.drawing.repository.DrawingHistoryRepository import com.sbl.sulmun2yong.global.data.PhoneNumber import org.springframework.stereotype.Component @@ -37,7 +36,11 @@ class DrawingHistoryAdapter( false -> drawingHistoryRepository.findBySurveyId(surveyId) } if (!dto.isPresent) { - throw InvalidDrawingHistoryException() + return DrawingHistoryGroup( + surveyId = dto.get().id, + count = 0, + histories = emptyList(), + ) } return DrawingHistoryGroup( surveyId = dto.get().id, From dd21cda15d2cda6e2e10e0c96b26785a06bd1500 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 1 Oct 2024 16:34:43 +0900 Subject: [PATCH 168/201] =?UTF-8?q?HOTFIX:=20=EC=B6=94=EC=B2=A8=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=EC=9D=B4=20=EC=97=86=EB=8A=94=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=8B=B9=EC=B2=A8=EC=9E=90=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=8B=9C=20=EC=98=A4=EB=A5=98=EA=B0=80=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=ED=95=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/drawing/adapter/DrawingHistoryAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingHistoryAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingHistoryAdapter.kt index 058b71a5..ac1e5f19 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingHistoryAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/drawing/adapter/DrawingHistoryAdapter.kt @@ -37,7 +37,7 @@ class DrawingHistoryAdapter( } if (!dto.isPresent) { return DrawingHistoryGroup( - surveyId = dto.get().id, + surveyId = surveyId, count = 0, histories = emptyList(), ) From c48d32ce4324c9de612ead476bc7f39970c4d064 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Tue, 1 Oct 2024 16:35:17 +0900 Subject: [PATCH 169/201] =?UTF-8?q?HOTFIX:=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=EC=84=A4=EB=AC=B8=20=EC=A0=9C=EB=AA=A9=EA=B3=BC=20=EC=A2=85?= =?UTF-8?q?=EB=A3=8C=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt index 5ccde8fd..c61b5d87 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Survey.kt @@ -45,9 +45,9 @@ data class Survey( } companion object { - const val DEFAULT_TITLE = "제목 없는 설문" + const val DEFAULT_TITLE = "" const val DEFAULT_DESCRIPTION = "" - const val DEFAULT_FINISH_MESSAGE = "설문에 참여해주셔서 감사합니다." + const val DEFAULT_FINISH_MESSAGE = "" fun create(makerId: UUID) = Survey( From 1046b345e227dc51c7a11a0e3f1b61c3ac354fb9 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 30 Sep 2024 04:17:16 +0900 Subject: [PATCH 170/201] =?UTF-8?q?[SBL-131]=20SurveyDocument=EC=97=90=20i?= =?UTF-8?q?sDeleted=20=EC=BB=AC=EB=9F=BC=20=EC=B6=94=EA=B0=80,=20=ED=95=84?= =?UTF-8?q?=EC=9A=94=EC=97=86=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/entity/SurveyDocument.kt | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt index 0637c920..89f94745 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/SurveyDocument.kt @@ -40,6 +40,7 @@ data class SurveyDocument( val makerId: UUID, val rewards: List, val sections: List, + val isDeleted: Boolean = false, ) : BaseTimeDocument() { companion object { fun from(survey: Survey) = @@ -151,15 +152,8 @@ data class SurveyDocument( val isAllowOther: Boolean, ) - fun toDomain(): Survey { - val sections = - if (this.sections.isEmpty()) { - listOf() - } else { - val sectionIds = SectionIds.from(this.sections.map { SectionId.Standard(it.sectionId) }) - this.sections.map { it.toDomain(sectionIds) } - } - return Survey( + fun toDomain(): Survey = + Survey( id = this.id, title = this.title, description = this.description, @@ -179,7 +173,6 @@ data class SurveyDocument( makerId = this.makerId, sections = this.sections.toDomain(), ) - } private fun List.toDomain() = if (this.isEmpty()) { From 7ef4a2d1197784691e44c6f4f3cecbede8bcb1d3 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 30 Sep 2024 04:20:35 +0900 Subject: [PATCH 171/201] =?UTF-8?q?[SBL-131]=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=EC=84=A4=EB=AC=B8=20=EC=A1=B0=ED=9A=8C=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=93=A4=EC=9D=B4=20isDeleted=EA=B0=80=20false?= =?UTF-8?q?=EC=9D=B8=20=EA=B2=83=EB=A7=8C=20=EC=A1=B0=ED=9A=8C=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt | 9 +++++---- .../survey/repository/SurveyCustomRepositoryImpl.kt | 2 +- .../sulmun2yong/survey/repository/SurveyRepository.kt | 6 ++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt index 6a40345c..cc7314cc 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt @@ -25,12 +25,13 @@ class SurveyAdapter( isAsc: Boolean, ): Page { val pageRequest = PageRequest.of(page, size, getSurveySort(sortType, isAsc)) - val surveyDocuments = surveyRepository.findByStatusAndIsVisibleTrue(SurveyStatus.IN_PROGRESS, pageRequest) + val surveyDocuments = surveyRepository.findByStatusAndIsVisibleTrueAndIsDeletedFalse(SurveyStatus.IN_PROGRESS, pageRequest) val surveys = surveyDocuments.content.map { it.toDomain() } return PageImpl(surveys, pageRequest, surveyDocuments.totalElements) } - fun getSurvey(surveyId: UUID) = surveyRepository.findById(surveyId).orElseThrow { SurveyNotFoundException() }.toDomain() + fun getSurvey(surveyId: UUID) = + surveyRepository.findByIdAndIsDeletedFalse(surveyId).orElseThrow { SurveyNotFoundException() }.toDomain() private fun getSurveySort( sortType: SurveySortType, @@ -46,7 +47,7 @@ class SurveyAdapter( } fun save(survey: Survey) { - val previousSurveyDocument = surveyRepository.findById(survey.id) + val previousSurveyDocument = surveyRepository.findByIdAndIsDeletedFalse(survey.id) val surveyDocument = SurveyDocument.from(survey) // 기존 설문을 업데이트하는 경우, createdAt을 유지 if (previousSurveyDocument.isPresent) surveyDocument.createdAt = previousSurveyDocument.get().createdAt @@ -56,7 +57,7 @@ class SurveyAdapter( fun getByIdAndMakerId( surveyId: UUID, makerId: UUID, - ) = surveyRepository.findByIdAndMakerId(surveyId, makerId).orElseThrow { SurveyNotFoundException() }.toDomain() + ) = surveyRepository.findByIdAndMakerIdAndIsDeletedFalse(surveyId, makerId).orElseThrow { SurveyNotFoundException() }.toDomain() fun getMyPageSurveysInfo( makerId: UUID, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt index 9d57b825..03722cc4 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt @@ -19,7 +19,7 @@ class SurveyCustomRepositoryImpl( ): List { val matchStage = Aggregation.match( - Criteria.where("makerId").`is`(makerId).apply { + Criteria.where("makerId").`is`(makerId).and("isDeleted").`is`(false).apply { status?.let { and("status").`is`(it) } }, ) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt index 83f8adcb..6e2d64cf 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt @@ -13,13 +13,15 @@ import java.util.UUID interface SurveyRepository : MongoRepository, SurveyCustomRepository { - fun findByStatusAndIsVisibleTrue( + fun findByStatusAndIsVisibleTrueAndIsDeletedFalse( status: SurveyStatus, pageable: Pageable, ): Page - fun findByIdAndMakerId( + fun findByIdAndMakerIdAndIsDeletedFalse( id: UUID, makerId: UUID, ): Optional + + fun findByIdAndIsDeletedFalse(id: UUID): Optional } From d3713fde9c9001d34150d4cb2ff48a6849e2baf3 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 30 Sep 2024 04:21:36 +0900 Subject: [PATCH 172/201] =?UTF-8?q?[SBL-131]=20softDelete=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EC=84=A4=EB=AC=B8=EC=9D=84=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=ED=95=98=EB=8A=94=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20Repository=EC=99=80=20Adapter=EC=97=90=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/adapter/SurveyAdapter.kt | 8 +++++++ .../repository/SurveyCustomRepository.kt | 5 ++++ .../repository/SurveyCustomRepositoryImpl.kt | 23 +++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt index cc7314cc..3516cdfa 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt @@ -64,4 +64,12 @@ class SurveyAdapter( status: SurveyStatus?, sortType: MySurveySortType, ) = surveyRepository.findSurveysWithResponseCount(makerId, status, sortType) + + fun delete( + surveyId: UUID, + makerId: UUID, + ) { + val isSuccess = surveyRepository.softDelete(surveyId, makerId) + if (!isSuccess) throw SurveyNotFoundException() + } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepository.kt index cd71d094..19d43723 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepository.kt @@ -11,4 +11,9 @@ interface SurveyCustomRepository { status: SurveyStatus?, sortType: MySurveySortType, ): List + + fun softDelete( + surveyId: UUID, + makerId: UUID, + ): Boolean } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt index 03722cc4..98cbf2d4 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyCustomRepositoryImpl.kt @@ -3,10 +3,13 @@ package com.sbl.sulmun2yong.survey.repository import com.sbl.sulmun2yong.survey.domain.SurveyStatus import com.sbl.sulmun2yong.survey.dto.request.MySurveySortType import com.sbl.sulmun2yong.survey.dto.response.MyPageSurveyInfoResponse +import com.sbl.sulmun2yong.survey.entity.SurveyDocument import org.springframework.data.domain.Sort import org.springframework.data.mongodb.core.MongoTemplate import org.springframework.data.mongodb.core.aggregation.Aggregation import org.springframework.data.mongodb.core.query.Criteria +import org.springframework.data.mongodb.core.query.Query +import org.springframework.data.mongodb.core.query.Update import java.util.UUID class SurveyCustomRepositoryImpl( @@ -40,6 +43,26 @@ class SurveyCustomRepositoryImpl( return results.mappedResults } + override fun softDelete( + surveyId: UUID, + makerId: UUID, + ): Boolean { + val query = + Query( + Criteria + .where("_id") + .`is`(surveyId) + .and("makerId") + .`is`(makerId), + ) + val update = Update().set("isDeleted", true) + + val result = mongoTemplate.updateFirst(query, update, SurveyDocument::class.java) + + // 변경된 문서가 있을 경우 true, 없을 경우 false 반환 + return result.modifiedCount > 0 + } + private fun MySurveySortType.toSort() = when (this) { MySurveySortType.LAST_MODIFIED -> Sort.by(Sort.Direction.DESC, "updatedAt") From 99bc1af44aa075dcef25dffca053c2816568f137 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 30 Sep 2024 04:21:50 +0900 Subject: [PATCH 173/201] =?UTF-8?q?[SBL-131]=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/controller/SurveyManagementController.kt | 7 +++++++ .../survey/controller/doc/SurveyManagementApiDoc.kt | 8 ++++++++ .../sulmun2yong/survey/service/SurveyManagementService.kt | 7 +++++++ 3 files changed, 22 insertions(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyManagementController.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyManagementController.kt index dca51d20..649a652c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyManagementController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyManagementController.kt @@ -5,6 +5,7 @@ import com.sbl.sulmun2yong.survey.controller.doc.SurveyManagementApiDoc import com.sbl.sulmun2yong.survey.dto.request.SurveySaveRequest import com.sbl.sulmun2yong.survey.service.SurveyManagementService import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PatchMapping import org.springframework.web.bind.annotation.PathVariable @@ -61,4 +62,10 @@ class SurveyManagementController( @PathVariable("surveyId") surveyId: UUID, @LoginUser id: UUID, ) = ResponseEntity.ok(surveyManagementService.finishSurvey(surveyId = surveyId, makerId = id)) + + @DeleteMapping("/delete/{surveyId}") + override fun deleteSurvey( + @PathVariable("surveyId") surveyId: UUID, + @LoginUser id: UUID, + ) = ResponseEntity.ok(surveyManagementService.deleteSurvey(surveyId = surveyId, makerId = id)) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyManagementApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyManagementApiDoc.kt index 68500f06..04a8f075 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyManagementApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyManagementApiDoc.kt @@ -7,6 +7,7 @@ import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PatchMapping import org.springframework.web.bind.annotation.PathVariable @@ -58,4 +59,11 @@ interface SurveyManagementApiDoc { @PathVariable("surveyId") surveyId: UUID, @LoginUser id: UUID, ): ResponseEntity + + @Operation(summary = "설문 삭제 API") + @DeleteMapping("/delete/{surveyId}") + fun deleteSurvey( + @PathVariable("surveyId") surveyId: UUID, + @LoginUser id: UUID, + ): ResponseEntity } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt index 70e6e648..ecf905e1 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyManagementService.kt @@ -88,4 +88,11 @@ class SurveyManagementService( val survey = surveyAdapter.getByIdAndMakerId(surveyId, makerId) surveyAdapter.save(survey.finish()) } + + fun deleteSurvey( + surveyId: UUID, + makerId: UUID, + ) { + surveyAdapter.delete(surveyId, makerId) + } } From ca52d13130993590f49c7a65aadb999b4992dfcb Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 30 Sep 2024 04:22:44 +0900 Subject: [PATCH 174/201] =?UTF-8?q?[SBL-131]=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=EC=84=A4=EB=AC=B8=EB=93=A4=EC=9D=80=20isDeleted=EA=B0=80=20nul?= =?UTF-8?q?l=EC=9D=B4=EB=AF=80=EB=A1=9C=20=EC=9D=B4=EB=A5=BC=20false?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=94=EA=BF=94=EC=A3=BC=EB=8A=94=20Migration=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AddIsDeletedFieldAtSurveyDocument.kt | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt new file mode 100644 index 00000000..9d2929ca --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt @@ -0,0 +1,31 @@ +package com.sbl.sulmun2yong.global.migration + +import com.sbl.sulmun2yong.global.error.GlobalExceptionHandler +import com.sbl.sulmun2yong.survey.entity.SurveyDocument +import io.mongock.api.annotations.ChangeUnit +import io.mongock.api.annotations.Execution +import io.mongock.api.annotations.RollbackExecution +import org.slf4j.LoggerFactory +import org.springframework.data.mongodb.core.MongoTemplate +import org.springframework.data.mongodb.core.query.Criteria +import org.springframework.data.mongodb.core.query.Query +import org.springframework.data.mongodb.core.query.Update + +/** Surveys 컬렉션의 isDelete가 null인 경우 기본값 false를 넣는 Migration Class */ +@ChangeUnit(id = "AddIsDeletedFieldAtSurveyDocument", order = "002", author = "hunhui") +class AddIsDeletedFieldAtSurveyDocument { + private val log = LoggerFactory.getLogger(GlobalExceptionHandler::class.java) + + @Execution + fun addIsDeletedField(mongoTemplate: MongoTemplate) { + val query = Query(Criteria.where("isDeleted").`is`(null)) + val update = Update().set("isDeleted", false) + mongoTemplate.updateMulti(query, update, SurveyDocument::class.java) + log.info("002-AddIsDeletedFieldAtSurveyDocument 완료") + } + + @RollbackExecution + fun rollback() { + log.warn("002-AddIsDeletedFieldAtSurveyDocument 롤백") + } +} From 7c5cd8cd42c70990927d54a046e5eb2d8d93a025 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 30 Sep 2024 05:24:23 +0900 Subject: [PATCH 175/201] =?UTF-8?q?[SBL-130]=20=EC=8A=A4=EC=BC=80=EC=A4=84?= =?UTF-8?q?=EB=9F=AC=EB=A5=BC=20=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/global/config/SchedulingConfig.kt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/config/SchedulingConfig.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SchedulingConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SchedulingConfig.kt new file mode 100644 index 00000000..a36357a6 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SchedulingConfig.kt @@ -0,0 +1,8 @@ +package com.sbl.sulmun2yong.global.config + +import org.springframework.context.annotation.Configuration +import org.springframework.scheduling.annotation.EnableScheduling + +@Configuration +@EnableScheduling +class SchedulingConfig From 8fdc1081b4e162235d6f6910e65ec5e7ef738daa Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 30 Sep 2024 05:25:13 +0900 Subject: [PATCH 176/201] =?UTF-8?q?[SBL-130]=20=EC=A2=85=EB=A3=8C=ED=95=A0?= =?UTF-8?q?=20=EC=84=A4=EB=AC=B8=EC=9D=84=20=EC=B0=BE=EB=8A=94=20Adapter?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=EC=99=80=20=EC=84=A4=EB=AC=B8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=EC=9D=84=20=EB=B0=9B=EC=95=84=EC=84=9C=20?= =?UTF-8?q?=ED=95=9C=20=EB=B2=88=EC=97=90=20=EC=A0=80=EC=9E=A5=ED=95=98?= =?UTF-8?q?=EB=8A=94=20saveAll=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt | 7 +++++++ .../sbl/sulmun2yong/survey/repository/SurveyRepository.kt | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt index 3516cdfa..2ea7cf02 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/SurveyAdapter.kt @@ -12,6 +12,7 @@ import org.springframework.data.domain.PageImpl import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Sort import org.springframework.stereotype.Component +import java.util.Date import java.util.UUID @Component @@ -72,4 +73,10 @@ class SurveyAdapter( val isSuccess = surveyRepository.softDelete(surveyId, makerId) if (!isSuccess) throw SurveyNotFoundException() } + + fun findFinishTargets(now: Date) = surveyRepository.findFinishTargets(now).map { it.toDomain() } + + fun saveAll(surveys: List) { + surveyRepository.saveAll(surveys.map { SurveyDocument.from(it) }) + } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt index 6e2d64cf..c23ebc84 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/SurveyRepository.kt @@ -5,7 +5,9 @@ import com.sbl.sulmun2yong.survey.entity.SurveyDocument import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.data.mongodb.repository.MongoRepository +import org.springframework.data.mongodb.repository.Query import org.springframework.stereotype.Repository +import java.util.Date import java.util.Optional import java.util.UUID @@ -24,4 +26,7 @@ interface SurveyRepository : ): Optional fun findByIdAndIsDeletedFalse(id: UUID): Optional + + @Query("{ 'finishedAt': { \$lt: ?0 }, 'status': { \$in: ['IN_PROGRESS', 'IN_MODIFICATION'] }, 'isDeleted': false }") + fun findFinishTargets(now: Date): List } From 85815a93c5dc8117986af65b27b2dd4dd04336f0 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 30 Sep 2024 05:26:02 +0900 Subject: [PATCH 177/201] =?UTF-8?q?[SBL-130]=20=EB=A7=A4=20=EC=8B=9C=20?= =?UTF-8?q?=EC=A0=95=EA=B0=81=20=EB=A7=88=EB=8B=A4=20=EC=A7=84=ED=96=89?= =?UTF-8?q?=EC=A4=91=20or=20=EC=88=98=EC=A0=95=EC=A4=91=EC=9D=B4=EB=A9=B4?= =?UTF-8?q?=EC=84=9C=20=EC=82=AD=EC=A0=9C=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EA=B3=A0=20=EC=A2=85=EB=A3=8C=EC=9D=BC=EC=9D=B4=20=EC=A7=80?= =?UTF-8?q?=EB=82=9C=20=EC=84=A4=EB=AC=B8=EC=9D=84=20=EC=B0=BE=EC=95=84=20?= =?UTF-8?q?=EC=A2=85=EB=A3=8C=EC=8B=9C=ED=82=A4=EB=8A=94=20=EC=8A=A4?= =?UTF-8?q?=EC=BC=80=EC=A4=84=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/scheduler/SurveyFinishScheduler.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/scheduler/SurveyFinishScheduler.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/scheduler/SurveyFinishScheduler.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/scheduler/SurveyFinishScheduler.kt new file mode 100644 index 00000000..913d724d --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/scheduler/SurveyFinishScheduler.kt @@ -0,0 +1,29 @@ +package com.sbl.sulmun2yong.survey.scheduler + +import com.sbl.sulmun2yong.global.error.GlobalExceptionHandler +import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter +import org.slf4j.LoggerFactory +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service +import java.util.Date + +@Service +class SurveyFinishScheduler( + private val surveyAdapter: SurveyAdapter, +) { + private val log = LoggerFactory.getLogger(GlobalExceptionHandler::class.java) + + @Scheduled(cron = "0 * * * * *") // 매 시 정각에 실행 + fun closeExpiredSurveys() { + val targetSurveys = surveyAdapter.findFinishTargets(Date()) + val finishedTargetSurveys = + targetSurveys.map { + log.info("설문 종료 시도: ${it.id}") + it.finish() + } + + surveyAdapter.saveAll(finishedTargetSurveys) + + log.info("${targetSurveys.size}개의 설문을 성공적으로 종료했습니다.") + } +} From 7ad0c6c38e069b24efe5a4ba67d4db780a282882 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Mon, 30 Sep 2024 05:50:06 +0900 Subject: [PATCH 178/201] =?UTF-8?q?[SBL-130]=20=EB=A7=A4=20=EB=B6=84=20?= =?UTF-8?q?=EB=A7=88=EB=8B=A4=20=EC=8B=A4=ED=96=89=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=84=A4=EC=A0=95=ED=95=9C=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sbl/sulmun2yong/survey/scheduler/SurveyFinishScheduler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/scheduler/SurveyFinishScheduler.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/scheduler/SurveyFinishScheduler.kt index 913d724d..6e643c82 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/scheduler/SurveyFinishScheduler.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/scheduler/SurveyFinishScheduler.kt @@ -13,7 +13,7 @@ class SurveyFinishScheduler( ) { private val log = LoggerFactory.getLogger(GlobalExceptionHandler::class.java) - @Scheduled(cron = "0 * * * * *") // 매 시 정각에 실행 + @Scheduled(cron = "0 0 * * * *") // 매 시 정각에 실행 fun closeExpiredSurveys() { val targetSurveys = surveyAdapter.findFinishTargets(Date()) val finishedTargetSurveys = From 32a3a7e423af722be59096bdad8f3b1ba5c262d9 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Tue, 1 Oct 2024 17:31:02 +0900 Subject: [PATCH 179/201] =?UTF-8?q?[SBL-169]=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 13 +++++++++++++ .../sulmun2yong/ai/adapter/GenerateAdapter.kt | 19 +++++++++++-------- .../ai/controller/doc/GenerateAPIDoc.kt | 6 +++--- .../ChatSessionIdWithSurveyGeneratedByAI.kt | 8 ++++++++ .../dto/response/SurveyGenerationResponse.kt | 16 ++++++++++++++++ .../sulmun2yong/ai/service/GenerateService.kt | 6 +++--- 6 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/dto/ChatSessionIdWithSurveyGeneratedByAI.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerationResponse.kt diff --git a/docker-compose.yml b/docker-compose.yml index 775388ec..c641efc9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,6 +32,19 @@ services: volumes: - sulmun2yong-test-mongodb:/data/db + sulmun2yong-test-redis: + image: redis:latest + container_name: sulmun2yong-test-redis + restart: always + environment: + REDIS_PASSWORD: ${REDIS_PASSWORD} + expose: + - "6379" + ports: + - "6379:6379" + volumes: + - sulmun2yong-test-redis:/data + volumes: sulmun2yong-test-db: sulmun2yong-test-mongodb: diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt index 0d18f28f..9ed7f40c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt @@ -1,6 +1,7 @@ package com.sbl.sulmun2yong.ai.adapter -import com.sbl.sulmun2yong.ai.dto.SurveyGeneratedByAI +import com.sbl.sulmun2yong.ai.dto.ChatSessionIdWithSurveyGeneratedByAI +import com.sbl.sulmun2yong.ai.dto.response.SurveyGenerationResponse import com.sbl.sulmun2yong.ai.exception.SurveyGenerationByAIFailedException import com.sbl.sulmun2yong.global.error.PythonServerExceptionMapper import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse @@ -20,7 +21,7 @@ class GenerateAdapter( groupName: String, fileUrl: String, userPrompt: String, - ): SurveyMakeInfoResponse { + ): SurveyGenerationResponse { val requestUrl = "$aiServerBaseUrl/generate/survey/file-url" val requestBody = @@ -39,7 +40,7 @@ class GenerateAdapter( groupName: String, textDocument: String, userPrompt: String, - ): SurveyMakeInfoResponse { + ): SurveyGenerationResponse { val requestUrl = "$aiServerBaseUrl/generate/survey/text-document" val requestBody = @@ -56,20 +57,22 @@ class GenerateAdapter( private fun requestToGenerateSurvey( requestUrl: String, requestBody: Map, - ): SurveyMakeInfoResponse { - val surveyGeneratedByAI = + ): SurveyGenerationResponse { + val chatSessionIdWithSurveyGeneratedByAI = try { restTemplate .postForEntity( requestUrl, requestBody, - SurveyGeneratedByAI::class.java, + ChatSessionIdWithSurveyGeneratedByAI::class.java, ).body ?: throw SurveyGenerationByAIFailedException() } catch (e: HttpClientErrorException) { throw PythonServerExceptionMapper.mapException(e) } - val survey = surveyGeneratedByAI.toDomain() - return SurveyMakeInfoResponse.of(survey) + val chatSessionId = chatSessionIdWithSurveyGeneratedByAI.chatSessionId + val survey = chatSessionIdWithSurveyGeneratedByAI.surveyGeneratedByAI.toDomain() + val surveyMakeInfoResponse = SurveyMakeInfoResponse.of(survey) + return SurveyGenerationResponse.from(chatSessionId, surveyMakeInfoResponse) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt index dadca8b9..ee596ae2 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt @@ -2,7 +2,7 @@ package com.sbl.sulmun2yong.ai.controller.doc import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithFileUrlRequest import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithTextDocumentRequest -import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse +import com.sbl.sulmun2yong.ai.dto.response.SurveyGenerationResponse import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity @@ -15,11 +15,11 @@ interface GenerateAPIDoc { @PostMapping("/survey/file-url") fun generateSurveyWithFileUrl( @RequestBody request: SurveyGenerationWithFileUrlRequest, - ): ResponseEntity + ): ResponseEntity @Operation(summary = "텍스트 입력을 통한 AI 설문 생성") @PostMapping("/survey/text-document") fun generateSurveyWithTextDocument( @RequestBody request: SurveyGenerationWithTextDocumentRequest, - ): ResponseEntity + ): ResponseEntity } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/ChatSessionIdWithSurveyGeneratedByAI.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/ChatSessionIdWithSurveyGeneratedByAI.kt new file mode 100644 index 00000000..04fcb940 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/ChatSessionIdWithSurveyGeneratedByAI.kt @@ -0,0 +1,8 @@ +package com.sbl.sulmun2yong.ai.dto + +import java.util.UUID + +class ChatSessionIdWithSurveyGeneratedByAI( + val chatSessionId: UUID, + val surveyGeneratedByAI: SurveyGeneratedByAI, +) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerationResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerationResponse.kt new file mode 100644 index 00000000..1b030e1c --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerationResponse.kt @@ -0,0 +1,16 @@ +package com.sbl.sulmun2yong.ai.dto.response + +import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse +import java.util.UUID + +class SurveyGenerationResponse( + val chatSessionId: UUID, + val generatedSurvey: SurveyMakeInfoResponse, +) { + companion object { + fun from( + chatSessionId: UUID, + generatedSurvey: SurveyMakeInfoResponse, + ): SurveyGenerationResponse = SurveyGenerationResponse(chatSessionId, generatedSurvey) + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index b981023e..a000e236 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -1,8 +1,8 @@ package com.sbl.sulmun2yong.ai.service import com.sbl.sulmun2yong.ai.adapter.GenerateAdapter +import com.sbl.sulmun2yong.ai.dto.response.SurveyGenerationResponse import com.sbl.sulmun2yong.global.util.validator.FileUrlValidator -import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse import org.springframework.stereotype.Service @Service @@ -15,7 +15,7 @@ class GenerateService( groupName: String, fileUrl: String, userPrompt: String, - ): SurveyMakeInfoResponse { + ): SurveyGenerationResponse { val allowedExtensions = mutableListOf(".txt", ".pdf") fileUrlValidator.validateFileUrlOf(fileUrl, allowedExtensions) @@ -27,5 +27,5 @@ class GenerateService( groupName: String, textDocument: String, userPrompt: String, - ): SurveyMakeInfoResponse = generateAdapter.requestSurveyGenerationWithTextDocument(job, groupName, textDocument, userPrompt) + ): SurveyGenerationResponse = generateAdapter.requestSurveyGenerationWithTextDocument(job, groupName, textDocument, userPrompt) } From 45d3ab06f0122956483af15fd1b423e075393a56 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 2 Oct 2024 14:37:15 +0900 Subject: [PATCH 180/201] =?UTF-8?q?[SBL-169]=20=EB=8F=84=EC=BB=A4=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EC=A6=88=EC=97=90=20=EB=B3=BC=EB=A5=A8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index c641efc9..ec244f9a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,3 +48,4 @@ services: volumes: sulmun2yong-test-db: sulmun2yong-test-mongodb: + sulmun2yong-test-redis: From 89b96df9cd094ceb3dd481901bef1615c27181c8 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 2 Oct 2024 15:57:18 +0900 Subject: [PATCH 181/201] =?UTF-8?q?[SBL-169]=20=EC=BF=A0=ED=82=A4=EB=A1=9C?= =?UTF-8?q?=20=EC=B1=84=ED=8C=85=EC=84=B8=EC=85=98=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EB=94=94=EB=A5=BC=20=EB=A7=8C=EB=93=A4=EC=96=B4=EC=A4=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/controller/AIChatController.kt | 8 +++++ .../ai/controller/GenerateController.kt | 29 ------------------- .../ai/controller/doc/GenerateAPIDoc.kt | 13 +++++---- .../sulmun2yong/ai/service/GenerateService.kt | 29 ++++++++++++------- 4 files changed, 34 insertions(+), 45 deletions(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIChatController.kt delete mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIChatController.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIChatController.kt new file mode 100644 index 00000000..c672c343 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIChatController.kt @@ -0,0 +1,8 @@ +package com.sbl.sulmun2yong.ai.controller + +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/v1/ai/") +class AIChatController diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt deleted file mode 100644 index 53a6e787..00000000 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/GenerateController.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.sbl.sulmun2yong.ai.controller - -import com.sbl.sulmun2yong.ai.controller.doc.GenerateAPIDoc -import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithFileUrlRequest -import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithTextDocumentRequest -import com.sbl.sulmun2yong.ai.service.GenerateService -import org.springframework.http.ResponseEntity -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.RestController - -@RestController -@RequestMapping("/api/v1/ai/generate") -class GenerateController( - private val generateService: GenerateService, -) : GenerateAPIDoc { - @PostMapping("/survey/file-url") - override fun generateSurveyWithFileUrl( - @RequestBody request: SurveyGenerationWithFileUrlRequest, - ) = ResponseEntity.ok(generateService.generateSurveyWithFileUrl(request.job, request.groupName, request.fileUrl, request.userPrompt)) - - @PostMapping("/survey/text-document") - override fun generateSurveyWithTextDocument( - @RequestBody request: SurveyGenerationWithTextDocumentRequest, - ) = ResponseEntity.ok( - generateService.generateSurveyWithTextDocument(request.job, request.groupName, request.textDocument, request.userPrompt), - ) -} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt index ee596ae2..e300aa07 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt @@ -2,9 +2,10 @@ package com.sbl.sulmun2yong.ai.controller.doc import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithFileUrlRequest import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithTextDocumentRequest -import com.sbl.sulmun2yong.ai.dto.response.SurveyGenerationResponse +import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.servlet.http.HttpServletResponse import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -14,12 +15,14 @@ interface GenerateAPIDoc { @Operation(summary = "파일을 통한 AI 설문 생성") @PostMapping("/survey/file-url") fun generateSurveyWithFileUrl( - @RequestBody request: SurveyGenerationWithFileUrlRequest, - ): ResponseEntity + @RequestBody surveyGenerationWithFileUrlRequest: SurveyGenerationWithFileUrlRequest, + response: HttpServletResponse, + ): ResponseEntity @Operation(summary = "텍스트 입력을 통한 AI 설문 생성") @PostMapping("/survey/text-document") fun generateSurveyWithTextDocument( - @RequestBody request: SurveyGenerationWithTextDocumentRequest, - ): ResponseEntity + @RequestBody surveyGenerationWithTextDocumentRequest: SurveyGenerationWithTextDocumentRequest, + response: HttpServletResponse, + ): ResponseEntity } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index a000e236..8be3e6c4 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -1,6 +1,8 @@ package com.sbl.sulmun2yong.ai.service import com.sbl.sulmun2yong.ai.adapter.GenerateAdapter +import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithFileUrlRequest +import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithTextDocumentRequest import com.sbl.sulmun2yong.ai.dto.response.SurveyGenerationResponse import com.sbl.sulmun2yong.global.util.validator.FileUrlValidator import org.springframework.stereotype.Service @@ -10,22 +12,27 @@ class GenerateService( private val fileUrlValidator: FileUrlValidator, private val generateAdapter: GenerateAdapter, ) { - fun generateSurveyWithFileUrl( - job: String, - groupName: String, - fileUrl: String, - userPrompt: String, - ): SurveyGenerationResponse { + fun generateSurveyWithFileUrl(surveyGenerationWithFileUrlRequest: SurveyGenerationWithFileUrlRequest): SurveyGenerationResponse { val allowedExtensions = mutableListOf(".txt", ".pdf") + + val job = surveyGenerationWithFileUrlRequest.job + val groupName = surveyGenerationWithFileUrlRequest.groupName + val fileUrl = surveyGenerationWithFileUrlRequest.fileUrl + val userPrompt = surveyGenerationWithFileUrlRequest.userPrompt + fileUrlValidator.validateFileUrlOf(fileUrl, allowedExtensions) return generateAdapter.requestSurveyGenerationWithFileUrl(job, groupName, fileUrl, userPrompt) } fun generateSurveyWithTextDocument( - job: String, - groupName: String, - textDocument: String, - userPrompt: String, - ): SurveyGenerationResponse = generateAdapter.requestSurveyGenerationWithTextDocument(job, groupName, textDocument, userPrompt) + surveyGenerationWithTextDocumentRequest: SurveyGenerationWithTextDocumentRequest, + ): SurveyGenerationResponse { + val job = surveyGenerationWithTextDocumentRequest.job + val groupName = surveyGenerationWithTextDocumentRequest.groupName + val textDocument = surveyGenerationWithTextDocumentRequest.textDocument + val userPrompt = surveyGenerationWithTextDocumentRequest.userPrompt + + return generateAdapter.requestSurveyGenerationWithTextDocument(job, groupName, textDocument, userPrompt) + } } From 5aa3f8b442d80841e6064fa80b58ec0920227da6 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 2 Oct 2024 16:11:44 +0900 Subject: [PATCH 182/201] =?UTF-8?q?[SBL-169]=20=EC=BF=A0=ED=82=A4=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/ai/adapter/GenerateAdapter.kt | 10 ++-- .../ai/controller/AIChatController.kt | 2 +- .../ai/controller/AIGenerateController.kt | 55 +++++++++++++++++++ ...ponse.kt => AISurveyGenerationResponse.kt} | 4 +- .../sulmun2yong/ai/service/GenerateService.kt | 6 +- 5 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIGenerateController.kt rename src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/{SurveyGenerationResponse.kt => AISurveyGenerationResponse.kt} (72%) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt index 9ed7f40c..46f998b8 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/adapter/GenerateAdapter.kt @@ -1,7 +1,7 @@ package com.sbl.sulmun2yong.ai.adapter import com.sbl.sulmun2yong.ai.dto.ChatSessionIdWithSurveyGeneratedByAI -import com.sbl.sulmun2yong.ai.dto.response.SurveyGenerationResponse +import com.sbl.sulmun2yong.ai.dto.response.AISurveyGenerationResponse import com.sbl.sulmun2yong.ai.exception.SurveyGenerationByAIFailedException import com.sbl.sulmun2yong.global.error.PythonServerExceptionMapper import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse @@ -21,7 +21,7 @@ class GenerateAdapter( groupName: String, fileUrl: String, userPrompt: String, - ): SurveyGenerationResponse { + ): AISurveyGenerationResponse { val requestUrl = "$aiServerBaseUrl/generate/survey/file-url" val requestBody = @@ -40,7 +40,7 @@ class GenerateAdapter( groupName: String, textDocument: String, userPrompt: String, - ): SurveyGenerationResponse { + ): AISurveyGenerationResponse { val requestUrl = "$aiServerBaseUrl/generate/survey/text-document" val requestBody = @@ -57,7 +57,7 @@ class GenerateAdapter( private fun requestToGenerateSurvey( requestUrl: String, requestBody: Map, - ): SurveyGenerationResponse { + ): AISurveyGenerationResponse { val chatSessionIdWithSurveyGeneratedByAI = try { restTemplate @@ -73,6 +73,6 @@ class GenerateAdapter( val chatSessionId = chatSessionIdWithSurveyGeneratedByAI.chatSessionId val survey = chatSessionIdWithSurveyGeneratedByAI.surveyGeneratedByAI.toDomain() val surveyMakeInfoResponse = SurveyMakeInfoResponse.of(survey) - return SurveyGenerationResponse.from(chatSessionId, surveyMakeInfoResponse) + return AISurveyGenerationResponse.from(chatSessionId, surveyMakeInfoResponse) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIChatController.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIChatController.kt index c672c343..957f9e56 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIChatController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIChatController.kt @@ -4,5 +4,5 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @RestController -@RequestMapping("/api/v1/ai/") +@RequestMapping("/api/v1/ai/generate") class AIChatController diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIGenerateController.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIGenerateController.kt new file mode 100644 index 00000000..575fa154 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIGenerateController.kt @@ -0,0 +1,55 @@ +package com.sbl.sulmun2yong.ai.controller + +import com.sbl.sulmun2yong.ai.controller.doc.GenerateAPIDoc +import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithFileUrlRequest +import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithTextDocumentRequest +import com.sbl.sulmun2yong.ai.service.GenerateService +import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse +import jakarta.servlet.http.Cookie +import jakarta.servlet.http.HttpServletResponse +import org.springframework.http.ResponseEntity +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.RestController +import java.util.UUID + +@RestController +@RequestMapping("/api/v1/ai/generate") +class AIGenerateController( + private val generateService: GenerateService, +) : GenerateAPIDoc { + @PostMapping("/survey/file-url") + override fun generateSurveyWithFileUrl( + @RequestBody surveyGenerationWithFileUrlRequest: SurveyGenerationWithFileUrlRequest, + response: HttpServletResponse, + ): ResponseEntity { + val aiSurveyGenerationResponse = generateService.generateSurveyWithFileUrl(surveyGenerationWithFileUrlRequest) + setChatSessionIdCookie(response, aiSurveyGenerationResponse.chatSessionId) + return ResponseEntity.ok(aiSurveyGenerationResponse.generatedSurvey) + } + + @PostMapping("/survey/text-document") + override fun generateSurveyWithTextDocument( + @RequestBody surveyGenerationWithTextDocumentRequest: SurveyGenerationWithTextDocumentRequest, + response: HttpServletResponse, + ): ResponseEntity { + val aiSurveyGenerationResponse = generateService.generateSurveyWithTextDocument(surveyGenerationWithTextDocumentRequest) + setChatSessionIdCookie(response, aiSurveyGenerationResponse.chatSessionId) + return ResponseEntity.ok(aiSurveyGenerationResponse.generatedSurvey) + } + + private fun setChatSessionIdCookie( + response: HttpServletResponse, + chatSessionId: UUID, + ) { + val cookie = + Cookie("chat-session-id", chatSessionId.toString()).apply { + maxAge = 3600 + path = "/" + isHttpOnly = true + } + + response.addCookie(cookie) + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerationResponse.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/AISurveyGenerationResponse.kt similarity index 72% rename from src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerationResponse.kt rename to src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/AISurveyGenerationResponse.kt index 1b030e1c..dfe952c3 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/SurveyGenerationResponse.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/dto/response/AISurveyGenerationResponse.kt @@ -3,7 +3,7 @@ package com.sbl.sulmun2yong.ai.dto.response import com.sbl.sulmun2yong.survey.dto.response.SurveyMakeInfoResponse import java.util.UUID -class SurveyGenerationResponse( +class AISurveyGenerationResponse( val chatSessionId: UUID, val generatedSurvey: SurveyMakeInfoResponse, ) { @@ -11,6 +11,6 @@ class SurveyGenerationResponse( fun from( chatSessionId: UUID, generatedSurvey: SurveyMakeInfoResponse, - ): SurveyGenerationResponse = SurveyGenerationResponse(chatSessionId, generatedSurvey) + ): AISurveyGenerationResponse = AISurveyGenerationResponse(chatSessionId, generatedSurvey) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt index 8be3e6c4..947f2a05 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/service/GenerateService.kt @@ -3,7 +3,7 @@ package com.sbl.sulmun2yong.ai.service import com.sbl.sulmun2yong.ai.adapter.GenerateAdapter import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithFileUrlRequest import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithTextDocumentRequest -import com.sbl.sulmun2yong.ai.dto.response.SurveyGenerationResponse +import com.sbl.sulmun2yong.ai.dto.response.AISurveyGenerationResponse import com.sbl.sulmun2yong.global.util.validator.FileUrlValidator import org.springframework.stereotype.Service @@ -12,7 +12,7 @@ class GenerateService( private val fileUrlValidator: FileUrlValidator, private val generateAdapter: GenerateAdapter, ) { - fun generateSurveyWithFileUrl(surveyGenerationWithFileUrlRequest: SurveyGenerationWithFileUrlRequest): SurveyGenerationResponse { + fun generateSurveyWithFileUrl(surveyGenerationWithFileUrlRequest: SurveyGenerationWithFileUrlRequest): AISurveyGenerationResponse { val allowedExtensions = mutableListOf(".txt", ".pdf") val job = surveyGenerationWithFileUrlRequest.job @@ -27,7 +27,7 @@ class GenerateService( fun generateSurveyWithTextDocument( surveyGenerationWithTextDocumentRequest: SurveyGenerationWithTextDocumentRequest, - ): SurveyGenerationResponse { + ): AISurveyGenerationResponse { val job = surveyGenerationWithTextDocumentRequest.job val groupName = surveyGenerationWithTextDocumentRequest.groupName val textDocument = surveyGenerationWithTextDocumentRequest.textDocument From 540aa18f0970742a9df619b444fde4fc6470f56b Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 2 Oct 2024 16:40:00 +0900 Subject: [PATCH 183/201] =?UTF-8?q?[SBL-169]=20413=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index 1a24c3c5..03fe225b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -59,7 +59,7 @@ enum class ErrorCode( // File Validator (FV) INVALID_EXTENSION(HttpStatus.BAD_REQUEST, "FV0001", "허용하지 않는 확장자입니다."), - OUT_OF_FILE_SIZE(HttpStatus.BAD_REQUEST, "FV0002", "파일 크기가 너무 큽니다."), + OUT_OF_FILE_SIZE(HttpStatus.REQUEST_ENTITY_TOO_LARGE, "FV0002", "파일 크기가 너무 큽니다."), FILE_NAME_TOO_SHORT(HttpStatus.BAD_REQUEST, "FV0003", "파일 이름이 너무 짧습니다."), FILE_NAME_TOO_LONG(HttpStatus.BAD_REQUEST, "FV0004", "파일 이름이 너무 깁니다."), NO_FILE_EXIST(HttpStatus.BAD_REQUEST, "FV0005", "파일이 존재하지 않습니다."), From 2e473b8c6d76c4fda65d0137087e861a8bdc0b47 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 2 Oct 2024 21:13:12 +0900 Subject: [PATCH 184/201] =?UTF-8?q?[SBL-162]=20=ED=95=91=EA=B1=B0=20?= =?UTF-8?q?=ED=94=84=EB=A6=B0=ED=8A=B8=20=EB=B3=B5=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 3 ++ .../sbl/sulmun2yong/global/error/ErrorCode.kt | 2 + .../sulmun2yong/global/util/FingerprintApi.kt | 50 +++++++++++++++++++ .../util/exception/UncleanVisitorException.kt | 6 +++ .../survey/adapter/ParticipantAdapter.kt | 9 ++++ .../controller/SurveyResponseController.kt | 4 +- .../controller/doc/SurveyResponseApiDoc.kt | 2 + .../sulmun2yong/survey/domain/Participant.kt | 4 +- .../dto/request/SurveyResponseRequest.kt | 2 + .../survey/entity/ParticipantDocument.kt | 3 ++ .../exception/AlreadyParticipatedException.kt | 6 +++ .../repository/ParticipantRepository.kt | 6 +++ .../survey/service/SurveyResponseService.kt | 22 +++++++- 13 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/util/FingerprintApi.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/UncleanVisitorException.kt create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/survey/exception/AlreadyParticipatedException.kt diff --git a/build.gradle.kts b/build.gradle.kts index 1d977d86..93ec6cce 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,6 +35,9 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.springframework.boot:spring-boot-starter-actuator") + // fingerprint + implementation("com.github.fingerprintjs:fingerprint-pro-server-api-java-sdk:v6.0.0") + // security implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-oauth2-client") diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index 1a24c3c5..63c4d7c8 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -12,8 +12,10 @@ enum class ErrorCode( INPUT_INVALID_VALUE(HttpStatus.BAD_REQUEST, "GL0002", "잘못된 입력입니다."), LOGIN_REQUIRED(HttpStatus.UNAUTHORIZED, "GL0003", "로그인이 필요합니다."), ACCESS_DENIED(HttpStatus.FORBIDDEN, "GL0004", "접근 권한이 없습니다."), + UNCLEAN_VISITOR(HttpStatus.FORBIDDEN, "GL0005", "유효하지 않은 visitorId입니다."), // Survey (SV) + ALREADY_PARTICIPATED(HttpStatus.BAD_REQUEST, "SV0001", "이미 참여한 설문입니다."), SURVEY_NOT_FOUND(HttpStatus.NOT_FOUND, "SV0002", "설문을 찾을 수 없습니다."), INVALID_QUESTION_RESPONSE(HttpStatus.BAD_REQUEST, "SV0004", "유효하지 않은 질문 응답입니다."), INVALID_SECTION(HttpStatus.BAD_REQUEST, "SV0005", "유효하지 않은 섹션입니다."), diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/FingerprintApi.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/FingerprintApi.kt new file mode 100644 index 00000000..ea548acc --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/FingerprintApi.kt @@ -0,0 +1,50 @@ +package com.sbl.sulmun2yong.global.fingerprint + +import com.fingerprint.api.FingerprintApi +import com.fingerprint.model.BotdDetectionResult +import com.fingerprint.model.EventResponse +import com.fingerprint.model.ProductsResponse +import com.fingerprint.model.Response +import com.fingerprint.model.ResponseVisits +import com.fingerprint.sdk.ApiClient +import com.fingerprint.sdk.Configuration +import com.fingerprint.sdk.Region +import com.sbl.sulmun2yong.global.util.exception.UncleanVisitorException +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Component + +@Component +class FingerprintApi( + @Value("\${fingerprint.secret-key}") + private val secretKey: String, +) { + private val client: ApiClient = + Configuration.getDefaultApiClient( + secretKey, + Region.ASIA, + ) + val api = FingerprintApi(client) + + fun validateVisitorId(visitorId: String) { + val visits = getVisits(visitorId) + if (visits.isNullOrEmpty()) { + throw Exception("Invalid visitorId") + } + } + + private fun getVisits(visitorId: String): MutableList? { + val response: Response = api.getVisits(visitorId, null, null, 1, null, null) + val product = getEvent(response.visits[0].requestId) + if (product.tampering.data.result == true || + product.botd.data.bot.result !== BotdDetectionResult.ResultEnum.NOT_DETECTED + ) { + throw UncleanVisitorException() + } + return response.visits + } + + fun getEvent(requestId: String): ProductsResponse { + val response: EventResponse = api.getEvent(requestId) + return response.products + } +} diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/UncleanVisitorException.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/UncleanVisitorException.kt new file mode 100644 index 00000000..6d336f0e --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/UncleanVisitorException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.global.util.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class UncleanVisitorException : BusinessException(ErrorCode.UNCLEAN_VISITOR) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt index b2c9b4ce..d3c8686f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/adapter/ParticipantAdapter.kt @@ -25,4 +25,13 @@ class ParticipantAdapter( participantRepository .findBySurveyId(surveyId) .map { it.toDomain() } + + fun findBySurveyIdAndVisitorId( + surveyId: UUID, + visitorId: String, + ): Participant? = + participantRepository + .findBySurveyIdAndVisitorId(surveyId, visitorId) + .map { it.toDomain() } + .orElse(null) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResponseController.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResponseController.kt index b613fc52..5900f69f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResponseController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/SurveyResponseController.kt @@ -1,5 +1,6 @@ package com.sbl.sulmun2yong.survey.controller +import com.sbl.sulmun2yong.global.annotation.IsAdmin import com.sbl.sulmun2yong.survey.controller.doc.SurveyResponseApiDoc import com.sbl.sulmun2yong.survey.dto.request.SurveyResponseRequest import com.sbl.sulmun2yong.survey.service.SurveyResponseService @@ -21,5 +22,6 @@ class SurveyResponseController( override fun responseToSurvey( @PathVariable("survey-id") surveyId: UUID, @Valid @RequestBody surveyResponseRequest: SurveyResponseRequest, - ) = ResponseEntity.ok(surveyResponseService.responseToSurvey(surveyId, surveyResponseRequest)) + @IsAdmin isAdmin: Boolean, + ) = ResponseEntity.ok(surveyResponseService.responseToSurvey(surveyId, surveyResponseRequest, isAdmin)) } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResponseApiDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResponseApiDoc.kt index 51ca7228..dfac482b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResponseApiDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/controller/doc/SurveyResponseApiDoc.kt @@ -1,5 +1,6 @@ package com.sbl.sulmun2yong.survey.controller.doc +import com.sbl.sulmun2yong.global.annotation.IsAdmin import com.sbl.sulmun2yong.survey.dto.request.SurveyResponseRequest import com.sbl.sulmun2yong.survey.dto.response.SurveyParticipantResponse import io.swagger.v3.oas.annotations.Operation @@ -18,5 +19,6 @@ interface SurveyResponseApiDoc { fun responseToSurvey( @PathVariable("survey-id") surveyId: UUID, @Valid @RequestBody surveyResponseRequest: SurveyResponseRequest, + @IsAdmin isAdmin: Boolean, ): ResponseEntity } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Participant.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Participant.kt index 6a61ff7a..12edb444 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Participant.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/domain/Participant.kt @@ -5,14 +5,16 @@ import java.util.UUID data class Participant( val id: UUID, + val visitorId: String, val surveyId: UUID, val userId: UUID?, val createdAt: Date, ) { companion object { fun create( + visitorId: String, surveyId: UUID, userId: UUID?, - ) = Participant(UUID.randomUUID(), surveyId, userId, Date()) + ) = Participant(UUID.randomUUID(), visitorId, surveyId, userId, Date()) } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveyResponseRequest.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveyResponseRequest.kt index a9fa4184..97a3581f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveyResponseRequest.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/dto/request/SurveyResponseRequest.kt @@ -12,6 +12,8 @@ import java.util.UUID data class SurveyResponseRequest( @field:Valid val sectionResponses: List, + @field:Size(max = 100, message = "visitorId는 최대 100자까지 입력 가능합니다.") + val visitorId: String, ) { data class SectionResponseRequest( val sectionId: UUID, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ParticipantDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ParticipantDocument.kt index 6e07d2d4..960da0e7 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ParticipantDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/entity/ParticipantDocument.kt @@ -10,6 +10,7 @@ import java.util.UUID data class ParticipantDocument( @Id val id: UUID, + val visitorId: String, val surveyId: UUID, val userId: UUID?, ) : BaseTimeDocument() { @@ -17,6 +18,7 @@ data class ParticipantDocument( fun of(participant: Participant) = ParticipantDocument( id = participant.id, + visitorId = participant.visitorId, surveyId = participant.surveyId, userId = participant.userId, ) @@ -25,6 +27,7 @@ data class ParticipantDocument( fun toDomain() = Participant( id = this.id, + visitorId = this.visitorId, surveyId = this.surveyId, userId = this.userId, createdAt = this.createdAt, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/AlreadyParticipatedException.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/AlreadyParticipatedException.kt new file mode 100644 index 00000000..115168f1 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/exception/AlreadyParticipatedException.kt @@ -0,0 +1,6 @@ +package com.sbl.sulmun2yong.survey.exception + +import com.sbl.sulmun2yong.global.error.BusinessException +import com.sbl.sulmun2yong.global.error.ErrorCode + +class AlreadyParticipatedException : BusinessException(ErrorCode.ALREADY_PARTICIPATED) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ParticipantRepository.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ParticipantRepository.kt index 0e4f46c2..39fd1163 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ParticipantRepository.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/repository/ParticipantRepository.kt @@ -3,9 +3,15 @@ package com.sbl.sulmun2yong.survey.repository import com.sbl.sulmun2yong.survey.entity.ParticipantDocument import org.springframework.data.mongodb.repository.MongoRepository import org.springframework.stereotype.Repository +import java.util.Optional import java.util.UUID @Repository interface ParticipantRepository : MongoRepository { fun findBySurveyId(surveyId: UUID): List + + fun findBySurveyIdAndVisitorId( + surveyId: UUID, + visitorId: String, + ): Optional } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt index 53d1c7c8..c720c138 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/survey/service/SurveyResponseService.kt @@ -1,11 +1,13 @@ package com.sbl.sulmun2yong.survey.service +import com.sbl.sulmun2yong.global.fingerprint.FingerprintApi import com.sbl.sulmun2yong.survey.adapter.ParticipantAdapter import com.sbl.sulmun2yong.survey.adapter.ResponseAdapter import com.sbl.sulmun2yong.survey.adapter.SurveyAdapter import com.sbl.sulmun2yong.survey.domain.Participant import com.sbl.sulmun2yong.survey.dto.request.SurveyResponseRequest import com.sbl.sulmun2yong.survey.dto.response.SurveyParticipantResponse +import com.sbl.sulmun2yong.survey.exception.AlreadyParticipatedException import org.springframework.stereotype.Service import java.util.UUID @@ -14,19 +16,37 @@ class SurveyResponseService( val surveyAdapter: SurveyAdapter, val participantAdapter: ParticipantAdapter, val responseAdapter: ResponseAdapter, + val fingerprintApi: FingerprintApi, ) { // TODO: 트랜잭션 처리 추가하기 fun responseToSurvey( surveyId: UUID, surveyResponseRequest: SurveyResponseRequest, + isAdmin: Boolean, ): SurveyParticipantResponse { + val visitorId = surveyResponseRequest.visitorId + // 이미 참여한 설문인지 검증(Admin인 경우 스킵) + if (!isAdmin) { + validateIsAlreadyParticipated(surveyId, visitorId) + fingerprintApi.validateVisitorId(visitorId) + } val survey = surveyAdapter.getSurvey(surveyId) val surveyResponse = surveyResponseRequest.toDomain(surveyId) survey.validateResponse(surveyResponse) // TODO: 참가자 객체의 UserId에 실제 유저 값 넣기 - val participant = Participant.create(surveyId, null) + val participant = Participant.create(visitorId, surveyId, null) participantAdapter.insert(participant) responseAdapter.insertSurveyResponse(surveyResponse, participant.id) return SurveyParticipantResponse(participant.id, survey.isImmediateDraw()) } + + private fun validateIsAlreadyParticipated( + surveyId: UUID, + visitorId: String, + ) { + val participant = participantAdapter.findBySurveyIdAndVisitorId(surveyId, visitorId) + participant?.let { + throw AlreadyParticipatedException() + } + } } From 37605a78d8e1630302c7d9bf9b84a1c0f18a0bef Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 2 Oct 2024 21:22:24 +0900 Subject: [PATCH 185/201] =?UTF-8?q?[SBL-162]=20test=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EC=97=90=20visitorID=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt index b76f6669..0b6695d4 100644 --- a/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt +++ b/src/test/kotlin/com/sbl/sulmun2yong/survey/domain/ParticipantTest.kt @@ -12,6 +12,7 @@ class ParticipantTest { // given val participantId = UUID.randomUUID() val surveyId = UUID.randomUUID() + val visitorId = "abcdefg" val userId = UUID.randomUUID() val createdAt = Date() @@ -19,16 +20,17 @@ class ParticipantTest { mockedUUID.`when` { UUID.randomUUID() }.thenReturn(participantId) // when - val participant = Participant.create(surveyId, userId) + val participant = Participant.create(visitorId, surveyId, userId) // then with(participant) { assertEquals(participantId, this.id) assertEquals(surveyId, this.surveyId) + assertEquals(visitorId, this.visitorId) assertEquals(userId, this.userId) } } - assertEquals(createdAt, Participant(participantId, surveyId, userId, createdAt).createdAt) + assertEquals(createdAt, Participant(participantId, visitorId, surveyId, userId, createdAt).createdAt) } } From 733ae4f97b5bd9ba8fdcb8a135c1e8d2c7a275fd Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 2 Oct 2024 22:20:37 +0900 Subject: [PATCH 186/201] =?UTF-8?q?[SBL-149]=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EC=9D=98=20logger=EC=9D=98=20class=EA=B0=80?= =?UTF-8?q?=20GlobalExceptionHandler=EB=A1=9C=20=EB=90=98=EC=96=B4?= =?UTF-8?q?=EC=9E=88=EB=8D=98=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/migration/AddIsDeletedFieldAtSurveyDocument.kt | 3 +-- .../sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt index 9d2929ca..5cf19612 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt @@ -1,6 +1,5 @@ package com.sbl.sulmun2yong.global.migration -import com.sbl.sulmun2yong.global.error.GlobalExceptionHandler import com.sbl.sulmun2yong.survey.entity.SurveyDocument import io.mongock.api.annotations.ChangeUnit import io.mongock.api.annotations.Execution @@ -14,7 +13,7 @@ import org.springframework.data.mongodb.core.query.Update /** Surveys 컬렉션의 isDelete가 null인 경우 기본값 false를 넣는 Migration Class */ @ChangeUnit(id = "AddIsDeletedFieldAtSurveyDocument", order = "002", author = "hunhui") class AddIsDeletedFieldAtSurveyDocument { - private val log = LoggerFactory.getLogger(GlobalExceptionHandler::class.java) + private val log = LoggerFactory.getLogger(AddIsDeletedFieldAtSurveyDocument::class.java) @Execution fun addIsDeletedField(mongoTemplate: MongoTemplate) { diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt index 2b9cc619..90010675 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt @@ -1,6 +1,5 @@ package com.sbl.sulmun2yong.global.migration -import com.sbl.sulmun2yong.global.error.GlobalExceptionHandler import io.mongock.api.annotations.ChangeUnit import io.mongock.api.annotations.Execution import io.mongock.api.annotations.RollbackExecution @@ -28,7 +27,7 @@ class InitialMongoDBSetUp { ) } - private val log = LoggerFactory.getLogger(GlobalExceptionHandler::class.java) + private val log = LoggerFactory.getLogger(InitialMongoDBSetUp::class.java) @Execution fun initialSetup(mongoTemplate: MongoTemplate) { From b2429ec03b31f5fde319fe5df169b489f6482189 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 2 Oct 2024 22:21:17 +0900 Subject: [PATCH 187/201] =?UTF-8?q?[SBL-149]=20Surveys=20=EC=BB=AC?= =?UTF-8?q?=EB=A0=89=EC=85=98=EC=97=90=20rewardSettingType=EC=9D=84=20?= =?UTF-8?q?=EB=84=A3=EB=8A=94=20Migration=20Class=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migration/AddRewardSettingTypeAtSurvey.kt | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddRewardSettingTypeAtSurvey.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddRewardSettingTypeAtSurvey.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddRewardSettingTypeAtSurvey.kt new file mode 100644 index 00000000..0dd40d5f --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddRewardSettingTypeAtSurvey.kt @@ -0,0 +1,61 @@ +package com.sbl.sulmun2yong.global.migration + +import com.sbl.sulmun2yong.survey.repository.SurveyRepository +import io.mongock.api.annotations.ChangeUnit +import io.mongock.api.annotations.Execution +import io.mongock.api.annotations.RollbackExecution +import org.slf4j.LoggerFactory +import org.springframework.data.mongodb.core.MongoTemplate +import org.springframework.data.mongodb.core.query.Criteria +import org.springframework.data.mongodb.core.query.Query +import org.springframework.data.mongodb.core.query.Update + +/** Surveys 컬렉션에 rewardSettingType을 넣는 Migration Class */ +@ChangeUnit(id = "AddRewardSettingTypeAtSurvey", order = "003", author = "hunhui") +class AddRewardSettingTypeAtSurvey( + private val mongoTemplate: MongoTemplate, + private val surveyRepository: SurveyRepository, +) { + private val log = LoggerFactory.getLogger(AddRewardSettingTypeAtSurvey::class.java) + + @Execution + fun addRewardSettingTypeAtSurvey() { + // 조건 1: finishedAt이 null이면 rewardSettingType은 NO_REWARD + val queryNoReward = Query(Criteria.where("finishedAt").exists(false)) + val updateNoReward = Update().set("rewardSettingType", "NO_REWARD") + mongoTemplate.updateMulti(queryNoReward, updateNoReward, "surveys") + + // 조건 2: finishedAt이 존재하고 targetParticipantCount가 null이면 SELF_MANAGEMENT + val querySelfManagement = + Query( + Criteria().andOperator( + Criteria.where("finishedAt").exists(true), + Criteria.where("targetParticipantCount").exists(false), + ), + ) + val updateSelfManagement = Update().set("rewardSettingType", "SELF_MANAGEMENT") + mongoTemplate.updateMulti(querySelfManagement, updateSelfManagement, "surveys") + + // 조건 3: 나머지 경우는 IMMEDIATE_DRAW + val queryImmediateDraw = + Query( + Criteria().andOperator( + Criteria.where("finishedAt").exists(true), + Criteria.where("targetParticipantCount").exists(true), + ), + ) + val updateImmediateDraw = Update().set("rewardSettingType", "IMMEDIATE_DRAW") + mongoTemplate.updateMulti(queryImmediateDraw, updateImmediateDraw, "surveys") + + log.info("003-AddRewardSettingTypeAtSurvey 완료") + } + + @RollbackExecution + fun rollback() { + val query = Query(Criteria.where("rewardSettingType").exists(true)) + val update = Update().unset("rewardSettingType") + mongoTemplate.updateMulti(query, update, "surveys") + + log.warn("003-AddRewardSettingTypeAtSurvey 롤백") + } +} From 88d5b3d7270bba52eb25a237c50a7a4c7fcbb8f8 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 2 Oct 2024 22:21:55 +0900 Subject: [PATCH 188/201] =?UTF-8?q?[SBL-149]=20Surveys=20=EC=BB=AC?= =?UTF-8?q?=EB=A0=89=EC=85=98=EC=9D=98=20finishedAt=EC=9D=98=20=EB=B6=84?= =?UTF-8?q?=20=EB=8B=A8=EC=9C=84=20=EC=9D=B4=ED=95=98=EB=A5=BC=200?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95=ED=95=98=EB=8A=94=20Mig?= =?UTF-8?q?ration=20Class=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migration/UpdateFinishedAtAtSurvey.kt | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/migration/UpdateFinishedAtAtSurvey.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/UpdateFinishedAtAtSurvey.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/UpdateFinishedAtAtSurvey.kt new file mode 100644 index 00000000..247cb7da --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/UpdateFinishedAtAtSurvey.kt @@ -0,0 +1,61 @@ +package com.sbl.sulmun2yong.global.migration + +import com.sbl.sulmun2yong.survey.entity.SurveyDocument +import io.mongock.api.annotations.ChangeUnit +import io.mongock.api.annotations.Execution +import io.mongock.api.annotations.RollbackExecution +import org.slf4j.LoggerFactory +import org.springframework.data.mongodb.core.MongoTemplate +import org.springframework.data.mongodb.core.query.Criteria +import org.springframework.data.mongodb.core.query.Query +import org.springframework.data.mongodb.core.query.Update +import java.time.ZoneId + +/** Surveys 컬렉션의 finishedAt의 분 단위 이하를 0으로 수정하는 Migration Class */ +@ChangeUnit(id = "UpdateFinishedAtAtSurvey", order = "004", author = "hunhui") +class UpdateFinishedAtAtSurvey( + private val mongoTemplate: MongoTemplate, +) { + private val log = LoggerFactory.getLogger(UpdateFinishedAtAtSurvey::class.java) + + @Execution + fun updateFinishedAtAtSurvey() { + // finishedAt 필드가 존재하는 도큐먼트를 모두 조회 + val query = Query(Criteria.where("finishedAt").exists(true)) + val surveys = mongoTemplate.find(query, SurveyDocument::class.java) + + surveys.forEach { survey -> + survey.finishedAt?.let { + // finishedAt 시간을 분 이하 단위(초, 나노초) 제거 + val updatedFinishedAt = + it + .toInstant() + .atZone(ZoneId.systemDefault()) + .withMinute(0) + .withSecond(0) + .withNano(0) + .toInstant() + + // 업데이트 내용을 설정 + val update = Update().set("finishedAt", updatedFinishedAt) + + // id 기준으로 해당 도큐먼트 업데이트, id 필드 확인 필요 + val updateQuery = Query(Criteria.where("_id").`is`(survey.id)) // MongoDB에서 _id를 사용 + val result = mongoTemplate.updateFirst(updateQuery, update, "surveys") + + // 업데이트 결과 확인 + if (result.modifiedCount == 0L) { + log.warn("Survey ${survey.id} 업데이트 실패") + } else { + log.info("Survey ${survey.id} 업데이트 성공") + } + } + } + log.info("004-UpdateFinishedAtAtSurvey 완료") + } + + @RollbackExecution + fun rollback() { + log.warn("004-UpdateFinishedAtAtSurvey 롤백") + } +} From 09f445af95e340aa79f11940141e956bbcb418ed Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 2 Oct 2024 22:22:21 +0900 Subject: [PATCH 189/201] =?UTF-8?q?[SBL-149]=20=08Responses=20=EC=BB=AC?= =?UTF-8?q?=EB=A0=89=EC=85=98=EC=97=90=20surveyId=EB=A5=BC=20=EB=84=A3?= =?UTF-8?q?=EB=8A=94=20Migration=20Class=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migration/AddSurveyIdAtResponses.kt | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt new file mode 100644 index 00000000..6bc6e4c5 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt @@ -0,0 +1,77 @@ +package com.sbl.sulmun2yong.global.migration + +import com.sbl.sulmun2yong.survey.repository.SurveyRepository +import io.mongock.api.annotations.ChangeUnit +import io.mongock.api.annotations.Execution +import io.mongock.api.annotations.RollbackExecution +import org.bson.types.Binary +import org.slf4j.LoggerFactory +import org.springframework.data.mongodb.core.MongoTemplate +import org.springframework.data.mongodb.core.query.Criteria +import org.springframework.data.mongodb.core.query.Query +import org.springframework.data.mongodb.core.query.Update +import java.nio.ByteBuffer +import java.util.UUID + +/** Responses 컬렉션에 surveyId를 넣는 Migration Class */ +@ChangeUnit(id = "AddSurveyIdAtResponses", order = "005", author = "hunhui") +class AddSurveyIdAtResponses( + private val mongoTemplate: MongoTemplate, + private val surveyRepository: SurveyRepository, +) { + private val log = LoggerFactory.getLogger(AddSurveyIdAtResponses::class.java) + + @Execution + fun addSurveyIdAtResponses() { + val questionIdSurveyIdMap = + surveyRepository + .findAll() + .flatMap { survey -> + survey.sections.flatMap { section -> + section.questions.map { question -> + question.questionId to survey.id + } + } + }.toMap() + + val responseDocuments = mongoTemplate.find(Query(), Map::class.java, "responses") + + responseDocuments.forEach { response -> + val questionIdBinary = response["questionId"] as? Binary + val questionId = questionIdBinary?.let { binaryToUUID(it) } + if (questionId != null) { + val surveyId = questionIdSurveyIdMap[questionId] + println(surveyId) + if (surveyId != null) { + val update = Update().set("surveyId", surveyId) + val updateQuery = Query(Criteria.where("_id").`is`(response["_id"])) + mongoTemplate.updateFirst(updateQuery, update, "responses") + } else { + log.warn("Response ${response["_id"]}에 해당하는 surveyId를 찾을 수 없습니다.") + } + } else { + log.warn("Response ${response["_id"]}에 questionId가 없습니다.") + } + } + + log.info("005-AddSurveyIdAtResponses 완료") + } + + private fun binaryToUUID(binary: Binary): UUID { + val byteBuffer = ByteBuffer.wrap(binary.data) + val mostSigBits = byteBuffer.long + val leastSigBits = byteBuffer.long + return UUID(mostSigBits, leastSigBits) + } + + @RollbackExecution + fun rollback() { + mongoTemplate.updateMulti( + Query(Criteria.where("surveyId").exists(true)), + Update().unset("surveyId"), + "responses", + ) + + log.warn("005-AddSurveyIdAtResponses 롤백") + } +} From 9368cba507ea7db1941123b048483cc36a93ea12 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 2 Oct 2024 22:22:22 +0900 Subject: [PATCH 190/201] =?UTF-8?q?[SBL-162]=20=EC=A1=B4=EC=9E=AC=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20visitorId=EC=99=80=20=EB=B6=80?= =?UTF-8?q?=EC=A0=95=ED=95=9C=20=EC=A0=91=EA=B7=BC=20=EA=B0=90=EC=A7=80=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sulmun2yong/global/util/FingerprintApi.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/FingerprintApi.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/FingerprintApi.kt index ea548acc..014aa85a 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/util/FingerprintApi.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/FingerprintApi.kt @@ -30,21 +30,25 @@ class FingerprintApi( if (visits.isNullOrEmpty()) { throw Exception("Invalid visitorId") } + checkIsVisitorClean(visits) } private fun getVisits(visitorId: String): MutableList? { val response: Response = api.getVisits(visitorId, null, null, 1, null, null) - val product = getEvent(response.visits[0].requestId) - if (product.tampering.data.result == true || - product.botd.data.bot.result !== BotdDetectionResult.ResultEnum.NOT_DETECTED - ) { - throw UncleanVisitorException() - } return response.visits } - fun getEvent(requestId: String): ProductsResponse { + private fun getEvent(requestId: String): ProductsResponse { val response: EventResponse = api.getEvent(requestId) return response.products } + + private fun checkIsVisitorClean(visits: MutableList) { + val product = getEvent(visits[0].requestId) + if (product.tampering.data.result == true || + product.botd.data.bot.result !== BotdDetectionResult.ResultEnum.NOT_DETECTED + ) { + throw UncleanVisitorException() + } + } } From 6e49c29689834a432acc1cc172d9a4cb464e169a Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 2 Oct 2024 22:26:18 +0900 Subject: [PATCH 191/201] =?UTF-8?q?[SBL-169]=20=EB=A6=AC=EB=B7=B0=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...{AIGenerateController.kt => AIAIGenerateController.kt} | 6 +++--- .../com/sbl/sulmun2yong/ai/controller/AIChatController.kt | 8 -------- .../doc/{GenerateAPIDoc.kt => AIGenerateApiDoc.kt} | 2 +- 3 files changed, 4 insertions(+), 12 deletions(-) rename src/main/kotlin/com/sbl/sulmun2yong/ai/controller/{AIGenerateController.kt => AIAIGenerateController.kt} (95%) delete mode 100644 src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIChatController.kt rename src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/{GenerateAPIDoc.kt => AIGenerateApiDoc.kt} (97%) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIGenerateController.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIAIGenerateController.kt similarity index 95% rename from src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIGenerateController.kt rename to src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIAIGenerateController.kt index 575fa154..5a841048 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIGenerateController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIAIGenerateController.kt @@ -1,6 +1,6 @@ package com.sbl.sulmun2yong.ai.controller -import com.sbl.sulmun2yong.ai.controller.doc.GenerateAPIDoc +import com.sbl.sulmun2yong.ai.controller.doc.AIGenerateApiDoc import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithFileUrlRequest import com.sbl.sulmun2yong.ai.dto.request.SurveyGenerationWithTextDocumentRequest import com.sbl.sulmun2yong.ai.service.GenerateService @@ -16,9 +16,9 @@ import java.util.UUID @RestController @RequestMapping("/api/v1/ai/generate") -class AIGenerateController( +class AIAIGenerateController( private val generateService: GenerateService, -) : GenerateAPIDoc { +) : AIGenerateApiDoc { @PostMapping("/survey/file-url") override fun generateSurveyWithFileUrl( @RequestBody surveyGenerationWithFileUrlRequest: SurveyGenerationWithFileUrlRequest, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIChatController.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIChatController.kt deleted file mode 100644 index 957f9e56..00000000 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIChatController.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.sbl.sulmun2yong.ai.controller - -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController - -@RestController -@RequestMapping("/api/v1/ai/generate") -class AIChatController diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/AIGenerateApiDoc.kt similarity index 97% rename from src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt rename to src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/AIGenerateApiDoc.kt index e300aa07..1ebaa506 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/GenerateAPIDoc.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/doc/AIGenerateApiDoc.kt @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @Tag(name = "AI", description = "AI 기능 관련 API") -interface GenerateAPIDoc { +interface AIGenerateApiDoc { @Operation(summary = "파일을 통한 AI 설문 생성") @PostMapping("/survey/file-url") fun generateSurveyWithFileUrl( From 76c6e30298d2fc3c81a5f0af4b206dcb4a10cc92 Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Wed, 2 Oct 2024 22:27:29 +0900 Subject: [PATCH 192/201] =?UTF-8?q?[SBL-169]=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{AIAIGenerateController.kt => AIGenerateController.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/kotlin/com/sbl/sulmun2yong/ai/controller/{AIAIGenerateController.kt => AIGenerateController.kt} (98%) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIAIGenerateController.kt b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIGenerateController.kt similarity index 98% rename from src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIAIGenerateController.kt rename to src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIGenerateController.kt index 5a841048..902779dc 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIAIGenerateController.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/ai/controller/AIGenerateController.kt @@ -16,7 +16,7 @@ import java.util.UUID @RestController @RequestMapping("/api/v1/ai/generate") -class AIAIGenerateController( +class AIGenerateController( private val generateService: GenerateService, ) : AIGenerateApiDoc { @PostMapping("/survey/file-url") From dfcbd3df1396af73c015950a59a6d81221d87b2f Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 2 Oct 2024 22:32:34 +0900 Subject: [PATCH 193/201] =?UTF-8?q?[SBL-149]=20Migration=20Class=EA=B0=80?= =?UTF-8?q?=20SurveyDocument=EC=97=90=20=EC=98=81=ED=96=A5=20=EB=B0=9B?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/migration/AddIsDeletedFieldAtSurveyDocument.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt index 5cf19612..c77a552c 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt @@ -1,6 +1,5 @@ package com.sbl.sulmun2yong.global.migration -import com.sbl.sulmun2yong.survey.entity.SurveyDocument import io.mongock.api.annotations.ChangeUnit import io.mongock.api.annotations.Execution import io.mongock.api.annotations.RollbackExecution @@ -19,7 +18,7 @@ class AddIsDeletedFieldAtSurveyDocument { fun addIsDeletedField(mongoTemplate: MongoTemplate) { val query = Query(Criteria.where("isDeleted").`is`(null)) val update = Update().set("isDeleted", false) - mongoTemplate.updateMulti(query, update, SurveyDocument::class.java) + mongoTemplate.updateMulti(query, update, "surveys") log.info("002-AddIsDeletedFieldAtSurveyDocument 완료") } From 990985ae8e047afb52bdb64cebe017a662254b37 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 2 Oct 2024 22:33:13 +0900 Subject: [PATCH 194/201] =?UTF-8?q?[SBL-149]=20RewardHistories=20=EC=BB=AC?= =?UTF-8?q?=EB=A0=89=EC=85=98=EC=97=90=20createdAt=EC=9D=84=20=EB=84=A3?= =?UTF-8?q?=EB=8A=94=20Migration=20Class=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AddCreatedAtAtDrawingHistories.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddCreatedAtAtDrawingHistories.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddCreatedAtAtDrawingHistories.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddCreatedAtAtDrawingHistories.kt new file mode 100644 index 00000000..0f3ebf02 --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddCreatedAtAtDrawingHistories.kt @@ -0,0 +1,35 @@ +package com.sbl.sulmun2yong.global.migration + +import com.sbl.sulmun2yong.survey.repository.SurveyRepository +import io.mongock.api.annotations.ChangeUnit +import io.mongock.api.annotations.Execution +import io.mongock.api.annotations.RollbackExecution +import org.slf4j.LoggerFactory +import org.springframework.data.mongodb.core.MongoTemplate +import org.springframework.data.mongodb.core.query.Criteria +import org.springframework.data.mongodb.core.query.Query +import org.springframework.data.mongodb.core.query.Update +import java.util.Date + +/** RewardHistories 컬렉션에 createdAt을 넣는 Migration Class */ +@ChangeUnit(id = "AddCreatedAtAtDrawingHistories", order = "006", author = "hunhui") +class AddCreatedAtAtDrawingHistories( + private val mongoTemplate: MongoTemplate, + private val surveyRepository: SurveyRepository, +) { + private val log = LoggerFactory.getLogger(AddCreatedAtAtDrawingHistories::class.java) + + @Execution + fun addCreatedAtAtDrawingHistories() { + val query = Query(Criteria.where("createdAt").`is`(null)) + val now = Date() + val update = Update().set("createdAt", now) + mongoTemplate.updateMulti(query, update, "drawingHistories") + log.info("006-AddCreatedAtAtDrawingHistories 완료") + } + + @RollbackExecution + fun rollback() { + log.warn("006-AddCreatedAtAtDrawingHistories 롤백") + } +} From 7545503c213659095095b16c33cfaf06ab4b497b Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Wed, 2 Oct 2024 22:36:18 +0900 Subject: [PATCH 195/201] =?UTF-8?q?[SBL-149]=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EA=B7=B8=EC=99=80=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/migration/AddSurveyIdAtResponses.kt | 1 - .../global/migration/UpdateFinishedAtAtSurvey.kt | 13 +------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt index 6bc6e4c5..38da47c3 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt @@ -41,7 +41,6 @@ class AddSurveyIdAtResponses( val questionId = questionIdBinary?.let { binaryToUUID(it) } if (questionId != null) { val surveyId = questionIdSurveyIdMap[questionId] - println(surveyId) if (surveyId != null) { val update = Update().set("surveyId", surveyId) val updateQuery = Query(Criteria.where("_id").`is`(response["_id"])) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/UpdateFinishedAtAtSurvey.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/UpdateFinishedAtAtSurvey.kt index 247cb7da..bb3aec7f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/UpdateFinishedAtAtSurvey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/UpdateFinishedAtAtSurvey.kt @@ -20,13 +20,11 @@ class UpdateFinishedAtAtSurvey( @Execution fun updateFinishedAtAtSurvey() { - // finishedAt 필드가 존재하는 도큐먼트를 모두 조회 val query = Query(Criteria.where("finishedAt").exists(true)) val surveys = mongoTemplate.find(query, SurveyDocument::class.java) surveys.forEach { survey -> survey.finishedAt?.let { - // finishedAt 시간을 분 이하 단위(초, 나노초) 제거 val updatedFinishedAt = it .toInstant() @@ -36,19 +34,10 @@ class UpdateFinishedAtAtSurvey( .withNano(0) .toInstant() - // 업데이트 내용을 설정 val update = Update().set("finishedAt", updatedFinishedAt) - // id 기준으로 해당 도큐먼트 업데이트, id 필드 확인 필요 val updateQuery = Query(Criteria.where("_id").`is`(survey.id)) // MongoDB에서 _id를 사용 - val result = mongoTemplate.updateFirst(updateQuery, update, "surveys") - - // 업데이트 결과 확인 - if (result.modifiedCount == 0L) { - log.warn("Survey ${survey.id} 업데이트 실패") - } else { - log.info("Survey ${survey.id} 업데이트 성공") - } + mongoTemplate.updateFirst(updateQuery, update, "surveys") } } log.info("004-UpdateFinishedAtAtSurvey 완료") From ae643298d9a4830ad11a9006fa9aa63730bdaacf Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 3 Oct 2024 00:57:17 +0900 Subject: [PATCH 196/201] =?UTF-8?q?[SBL-149]=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=B4=88=EA=B8=B0=ED=99=94=20Migration=20Class=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0,=20=EC=9D=B4=EC=97=90=20=EB=94=B0=EB=A5=B8?= =?UTF-8?q?=20=EA=B0=81=20Migration=20Class=EB=93=A4=EC=9D=98=20order=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AddCreatedAtAtDrawingHistories.kt | 6 +-- .../AddIsDeletedFieldAtSurveyDocument.kt | 6 +-- .../migration/AddSurveyIdAtResponses.kt | 6 +-- .../global/migration/InitialMongoDBSetUp.kt | 47 ------------------- 4 files changed, 9 insertions(+), 56 deletions(-) delete mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddCreatedAtAtDrawingHistories.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddCreatedAtAtDrawingHistories.kt index 0f3ebf02..24c8a1cb 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddCreatedAtAtDrawingHistories.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddCreatedAtAtDrawingHistories.kt @@ -12,7 +12,7 @@ import org.springframework.data.mongodb.core.query.Update import java.util.Date /** RewardHistories 컬렉션에 createdAt을 넣는 Migration Class */ -@ChangeUnit(id = "AddCreatedAtAtDrawingHistories", order = "006", author = "hunhui") +@ChangeUnit(id = "AddCreatedAtAtDrawingHistories", order = "005", author = "hunhui") class AddCreatedAtAtDrawingHistories( private val mongoTemplate: MongoTemplate, private val surveyRepository: SurveyRepository, @@ -25,11 +25,11 @@ class AddCreatedAtAtDrawingHistories( val now = Date() val update = Update().set("createdAt", now) mongoTemplate.updateMulti(query, update, "drawingHistories") - log.info("006-AddCreatedAtAtDrawingHistories 완료") + log.info("005-AddCreatedAtAtDrawingHistories 완료") } @RollbackExecution fun rollback() { - log.warn("006-AddCreatedAtAtDrawingHistories 롤백") + log.warn("005-AddCreatedAtAtDrawingHistories 롤백") } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt index c77a552c..65075ea6 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsDeletedFieldAtSurveyDocument.kt @@ -10,7 +10,7 @@ import org.springframework.data.mongodb.core.query.Query import org.springframework.data.mongodb.core.query.Update /** Surveys 컬렉션의 isDelete가 null인 경우 기본값 false를 넣는 Migration Class */ -@ChangeUnit(id = "AddIsDeletedFieldAtSurveyDocument", order = "002", author = "hunhui") +@ChangeUnit(id = "AddIsDeletedFieldAtSurveyDocument", order = "001", author = "hunhui") class AddIsDeletedFieldAtSurveyDocument { private val log = LoggerFactory.getLogger(AddIsDeletedFieldAtSurveyDocument::class.java) @@ -19,11 +19,11 @@ class AddIsDeletedFieldAtSurveyDocument { val query = Query(Criteria.where("isDeleted").`is`(null)) val update = Update().set("isDeleted", false) mongoTemplate.updateMulti(query, update, "surveys") - log.info("002-AddIsDeletedFieldAtSurveyDocument 완료") + log.info("001-AddIsDeletedFieldAtSurveyDocument 완료") } @RollbackExecution fun rollback() { - log.warn("002-AddIsDeletedFieldAtSurveyDocument 롤백") + log.warn("001-AddIsDeletedFieldAtSurveyDocument 롤백") } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt index 38da47c3..57059f84 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt @@ -14,7 +14,7 @@ import java.nio.ByteBuffer import java.util.UUID /** Responses 컬렉션에 surveyId를 넣는 Migration Class */ -@ChangeUnit(id = "AddSurveyIdAtResponses", order = "005", author = "hunhui") +@ChangeUnit(id = "AddSurveyIdAtResponses", order = "002", author = "hunhui") class AddSurveyIdAtResponses( private val mongoTemplate: MongoTemplate, private val surveyRepository: SurveyRepository, @@ -53,7 +53,7 @@ class AddSurveyIdAtResponses( } } - log.info("005-AddSurveyIdAtResponses 완료") + log.info("002-AddSurveyIdAtResponses 완료") } private fun binaryToUUID(binary: Binary): UUID { @@ -71,6 +71,6 @@ class AddSurveyIdAtResponses( "responses", ) - log.warn("005-AddSurveyIdAtResponses 롤백") + log.warn("002-AddSurveyIdAtResponses 롤백") } } diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt deleted file mode 100644 index 90010675..00000000 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/InitialMongoDBSetUp.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.sbl.sulmun2yong.global.migration - -import io.mongock.api.annotations.ChangeUnit -import io.mongock.api.annotations.Execution -import io.mongock.api.annotations.RollbackExecution -import org.slf4j.LoggerFactory -import org.springframework.data.mongodb.core.MongoTemplate - -/** 최초에 컬렉션들을 생성하는 Migration Class */ -@ChangeUnit(id = "InitialMongoDBSetUp", order = "001", author = "hunhui") -class InitialMongoDBSetUp { - companion object { - private const val SURVEY_COLLECTION = "surveys" - private const val DRAWING_BOARD_COLLECTION = "drawingBoards" - private const val DRAWING_HISTORY_COLLECTION = "drawingHistories" - private const val PARTICIPANT_COLLECTION = "participants" - private const val RESPONSE_COLLECTION = "responses" - private const val USER_COLLECTION = "users" - val COLLECTIONS = - listOf( - SURVEY_COLLECTION, - DRAWING_BOARD_COLLECTION, - DRAWING_HISTORY_COLLECTION, - PARTICIPANT_COLLECTION, - RESPONSE_COLLECTION, - USER_COLLECTION, - ) - } - - private val log = LoggerFactory.getLogger(InitialMongoDBSetUp::class.java) - - @Execution - fun initialSetup(mongoTemplate: MongoTemplate) { - val collectionNames = mongoTemplate.db.listCollectionNames().toList() - for (collection in COLLECTIONS) { - if (!collectionNames.contains(collection)) { - mongoTemplate.db.createCollection(collection) - } - } - log.info("001-InitialMongoDBSetUp 완료") - } - - @RollbackExecution - fun rollback() { - log.warn("001-InitialMongoDBSetUp 롤백") - } -} From 8f56f0468058d466104a45e5d1c2a2fbf5b48c07 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 3 Oct 2024 01:00:24 +0900 Subject: [PATCH 197/201] =?UTF-8?q?[SBL-149]=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0,=20Document?= =?UTF-8?q?=20Class=EC=97=90=20=EC=9D=98=EC=A1=B4=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AddCreatedAtAtDrawingHistories.kt | 2 -- .../migration/AddRewardSettingTypeAtSurvey.kt | 2 -- .../migration/AddSurveyIdAtResponses.kt | 24 +++++-------------- .../migration/UpdateFinishedAtAtSurvey.kt | 11 ++++----- 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddCreatedAtAtDrawingHistories.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddCreatedAtAtDrawingHistories.kt index 24c8a1cb..5a2ada21 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddCreatedAtAtDrawingHistories.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddCreatedAtAtDrawingHistories.kt @@ -1,6 +1,5 @@ package com.sbl.sulmun2yong.global.migration -import com.sbl.sulmun2yong.survey.repository.SurveyRepository import io.mongock.api.annotations.ChangeUnit import io.mongock.api.annotations.Execution import io.mongock.api.annotations.RollbackExecution @@ -15,7 +14,6 @@ import java.util.Date @ChangeUnit(id = "AddCreatedAtAtDrawingHistories", order = "005", author = "hunhui") class AddCreatedAtAtDrawingHistories( private val mongoTemplate: MongoTemplate, - private val surveyRepository: SurveyRepository, ) { private val log = LoggerFactory.getLogger(AddCreatedAtAtDrawingHistories::class.java) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddRewardSettingTypeAtSurvey.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddRewardSettingTypeAtSurvey.kt index 0dd40d5f..7ee7ec2a 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddRewardSettingTypeAtSurvey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddRewardSettingTypeAtSurvey.kt @@ -1,6 +1,5 @@ package com.sbl.sulmun2yong.global.migration -import com.sbl.sulmun2yong.survey.repository.SurveyRepository import io.mongock.api.annotations.ChangeUnit import io.mongock.api.annotations.Execution import io.mongock.api.annotations.RollbackExecution @@ -14,7 +13,6 @@ import org.springframework.data.mongodb.core.query.Update @ChangeUnit(id = "AddRewardSettingTypeAtSurvey", order = "003", author = "hunhui") class AddRewardSettingTypeAtSurvey( private val mongoTemplate: MongoTemplate, - private val surveyRepository: SurveyRepository, ) { private val log = LoggerFactory.getLogger(AddRewardSettingTypeAtSurvey::class.java) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt index 57059f84..ff0f4641 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddSurveyIdAtResponses.kt @@ -1,6 +1,5 @@ package com.sbl.sulmun2yong.global.migration -import com.sbl.sulmun2yong.survey.repository.SurveyRepository import io.mongock.api.annotations.ChangeUnit import io.mongock.api.annotations.Execution import io.mongock.api.annotations.RollbackExecution @@ -10,26 +9,23 @@ import org.springframework.data.mongodb.core.MongoTemplate import org.springframework.data.mongodb.core.query.Criteria import org.springframework.data.mongodb.core.query.Query import org.springframework.data.mongodb.core.query.Update -import java.nio.ByteBuffer -import java.util.UUID /** Responses 컬렉션에 surveyId를 넣는 Migration Class */ @ChangeUnit(id = "AddSurveyIdAtResponses", order = "002", author = "hunhui") class AddSurveyIdAtResponses( private val mongoTemplate: MongoTemplate, - private val surveyRepository: SurveyRepository, ) { private val log = LoggerFactory.getLogger(AddSurveyIdAtResponses::class.java) @Execution fun addSurveyIdAtResponses() { + val surveys = mongoTemplate.find(Query(), Map::class.java, "surveys") val questionIdSurveyIdMap = - surveyRepository - .findAll() + surveys .flatMap { survey -> - survey.sections.flatMap { section -> - section.questions.map { question -> - question.questionId to survey.id + (survey["sections"] as List>).flatMap { section -> + (section["questions"] as List>).map { question -> + question["questionId"] to survey["_id"] } } }.toMap() @@ -37,8 +33,7 @@ class AddSurveyIdAtResponses( val responseDocuments = mongoTemplate.find(Query(), Map::class.java, "responses") responseDocuments.forEach { response -> - val questionIdBinary = response["questionId"] as? Binary - val questionId = questionIdBinary?.let { binaryToUUID(it) } + val questionId = response["questionId"] as? Binary if (questionId != null) { val surveyId = questionIdSurveyIdMap[questionId] if (surveyId != null) { @@ -56,13 +51,6 @@ class AddSurveyIdAtResponses( log.info("002-AddSurveyIdAtResponses 완료") } - private fun binaryToUUID(binary: Binary): UUID { - val byteBuffer = ByteBuffer.wrap(binary.data) - val mostSigBits = byteBuffer.long - val leastSigBits = byteBuffer.long - return UUID(mostSigBits, leastSigBits) - } - @RollbackExecution fun rollback() { mongoTemplate.updateMulti( diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/UpdateFinishedAtAtSurvey.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/UpdateFinishedAtAtSurvey.kt index bb3aec7f..f3df326b 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/UpdateFinishedAtAtSurvey.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/UpdateFinishedAtAtSurvey.kt @@ -1,6 +1,5 @@ package com.sbl.sulmun2yong.global.migration -import com.sbl.sulmun2yong.survey.entity.SurveyDocument import io.mongock.api.annotations.ChangeUnit import io.mongock.api.annotations.Execution import io.mongock.api.annotations.RollbackExecution @@ -10,6 +9,7 @@ import org.springframework.data.mongodb.core.query.Criteria import org.springframework.data.mongodb.core.query.Query import org.springframework.data.mongodb.core.query.Update import java.time.ZoneId +import java.util.Date /** Surveys 컬렉션의 finishedAt의 분 단위 이하를 0으로 수정하는 Migration Class */ @ChangeUnit(id = "UpdateFinishedAtAtSurvey", order = "004", author = "hunhui") @@ -20,13 +20,12 @@ class UpdateFinishedAtAtSurvey( @Execution fun updateFinishedAtAtSurvey() { - val query = Query(Criteria.where("finishedAt").exists(true)) - val surveys = mongoTemplate.find(query, SurveyDocument::class.java) + val surveys = mongoTemplate.find(Query(), Map::class.java, "surveys") surveys.forEach { survey -> - survey.finishedAt?.let { + survey["finishedAt"]?.let { val updatedFinishedAt = - it + (it as Date) .toInstant() .atZone(ZoneId.systemDefault()) .withMinute(0) @@ -36,7 +35,7 @@ class UpdateFinishedAtAtSurvey( val update = Update().set("finishedAt", updatedFinishedAt) - val updateQuery = Query(Criteria.where("_id").`is`(survey.id)) // MongoDB에서 _id를 사용 + val updateQuery = Query(Criteria.where("_id").`is`(survey["_id"])) mongoTemplate.updateFirst(updateQuery, update, "surveys") } } From bead4925abec61c27c5cbb857c5c93a77962e829 Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 3 Oct 2024 01:01:17 +0900 Subject: [PATCH 198/201] =?UTF-8?q?[SBL-149]=20Surveys=20=EC=BB=AC?= =?UTF-8?q?=EB=A0=89=EC=85=98=EC=9D=98=20isVisible=EC=9D=B4=20null?= =?UTF-8?q?=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20=EA=B8=B0=EB=B3=B8=EA=B0=92=20?= =?UTF-8?q?true=EB=A5=BC=20=EB=84=A3=EB=8A=94=20Migration=20Class=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AddIsVisibleFieldAtSurveyDocument.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsVisibleFieldAtSurveyDocument.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsVisibleFieldAtSurveyDocument.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsVisibleFieldAtSurveyDocument.kt new file mode 100644 index 00000000..ea7fb74c --- /dev/null +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/migration/AddIsVisibleFieldAtSurveyDocument.kt @@ -0,0 +1,29 @@ +package com.sbl.sulmun2yong.global.migration + +import io.mongock.api.annotations.ChangeUnit +import io.mongock.api.annotations.Execution +import io.mongock.api.annotations.RollbackExecution +import org.slf4j.LoggerFactory +import org.springframework.data.mongodb.core.MongoTemplate +import org.springframework.data.mongodb.core.query.Criteria +import org.springframework.data.mongodb.core.query.Query +import org.springframework.data.mongodb.core.query.Update + +/** Surveys 컬렉션의 isVisible이 null인 경우 기본값 true를 넣는 Migration Class */ +@ChangeUnit(id = "AddIsVisibleFieldAtSurveyDocument", order = "006", author = "hunhui") +class AddIsVisibleFieldAtSurveyDocument { + private val log = LoggerFactory.getLogger(AddIsVisibleFieldAtSurveyDocument::class.java) + + @Execution + fun addIsDeletedField(mongoTemplate: MongoTemplate) { + val query = Query(Criteria.where("isVisible").`is`(null)) + val update = Update().set("isVisible", true) + mongoTemplate.updateMulti(query, update, "surveys") + log.info("006-AddIsVisibleFieldAtSurveyDocument 완료") + } + + @RollbackExecution + fun rollback() { + log.warn("006-AddIsVisibleFieldAtSurveyDocument 롤백") + } +} From 12b79abf3d850bfa59e125bfa43dfce1a2e5accb Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 3 Oct 2024 01:27:54 +0900 Subject: [PATCH 199/201] =?UTF-8?q?[SBL-149]=20ADMIN=EC=9D=B4=20=EC=95=84?= =?UTF-8?q?=EB=8B=88=EC=97=AC=EB=8F=84=20=EC=84=A4=EB=AC=B8=20=EC=A0=9C?= =?UTF-8?q?=EC=9E=91=EC=9D=B4=20=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt index 97ad5a28..596c1fde 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/SecurityConfig.kt @@ -126,8 +126,7 @@ class SecurityConfig( authorize("/api/v1/surveys/results/**", authenticated) authorize("/api/v1/s3/**", authenticated) authorize("/api/v1/ai/**", authenticated) - // TODO: 추후에 AUTHENTICATED_USER 로 수정 - authorize("/api/v1/surveys/workbench/**", hasRole("ADMIN")) + authorize("/api/v1/surveys/workbench/**", authenticated) authorize("/**", permitAll) } exceptionHandling { From 7ddf6c9bedb12fa29b34e6c884b1a52f9bea3d0c Mon Sep 17 00:00:00 2001 From: Oh YoungJe Date: Thu, 3 Oct 2024 02:15:02 +0900 Subject: [PATCH 200/201] =?UTF-8?q?[SBL-175]=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=ED=81=AC=EA=B8=B0=20=EA=B2=80=EC=A6=9D=20=EB=B9=84=EC=A6=88?= =?UTF-8?q?=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/UserFileManagementConfig.kt | 4 ---- .../com/sbl/sulmun2yong/global/error/ErrorCode.kt | 1 - .../global/util/exception/OutOfFileSizeException.kt | 6 ------ .../global/util/validator/FileUploadValidator.kt | 10 ---------- 4 files changed, 21 deletions(-) delete mode 100644 src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/OutOfFileSizeException.kt diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/config/UserFileManagementConfig.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/config/UserFileManagementConfig.kt index f344cf7a..e8a36b82 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/config/UserFileManagementConfig.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/config/UserFileManagementConfig.kt @@ -5,7 +5,6 @@ import com.sbl.sulmun2yong.global.util.validator.FileUrlValidator import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.util.unit.DataSize import software.amazon.awssdk.auth.credentials.AwsBasicCredentials import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider import software.amazon.awssdk.regions.Region @@ -19,8 +18,6 @@ class UserFileManagementConfig( @Value("\${aws.s3.secret-key}") private val secretKey: String, // 파일 예외처리 관련 - @Value("\${aws.s3.max-file-size}") - private val maxFileSize: DataSize, @Value("\${aws.s3.max-file-name-length}") private val maxFileNameLength: Int, @Value("\${aws.s3.allowed-extensions}") @@ -44,7 +41,6 @@ class UserFileManagementConfig( @Bean fun createFileUploadValidator(): FileUploadValidator = FileUploadValidator.from( - maxFileSize = maxFileSize, maxFileNameLength = maxFileNameLength, allowedExtensions = allowedExtensions, allowedContentTypes = allowedContentTypes, diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index 1a24c3c5..4603415f 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -59,7 +59,6 @@ enum class ErrorCode( // File Validator (FV) INVALID_EXTENSION(HttpStatus.BAD_REQUEST, "FV0001", "허용하지 않는 확장자입니다."), - OUT_OF_FILE_SIZE(HttpStatus.BAD_REQUEST, "FV0002", "파일 크기가 너무 큽니다."), FILE_NAME_TOO_SHORT(HttpStatus.BAD_REQUEST, "FV0003", "파일 이름이 너무 짧습니다."), FILE_NAME_TOO_LONG(HttpStatus.BAD_REQUEST, "FV0004", "파일 이름이 너무 깁니다."), NO_FILE_EXIST(HttpStatus.BAD_REQUEST, "FV0005", "파일이 존재하지 않습니다."), diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/OutOfFileSizeException.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/OutOfFileSizeException.kt deleted file mode 100644 index b5688e4b..00000000 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/util/exception/OutOfFileSizeException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.sbl.sulmun2yong.global.util.exception - -import com.sbl.sulmun2yong.global.error.BusinessException -import com.sbl.sulmun2yong.global.error.ErrorCode - -class OutOfFileSizeException : BusinessException(ErrorCode.OUT_OF_FILE_SIZE) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/util/validator/FileUploadValidator.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/util/validator/FileUploadValidator.kt index d91f5bb1..7a958ac2 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/util/validator/FileUploadValidator.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/util/validator/FileUploadValidator.kt @@ -5,29 +5,23 @@ import com.sbl.sulmun2yong.global.util.exception.FileNameTooShortException import com.sbl.sulmun2yong.global.util.exception.InvalidExtensionException import com.sbl.sulmun2yong.global.util.exception.NoExtensionExistException import com.sbl.sulmun2yong.global.util.exception.NoFileExistException -import com.sbl.sulmun2yong.global.util.exception.OutOfFileSizeException -import org.springframework.util.unit.DataSize import org.springframework.web.multipart.MultipartFile class FileUploadValidator( - private val maxFileSize: Long, private val maxFileNameLength: Int, private val allowedExtensions: MutableList, private val allowedContentTypes: MutableList, ) { companion object { fun from( - maxFileSize: DataSize, maxFileNameLength: Int, allowedExtensions: String, allowedContentTypes: String, ): FileUploadValidator { - val fileSize = maxFileSize.toBytes() val extensions = allowedExtensions.split(",").toMutableList() val contentTypes = allowedContentTypes.split(",").toMutableList() return FileUploadValidator( - fileSize, maxFileNameLength, extensions, contentTypes, @@ -39,7 +33,6 @@ class FileUploadValidator( fun checkIsAllowedExtensionOrType(contentType: String): Boolean = allowedExtensions.contains(contentType) || allowedContentTypes.any { contentType.startsWith(it) } - val fileSize = file.size val fileName: String? = file.originalFilename val contentType = file.contentType @@ -49,9 +42,6 @@ class FileUploadValidator( if (contentType.isNullOrBlank()) { throw NoExtensionExistException() } - if (fileSize > maxFileSize) { - throw OutOfFileSizeException() - } if (fileName.isNullOrBlank()) { throw FileNameTooShortException() } From e96c763295cfa0d1aa3c425e004872315a71ba1d Mon Sep 17 00:00:00 2001 From: JeongHunHui Date: Thu, 3 Oct 2024 02:43:57 +0900 Subject: [PATCH 201/201] =?UTF-8?q?[SBL-175]=20GlobalExceptionHandler?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=ED=8C=8C=EC=9D=BC=20=EC=82=AC=EC=9D=B4?= =?UTF-8?q?=EC=A6=88=EA=B0=80=20=EB=84=88=EB=AC=B4=20=ED=81=B4=20=EB=95=8C?= =?UTF-8?q?=20=EB=B0=9C=EC=83=9D=ED=95=98=EB=8A=94=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EB=A1=9C=20=ED=95=B8=EB=93=A4=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt | 1 + .../sbl/sulmun2yong/global/error/GlobalExceptionHandler.kt | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt index 4603415f..99bf49b9 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/ErrorCode.kt @@ -12,6 +12,7 @@ enum class ErrorCode( INPUT_INVALID_VALUE(HttpStatus.BAD_REQUEST, "GL0002", "잘못된 입력입니다."), LOGIN_REQUIRED(HttpStatus.UNAUTHORIZED, "GL0003", "로그인이 필요합니다."), ACCESS_DENIED(HttpStatus.FORBIDDEN, "GL0004", "접근 권한이 없습니다."), + FILE_SIZE_EXCEEDED(HttpStatus.PAYLOAD_TOO_LARGE, "GL0005", "파일은 최대 5mb까지만 업로드할 수 있습니다."), // Survey (SV) SURVEY_NOT_FOUND(HttpStatus.NOT_FOUND, "SV0002", "설문을 찾을 수 없습니다."), diff --git a/src/main/kotlin/com/sbl/sulmun2yong/global/error/GlobalExceptionHandler.kt b/src/main/kotlin/com/sbl/sulmun2yong/global/error/GlobalExceptionHandler.kt index 5d104c53..6fa02f96 100644 --- a/src/main/kotlin/com/sbl/sulmun2yong/global/error/GlobalExceptionHandler.kt +++ b/src/main/kotlin/com/sbl/sulmun2yong/global/error/GlobalExceptionHandler.kt @@ -7,6 +7,7 @@ import org.springframework.security.core.AuthenticationException import org.springframework.web.bind.MethodArgumentNotValidException import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.RestControllerAdvice +import org.springframework.web.multipart.MaxUploadSizeExceededException @RestControllerAdvice class GlobalExceptionHandler { @@ -49,4 +50,10 @@ class GlobalExceptionHandler { log.warn(e.message, e) return ErrorResponse.of(ErrorCode.INPUT_INVALID_VALUE, e.bindingResult) } + + @ExceptionHandler(MaxUploadSizeExceededException::class) + protected fun handleMaxUploadSizeExceededException(e: MaxUploadSizeExceededException): ErrorResponse { + log.warn(e.message, e) + return ErrorResponse.of(ErrorCode.FILE_SIZE_EXCEEDED) + } }