From 82b99226d499588d05c1932950808bed9a066108 Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Fri, 20 Dec 2024 20:03:10 +0900 Subject: [PATCH 01/16] =?UTF-8?q?ev-service=20=EB=A7=88=EC=9D=B4=EA=B7=B8?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/handler/EvServiceHandler.kt | 43 +++++++++++++++++++ api/src/main/kotlin/router/MainRouter.kt | 11 +++++ 2 files changed, 54 insertions(+) create mode 100644 api/src/main/kotlin/handler/EvServiceHandler.kt diff --git a/api/src/main/kotlin/handler/EvServiceHandler.kt b/api/src/main/kotlin/handler/EvServiceHandler.kt new file mode 100644 index 00000000..5126fab6 --- /dev/null +++ b/api/src/main/kotlin/handler/EvServiceHandler.kt @@ -0,0 +1,43 @@ +package com.wafflestudio.snu4t.handler + +import com.wafflestudio.snu4t.config.SnuttEvWebClient +import com.wafflestudio.snu4t.middleware.SnuttRestApiDefaultMiddleware +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod +import org.springframework.http.MediaType +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.BodyInserters +import org.springframework.web.reactive.function.client.awaitBody +import org.springframework.web.reactive.function.server.ServerRequest +import org.springframework.web.reactive.function.server.awaitBody + +@Component +class EvServiceHandler( + private val snuttEvWebClient: SnuttEvWebClient, + snuttRestApiDefaultMiddleware: SnuttRestApiDefaultMiddleware, +) : ServiceHandler(snuttRestApiDefaultMiddleware) { + suspend fun handleGet(req: ServerRequest) = handleRouting(req, HttpMethod.GET) + + suspend fun handlePost(req: ServerRequest) = handleRouting(req, HttpMethod.POST) + + suspend fun handleDelete(req: ServerRequest) = handleRouting(req, HttpMethod.DELETE) + + suspend fun handlePatch(req: ServerRequest) = handleRouting(req, HttpMethod.PATCH) + + suspend fun handleRouting( + req: ServerRequest, + method: HttpMethod, + ) = handle(req) { + val userId = req.userId + val requestPath = req.pathVariable("requestPath") + val originalBody = req.awaitBody() + + snuttEvWebClient.method(method) + .uri { builder -> builder.path("/v1").path(requestPath).build() } + .header("Snutt-User-Id", userId) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .body(BodyInserters.fromValue(originalBody)) + .retrieve() + .awaitBody() + } +} diff --git a/api/src/main/kotlin/router/MainRouter.kt b/api/src/main/kotlin/router/MainRouter.kt index 7c8c0874..cb7ce974 100644 --- a/api/src/main/kotlin/router/MainRouter.kt +++ b/api/src/main/kotlin/router/MainRouter.kt @@ -8,6 +8,7 @@ import com.wafflestudio.snu4t.handler.ConfigHandler import com.wafflestudio.snu4t.handler.CoursebookHandler import com.wafflestudio.snu4t.handler.DeviceHandler import com.wafflestudio.snu4t.handler.EvHandler +import com.wafflestudio.snu4t.handler.EvServiceHandler import com.wafflestudio.snu4t.handler.FeedbackHandler import com.wafflestudio.snu4t.handler.FriendHandler import com.wafflestudio.snu4t.handler.FriendTableHandler @@ -68,6 +69,7 @@ class MainRouter( private val tagHandler: TagHandler, private val feedbackHandler: FeedbackHandler, private val staticPageHandler: StaticPageHandler, + private val evServiceHandler: EvServiceHandler, ) { @Bean fun healthCheck() = @@ -295,6 +297,15 @@ class MainRouter( GET("/ev/lectures/{lectureId}/summary", evHandler::getLectureEvaluationSummary) } + @Bean + fun evServiceRouter() = + v1CoRouter { + GET("/ev-service/{*requestPath}", evServiceHandler::handleGet) + POST("/ev-service/{*requestPath}", evServiceHandler::handlePost) + DELETE("/ev-service/{*requestPath}", evServiceHandler::handleDelete) + PATCH("/ev-service/{*requestPath}", evServiceHandler::handlePatch) + } + @Bean @CoursebookDocs fun coursebookRouter() = From dfbd1c8268cc737f462af49aa7838b63026ad296 Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Fri, 20 Dec 2024 21:24:31 +0900 Subject: [PATCH 02/16] =?UTF-8?q?/v1/users/me/lectures/latest=20=EB=A7=88?= =?UTF-8?q?=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/handler/EvServiceHandler.kt | 62 ++++++++++++++++++- api/src/main/kotlin/router/MainRouter.kt | 1 + .../repository/CoursebookRepository.kt | 2 + .../coursebook/service/CoursebookService.kt | 4 ++ 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/api/src/main/kotlin/handler/EvServiceHandler.kt b/api/src/main/kotlin/handler/EvServiceHandler.kt index 5126fab6..cbbaa7d0 100644 --- a/api/src/main/kotlin/handler/EvServiceHandler.kt +++ b/api/src/main/kotlin/handler/EvServiceHandler.kt @@ -1,7 +1,13 @@ package com.wafflestudio.snu4t.handler +import com.fasterxml.jackson.databind.ObjectMapper +import com.wafflestudio.snu4t.common.enum.Semester import com.wafflestudio.snu4t.config.SnuttEvWebClient +import com.wafflestudio.snu4t.coursebook.service.CoursebookService import com.wafflestudio.snu4t.middleware.SnuttRestApiDefaultMiddleware +import com.wafflestudio.snu4t.timetables.data.TimetableLecture +import com.wafflestudio.snu4t.timetables.service.TimetableService +import kotlinx.coroutines.flow.toList import org.springframework.http.HttpHeaders import org.springframework.http.HttpMethod import org.springframework.http.MediaType @@ -10,9 +16,13 @@ import org.springframework.web.reactive.function.BodyInserters import org.springframework.web.reactive.function.client.awaitBody import org.springframework.web.reactive.function.server.ServerRequest import org.springframework.web.reactive.function.server.awaitBody +import org.springframework.web.util.UriComponentsBuilder +import java.net.URLEncoder @Component class EvServiceHandler( + private val coursebookService: CoursebookService, + private val timetableService: TimetableService, private val snuttEvWebClient: SnuttEvWebClient, snuttRestApiDefaultMiddleware: SnuttRestApiDefaultMiddleware, ) : ServiceHandler(snuttRestApiDefaultMiddleware) { @@ -24,7 +34,7 @@ class EvServiceHandler( suspend fun handlePatch(req: ServerRequest) = handleRouting(req, HttpMethod.PATCH) - suspend fun handleRouting( + private suspend fun handleRouting( req: ServerRequest, method: HttpMethod, ) = handle(req) { @@ -40,4 +50,54 @@ class EvServiceHandler( .retrieve() .awaitBody() } + + suspend fun getMyLatestLectures(req: ServerRequest) = + handle(req) { + val userId = req.userId + val requestQueryParams = req.queryParams() + val recentLectures = + coursebookService.getLastTwoCourseBooks().flatMap { coursebook -> + timetableService.getTimetablesBySemester(userId, coursebook.year, coursebook.semester) + .toList() + .flatMap { + timetable -> + timetable.lectures.map { lecture -> EvLectureDto(lecture, coursebook.year, coursebook.semester) } + } + } + val recentLecturesJson = ObjectMapper().writeValueAsString(recentLectures).filterNot { it.isWhitespace() } + val encodedJson = URLEncoder.encode(recentLecturesJson, "EUC-KR") + + snuttEvWebClient.get() + .uri { builder -> + UriComponentsBuilder.fromUri(builder.build()) + .path("/v1/users/me/lectures/latest") + .queryParam("snutt_lecture_info", encodedJson) + .queryParams(requestQueryParams) + .build(true).toUri() + } + .header("Snutt-User-Id", userId) + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .awaitBody() + } } + +data class EvLectureDto( + val year: Int, + val semester: Int, + val instructor: String?, + val courseNumber: String?, +) + +fun EvLectureDto( + timetableLecture: TimetableLecture, + year: Int, + semester: Semester, +): EvLectureDto = + EvLectureDto( + year = year, + semester = semester.value, + instructor = timetableLecture.instructor, + courseNumber = timetableLecture.courseNumber, + ) diff --git a/api/src/main/kotlin/router/MainRouter.kt b/api/src/main/kotlin/router/MainRouter.kt index cb7ce974..55b605a7 100644 --- a/api/src/main/kotlin/router/MainRouter.kt +++ b/api/src/main/kotlin/router/MainRouter.kt @@ -300,6 +300,7 @@ class MainRouter( @Bean fun evServiceRouter() = v1CoRouter { + GET("/ev-service/v1/users/me/lectures/latest", evServiceHandler::getMyLatestLectures) GET("/ev-service/{*requestPath}", evServiceHandler::handleGet) POST("/ev-service/{*requestPath}", evServiceHandler::handlePost) DELETE("/ev-service/{*requestPath}", evServiceHandler::handleDelete) diff --git a/core/src/main/kotlin/coursebook/repository/CoursebookRepository.kt b/core/src/main/kotlin/coursebook/repository/CoursebookRepository.kt index 3524136f..527d9ece 100644 --- a/core/src/main/kotlin/coursebook/repository/CoursebookRepository.kt +++ b/core/src/main/kotlin/coursebook/repository/CoursebookRepository.kt @@ -9,4 +9,6 @@ interface CoursebookRepository : CoroutineCrudRepository { suspend fun findFirstByOrderByYearDescSemesterDesc(): Coursebook suspend fun findAllByOrderByYearDescSemesterDesc(): List + + suspend fun findTop2ByOrderByYearDescSemesterDesc(): List } diff --git a/core/src/main/kotlin/coursebook/service/CoursebookService.kt b/core/src/main/kotlin/coursebook/service/CoursebookService.kt index 72186b53..46a2ba49 100644 --- a/core/src/main/kotlin/coursebook/service/CoursebookService.kt +++ b/core/src/main/kotlin/coursebook/service/CoursebookService.kt @@ -8,6 +8,8 @@ interface CoursebookService { suspend fun getLatestCoursebook(): Coursebook suspend fun getCoursebooks(): List + + suspend fun getLastTwoCourseBooks(): List } @Service @@ -15,4 +17,6 @@ class CoursebookServiceImpl(private val coursebookRepository: CoursebookReposito override suspend fun getLatestCoursebook(): Coursebook = coursebookRepository.findFirstByOrderByYearDescSemesterDesc() override suspend fun getCoursebooks(): List = coursebookRepository.findAllByOrderByYearDescSemesterDesc() + + override suspend fun getLastTwoCourseBooks(): List = coursebookRepository.findTop2ByOrderByYearDescSemesterDesc() } From 1e6d62d0e710531a2bedc097d77887081d833eaf Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Fri, 20 Dec 2024 21:45:10 +0900 Subject: [PATCH 03/16] =?UTF-8?q?dto=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/handler/EvServiceHandler.kt | 24 ++----------------- .../kotlin/evaluation/dto/EvLectureInfoDto.kt | 23 ++++++++++++++++++ 2 files changed, 25 insertions(+), 22 deletions(-) create mode 100644 core/src/main/kotlin/evaluation/dto/EvLectureInfoDto.kt diff --git a/api/src/main/kotlin/handler/EvServiceHandler.kt b/api/src/main/kotlin/handler/EvServiceHandler.kt index cbbaa7d0..8bd5fa32 100644 --- a/api/src/main/kotlin/handler/EvServiceHandler.kt +++ b/api/src/main/kotlin/handler/EvServiceHandler.kt @@ -1,11 +1,10 @@ package com.wafflestudio.snu4t.handler import com.fasterxml.jackson.databind.ObjectMapper -import com.wafflestudio.snu4t.common.enum.Semester import com.wafflestudio.snu4t.config.SnuttEvWebClient import com.wafflestudio.snu4t.coursebook.service.CoursebookService +import com.wafflestudio.snu4t.evaluation.dto.EvLectureInfoDto import com.wafflestudio.snu4t.middleware.SnuttRestApiDefaultMiddleware -import com.wafflestudio.snu4t.timetables.data.TimetableLecture import com.wafflestudio.snu4t.timetables.service.TimetableService import kotlinx.coroutines.flow.toList import org.springframework.http.HttpHeaders @@ -61,7 +60,7 @@ class EvServiceHandler( .toList() .flatMap { timetable -> - timetable.lectures.map { lecture -> EvLectureDto(lecture, coursebook.year, coursebook.semester) } + timetable.lectures.map { lecture -> EvLectureInfoDto(lecture, coursebook.year, coursebook.semester) } } } val recentLecturesJson = ObjectMapper().writeValueAsString(recentLectures).filterNot { it.isWhitespace() } @@ -82,22 +81,3 @@ class EvServiceHandler( .awaitBody() } } - -data class EvLectureDto( - val year: Int, - val semester: Int, - val instructor: String?, - val courseNumber: String?, -) - -fun EvLectureDto( - timetableLecture: TimetableLecture, - year: Int, - semester: Semester, -): EvLectureDto = - EvLectureDto( - year = year, - semester = semester.value, - instructor = timetableLecture.instructor, - courseNumber = timetableLecture.courseNumber, - ) diff --git a/core/src/main/kotlin/evaluation/dto/EvLectureInfoDto.kt b/core/src/main/kotlin/evaluation/dto/EvLectureInfoDto.kt new file mode 100644 index 00000000..1537e9fc --- /dev/null +++ b/core/src/main/kotlin/evaluation/dto/EvLectureInfoDto.kt @@ -0,0 +1,23 @@ +package com.wafflestudio.snu4t.evaluation.dto + +import com.wafflestudio.snu4t.common.enum.Semester +import com.wafflestudio.snu4t.timetables.data.TimetableLecture + +data class EvLectureInfoDto( + val year: Int, + val semester: Int, + val instructor: String?, + val courseNumber: String?, +) + +fun EvLectureInfoDto( + timetableLecture: TimetableLecture, + year: Int, + semester: Semester, +): EvLectureInfoDto = + EvLectureInfoDto( + year = year, + semester = semester.value, + instructor = timetableLecture.instructor, + courseNumber = timetableLecture.courseNumber, + ) From 7301714d41da559b52e7ab4baa6f184fbd97d9fd Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Fri, 20 Dec 2024 22:53:30 +0900 Subject: [PATCH 04/16] =?UTF-8?q?service=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F?= =?UTF-8?q?=20=EC=B5=9C=EA=B7=BC=20=EA=B3=BC=EB=AA=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/handler/EvServiceHandler.kt | 82 ++++------------- .../kotlin/timetable/TimetableIntegTest.kt | 2 + .../repository/CoursebookRepository.kt | 2 +- .../coursebook/service/CoursebookService.kt | 7 +- .../kotlin/evaluation/dto/EvLectureInfoDto.kt | 2 + .../kotlin/evaluation/service/EvService.kt | 88 +++++++++++++++++++ 6 files changed, 117 insertions(+), 66 deletions(-) create mode 100644 core/src/main/kotlin/evaluation/service/EvService.kt diff --git a/api/src/main/kotlin/handler/EvServiceHandler.kt b/api/src/main/kotlin/handler/EvServiceHandler.kt index 8bd5fa32..5273154f 100644 --- a/api/src/main/kotlin/handler/EvServiceHandler.kt +++ b/api/src/main/kotlin/handler/EvServiceHandler.kt @@ -1,83 +1,39 @@ package com.wafflestudio.snu4t.handler -import com.fasterxml.jackson.databind.ObjectMapper -import com.wafflestudio.snu4t.config.SnuttEvWebClient -import com.wafflestudio.snu4t.coursebook.service.CoursebookService -import com.wafflestudio.snu4t.evaluation.dto.EvLectureInfoDto +import com.wafflestudio.snu4t.evaluation.service.EvService import com.wafflestudio.snu4t.middleware.SnuttRestApiDefaultMiddleware -import com.wafflestudio.snu4t.timetables.service.TimetableService -import kotlinx.coroutines.flow.toList -import org.springframework.http.HttpHeaders import org.springframework.http.HttpMethod -import org.springframework.http.MediaType import org.springframework.stereotype.Component -import org.springframework.web.reactive.function.BodyInserters -import org.springframework.web.reactive.function.client.awaitBody import org.springframework.web.reactive.function.server.ServerRequest import org.springframework.web.reactive.function.server.awaitBody -import org.springframework.web.util.UriComponentsBuilder -import java.net.URLEncoder @Component class EvServiceHandler( - private val coursebookService: CoursebookService, - private val timetableService: TimetableService, - private val snuttEvWebClient: SnuttEvWebClient, + private val evService: EvService, snuttRestApiDefaultMiddleware: SnuttRestApiDefaultMiddleware, ) : ServiceHandler(snuttRestApiDefaultMiddleware) { - suspend fun handleGet(req: ServerRequest) = handleRouting(req, HttpMethod.GET) - - suspend fun handlePost(req: ServerRequest) = handleRouting(req, HttpMethod.POST) - - suspend fun handleDelete(req: ServerRequest) = handleRouting(req, HttpMethod.DELETE) + suspend fun handleGet(req: ServerRequest) = + handle(req) { + evService.handleRouting(req.userId, req.pathVariable("requestPath"), req.queryParams(), req.awaitBody(), HttpMethod.GET) + } - suspend fun handlePatch(req: ServerRequest) = handleRouting(req, HttpMethod.PATCH) + suspend fun handlePost(req: ServerRequest) = + handle(req) { + evService.handleRouting(req.userId, req.pathVariable("requestPath"), req.queryParams(), req.awaitBody(), HttpMethod.POST) + } - private suspend fun handleRouting( - req: ServerRequest, - method: HttpMethod, - ) = handle(req) { - val userId = req.userId - val requestPath = req.pathVariable("requestPath") - val originalBody = req.awaitBody() + suspend fun handleDelete(req: ServerRequest) = + handle(req) { + evService.handleRouting(req.userId, req.pathVariable("requestPath"), req.queryParams(), req.awaitBody(), HttpMethod.DELETE) + } - snuttEvWebClient.method(method) - .uri { builder -> builder.path("/v1").path(requestPath).build() } - .header("Snutt-User-Id", userId) - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .body(BodyInserters.fromValue(originalBody)) - .retrieve() - .awaitBody() - } + suspend fun handlePatch(req: ServerRequest) = + handle(req) { + evService.handleRouting(req.userId, req.pathVariable("requestPath"), req.queryParams(), req.awaitBody(), HttpMethod.PATCH) + } suspend fun getMyLatestLectures(req: ServerRequest) = handle(req) { - val userId = req.userId - val requestQueryParams = req.queryParams() - val recentLectures = - coursebookService.getLastTwoCourseBooks().flatMap { coursebook -> - timetableService.getTimetablesBySemester(userId, coursebook.year, coursebook.semester) - .toList() - .flatMap { - timetable -> - timetable.lectures.map { lecture -> EvLectureInfoDto(lecture, coursebook.year, coursebook.semester) } - } - } - val recentLecturesJson = ObjectMapper().writeValueAsString(recentLectures).filterNot { it.isWhitespace() } - val encodedJson = URLEncoder.encode(recentLecturesJson, "EUC-KR") - - snuttEvWebClient.get() - .uri { builder -> - UriComponentsBuilder.fromUri(builder.build()) - .path("/v1/users/me/lectures/latest") - .queryParam("snutt_lecture_info", encodedJson) - .queryParams(requestQueryParams) - .build(true).toUri() - } - .header("Snutt-User-Id", userId) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .retrieve() - .awaitBody() + evService.getMyLatestLectures(req.userId, req.queryParams()) } } diff --git a/api/src/test/kotlin/timetable/TimetableIntegTest.kt b/api/src/test/kotlin/timetable/TimetableIntegTest.kt index d9166044..77336b28 100644 --- a/api/src/test/kotlin/timetable/TimetableIntegTest.kt +++ b/api/src/test/kotlin/timetable/TimetableIntegTest.kt @@ -3,6 +3,7 @@ package com.wafflestudio.snu4t.timetable import BaseIntegTest import com.ninjasquad.springmockk.MockkBean import com.wafflestudio.snu4t.evaluation.repository.SnuttEvRepository +import com.wafflestudio.snu4t.evaluation.service.EvService import com.wafflestudio.snu4t.fixture.TimetableFixture import com.wafflestudio.snu4t.fixture.UserFixture import com.wafflestudio.snu4t.handler.RequestContext @@ -25,6 +26,7 @@ import timetables.dto.TimetableBriefDto class TimetableIntegTest( @MockkBean private val mockMiddleware: SnuttRestApiDefaultMiddleware, @MockkBean private val mockSnuttEvRepository: SnuttEvRepository, + @MockkBean private val evService: EvService, val mainRouter: MainRouter, val timetableFixture: TimetableFixture, val userFixture: UserFixture, diff --git a/core/src/main/kotlin/coursebook/repository/CoursebookRepository.kt b/core/src/main/kotlin/coursebook/repository/CoursebookRepository.kt index 527d9ece..fef885db 100644 --- a/core/src/main/kotlin/coursebook/repository/CoursebookRepository.kt +++ b/core/src/main/kotlin/coursebook/repository/CoursebookRepository.kt @@ -10,5 +10,5 @@ interface CoursebookRepository : CoroutineCrudRepository { suspend fun findAllByOrderByYearDescSemesterDesc(): List - suspend fun findTop2ByOrderByYearDescSemesterDesc(): List + suspend fun findTop3ByOrderByYearDescSemesterDesc(): List } diff --git a/core/src/main/kotlin/coursebook/service/CoursebookService.kt b/core/src/main/kotlin/coursebook/service/CoursebookService.kt index 46a2ba49..464b59c3 100644 --- a/core/src/main/kotlin/coursebook/service/CoursebookService.kt +++ b/core/src/main/kotlin/coursebook/service/CoursebookService.kt @@ -9,7 +9,7 @@ interface CoursebookService { suspend fun getCoursebooks(): List - suspend fun getLastTwoCourseBooks(): List + suspend fun getLastTwoCourseBooksBeforeCurrent(): List } @Service @@ -18,5 +18,8 @@ class CoursebookServiceImpl(private val coursebookRepository: CoursebookReposito override suspend fun getCoursebooks(): List = coursebookRepository.findAllByOrderByYearDescSemesterDesc() - override suspend fun getLastTwoCourseBooks(): List = coursebookRepository.findTop2ByOrderByYearDescSemesterDesc() + override suspend fun getLastTwoCourseBooksBeforeCurrent(): List = + coursebookRepository.findTop3ByOrderByYearDescSemesterDesc().slice( + 1..2, + ) } diff --git a/core/src/main/kotlin/evaluation/dto/EvLectureInfoDto.kt b/core/src/main/kotlin/evaluation/dto/EvLectureInfoDto.kt index 1537e9fc..54389f33 100644 --- a/core/src/main/kotlin/evaluation/dto/EvLectureInfoDto.kt +++ b/core/src/main/kotlin/evaluation/dto/EvLectureInfoDto.kt @@ -1,5 +1,6 @@ package com.wafflestudio.snu4t.evaluation.dto +import com.fasterxml.jackson.annotation.JsonProperty import com.wafflestudio.snu4t.common.enum.Semester import com.wafflestudio.snu4t.timetables.data.TimetableLecture @@ -7,6 +8,7 @@ data class EvLectureInfoDto( val year: Int, val semester: Int, val instructor: String?, + @JsonProperty("course_number") val courseNumber: String?, ) diff --git a/core/src/main/kotlin/evaluation/service/EvService.kt b/core/src/main/kotlin/evaluation/service/EvService.kt new file mode 100644 index 00000000..8dcb0127 --- /dev/null +++ b/core/src/main/kotlin/evaluation/service/EvService.kt @@ -0,0 +1,88 @@ +package com.wafflestudio.snu4t.evaluation.service + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.wafflestudio.snu4t.common.util.buildMultiValueMap +import com.wafflestudio.snu4t.config.SnuttEvWebClient +import com.wafflestudio.snu4t.coursebook.service.CoursebookService +import com.wafflestudio.snu4t.evaluation.dto.EvLectureInfoDto +import com.wafflestudio.snu4t.timetables.service.TimetableService +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.withContext +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod +import org.springframework.http.MediaType +import org.springframework.stereotype.Service +import org.springframework.util.MultiValueMap +import org.springframework.web.reactive.function.BodyInserters +import org.springframework.web.reactive.function.client.awaitBody +import org.springframework.web.util.UriComponentsBuilder +import java.net.URLEncoder + +@Service +class EvService( + @Qualifier("snuttevServer") + private val snuttEvWebClient: SnuttEvWebClient, + private val timetableService: TimetableService, + private val coursebookService: CoursebookService, +) { + suspend fun handleRouting( + userId: String, + requestPath: String, + requestQueryParams: MultiValueMap = buildMultiValueMap(mapOf()), + originalBody: String, + method: HttpMethod, + ): Map = + snuttEvWebClient.method(method) + .uri { builder -> builder.path("/v1").path(requestPath).queryParams(requestQueryParams).build() } + .header("Snutt-User-Id", userId) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .body(BodyInserters.fromValue(originalBody)) + .retrieve() + .awaitBody() + + suspend fun getMyLatestLectures( + userId: String, + requestQueryParams: MultiValueMap? = null, + ): Map { + val recentLectures: List = + coursebookService.getLastTwoCourseBooksBeforeCurrent().flatMap { coursebook -> + timetableService.getTimetablesBySemester(userId, coursebook.year, coursebook.semester) + .toList() + .flatMap { timetable -> + timetable.lectures.map { lecture -> + EvLectureInfoDto( + lecture, + coursebook.year, + coursebook.semester, + ) + } + } + } + + val recentLecturesJson = + ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE) + .writeValueAsString(recentLectures) + val encodedJson = + withContext(Dispatchers.IO) { + URLEncoder.encode(recentLecturesJson, "UTF-8") + } + + return snuttEvWebClient.get() + .uri { builder -> + UriComponentsBuilder.fromUri(builder.build()) + .path("/v1/users/me/lectures/latest") + .queryParam("snutt_lecture_info", encodedJson) + .queryParams(requestQueryParams) + .build(true).toUri() + } + .header("Snutt-User-Id", userId) + .header(HttpHeaders.CONTENT_ENCODING, "UTF-8") + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .awaitBody() + } +} From f4465d75bb14b74db13d3c97a6828fbee2ed59bc Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Sat, 21 Dec 2024 10:29:34 +0900 Subject: [PATCH 05/16] handle empty body --- api/src/main/kotlin/handler/EvServiceHandler.kt | 15 ++++++++++----- .../main/kotlin/evaluation/service/EvService.kt | 2 -- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/api/src/main/kotlin/handler/EvServiceHandler.kt b/api/src/main/kotlin/handler/EvServiceHandler.kt index 5273154f..814bea54 100644 --- a/api/src/main/kotlin/handler/EvServiceHandler.kt +++ b/api/src/main/kotlin/handler/EvServiceHandler.kt @@ -2,10 +2,11 @@ package com.wafflestudio.snu4t.handler import com.wafflestudio.snu4t.evaluation.service.EvService import com.wafflestudio.snu4t.middleware.SnuttRestApiDefaultMiddleware +import kotlinx.coroutines.reactor.awaitSingleOrNull import org.springframework.http.HttpMethod import org.springframework.stereotype.Component import org.springframework.web.reactive.function.server.ServerRequest -import org.springframework.web.reactive.function.server.awaitBody +import org.springframework.web.reactive.function.server.bodyToMono @Component class EvServiceHandler( @@ -14,22 +15,26 @@ class EvServiceHandler( ) : ServiceHandler(snuttRestApiDefaultMiddleware) { suspend fun handleGet(req: ServerRequest) = handle(req) { - evService.handleRouting(req.userId, req.pathVariable("requestPath"), req.queryParams(), req.awaitBody(), HttpMethod.GET) + val body = req.bodyToMono().awaitSingleOrNull() ?: "" + evService.handleRouting(req.userId, req.pathVariable("requestPath"), req.queryParams(), body, HttpMethod.GET) } suspend fun handlePost(req: ServerRequest) = handle(req) { - evService.handleRouting(req.userId, req.pathVariable("requestPath"), req.queryParams(), req.awaitBody(), HttpMethod.POST) + val body = req.bodyToMono().awaitSingleOrNull() ?: "" + evService.handleRouting(req.userId, req.pathVariable("requestPath"), req.queryParams(), body, HttpMethod.POST) } suspend fun handleDelete(req: ServerRequest) = handle(req) { - evService.handleRouting(req.userId, req.pathVariable("requestPath"), req.queryParams(), req.awaitBody(), HttpMethod.DELETE) + val body = req.bodyToMono().awaitSingleOrNull() ?: "" + evService.handleRouting(req.userId, req.pathVariable("requestPath"), req.queryParams(), body, HttpMethod.DELETE) } suspend fun handlePatch(req: ServerRequest) = handle(req) { - evService.handleRouting(req.userId, req.pathVariable("requestPath"), req.queryParams(), req.awaitBody(), HttpMethod.PATCH) + val body = req.bodyToMono().awaitSingleOrNull() ?: "" + evService.handleRouting(req.userId, req.pathVariable("requestPath"), req.queryParams(), body, HttpMethod.PATCH) } suspend fun getMyLatestLectures(req: ServerRequest) = diff --git a/core/src/main/kotlin/evaluation/service/EvService.kt b/core/src/main/kotlin/evaluation/service/EvService.kt index 8dcb0127..4dc0dd0d 100644 --- a/core/src/main/kotlin/evaluation/service/EvService.kt +++ b/core/src/main/kotlin/evaluation/service/EvService.kt @@ -10,7 +10,6 @@ import com.wafflestudio.snu4t.timetables.service.TimetableService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.toList import kotlinx.coroutines.withContext -import org.springframework.beans.factory.annotation.Qualifier import org.springframework.http.HttpHeaders import org.springframework.http.HttpMethod import org.springframework.http.MediaType @@ -23,7 +22,6 @@ import java.net.URLEncoder @Service class EvService( - @Qualifier("snuttevServer") private val snuttEvWebClient: SnuttEvWebClient, private val timetableService: TimetableService, private val coursebookService: CoursebookService, From 0f009154a86d5f68daf451a4e667225175edf81e Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Sat, 21 Dec 2024 10:48:30 +0900 Subject: [PATCH 06/16] fix endpoint --- core/src/main/kotlin/evaluation/service/EvService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/kotlin/evaluation/service/EvService.kt b/core/src/main/kotlin/evaluation/service/EvService.kt index 4dc0dd0d..0fb53003 100644 --- a/core/src/main/kotlin/evaluation/service/EvService.kt +++ b/core/src/main/kotlin/evaluation/service/EvService.kt @@ -34,7 +34,7 @@ class EvService( method: HttpMethod, ): Map = snuttEvWebClient.method(method) - .uri { builder -> builder.path("/v1").path(requestPath).queryParams(requestQueryParams).build() } + .uri { builder -> builder.path(requestPath).queryParams(requestQueryParams).build() } .header("Snutt-User-Id", userId) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .body(BodyInserters.fromValue(originalBody)) From 774bfa414503cb4bfec3dbe15b5222aa98ccbf57 Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Sat, 21 Dec 2024 11:29:54 +0900 Subject: [PATCH 07/16] =?UTF-8?q?=EC=9D=91=EB=8B=B5=EC=9D=98=20user=5Fid?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=A0=95=EB=B3=B4=EB=A1=9C=20?= =?UTF-8?q?=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/evaluation/dto/EvUserDto.kt | 16 ++++++++ .../kotlin/evaluation/service/EvService.kt | 41 +++++++++++++++---- 2 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 core/src/main/kotlin/evaluation/dto/EvUserDto.kt diff --git a/core/src/main/kotlin/evaluation/dto/EvUserDto.kt b/core/src/main/kotlin/evaluation/dto/EvUserDto.kt new file mode 100644 index 00000000..73a69fda --- /dev/null +++ b/core/src/main/kotlin/evaluation/dto/EvUserDto.kt @@ -0,0 +1,16 @@ +package com.wafflestudio.snu4t.evaluation.dto + +import com.wafflestudio.snu4t.users.data.User + +data class EvUserDto( + val id: String?, + val email: String?, + val local_id: String?, +) + +fun EvUserDto(user: User) = + EvUserDto( + id = user.id, + email = user.email, + local_id = user.credential.localId, + ) diff --git a/core/src/main/kotlin/evaluation/service/EvService.kt b/core/src/main/kotlin/evaluation/service/EvService.kt index 0fb53003..16d402ee 100644 --- a/core/src/main/kotlin/evaluation/service/EvService.kt +++ b/core/src/main/kotlin/evaluation/service/EvService.kt @@ -6,7 +6,9 @@ import com.wafflestudio.snu4t.common.util.buildMultiValueMap import com.wafflestudio.snu4t.config.SnuttEvWebClient import com.wafflestudio.snu4t.coursebook.service.CoursebookService import com.wafflestudio.snu4t.evaluation.dto.EvLectureInfoDto +import com.wafflestudio.snu4t.evaluation.dto.EvUserDto import com.wafflestudio.snu4t.timetables.service.TimetableService +import com.wafflestudio.snu4t.users.service.UserService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.toList import kotlinx.coroutines.withContext @@ -25,6 +27,7 @@ class EvService( private val snuttEvWebClient: SnuttEvWebClient, private val timetableService: TimetableService, private val coursebookService: CoursebookService, + private val userService: UserService, ) { suspend fun handleRouting( userId: String, @@ -32,19 +35,22 @@ class EvService( requestQueryParams: MultiValueMap = buildMultiValueMap(mapOf()), originalBody: String, method: HttpMethod, - ): Map = - snuttEvWebClient.method(method) - .uri { builder -> builder.path(requestPath).queryParams(requestQueryParams).build() } - .header("Snutt-User-Id", userId) - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .body(BodyInserters.fromValue(originalBody)) - .retrieve() - .awaitBody() + ): Map { + val result: MutableMap = + snuttEvWebClient.method(method) + .uri { builder -> builder.path(requestPath).queryParams(requestQueryParams).build() } + .header("Snutt-User-Id", userId) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .body(BodyInserters.fromValue(originalBody)) + .retrieve() + .awaitBody>() + return updateUserInfo(result) + } suspend fun getMyLatestLectures( userId: String, requestQueryParams: MultiValueMap? = null, - ): Map { + ): Map { val recentLectures: List = coursebookService.getLastTwoCourseBooksBeforeCurrent().flatMap { coursebook -> timetableService.getTimetablesBySemester(userId, coursebook.year, coursebook.semester) @@ -83,4 +89,21 @@ class EvService( .retrieve() .awaitBody() } + + private suspend fun updateUserInfo(data: MutableMap): MutableMap { + val updatedMap: MutableMap = mutableMapOf() + for ((k, v) in data.entries) { + if (k == "user_id") { + val userDto = runCatching { EvUserDto(userService.getUser(v as String)) }.getOrNull() + updatedMap["user"] = userDto + } else { + when (v) { + is List<*> -> updatedMap[k] = v.map { updateUserInfo(it as MutableMap) } + is MutableMap<*, *> -> updatedMap[k] = updateUserInfo(v as MutableMap) + else -> updatedMap[k] = v + } + } + } + return updatedMap + } } From 9a392cd1786d066ca410d2e7ad6f4c259df273ae Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Sat, 21 Dec 2024 20:37:44 +0900 Subject: [PATCH 08/16] throw errors from ev --- .../main/kotlin/evaluation/service/EvService.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/core/src/main/kotlin/evaluation/service/EvService.kt b/core/src/main/kotlin/evaluation/service/EvService.kt index 16d402ee..e21852f0 100644 --- a/core/src/main/kotlin/evaluation/service/EvService.kt +++ b/core/src/main/kotlin/evaluation/service/EvService.kt @@ -11,15 +11,19 @@ import com.wafflestudio.snu4t.timetables.service.TimetableService import com.wafflestudio.snu4t.users.service.UserService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.reactor.awaitSingle import kotlinx.coroutines.withContext import org.springframework.http.HttpHeaders import org.springframework.http.HttpMethod +import org.springframework.http.HttpStatusCode import org.springframework.http.MediaType import org.springframework.stereotype.Service import org.springframework.util.MultiValueMap import org.springframework.web.reactive.function.BodyInserters -import org.springframework.web.reactive.function.client.awaitBody +import org.springframework.web.reactive.function.client.bodyToMono +import org.springframework.web.server.ResponseStatusException import org.springframework.web.util.UriComponentsBuilder +import reactor.core.publisher.Mono import java.net.URLEncoder @Service @@ -43,7 +47,9 @@ class EvService( .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .body(BodyInserters.fromValue(originalBody)) .retrieve() - .awaitBody>() + .onStatus(HttpStatusCode::isError) { response -> Mono.error(ResponseStatusException(response.statusCode())) } + .bodyToMono>() + .awaitSingle() return updateUserInfo(result) } @@ -87,7 +93,9 @@ class EvService( .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .retrieve() - .awaitBody() + .onStatus(HttpStatusCode::isError) { response -> Mono.error(ResponseStatusException(response.statusCode())) } + .bodyToMono>() + .awaitSingle() } private suspend fun updateUserInfo(data: MutableMap): MutableMap { From e14d853f0fe4b41b291e85c95ab750e59f42dbd5 Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Sat, 21 Dec 2024 20:38:50 +0900 Subject: [PATCH 09/16] add headers --- core/src/main/kotlin/evaluation/service/EvService.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/kotlin/evaluation/service/EvService.kt b/core/src/main/kotlin/evaluation/service/EvService.kt index e21852f0..e18e2efc 100644 --- a/core/src/main/kotlin/evaluation/service/EvService.kt +++ b/core/src/main/kotlin/evaluation/service/EvService.kt @@ -44,6 +44,8 @@ class EvService( snuttEvWebClient.method(method) .uri { builder -> builder.path(requestPath).queryParams(requestQueryParams).build() } .header("Snutt-User-Id", userId) + .header(HttpHeaders.CONTENT_ENCODING, "UTF-8") + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .body(BodyInserters.fromValue(originalBody)) .retrieve() From 4013766cd6be29969ebff2b9bf3a1fb83c9e8055 Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Sat, 21 Dec 2024 20:43:07 +0900 Subject: [PATCH 10/16] suppress unchecked cast warning --- core/src/main/kotlin/evaluation/service/EvService.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/kotlin/evaluation/service/EvService.kt b/core/src/main/kotlin/evaluation/service/EvService.kt index e18e2efc..8a93d710 100644 --- a/core/src/main/kotlin/evaluation/service/EvService.kt +++ b/core/src/main/kotlin/evaluation/service/EvService.kt @@ -100,6 +100,7 @@ class EvService( .awaitSingle() } + @Suppress("UNCHECKED_CAST") private suspend fun updateUserInfo(data: MutableMap): MutableMap { val updatedMap: MutableMap = mutableMapOf() for ((k, v) in data.entries) { From 563f222cf61a48d460a3840fcdb4c32ea1aad3ed Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Mon, 23 Dec 2024 22:54:06 +0900 Subject: [PATCH 11/16] =?UTF-8?q?webclient=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EA=B7=B8=EB=8C=80=EB=A1=9C=20=EB=84=98=EA=B2=A8=EC=A3=BC?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/src/main/kotlin/filter/ErrorWebFilter.kt | 73 ++++++++++++------- .../kotlin/common/exception/ProxyException.kt | 8 ++ .../kotlin/evaluation/service/EvService.kt | 26 ++++--- 3 files changed, 70 insertions(+), 37 deletions(-) create mode 100644 core/src/main/kotlin/common/exception/ProxyException.kt diff --git a/api/src/main/kotlin/filter/ErrorWebFilter.kt b/api/src/main/kotlin/filter/ErrorWebFilter.kt index 0ded5dc5..b9197d43 100644 --- a/api/src/main/kotlin/filter/ErrorWebFilter.kt +++ b/api/src/main/kotlin/filter/ErrorWebFilter.kt @@ -2,6 +2,7 @@ package com.wafflestudio.snu4t.filter import com.fasterxml.jackson.databind.ObjectMapper import com.wafflestudio.snu4t.common.exception.ErrorType +import com.wafflestudio.snu4t.common.exception.ProxyException import com.wafflestudio.snu4t.common.exception.Snu4tException import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus @@ -26,36 +27,52 @@ class ErrorWebFilter( ): Mono { return chain.filter(exchange) .onErrorResume { throwable -> - val errorBody: ErrorBody - val httpStatusCode: HttpStatusCode - when (throwable) { - is Snu4tException -> { - httpStatusCode = throwable.error.httpStatus - errorBody = makeErrorBody(throwable) - } - is ResponseStatusException -> { - httpStatusCode = throwable.statusCode - errorBody = - makeErrorBody( - Snu4tException(errorMessage = throwable.body.title ?: ErrorType.DEFAULT_ERROR.errorMessage), - ) - } - else -> { - log.error(throwable.message, throwable) - httpStatusCode = HttpStatus.INTERNAL_SERVER_ERROR - errorBody = makeErrorBody(Snu4tException()) + if (throwable is ProxyException) { + exchange.response.statusCode = throwable.statusCode + exchange.response.headers.contentType = MediaType.APPLICATION_JSON + exchange.response.writeWith( + Mono.just( + exchange.response + .bufferFactory() + .wrap(objectMapper.writeValueAsBytes(throwable.errorBody)), + ), + ) + } else { + val errorBody: ErrorBody + val httpStatusCode: HttpStatusCode + when (throwable) { + is Snu4tException -> { + httpStatusCode = throwable.error.httpStatus + errorBody = makeErrorBody(throwable) + } + + is ResponseStatusException -> { + httpStatusCode = throwable.statusCode + errorBody = + makeErrorBody( + Snu4tException( + errorMessage = throwable.body.title ?: ErrorType.DEFAULT_ERROR.errorMessage, + ), + ) + } + + else -> { + log.error(throwable.message, throwable) + httpStatusCode = HttpStatus.INTERNAL_SERVER_ERROR + errorBody = makeErrorBody(Snu4tException()) + } } - } - exchange.response.statusCode = httpStatusCode - exchange.response.headers.contentType = MediaType.APPLICATION_JSON - exchange.response.writeWith( - Mono.just( - exchange.response - .bufferFactory() - .wrap(objectMapper.writeValueAsBytes(errorBody)), - ), - ) + exchange.response.statusCode = httpStatusCode + exchange.response.headers.contentType = MediaType.APPLICATION_JSON + exchange.response.writeWith( + Mono.just( + exchange.response + .bufferFactory() + .wrap(objectMapper.writeValueAsBytes(errorBody)), + ), + ) + } } } diff --git a/core/src/main/kotlin/common/exception/ProxyException.kt b/core/src/main/kotlin/common/exception/ProxyException.kt new file mode 100644 index 00000000..f4e6c668 --- /dev/null +++ b/core/src/main/kotlin/common/exception/ProxyException.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.snu4t.common.exception + +import org.springframework.http.HttpStatusCode + +class ProxyException( + val statusCode: HttpStatusCode, + val errorBody: Map, +) : RuntimeException(errorBody.toString()) diff --git a/core/src/main/kotlin/evaluation/service/EvService.kt b/core/src/main/kotlin/evaluation/service/EvService.kt index 8a93d710..d13e08c1 100644 --- a/core/src/main/kotlin/evaluation/service/EvService.kt +++ b/core/src/main/kotlin/evaluation/service/EvService.kt @@ -2,6 +2,7 @@ package com.wafflestudio.snu4t.evaluation.service import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.wafflestudio.snu4t.common.exception.ProxyException import com.wafflestudio.snu4t.common.util.buildMultiValueMap import com.wafflestudio.snu4t.config.SnuttEvWebClient import com.wafflestudio.snu4t.coursebook.service.CoursebookService @@ -21,7 +22,6 @@ import org.springframework.stereotype.Service import org.springframework.util.MultiValueMap import org.springframework.web.reactive.function.BodyInserters import org.springframework.web.reactive.function.client.bodyToMono -import org.springframework.web.server.ResponseStatusException import org.springframework.web.util.UriComponentsBuilder import reactor.core.publisher.Mono import java.net.URLEncoder @@ -45,11 +45,15 @@ class EvService( .uri { builder -> builder.path(requestPath).queryParams(requestQueryParams).build() } .header("Snutt-User-Id", userId) .header(HttpHeaders.CONTENT_ENCODING, "UTF-8") - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .body(BodyInserters.fromValue(originalBody)) .retrieve() - .onStatus(HttpStatusCode::isError) { response -> Mono.error(ResponseStatusException(response.statusCode())) } + .onStatus(HttpStatusCode::isError) { response -> + response.bodyToMono>() + .flatMap { errorBody -> + Mono.error(ProxyException(response.statusCode(), errorBody)) + } + } .bodyToMono>() .awaitSingle() return updateUserInfo(result) @@ -74,12 +78,12 @@ class EvService( } } - val recentLecturesJson = - ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE) - .writeValueAsString(recentLectures) val encodedJson = withContext(Dispatchers.IO) { - URLEncoder.encode(recentLecturesJson, "UTF-8") + URLEncoder.encode( + ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE).writeValueAsString(recentLectures), + "UTF-8", + ) } return snuttEvWebClient.get() @@ -92,10 +96,14 @@ class EvService( } .header("Snutt-User-Id", userId) .header(HttpHeaders.CONTENT_ENCODING, "UTF-8") - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .retrieve() - .onStatus(HttpStatusCode::isError) { response -> Mono.error(ResponseStatusException(response.statusCode())) } + .onStatus(HttpStatusCode::isError) { response -> + response.bodyToMono>() + .flatMap { errorBody -> + Mono.error(ProxyException(response.statusCode(), errorBody)) + } + } .bodyToMono>() .awaitSingle() } From 04a15fd41232f02c09602f0d2d785f97efc12eae Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Tue, 24 Dec 2024 10:46:07 +0900 Subject: [PATCH 12/16] serialize error body --- core/src/main/kotlin/common/exception/ProxyException.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/common/exception/ProxyException.kt b/core/src/main/kotlin/common/exception/ProxyException.kt index f4e6c668..ceb44182 100644 --- a/core/src/main/kotlin/common/exception/ProxyException.kt +++ b/core/src/main/kotlin/common/exception/ProxyException.kt @@ -1,8 +1,9 @@ package com.wafflestudio.snu4t.common.exception +import com.fasterxml.jackson.databind.ObjectMapper import org.springframework.http.HttpStatusCode class ProxyException( val statusCode: HttpStatusCode, val errorBody: Map, -) : RuntimeException(errorBody.toString()) +) : RuntimeException(ObjectMapper().writeValueAsString(errorBody)) From 94709ce84067f8dd90ffded6a0e8a0f6656ffeeb Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Wed, 8 Jan 2025 14:41:15 +0900 Subject: [PATCH 13/16] =?UTF-8?q?=EC=97=90=EB=9F=AC=20=ED=95=B8=EB=93=A4?= =?UTF-8?q?=EB=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/src/main/kotlin/filter/ErrorWebFilter.kt | 78 ++++++++----------- ...xception.kt => EvServiceProxyException.kt} | 5 +- .../kotlin/evaluation/service/EvService.kt | 6 +- 3 files changed, 38 insertions(+), 51 deletions(-) rename core/src/main/kotlin/common/exception/{ProxyException.kt => EvServiceProxyException.kt} (54%) diff --git a/api/src/main/kotlin/filter/ErrorWebFilter.kt b/api/src/main/kotlin/filter/ErrorWebFilter.kt index b9197d43..3647edbe 100644 --- a/api/src/main/kotlin/filter/ErrorWebFilter.kt +++ b/api/src/main/kotlin/filter/ErrorWebFilter.kt @@ -2,7 +2,7 @@ package com.wafflestudio.snu4t.filter import com.fasterxml.jackson.databind.ObjectMapper import com.wafflestudio.snu4t.common.exception.ErrorType -import com.wafflestudio.snu4t.common.exception.ProxyException +import com.wafflestudio.snu4t.common.exception.EvServiceProxyException import com.wafflestudio.snu4t.common.exception.Snu4tException import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus @@ -27,52 +27,40 @@ class ErrorWebFilter( ): Mono { return chain.filter(exchange) .onErrorResume { throwable -> - if (throwable is ProxyException) { - exchange.response.statusCode = throwable.statusCode - exchange.response.headers.contentType = MediaType.APPLICATION_JSON - exchange.response.writeWith( - Mono.just( - exchange.response - .bufferFactory() - .wrap(objectMapper.writeValueAsBytes(throwable.errorBody)), - ), - ) - } else { - val errorBody: ErrorBody - val httpStatusCode: HttpStatusCode - when (throwable) { - is Snu4tException -> { - httpStatusCode = throwable.error.httpStatus - errorBody = makeErrorBody(throwable) - } - - is ResponseStatusException -> { - httpStatusCode = throwable.statusCode - errorBody = - makeErrorBody( - Snu4tException( - errorMessage = throwable.body.title ?: ErrorType.DEFAULT_ERROR.errorMessage, - ), - ) - } - - else -> { - log.error(throwable.message, throwable) - httpStatusCode = HttpStatus.INTERNAL_SERVER_ERROR - errorBody = makeErrorBody(Snu4tException()) - } + val errorBody: Any + val httpStatusCode: HttpStatusCode + when (throwable) { + is EvServiceProxyException -> { + httpStatusCode = throwable.statusCode + errorBody = throwable.errorBody + } + is Snu4tException -> { + httpStatusCode = throwable.error.httpStatus + errorBody = makeErrorBody(throwable) + } + is ResponseStatusException -> { + httpStatusCode = throwable.statusCode + errorBody = + makeErrorBody( + Snu4tException(errorMessage = throwable.body.title ?: ErrorType.DEFAULT_ERROR.errorMessage), + ) + } + else -> { + log.error(throwable.message, throwable) + httpStatusCode = HttpStatus.INTERNAL_SERVER_ERROR + errorBody = makeErrorBody(Snu4tException()) } - - exchange.response.statusCode = httpStatusCode - exchange.response.headers.contentType = MediaType.APPLICATION_JSON - exchange.response.writeWith( - Mono.just( - exchange.response - .bufferFactory() - .wrap(objectMapper.writeValueAsBytes(errorBody)), - ), - ) } + + exchange.response.statusCode = httpStatusCode + exchange.response.headers.contentType = MediaType.APPLICATION_JSON + exchange.response.writeWith( + Mono.just( + exchange.response + .bufferFactory() + .wrap(objectMapper.writeValueAsBytes(errorBody)), + ), + ) } } diff --git a/core/src/main/kotlin/common/exception/ProxyException.kt b/core/src/main/kotlin/common/exception/EvServiceProxyException.kt similarity index 54% rename from core/src/main/kotlin/common/exception/ProxyException.kt rename to core/src/main/kotlin/common/exception/EvServiceProxyException.kt index ceb44182..5379d941 100644 --- a/core/src/main/kotlin/common/exception/ProxyException.kt +++ b/core/src/main/kotlin/common/exception/EvServiceProxyException.kt @@ -1,9 +1,8 @@ package com.wafflestudio.snu4t.common.exception -import com.fasterxml.jackson.databind.ObjectMapper import org.springframework.http.HttpStatusCode -class ProxyException( +class EvServiceProxyException( val statusCode: HttpStatusCode, val errorBody: Map, -) : RuntimeException(ObjectMapper().writeValueAsString(errorBody)) +) : RuntimeException() diff --git a/core/src/main/kotlin/evaluation/service/EvService.kt b/core/src/main/kotlin/evaluation/service/EvService.kt index d13e08c1..5916869a 100644 --- a/core/src/main/kotlin/evaluation/service/EvService.kt +++ b/core/src/main/kotlin/evaluation/service/EvService.kt @@ -2,7 +2,7 @@ package com.wafflestudio.snu4t.evaluation.service import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.PropertyNamingStrategies -import com.wafflestudio.snu4t.common.exception.ProxyException +import com.wafflestudio.snu4t.common.exception.EvServiceProxyException import com.wafflestudio.snu4t.common.util.buildMultiValueMap import com.wafflestudio.snu4t.config.SnuttEvWebClient import com.wafflestudio.snu4t.coursebook.service.CoursebookService @@ -51,7 +51,7 @@ class EvService( .onStatus(HttpStatusCode::isError) { response -> response.bodyToMono>() .flatMap { errorBody -> - Mono.error(ProxyException(response.statusCode(), errorBody)) + Mono.error(EvServiceProxyException(response.statusCode(), errorBody)) } } .bodyToMono>() @@ -101,7 +101,7 @@ class EvService( .onStatus(HttpStatusCode::isError) { response -> response.bodyToMono>() .flatMap { errorBody -> - Mono.error(ProxyException(response.statusCode(), errorBody)) + Mono.error(EvServiceProxyException(response.statusCode(), errorBody)) } } .bodyToMono>() From b0faccfff70a4b94d326d670bf9c53a2eb93bf76 Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Wed, 8 Jan 2025 14:46:01 +0900 Subject: [PATCH 14/16] =?UTF-8?q?url=20=EB=B3=80=ED=99=98=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/evaluation/service/EvService.kt | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/core/src/main/kotlin/evaluation/service/EvService.kt b/core/src/main/kotlin/evaluation/service/EvService.kt index 5916869a..7a606004 100644 --- a/core/src/main/kotlin/evaluation/service/EvService.kt +++ b/core/src/main/kotlin/evaluation/service/EvService.kt @@ -10,10 +10,8 @@ import com.wafflestudio.snu4t.evaluation.dto.EvLectureInfoDto import com.wafflestudio.snu4t.evaluation.dto.EvUserDto import com.wafflestudio.snu4t.timetables.service.TimetableService import com.wafflestudio.snu4t.users.service.UserService -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.toList import kotlinx.coroutines.reactor.awaitSingle -import kotlinx.coroutines.withContext import org.springframework.http.HttpHeaders import org.springframework.http.HttpMethod import org.springframework.http.HttpStatusCode @@ -22,9 +20,7 @@ import org.springframework.stereotype.Service import org.springframework.util.MultiValueMap import org.springframework.web.reactive.function.BodyInserters import org.springframework.web.reactive.function.client.bodyToMono -import org.springframework.web.util.UriComponentsBuilder import reactor.core.publisher.Mono -import java.net.URLEncoder @Service class EvService( @@ -61,7 +57,7 @@ class EvService( suspend fun getMyLatestLectures( userId: String, - requestQueryParams: MultiValueMap? = null, + requestQueryParams: MultiValueMap = buildMultiValueMap(mapOf()), ): Map { val recentLectures: List = coursebookService.getLastTwoCourseBooksBeforeCurrent().flatMap { coursebook -> @@ -78,21 +74,17 @@ class EvService( } } - val encodedJson = - withContext(Dispatchers.IO) { - URLEncoder.encode( - ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE).writeValueAsString(recentLectures), - "UTF-8", - ) - } - + val lectureInfoParam = + ObjectMapper().setPropertyNamingStrategy( + PropertyNamingStrategies.SNAKE_CASE, + ).writeValueAsString(recentLectures) return snuttEvWebClient.get() .uri { builder -> - UriComponentsBuilder.fromUri(builder.build()) + builder .path("/v1/users/me/lectures/latest") - .queryParam("snutt_lecture_info", encodedJson) + .queryParam("snutt_lecture_info", "{lectureInfoParam}") .queryParams(requestQueryParams) - .build(true).toUri() + .build(lectureInfoParam) } .header("Snutt-User-Id", userId) .header(HttpHeaders.CONTENT_ENCODING, "UTF-8") From 077fd3b9d2520fcd7172db981034b22dbef890ee Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Wed, 8 Jan 2025 14:51:21 +0900 Subject: [PATCH 15/16] use DI for objectMapper --- core/src/main/kotlin/evaluation/service/EvService.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/core/src/main/kotlin/evaluation/service/EvService.kt b/core/src/main/kotlin/evaluation/service/EvService.kt index 7a606004..6c9853f9 100644 --- a/core/src/main/kotlin/evaluation/service/EvService.kt +++ b/core/src/main/kotlin/evaluation/service/EvService.kt @@ -1,7 +1,6 @@ package com.wafflestudio.snu4t.evaluation.service import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.wafflestudio.snu4t.common.exception.EvServiceProxyException import com.wafflestudio.snu4t.common.util.buildMultiValueMap import com.wafflestudio.snu4t.config.SnuttEvWebClient @@ -28,6 +27,7 @@ class EvService( private val timetableService: TimetableService, private val coursebookService: CoursebookService, private val userService: UserService, + private val objectMapper: ObjectMapper, ) { suspend fun handleRouting( userId: String, @@ -74,10 +74,7 @@ class EvService( } } - val lectureInfoParam = - ObjectMapper().setPropertyNamingStrategy( - PropertyNamingStrategies.SNAKE_CASE, - ).writeValueAsString(recentLectures) + val lectureInfoParam = objectMapper.writeValueAsString(recentLectures) return snuttEvWebClient.get() .uri { builder -> builder From ca95dd6dcecb73defc52b3fb5bfd13c1cde793d6 Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Thu, 9 Jan 2025 12:37:24 +0900 Subject: [PATCH 16/16] remove snake case --- core/src/main/kotlin/evaluation/dto/EvUserDto.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/evaluation/dto/EvUserDto.kt b/core/src/main/kotlin/evaluation/dto/EvUserDto.kt index 73a69fda..1dbcf154 100644 --- a/core/src/main/kotlin/evaluation/dto/EvUserDto.kt +++ b/core/src/main/kotlin/evaluation/dto/EvUserDto.kt @@ -1,16 +1,18 @@ package com.wafflestudio.snu4t.evaluation.dto +import com.fasterxml.jackson.annotation.JsonProperty import com.wafflestudio.snu4t.users.data.User data class EvUserDto( val id: String?, val email: String?, - val local_id: String?, + @JsonProperty("local_id") + val localId: String?, ) fun EvUserDto(user: User) = EvUserDto( id = user.id, email = user.email, - local_id = user.credential.localId, + localId = user.credential.localId, )