From 9ad861725eda42dc0df96d4f150cdc97cb7e9e62 Mon Sep 17 00:00:00 2001 From: ManHyuk Date: Wed, 6 Mar 2024 01:06:01 +0900 Subject: [PATCH 01/60] feat: remove forward header optiion --- .../api/src/main/resources/application-security-dev.yml | 1 - .../api/src/main/resources/application-security-live.yml | 2 -- 2 files changed, 3 deletions(-) diff --git a/backend/application/api/src/main/resources/application-security-dev.yml b/backend/application/api/src/main/resources/application-security-dev.yml index 60418211..ed824029 100644 --- a/backend/application/api/src/main/resources/application-security-dev.yml +++ b/backend/application/api/src/main/resources/application-security-dev.yml @@ -5,7 +5,6 @@ server: port: 8888 servlet: context-path: /dev/api - forward-headers-strategy: framework apple-login: url: https://appleid.apple.com diff --git a/backend/application/api/src/main/resources/application-security-live.yml b/backend/application/api/src/main/resources/application-security-live.yml index 1cb37055..d1f149fa 100644 --- a/backend/application/api/src/main/resources/application-security-live.yml +++ b/backend/application/api/src/main/resources/application-security-live.yml @@ -4,8 +4,6 @@ server: port: 8080 servlet: context-path: /live/api - forward-headers-strategy: framework - apple-login: url: https://appleid.apple.com From bb412c6eaeb0fa755a0ba1a0d1f6e902842ff84d Mon Sep 17 00:00:00 2001 From: binary_ho Date: Thu, 7 Mar 2024 13:17:22 +0900 Subject: [PATCH 02/60] =?UTF-8?q?refactor=20:=20birth=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=9C=A0=EC=A0=80=20=EC=A0=95=EB=B3=B4=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20API=EC=97=90=20birth=EB=A5=BC=20nullable?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#206)=20(#207)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/auth/controller/request/UpdateUserRequest.kt | 4 ++-- .../main/kotlin/io/raemian/api/user/service/UserService.kt | 6 +++--- .../src/main/kotlin/io/raemian/storage/db/core/user/User.kt | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/auth/controller/request/UpdateUserRequest.kt b/backend/application/api/src/main/kotlin/io/raemian/api/auth/controller/request/UpdateUserRequest.kt index 04ceda60..977c515e 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/auth/controller/request/UpdateUserRequest.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/auth/controller/request/UpdateUserRequest.kt @@ -4,12 +4,12 @@ import java.time.LocalDate data class UpdateUserRequest( val nickname: String, - val birth: LocalDate, + val birth: LocalDate?, ) data class UpdateUserInfoRequest( val nickname: String, - val birth: LocalDate, + val birth: LocalDate?, val username: String, val image: String, ) { diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/user/service/UserService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/user/service/UserService.kt index 00da5101..893aa985 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/user/service/UserService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/user/service/UserService.kt @@ -17,7 +17,7 @@ class UserService( return UserDTO.of(user) } - fun updateNicknameAndBirth(id: Long, nickname: String, birth: LocalDate): UserDTO { + fun updateNicknameAndBirth(id: Long, nickname: String, birth: LocalDate?): UserDTO { val user = userRepository.getById(id) val updated = user.updateNicknameAndBirth( @@ -28,7 +28,7 @@ class UserService( return UserDTO.of(userRepository.save(updated)) } - fun updateBaseInfo(id: Long, nickname: String, birth: LocalDate, image: String): UserDTO { + fun updateBaseInfo(id: Long, nickname: String, birth: LocalDate?, image: String): UserDTO { val user = userRepository.getById(id) val updated = user.updateNicknameAndBirth( @@ -39,7 +39,7 @@ class UserService( return UserDTO.of(userRepository.save(updated)) } - fun update(id: Long, nickname: String, birth: LocalDate, username: String, image: String): UserDTO { + fun update(id: Long, nickname: String, birth: LocalDate?, username: String, image: String): UserDTO { val user = userRepository.getById(id) val updated = user diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/User.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/User.kt index cd821758..261cbb91 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/User.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/User.kt @@ -43,7 +43,7 @@ class User( @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null, ) : BaseEntity() { - fun updateNicknameAndBirth(nickname: String, birth: LocalDate): User { + fun updateNicknameAndBirth(nickname: String, birth: LocalDate?): User { return User( email = email, nickname = nickname, From 6c19f90d1ef468545b8fba122f26344cd08572be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=9A=B0=EC=84=9D=20=28Woosuk=20Kwon=29?= Date: Sun, 10 Mar 2024 14:07:02 +0900 Subject: [PATCH 03/60] =?UTF-8?q?feat(#209):=20spring=20security=20authori?= =?UTF-8?q?zation=5Frequest=5Fnot=5Ffound=20=EC=88=98=EC=A0=95=20(#210)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../raemian/api/config/WebSecurityConfig.kt | 5 +- .../io/raemian/api/support/CookieUtils.kt | 55 +++++++++++++++ ...kieOAuth2AuthorizationRequestRepository.kt | 68 +++++++++++++++++++ 3 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/CookieUtils.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/HttpCookieOAuth2AuthorizationRequestRepository.kt diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt index ab27fd98..fe3784b6 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt @@ -3,6 +3,7 @@ package io.raemian.api.config import io.raemian.api.auth.converter.TokenRequestEntityConverter import io.raemian.api.auth.domain.CurrentUser import io.raemian.api.auth.service.OAuth2UserService +import io.raemian.api.support.HttpCookieOAuth2AuthorizationRequestRepository import io.raemian.api.support.TokenProvider import jakarta.servlet.http.HttpServletResponse import org.slf4j.LoggerFactory @@ -38,6 +39,7 @@ class WebSecurityConfig( @Value("\${spring.profiles.active:local}") private val profile: String, private val tokenRequestEntityConverter: TokenRequestEntityConverter, + private val httpCookieOAuth2AuthorizationRequestRepository: HttpCookieOAuth2AuthorizationRequestRepository, ) : SecurityConfigurerAdapter() { private val log = LoggerFactory.getLogger(javaClass) @@ -81,7 +83,8 @@ class WebSecurityConfig( } .oauth2Login { it.tokenEndpoint { it.accessTokenResponseClient(accessTokenResponseClient()) } - .userInfoEndpoint { endpoint -> endpoint.userService(oAuth2UserService) } + it.userInfoEndpoint { it.userService(oAuth2UserService) } + it.authorizationEndpoint { it.authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository) } it.successHandler { request, response, authentication -> val user = authentication.principal as CurrentUser response.contentType = MediaType.APPLICATION_JSON_VALUE diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/CookieUtils.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/CookieUtils.kt new file mode 100644 index 00000000..d049311e --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/CookieUtils.kt @@ -0,0 +1,55 @@ +package io.raemian.api.support + +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 = request.cookies + if (cookies != null && cookies.isNotEmpty()) { + for (cookie in cookies) { + if (cookie.name == name) { + return Optional.of(cookie) + } + } + } + return Optional.empty() + } + + fun addCookie(response: HttpServletResponse, name: String, value: String, maxAge: Int) { + val cookie = Cookie(name, value) + cookie.path = "/" + cookie.isHttpOnly = true + cookie.maxAge = maxAge + response.addCookie(cookie) + } + + fun deleteCookie(request: HttpServletRequest, response: HttpServletResponse, name: String) { + val cookies = request.cookies + if (cookies != null && cookies.isNotEmpty()) { + for (cookie in cookies) { + if (cookie.name == name) { + cookie.value = "" + cookie.path = "/" + cookie.maxAge = 0 + response.addCookie(cookie) + } + } + } + } + + fun serialize(obj: Any?): String { + return Base64.getUrlEncoder() + .encodeToString( + SerializationUtils.serialize(obj), + ) + } + + fun deserialize(cookie: Cookie, cls: Class): T { + return cls.cast(SerializationUtils.deserialize(Base64.getUrlDecoder().decode(cookie.value))) + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/HttpCookieOAuth2AuthorizationRequestRepository.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/HttpCookieOAuth2AuthorizationRequestRepository.kt new file mode 100644 index 00000000..995034cc --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/HttpCookieOAuth2AuthorizationRequestRepository.kt @@ -0,0 +1,68 @@ +package io.raemian.api.support + +import jakarta.servlet.http.Cookie +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 +import java.time.Duration + +@Component +class HttpCookieOAuth2AuthorizationRequestRepository() : AuthorizationRequestRepository { + + private val AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request" + private val EXPIRE_SECONDS: Int = Duration.ofSeconds(180).toMillis().toInt() + + override fun loadAuthorizationRequest(request: HttpServletRequest): OAuth2AuthorizationRequest? { + val state = this.getStateParameter(request) ?: return null + + val authorizationRequest: OAuth2AuthorizationRequest? = + CookieUtils.getCookie(request, AUTHORIZATION_REQUEST_COOKIE_NAME) + .map { cookie: Cookie -> + CookieUtils.deserialize(cookie, OAuth2AuthorizationRequest::class.java) + }.orElse(null) + + return if (authorizationRequest != null && state == authorizationRequest.state) { + authorizationRequest + } else { + null + } + } + override fun saveAuthorizationRequest( + authorizationRequest: OAuth2AuthorizationRequest?, + request: HttpServletRequest, + response: HttpServletResponse, + ) { + if (authorizationRequest == null) { + removeAuthorizationRequest(request, response) + return + } + + CookieUtils.addCookie( + response, + AUTHORIZATION_REQUEST_COOKIE_NAME, + CookieUtils.serialize(authorizationRequest), + EXPIRE_SECONDS, + ) + } + + override fun removeAuthorizationRequest( + request: HttpServletRequest, + response: HttpServletResponse, + ): OAuth2AuthorizationRequest? { + val authorizationRequest: OAuth2AuthorizationRequest? = this.loadAuthorizationRequest(request) + + if (authorizationRequest != null) { + removeAuthorizationRequestCookies(request, response) + } + + return authorizationRequest + } + + private fun removeAuthorizationRequestCookies(request: HttpServletRequest, response: HttpServletResponse) { + CookieUtils.deleteCookie(request, response, AUTHORIZATION_REQUEST_COOKIE_NAME) + } + + private fun getStateParameter(request: HttpServletRequest): String? = request.getParameter("state") +} From 73d7b17fa19170f18772b99e8140dc5dab9b10ab Mon Sep 17 00:00:00 2001 From: binary_ho Date: Tue, 12 Mar 2024 23:23:49 +0900 Subject: [PATCH 04/60] =?UTF-8?q?[=F0=9F=8C=8E=20Feature]=20Goal=20?= =?UTF-8?q?=EB=8C=93=EA=B8=80=20API=20=EA=B0=9C=EB=B0=9C=20(#208)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat : 댓글 엔티티와 repository 생성 (#196) * feat : Goal 댓글 오름차순 전체 조회 메서드 구현 (#196) * feat : Goal 댓글 작성 삭제 메서드 구현 (#196) * feat : Goal의 마지막 댓글 Read 시간을 저장하는 GoalCommentReadTime 구현 (#196) * feat : Goal의 생성자가 읽지 않은 댓글이 있는지 확인하는 메서드 구현 (#196) * feat : 비동기적으로 마지막 댓글 확인 시간을 update하는 메서드 구현 (#196) * ktlint formatting * refactor : 마지막 댓글 Read 시간을 분리된 테이블이 아닌 Goal이 갖도록 변경 (#196) * feat : 댓글 조회시 요청자가 Goal의 주인이라면 comment read time을 update하는 이벤트 발행 (#196) * test : Goal 필드 추가에 따른 테스트 코드 변경 (#196) * refactor : CoreApiException이 직접 errorMessage를 입력 받을 수 있도록 변경 (#196) * refactor : Comment가 스스로 제한 조건을 검증하도록 변경 (#196) * refactor : CommentService의 필드들에 private 접근 제한자 추가 (#196) * refactor : comment require 조건 정정 (#196) * refactor : 불필요한 트랜잭션 전파 및 Transactional 이벤트 리스너 제거 (#196) * refactor : 불필요한 Goal 조회 쿼리 제거 (#196) * test : Comment 관련 기능 통합 테스트 코드 작성 (#196) * ktlint formatting * refactor : getAllByGoalId 메서드의 이름을 역할에 맞게 findAllByGoalId로 변경 (#196) * feat : 댓글 API 구현 (#196) * chore : 잘못 작성된 메서드 이름 변경 (#196) * refactor : DTO 패키지 변경 (#196) * refactor : comments에서 goal을 꺼낼 때, first 메서드 활용 (#196) * refactor : 불필요한 @Async 어노테이션 제거 (#196) * refactor : findAllByGoalId 가독성 개선 (#196) --- .../io/raemian/api/comment/CommentService.kt | 78 +++++++ .../comment/controller/CommentController.kt | 64 ++++++ .../controller/request/WriteCommentRequest.kt | 5 + .../controller/response/CommentsResponse.kt | 48 ++++ .../event/UpdateCommentReadAtEventHandler.kt | 20 ++ .../event/UpdateLastCommentReadAtEvent.kt | 8 + .../kotlin/io/raemian/api/goal/GoalService.kt | 11 +- .../api/support/error/CoreApiException.kt | 3 +- .../io/raemian/api/support/error/ErrorInfo.kt | 16 +- .../api/task/controller/TaskController.kt | 2 +- .../integration/comment/CommentServiceTest.kt | 205 ++++++++++++++++++ .../api/integration/emoji/EmojiServiceTest.kt | 2 + .../api/integration/goal/GoalServiceTest.kt | 8 + .../integration/lifemap/LifeMapServiceTest.kt | 10 + .../api/integration/task/TaskServiceTest.kt | 2 + .../support/CoreApiExceptionTestSupporter.kt | 32 +++ .../storage/db/core/comment/Comment.kt | 44 ++++ .../db/core/comment/CommentRepository.kt | 14 ++ .../io/raemian/storage/db/core/goal/Goal.kt | 11 +- .../storage/db/core/goal/GoalRepository.kt | 10 + 20 files changed, 586 insertions(+), 7 deletions(-) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/comment/CommentService.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/CommentController.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/request/WriteCommentRequest.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/response/CommentsResponse.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/comment/event/UpdateCommentReadAtEventHandler.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/comment/event/UpdateLastCommentReadAtEvent.kt create mode 100644 backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt create mode 100644 backend/application/api/src/test/kotlin/io/raemian/api/support/CoreApiExceptionTestSupporter.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentRepository.kt diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/CommentService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/CommentService.kt new file mode 100644 index 00000000..0a0d478e --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/CommentService.kt @@ -0,0 +1,78 @@ +package io.raemian.api.comment + +import io.raemian.api.comment.controller.request.WriteCommentRequest +import io.raemian.api.comment.controller.response.CommentsResponse +import io.raemian.api.comment.event.UpdateLastCommentReadAtEvent +import io.raemian.api.support.error.CoreApiException +import io.raemian.api.support.error.ErrorInfo +import io.raemian.storage.db.core.comment.Comment +import io.raemian.storage.db.core.comment.CommentRepository +import io.raemian.storage.db.core.goal.Goal +import io.raemian.storage.db.core.goal.GoalRepository +import io.raemian.storage.db.core.user.User +import io.raemian.storage.db.core.user.UserRepository +import org.springframework.context.ApplicationEventPublisher +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDateTime + +@Service +class CommentService( + private val commentRepository: CommentRepository, + private val goalRepository: GoalRepository, + private val userRepository: UserRepository, + private val applicationEventPublisher: ApplicationEventPublisher, +) { + + @Transactional(readOnly = true) + fun findAllByGoalId(goalId: Long, currentUserId: Long): CommentsResponse { + val comments = commentRepository.findAllByGoalId(goalId) + .ifEmpty { return CommentsResponse.from(emptyList(), currentUserId) } + + val goal = comments.first().goal + if (isCurrentUserGoalOwner(currentUserId, goal)) { + publishUpdateCommentReadAtEvent(goalId) + } + return CommentsResponse.from(comments, currentUserId) + } + + @Transactional + fun isNewComment(goalId: Long): Boolean { + val goal = goalRepository.getById(goalId) + return commentRepository.existsByCreatedAtGreaterThan(goal.lastCommentReadAt) + } + + @Transactional + fun write(goalId: Long, currentUserId: Long, request: WriteCommentRequest) { + val goal = goalRepository.getReferenceById(goalId) + val currentUser = userRepository.getReferenceById(currentUserId) + val comment = createComment(goal, currentUser, request.content) + commentRepository.save(comment) + } + + @Transactional + fun delete(commentId: Long, currentUserId: Long) { + val comment = commentRepository.getById(commentId) + if (currentUserId != comment.commenter.id) { + throw CoreApiException(ErrorInfo.RESOURCE_DELETE_FORBIDDEN) + } + + commentRepository.delete(comment) + } + + private fun createComment(goal: Goal, currentUser: User, content: String): Comment { + return try { + Comment(goal, currentUser, content) + } catch (exception: IllegalArgumentException) { + throw CoreApiException(ErrorInfo.COMMENT_CHARACTER_LIMIT_EXCEED, exception.message) + } + } + + private fun isCurrentUserGoalOwner(currentUserId: Long, goal: Goal): Boolean = + currentUserId == goal.lifeMap.user.id + + private fun publishUpdateCommentReadAtEvent(goalId: Long) { + val event = UpdateLastCommentReadAtEvent(goalId, LocalDateTime.now()) + applicationEventPublisher.publishEvent(event) + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/CommentController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/CommentController.kt new file mode 100644 index 00000000..85542b1b --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/CommentController.kt @@ -0,0 +1,64 @@ +package io.raemian.api.comment.controller + +import io.raemian.api.auth.domain.CurrentUser +import io.raemian.api.comment.CommentService +import io.raemian.api.comment.controller.request.WriteCommentRequest +import io.raemian.api.comment.controller.response.CommentsResponse +import io.raemian.api.support.response.ApiResponse +import io.swagger.v3.oas.annotations.Operation +import org.springframework.http.ResponseEntity +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.DeleteMapping +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 + +@RestController +@RequestMapping +class CommentController( + private val commentService: CommentService, +) { + + @Operation(summary = "Goal에 달린 댓글 전체 조회 API") + @GetMapping("/goal/{goalId}/comment") + fun findAllReactedEmojisAtGoal( + @PathVariable goalId: Long, + @AuthenticationPrincipal currentUser: CurrentUser, + ): ResponseEntity> = + ResponseEntity.ok( + ApiResponse.success(commentService.findAllByGoalId(goalId, currentUser.id)), + ) + + @Operation(summary = "Goal 주인이 읽지 못한 새 댓글이 있는지 확인하는 API") + @GetMapping("/goal/{goalId}/comment/new") + fun isNewComment( + @PathVariable goalId: Long, + ): ResponseEntity> = + ResponseEntity.ok( + ApiResponse.success(commentService.isNewComment(goalId)), + ) + + @Operation(summary = "Goal에 댓글을 작성하는 API") + @PostMapping("/goal/{goalId}/comment") + fun write( + @PathVariable goalId: Long, + @AuthenticationPrincipal currentUser: CurrentUser, + @RequestBody request: WriteCommentRequest, + ): ResponseEntity> = + ResponseEntity.ok( + ApiResponse.success(commentService.write(goalId, currentUser.id, request)), + ) + + @Operation(summary = "댓글 삭제 API") + @DeleteMapping("comment/{commentId}") + fun delete( + @PathVariable commentId: Long, + @AuthenticationPrincipal currentUser: CurrentUser, + ): ResponseEntity { + commentService.delete(commentId, currentUser.id) + return ResponseEntity.noContent().build() + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/request/WriteCommentRequest.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/request/WriteCommentRequest.kt new file mode 100644 index 00000000..038607d1 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/request/WriteCommentRequest.kt @@ -0,0 +1,5 @@ +package io.raemian.api.comment.controller.request + +data class WriteCommentRequest( + val content: String, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/response/CommentsResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/response/CommentsResponse.kt new file mode 100644 index 00000000..72365794 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/response/CommentsResponse.kt @@ -0,0 +1,48 @@ +package io.raemian.api.comment.controller.response + +import io.raemian.storage.db.core.comment.Comment +import io.raemian.storage.db.core.user.User +import java.time.LocalDateTime + +data class CommentsResponse(val comments: List) { + companion object { + fun from(comments: List, userId: Long): CommentsResponse { + val comments = comments + .map { CommentResponse.from(it, userId) } + .sortedBy { it.writtenAt } + return CommentsResponse(comments) + } + } + + data class CommentResponse( + val id: Long, + val content: String, + val writtenAt: LocalDateTime, + val commenter: Commenter, + val isMyComment: Boolean, + ) { + companion object { + fun from(comment: Comment, userId: Long) = CommentResponse( + id = comment.id!!, + content = comment.content, + writtenAt = comment.createdAt ?: LocalDateTime.now(), + commenter = Commenter.from(comment.commenter), + isMyComment = comment.commenter.id == userId, + ) + } + } + + data class Commenter( + val username: String, + val nickname: String, + val image: String, + ) { + companion object { + fun from(user: User) = Commenter( + username = user.username!!, + nickname = user.nickname!!, + image = user.image, + ) + } + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/event/UpdateCommentReadAtEventHandler.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/event/UpdateCommentReadAtEventHandler.kt new file mode 100644 index 00000000..8cf6bbe9 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/event/UpdateCommentReadAtEventHandler.kt @@ -0,0 +1,20 @@ +package io.raemian.api.comment.event + +import io.raemian.storage.db.core.goal.GoalRepository +import org.springframework.context.event.EventListener +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class UpdateCommentReadAtEventHandler( + private val goalRepository: GoalRepository, +) { + @Transactional + @EventListener + fun updateLastCommentReadTime(event: UpdateLastCommentReadAtEvent) { + goalRepository.updateLastCommentReadAtByGoalId( + event.goalId, + event.commentReadAt, + ) + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/event/UpdateLastCommentReadAtEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/event/UpdateLastCommentReadAtEvent.kt new file mode 100644 index 00000000..f0858cf2 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/event/UpdateLastCommentReadAtEvent.kt @@ -0,0 +1,8 @@ +package io.raemian.api.comment.event + +import java.time.LocalDateTime + +data class UpdateLastCommentReadAtEvent( + val goalId: Long, + val commentReadAt: LocalDateTime, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt index 4da845c2..4874c6e7 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt @@ -19,6 +19,7 @@ import io.raemian.storage.db.core.user.UserRepository import org.springframework.context.ApplicationEventPublisher import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import java.time.LocalDateTime @Service class GoalService( @@ -105,7 +106,15 @@ class GoalService( val deadline = RaemianLocalDate.of(yearOfDeadline, monthOfDeadline) val sticker = stickerService.getReferenceById(stickerId) val tag = tagService.getReferenceById(tagId) - return Goal(lifeMap, title, deadline, sticker, tag, description!!) + return Goal( + lifeMap = lifeMap, + title = title, + deadline = deadline, + sticker = sticker, + tag = tag, + description = description!!, + lastCommentReadAt = LocalDateTime.now(), + ) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/error/CoreApiException.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/error/CoreApiException.kt index eece823b..83d31891 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/error/CoreApiException.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/error/CoreApiException.kt @@ -2,4 +2,5 @@ package io.raemian.api.support.error open class CoreApiException( val errorInfo: ErrorInfo, -) : RuntimeException(errorInfo.message) + errorMessage: String? = errorInfo.message, +) : RuntimeException(errorMessage) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/error/ErrorInfo.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/error/ErrorInfo.kt index 5bfe110c..2d1405ad 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/error/ErrorInfo.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/error/ErrorInfo.kt @@ -16,6 +16,13 @@ enum class ErrorInfo( LogLevel.ERROR, ), + RESOURCE_DELETE_FORBIDDEN( + HttpStatus.FORBIDDEN, + HttpStatus.FORBIDDEN.value(), + "해당 리소스를 삭제할 권한이 없습니다.", + LogLevel.INFO, + ), + RESOURCE_NOT_FOUND( HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.value(), @@ -54,7 +61,14 @@ enum class ErrorInfo( INVALID_USERNAME( HttpStatus.BAD_REQUEST, 1005, - "유효하지 않은 username 입니다.", + "유효하지 않은 username입니다.", + LogLevel.INFO, + ), + + COMMENT_CHARACTER_LIMIT_EXCEED( + HttpStatus.BAD_REQUEST, + 1005, + "유효하지 않은 username입니다.", LogLevel.INFO, ), } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/task/controller/TaskController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/task/controller/TaskController.kt index e59d6460..ee74900d 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/task/controller/TaskController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/task/controller/TaskController.kt @@ -60,7 +60,7 @@ class TaskController( @Operation(summary = "Task를 삭제하는 API입니다.") @DeleteMapping("/{taskId}") - fun updateTaskCompletion( + fun delete( @AuthenticationPrincipal currentUser: CurrentUser, @PathVariable("taskId") taskId: Long, ): ResponseEntity { diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt new file mode 100644 index 00000000..dd87ddad --- /dev/null +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt @@ -0,0 +1,205 @@ +package io.raemian.api.integration.comment + +import io.raemian.api.comment.CommentService +import io.raemian.api.comment.controller.request.WriteCommentRequest +import io.raemian.api.support.CoreApiExceptionTestSupporter.Companion.assertThrowsCoreApiExceptionExactly +import io.raemian.api.support.error.CoreApiException +import io.raemian.api.support.error.ErrorInfo +import io.raemian.storage.db.core.comment.Comment +import io.raemian.storage.db.core.comment.CommentRepository +import io.raemian.storage.db.core.goal.Goal +import io.raemian.storage.db.core.goal.GoalRepository +import io.raemian.storage.db.core.lifemap.LifeMap +import io.raemian.storage.db.core.sticker.Sticker +import io.raemian.storage.db.core.tag.Tag +import io.raemian.storage.db.core.user.Authority +import io.raemian.storage.db.core.user.User +import io.raemian.storage.db.core.user.enums.OAuthProvider +import jakarta.persistence.EntityManager +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.fail +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDate +import java.time.LocalDateTime + +@Transactional +@SpringBootTest +class CommentServiceTest { + + companion object { + val USER_FIXTURE = User( + email = "dfghcvb111@naver.com", + username = "binaryHoHo", + nickname = "binaryHoHoHo", + image = "", + birth = LocalDate.MIN, + provider = OAuthProvider.NAVER, + authority = Authority.ROLE_USER, + ) + + val LIFE_MAP_FIXTURE = LifeMap(USER_FIXTURE, true) + val STICKER_FIXTURE = Sticker("sticker", "image yeah") + val TAG_FIXTURE = Tag("꿈") + val GOAL_FIXTURE = Goal( + lifeMap = LIFE_MAP_FIXTURE, + title = "title", + deadline = LocalDate.MAX, + sticker = STICKER_FIXTURE, + tag = TAG_FIXTURE, + description = "description", + lastCommentReadAt = LocalDateTime.now(), + ) + } + + @Autowired + private lateinit var goalRepository: GoalRepository + + @Autowired + private lateinit var entityManager: EntityManager + + @Autowired + private lateinit var commentService: CommentService + + @Autowired + private lateinit var commentRepository: CommentRepository + + @BeforeEach + fun saveEntities() { + entityManager.merge(USER_FIXTURE) + entityManager.merge(LIFE_MAP_FIXTURE) + entityManager.merge(STICKER_FIXTURE) + entityManager.merge(TAG_FIXTURE) + goalRepository.save(GOAL_FIXTURE) + } + + @Test + @DisplayName("Goal의 Comment들을 조회할 수 있다") + fun getAllByGoalIdTest() { + // given + commentRepository.save(Comment(GOAL_FIXTURE, USER_FIXTURE, "comment0")) + commentRepository.save(Comment(GOAL_FIXTURE, USER_FIXTURE, "comment1")) + commentRepository.save(Comment(GOAL_FIXTURE, USER_FIXTURE, "comment2")) + + // when + val (comments) = commentService.findAllByGoalId(GOAL_FIXTURE.id!!, USER_FIXTURE.id!!) + + // then + assertThat(comments[0].content).isEqualTo("comment0") + assertThat(comments[1].content).isEqualTo("comment1") + assertThat(comments[2].content).isEqualTo("comment2") + } + + @Test + @DisplayName("유저는 읽지 않은 Comment가 있는지 확인할 수 있다.") + fun isNewTest() { + // given + val comment1 = Comment(GOAL_FIXTURE, USER_FIXTURE, "comment1") + val comment2 = Comment(GOAL_FIXTURE, USER_FIXTURE, "comment2") + commentRepository.save(comment1) + commentRepository.save(comment2) + + // when + // 처음 저장된 댓글의 시간으로 last comment read at 업데이트 + goalRepository.updateLastCommentReadAtByGoalId(GOAL_FIXTURE.id!!, comment1.createdAt!!) + val result1 = commentService.isNewComment(GOAL_FIXTURE.id!!) + + // 세 번째로 저장된 댓글의 시간으로 last comment read at 업데이트 + goalRepository.updateLastCommentReadAtByGoalId(GOAL_FIXTURE.id!!, comment2.createdAt!!) + val result2 = commentService.isNewComment(GOAL_FIXTURE.id!!) + + // then + assertThat(result1).isTrue() + assertThat(result2).isFalse() + } + + @Test + @DisplayName("유저는 Comment를 작성할 수 있다.") + fun writeTest() { + // given + val content = "댓글" + + // when + val request = WriteCommentRequest(content) + commentService.write(GOAL_FIXTURE.id!!, USER_FIXTURE.id!!, request) + + // then + val comments = commentRepository.findAllByGoalId(GOAL_FIXTURE.id!!) + assertThat(comments.size).isEqualTo(1) + assertThat(comments[0].content).isEqualTo(content) + assertThat(comments[0].goal.id).isEqualTo(GOAL_FIXTURE.id) + assertThat(comments[0].commenter.id).isEqualTo(USER_FIXTURE.id) + } + + @Test + @DisplayName("Comment 생성시 글자수 제한을 초과하는 경우 예외가 발생한다.") + fun writeCommentCharacterLimitExceedTest() { + // given + // when + val expectedLimit = 50 + val stringBuilder = StringBuilder("") + repeat(expectedLimit) { + stringBuilder.append("A") + } + + val contentNotExceedLimit = stringBuilder.toString() + stringBuilder.append("A") + val contentExceedLimit = stringBuilder.toString() + + val requestNotContentLimitExceed = + WriteCommentRequest(contentNotExceedLimit) + val requestContentLimitExceed = + WriteCommentRequest(contentExceedLimit) + + // then + assertDoesNotThrow { + commentService.write(GOAL_FIXTURE.id!!, USER_FIXTURE.id!!, requestNotContentLimitExceed) + } + + try { + commentService.write(GOAL_FIXTURE.id!!, USER_FIXTURE.id!!, requestContentLimitExceed) + fail() + } catch (coreApiException: CoreApiException) { + assertThat(coreApiException.errorInfo) + .isEqualTo(ErrorInfo.COMMENT_CHARACTER_LIMIT_EXCEED) + } + } + + @Test + @DisplayName("자신의 Comment를 지울 수 있다.") + fun deleteTest() { + // given + val comment = Comment(GOAL_FIXTURE, USER_FIXTURE, "contnet") + commentRepository.save(comment) + + // when + val commentsBeforeDelete = commentRepository.findAllByGoalId(GOAL_FIXTURE.id!!) + commentService.delete(comment.id!!, USER_FIXTURE.id!!) + + // then + val commentsAfterDelete = commentRepository.findAllByGoalId(GOAL_FIXTURE.id!!) + assertThat(commentsBeforeDelete.size) + .isNotEqualTo(commentsAfterDelete.size) + } + + @Test + @DisplayName("다른 사람의 Comment를 지우는 경우 예외가 발생한다.") + fun deleteOthersCommentTest() { + // given + val comment = Comment(GOAL_FIXTURE, USER_FIXTURE, "contnet") + commentRepository.save(comment) + + // when + // then + assertThrowsCoreApiExceptionExactly( + ErrorInfo.RESOURCE_DELETE_FORBIDDEN, + ) { + commentService.delete(comment.id!!, USER_FIXTURE.id!! + 1L) + } + } +} diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt index 78d346b1..872ff4ee 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt @@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.transaction.annotation.Transactional import java.time.LocalDate +import java.time.LocalDateTime @SpringBootTest @Transactional @@ -58,6 +59,7 @@ class EmojiServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "내용", + lastCommentReadAt = LocalDateTime.now(), ) } diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/goal/GoalServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/goal/GoalServiceTest.kt index 8eab9ff6..38e52811 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/goal/GoalServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/goal/GoalServiceTest.kt @@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.transaction.annotation.Transactional import java.time.LocalDate +import java.time.LocalDateTime @SpringBootTest class GoalServiceTest { @@ -75,6 +76,7 @@ class GoalServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "내용", + lastCommentReadAt = LocalDateTime.now(), ) goalRepository.save(goal) @@ -100,6 +102,7 @@ class GoalServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "목표 설명.", + lastCommentReadAt = LocalDateTime.now(), ) goalRepository.save(goal) @@ -125,6 +128,7 @@ class GoalServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "목표 설명.", + lastCommentReadAt = LocalDateTime.now(), ) goalRepository.save(goal) @@ -149,6 +153,7 @@ class GoalServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "목표 설명.", + lastCommentReadAt = LocalDateTime.now(), ) goalRepository.save(goal) @@ -247,6 +252,7 @@ class GoalServiceTest { deadline = deadline, sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, + lastCommentReadAt = LocalDateTime.now(), ) goalRepository.save(goal) @@ -298,6 +304,7 @@ class GoalServiceTest { deadline = LocalDate.now(), sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, + lastCommentReadAt = LocalDateTime.now(), ) goalRepository.save(goal) @@ -317,6 +324,7 @@ class GoalServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "목표 설명", + lastCommentReadAt = LocalDateTime.now(), ) lifeMap.addGoal(goal) diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/lifemap/LifeMapServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/lifemap/LifeMapServiceTest.kt index 6f1c6c1e..2a3be870 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/lifemap/LifeMapServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/lifemap/LifeMapServiceTest.kt @@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.transaction.annotation.Transactional import java.time.LocalDate +import java.time.LocalDateTime @SpringBootTest class LifeMapServiceTest { @@ -72,6 +73,7 @@ class LifeMapServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "", + lastCommentReadAt = LocalDateTime.now(), ) val goal2 = Goal( @@ -81,6 +83,7 @@ class LifeMapServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "", + lastCommentReadAt = LocalDateTime.now(), ) val lifeMap = lifeMapRepository.findFirstByUserId(USER_FIXTURE.id!!) ?: fail() @@ -113,6 +116,7 @@ class LifeMapServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "", + lastCommentReadAt = LocalDateTime.now(), ) val goal2 = Goal( @@ -122,6 +126,7 @@ class LifeMapServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "", + lastCommentReadAt = LocalDateTime.now(), ) val lifeMap = lifeMapRepository.findFirstByUserId(USER_FIXTURE.id!!) ?: fail() @@ -169,6 +174,7 @@ class LifeMapServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "", + lastCommentReadAt = LocalDateTime.now(), ) val goal2 = Goal( @@ -178,6 +184,7 @@ class LifeMapServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "", + lastCommentReadAt = LocalDateTime.now(), ) val lifeMap = lifeMapRepository.findFirstByUserId(USER_FIXTURE.id!!) ?: fail() @@ -215,6 +222,7 @@ class LifeMapServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "", + lastCommentReadAt = LocalDateTime.now(), ) val deadline이_내일이고_가장_나중에_만들어진_객체 = Goal( @@ -225,6 +233,7 @@ class LifeMapServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "", + lastCommentReadAt = LocalDateTime.now(), ) val deadline이_오늘인_객체 = Goal( @@ -234,6 +243,7 @@ class LifeMapServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "", + lastCommentReadAt = LocalDateTime.now(), ) // when diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/task/TaskServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/task/TaskServiceTest.kt index fc2e11b4..359af477 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/task/TaskServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/task/TaskServiceTest.kt @@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.transaction.annotation.Transactional import java.time.LocalDate +import java.time.LocalDateTime @SpringBootTest @Transactional @@ -51,6 +52,7 @@ class TaskServiceTest { sticker = STICKER_FIXTURE, tag = TAG_FIXTURE, description = "description", + lastCommentReadAt = LocalDateTime.now(), ) } diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/support/CoreApiExceptionTestSupporter.kt b/backend/application/api/src/test/kotlin/io/raemian/api/support/CoreApiExceptionTestSupporter.kt new file mode 100644 index 00000000..7cb588b5 --- /dev/null +++ b/backend/application/api/src/test/kotlin/io/raemian/api/support/CoreApiExceptionTestSupporter.kt @@ -0,0 +1,32 @@ +package io.raemian.api.support + +import io.raemian.api.support.error.CoreApiException +import io.raemian.api.support.error.ErrorInfo + +class CoreApiExceptionTestSupporter { + + companion object { + fun assertThrowsCoreApiExceptionExactly( + expectedErrorInfo: ErrorInfo, + shouldRaiseCoreApiException: () -> Any, + ) { + try { + shouldRaiseCoreApiException() + throw RuntimeException("아무 예외도 발생하지 않았습니다.") + } catch (coreApiException: CoreApiException) { + validateErrorInfo(coreApiException, expectedErrorInfo) + } catch (exception: Exception) { + throw RuntimeException("CoreApiException이 아닌 예와가 발생했습니다.") + } + } + + private fun validateErrorInfo( + coreApiException: CoreApiException, + expectedErrorInfo: ErrorInfo, + ) { + if (coreApiException.errorInfo != expectedErrorInfo) { + throw RuntimeException("CoreApiException이 발생했으나, 예상한 ErorrInfo와 다릅니다.") + } + } + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt new file mode 100644 index 00000000..5ce6ee39 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt @@ -0,0 +1,44 @@ +package io.raemian.storage.db.core.comment + +import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.goal.Goal +import io.raemian.storage.db.core.user.User +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table +import org.hibernate.annotations.Nationalized + +@Entity +@Table(name = "COMMENTS") +class Comment( + @ManyToOne + @JoinColumn(name = "goal_id") + val goal: Goal, + + @ManyToOne + @JoinColumn(name = "commenter_id") + val commenter: User, + + @Column(nullable = false) + @Nationalized + var content: String, + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, +) : BaseEntity() { + companion object { + private const val CONTENT_CHARACTER_LIMIT = 50 + } + + init { + require(content.length <= CONTENT_CHARACTER_LIMIT) { + "글자수 제한${CONTENT_CHARACTER_LIMIT}자를 초과했습니다." + } + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentRepository.kt new file mode 100644 index 00000000..082d605f --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentRepository.kt @@ -0,0 +1,14 @@ +package io.raemian.storage.db.core.comment + +import org.springframework.data.jpa.repository.JpaRepository +import java.time.LocalDateTime + +interface CommentRepository : JpaRepository { + + fun findAllByGoalId(goalId: Long): List + + fun existsByCreatedAtGreaterThan(createdAt: LocalDateTime): Boolean + + override fun getById(id: Long): Comment = + findById(id).orElseThrow() { NoSuchElementException("Comment가 없습니다 $id") } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt index 4ce58dc3..9669bc36 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt @@ -18,6 +18,7 @@ import jakarta.persistence.OneToMany import jakarta.persistence.Table import org.hibernate.annotations.Nationalized import java.time.LocalDate +import java.time.LocalDateTime @Entity @Table(name = "GOALS") @@ -44,6 +45,9 @@ class Goal( @Nationalized var description: String = "", + @Column(name = "lastCommentReadAt", nullable = false) + val lastCommentReadAt: LocalDateTime, + @OneToMany( mappedBy = "goal", cascade = [CascadeType.REMOVE, CascadeType.MERGE], @@ -69,13 +73,14 @@ class Goal( title: String, deadline: LocalDate, description: String, - ): Goal = Goal(lifeMap, title, deadline, sticker, tag, description, tasks, id) + ): Goal = + Goal(lifeMap, title, deadline, sticker, tag, description, lastCommentReadAt, tasks, id) fun updateSticker(sticker: Sticker): Goal = - Goal(lifeMap, title, deadline, sticker, tag, description, tasks, id) + Goal(lifeMap, title, deadline, sticker, tag, description, lastCommentReadAt, tasks, id) fun updateTag(tag: Tag): Goal = - Goal(lifeMap, title, deadline, sticker, tag, description, tasks, id) + Goal(lifeMap, title, deadline, sticker, tag, description, lastCommentReadAt, tasks, id) private fun validateMaxTaskCount() = require(tasks.size < MAX_TASK_COUNT) diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt index 48515155..0dd670a5 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt @@ -3,6 +3,7 @@ package io.raemian.storage.db.core.goal import io.raemian.storage.db.core.lifemap.LifeMap import io.raemian.storage.db.core.model.GoalExploreQueryResult import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.query.Param import java.time.LocalDateTime @@ -52,4 +53,13 @@ interface GoalRepository : JpaRepository { """, ) fun explore(@Param("cursor") goalId: Long): List + + @Modifying(clearAutomatically = true) + @Query( + """ + UPDATE Goal G + SET G.lastCommentReadAt = :lastCommentReadAt + WHERE G.id = :goalId""", + ) + fun updateLastCommentReadAtByGoalId(goalId: Long, lastCommentReadAt: LocalDateTime) } From ca2b42e10d49df7ce3f0295119fb3cdaf38cc367 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Wed, 13 Mar 2024 00:23:17 +0900 Subject: [PATCH 05/60] =?UTF-8?q?refactor=20:=20Emoji=20API=20Restful?= =?UTF-8?q?=ED=95=98=EA=B2=8C=20=EA=B0=9C=EC=84=A0=20(#196)=20(#215)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/emoji/controller/EmojiController.kt | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/EmojiController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/EmojiController.kt index 5847d1a2..4ec1e525 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/EmojiController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/EmojiController.kt @@ -2,8 +2,6 @@ package io.raemian.api.emoji.controller import io.raemian.api.auth.domain.CurrentUser import io.raemian.api.emoji.EmojiService -import io.raemian.api.emoji.controller.request.ReactEmojiRequest -import io.raemian.api.emoji.controller.request.RemoveEmojiRequest import io.raemian.api.emoji.controller.response.EmojiResponse import io.raemian.api.emoji.controller.response.ReactedEmojisResponse import io.raemian.api.support.response.ApiResponse @@ -14,25 +12,22 @@ import org.springframework.web.bind.annotation.DeleteMapping 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 @RestController -@RequestMapping("/emoji") class EmojiController( private val emojiService: EmojiService, ) { @Operation(summary = "이모지 전체 조회 API") - @GetMapping + @GetMapping("/emoji") fun findAll(): ResponseEntity>> = ResponseEntity.ok( ApiResponse.success(emojiService.findAll()), ) @Operation(summary = "Goal에 반응된 이모지와 유저 정보 전체 조회 API") - @GetMapping("/{goalId}") + @GetMapping("/goal/{goalId}/emoji") fun findAllReactedEmojisAtGoal( @PathVariable goalId: Long, @AuthenticationPrincipal currentUser: CurrentUser, @@ -42,24 +37,24 @@ class EmojiController( } @Operation(summary = "Goal에 이모지 반응하기 API") - @PostMapping("/{goalId}") + @PostMapping("/goal/{goalId}/emoji/{emojiId}") fun react( - @RequestBody reactEmojiRequest: ReactEmojiRequest, @PathVariable goalId: Long, + @PathVariable emojiId: Long, @AuthenticationPrincipal currentUser: CurrentUser, ): ResponseEntity> { - emojiService.react(reactEmojiRequest.emojiId, goalId, currentUser.id) + emojiService.react(emojiId, goalId, currentUser.id) return ResponseEntity.ok(ApiResponse.success()) } @Operation(summary = "Goal에 반응한 이모지 삭제 API") - @DeleteMapping("/{goalId}") + @DeleteMapping("/goal/{goalId}/emoji/{emojiId}") fun remove( - @RequestBody removeEmojiRequest: RemoveEmojiRequest, @PathVariable goalId: Long, + @PathVariable emojiId: Long, @AuthenticationPrincipal currentUser: CurrentUser, ): ResponseEntity> { - emojiService.remove(removeEmojiRequest.emojiId, goalId, currentUser.id) + emojiService.remove(emojiId, goalId, currentUser.id) return ResponseEntity.ok(ApiResponse.success()) } } From aaba8f47183b9150672f724df339f22515027d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=9A=B0=EC=84=9D=20=28Woosuk=20Kwon=29?= Date: Wed, 13 Mar 2024 01:28:21 +0900 Subject: [PATCH 06/60] =?UTF-8?q?[=F0=9F=9A=8C=20Issue=20]=20Emoji=20Count?= =?UTF-8?q?=20=EC=9E=91=EC=97=85=20=EB=B0=8F=20Count=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0=20(#213)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(#211): count관련 eventListener 단일 component화 * feat(#211): emoji count 테이블 추가 및 count관련 event application lock 적용 * feat(#211): add goal count event에 exclusive runner 적용 * refactor: event 같은 패키지로 이동 * feat: lock 적용 시점 비동기 이벤트 실행 Thread로 변경 * refactor: event 네이밍 컨벤션 적용 * fix: key에 매핑된 lock 대기 thread가 없다면 map에서 제거 --- .../io/raemian/api/cheer/CheeringService.kt | 4 +- .../raemian/api/cheer/event/CheeringEvent.kt | 5 -- .../api/cheer/event/CheeringEventHandler.kt | 22 ------ .../io/raemian/api/comment/CommentService.kt | 4 +- .../io/raemian/api/emoji/EmojiService.kt | 8 +++ .../io/raemian/api/event/CheeredEvent.kt | 5 ++ .../CommentReadEvent.kt} | 4 +- .../io/raemian/api/event/CountEventHandler.kt | 68 +++++++++++++++++++ .../io/raemian/api/event/CreatedGoalEvent.kt | 6 ++ .../io/raemian/api/event/ExclusiveRunner.kt | 35 ++++++++++ .../io/raemian/api/event/ReactedEmojiEvent.kt | 6 ++ .../ReadEventHandler.kt} | 6 +- .../io/raemian/api/event/RemovedEmojiEvent.kt | 6 ++ .../kotlin/io/raemian/api/event/RunnerLock.kt | 16 +++++ .../kotlin/io/raemian/api/goal/GoalService.kt | 8 +-- .../raemian/api/goal/event/CreateGoalEvent.kt | 6 -- .../api/goal/event/GoalEventHandler.kt | 32 --------- .../raemian/storage/db/core/cheer/Cheering.kt | 2 +- .../db/core/cheer/CheeringRepository.kt | 8 --- .../storage/db/core/emoji/EmojiCount.kt | 37 ++++++++++ .../db/core/emoji/EmojiCountRepository.kt | 7 ++ 21 files changed, 207 insertions(+), 88 deletions(-) delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/cheer/event/CheeringEvent.kt delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/cheer/event/CheeringEventHandler.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/event/CheeredEvent.kt rename backend/application/api/src/main/kotlin/io/raemian/api/{comment/event/UpdateLastCommentReadAtEvent.kt => event/CommentReadEvent.kt} (54%) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/event/CountEventHandler.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/event/CreatedGoalEvent.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/event/ExclusiveRunner.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/event/ReactedEmojiEvent.kt rename backend/application/api/src/main/kotlin/io/raemian/api/{comment/event/UpdateCommentReadAtEventHandler.kt => event/ReadEventHandler.kt} (75%) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/event/RemovedEmojiEvent.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/event/RunnerLock.kt delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/goal/event/CreateGoalEvent.kt delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/goal/event/GoalEventHandler.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/CheeringService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/CheeringService.kt index f26af79e..a8520ae7 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/CheeringService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/CheeringService.kt @@ -4,7 +4,7 @@ import io.raemian.api.cheer.controller.request.CheeringRequest import io.raemian.api.cheer.controller.request.CheeringSquadPagingRequest import io.raemian.api.cheer.controller.response.CheererResponse import io.raemian.api.cheer.controller.response.CheeringCountResponse -import io.raemian.api.cheer.event.CheeringEvent +import io.raemian.api.event.CheeredEvent import io.raemian.api.support.error.CoreApiException import io.raemian.api.support.error.ErrorInfo import io.raemian.api.support.response.PageResult @@ -35,7 +35,7 @@ class CheeringService( saveCheerer(request.lifeMapId, request.cheererId) - applicationEventPublisher.publishEvent(CheeringEvent(request.lifeMapId)) + applicationEventPublisher.publishEvent(CheeredEvent(request.lifeMapId)) cheeringLimiter.put(request.lifeMapId, request.cheererId) } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/event/CheeringEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/event/CheeringEvent.kt deleted file mode 100644 index 5d492d64..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/event/CheeringEvent.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.raemian.api.cheer.event - -data class CheeringEvent( - val lifeMapId: Long, -) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/event/CheeringEventHandler.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/event/CheeringEventHandler.kt deleted file mode 100644 index ddc595de..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/event/CheeringEventHandler.kt +++ /dev/null @@ -1,22 +0,0 @@ -package io.raemian.api.cheer.event - -import io.raemian.storage.db.core.cheer.Cheering -import io.raemian.storage.db.core.cheer.CheeringRepository -import org.springframework.context.event.EventListener -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class CheeringEventHandler( - private val cheeringRepository: CheeringRepository, -) { - - @Transactional - @EventListener - fun addCheeringCount(cheeringEvent: CheeringEvent) { - val cheering = cheeringRepository.findByLifeMapIdForUpdate(cheeringEvent.lifeMapId) - ?: Cheering(0, cheeringEvent.lifeMapId) - - cheeringRepository.save(cheering.addCount()) - } -} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/CommentService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/CommentService.kt index 0a0d478e..261a21f1 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/CommentService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/CommentService.kt @@ -2,7 +2,7 @@ package io.raemian.api.comment import io.raemian.api.comment.controller.request.WriteCommentRequest import io.raemian.api.comment.controller.response.CommentsResponse -import io.raemian.api.comment.event.UpdateLastCommentReadAtEvent +import io.raemian.api.event.CommentReadEvent import io.raemian.api.support.error.CoreApiException import io.raemian.api.support.error.ErrorInfo import io.raemian.storage.db.core.comment.Comment @@ -72,7 +72,7 @@ class CommentService( currentUserId == goal.lifeMap.user.id private fun publishUpdateCommentReadAtEvent(goalId: Long) { - val event = UpdateLastCommentReadAtEvent(goalId, LocalDateTime.now()) + val event = CommentReadEvent(goalId, LocalDateTime.now()) applicationEventPublisher.publishEvent(event) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt index 7bb0fa68..dd394c2d 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt @@ -2,11 +2,14 @@ package io.raemian.api.emoji import io.raemian.api.emoji.controller.response.EmojiResponse import io.raemian.api.emoji.controller.response.ReactedEmojisResponse +import io.raemian.api.event.ReactedEmojiEvent +import io.raemian.api.event.RemovedEmojiEvent import io.raemian.storage.db.core.emoji.EmojiRepository import io.raemian.storage.db.core.emoji.ReactedEmoji import io.raemian.storage.db.core.emoji.ReactedEmojiRepository import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.user.UserRepository +import org.springframework.context.ApplicationEventPublisher import org.springframework.dao.DataIntegrityViolationException import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -17,6 +20,7 @@ class EmojiService( private val goalRepository: GoalRepository, private val userRepository: UserRepository, private val reactedEmojiRepository: ReactedEmojiRepository, + private val applicationEventPublisher: ApplicationEventPublisher, ) { @Transactional(readOnly = true) fun findAll(): List = @@ -40,6 +44,8 @@ class EmojiService( ignoreDuplicatedReactedEmojiException { reactedEmojiRepository.save(reactedEmoji) } + + applicationEventPublisher.publishEvent(ReactedEmojiEvent(goalId, emojiId)) } @Transactional @@ -50,6 +56,8 @@ class EmojiService( reactedEmojiRepository .deleteByEmojiAndGoalAndReactUser(emoji, goal, emojiReactUser) + + applicationEventPublisher.publishEvent(RemovedEmojiEvent(goalId, emojiId)) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/CheeredEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/CheeredEvent.kt new file mode 100644 index 00000000..b6b9eecf --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/CheeredEvent.kt @@ -0,0 +1,5 @@ +package io.raemian.api.event + +data class CheeredEvent( + val lifeMapId: Long, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/event/UpdateLastCommentReadAtEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/CommentReadEvent.kt similarity index 54% rename from backend/application/api/src/main/kotlin/io/raemian/api/comment/event/UpdateLastCommentReadAtEvent.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/event/CommentReadEvent.kt index f0858cf2..673613f9 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/event/UpdateLastCommentReadAtEvent.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/CommentReadEvent.kt @@ -1,8 +1,8 @@ -package io.raemian.api.comment.event +package io.raemian.api.event import java.time.LocalDateTime -data class UpdateLastCommentReadAtEvent( +data class CommentReadEvent( val goalId: Long, val commentReadAt: LocalDateTime, ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/CountEventHandler.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/CountEventHandler.kt new file mode 100644 index 00000000..d8e68b97 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/CountEventHandler.kt @@ -0,0 +1,68 @@ +package io.raemian.api.event + +import io.raemian.storage.db.core.cheer.Cheering +import io.raemian.storage.db.core.cheer.CheeringRepository +import io.raemian.storage.db.core.emoji.EmojiCount +import io.raemian.storage.db.core.emoji.EmojiCountRepository +import io.raemian.storage.db.core.lifemap.LifeMapCount +import io.raemian.storage.db.core.lifemap.LifeMapCountRepository +import org.springframework.context.event.EventListener +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.time.Duration + +@Service +class CountEventHandler( + private val cheeringRepository: CheeringRepository, + private val lifeMapCountRepository: LifeMapCountRepository, + private val emojiCountRepository: EmojiCountRepository, + private val exclusiveRunner: ExclusiveRunner, +) { + @Transactional + @EventListener + fun addCheeringCount(cheeringEvent: CheeredEvent) { + exclusiveRunner.call("cheering:${cheeringEvent.lifeMapId}", Duration.ofSeconds(10)) { + val cheering = cheeringRepository.findByLifeMapId(cheeringEvent.lifeMapId) + ?: Cheering(0, cheeringEvent.lifeMapId) + + cheeringRepository.save(cheering.addCount()) + } + } + + @Transactional + @EventListener + fun addGoalCount(createGoalEvent: CreatedGoalEvent) { + // TODO goal 테이블에서 life map 기준으로 전체 count 한 값으로 업데이트 + exclusiveRunner.call("goal:${createGoalEvent.lifeMapId}:${createGoalEvent.goalId}", Duration.ofSeconds(10)) { + val mapCount = lifeMapCountRepository.findByLifeMapId(createGoalEvent.lifeMapId) + ?: LifeMapCount.of(createGoalEvent.lifeMapId) + + val added = mapCount.addGoalCount() + + lifeMapCountRepository.save(added) + } + } + + @Transactional + @EventListener + fun addEmojiCount(reactEmojiEvent: ReactedEmojiEvent) { + exclusiveRunner.call("emoji:${reactEmojiEvent.goalId}:${reactEmojiEvent.emojiId}", Duration.ofSeconds(10)) { + val emojiCount = emojiCountRepository.findByGoalIdAndEmojiId(reactEmojiEvent.goalId, reactEmojiEvent.emojiId) + ?: EmojiCount(0, reactEmojiEvent.emojiId, reactEmojiEvent.goalId) + + emojiCountRepository.save(emojiCount.addCount()) + } + } + + @Transactional + @EventListener + fun minusEmojiCount(removeEmojiEvent: RemovedEmojiEvent) { + exclusiveRunner.call("emoji:${removeEmojiEvent.goalId}:${removeEmojiEvent.emojiId}", Duration.ofSeconds(10)) { + val emojiCount = emojiCountRepository.findByGoalIdAndEmojiId(removeEmojiEvent.goalId, removeEmojiEvent.emojiId) + + if (emojiCount != null && 0 < emojiCount.count) { + emojiCountRepository.save(emojiCount.minusCount()) + } + } + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/CreatedGoalEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/CreatedGoalEvent.kt new file mode 100644 index 00000000..23d896e6 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/CreatedGoalEvent.kt @@ -0,0 +1,6 @@ +package io.raemian.api.event + +data class CreatedGoalEvent( + val goalId: Long, + val lifeMapId: Long, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/ExclusiveRunner.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/ExclusiveRunner.kt new file mode 100644 index 00000000..f91d8177 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/ExclusiveRunner.kt @@ -0,0 +1,35 @@ +package io.raemian.api.event + +import org.springframework.stereotype.Component +import java.time.Duration +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException + +@Component +class ExclusiveRunner { + private val map: ConcurrentHashMap = ConcurrentHashMap() + + fun call(key: String, tryLockTimeout: Duration, f: Runnable) { + val lock = map.computeIfAbsent(key) { key -> RunnerLock() } + + try { + lock.increase() + if (lock.tryLock(tryLockTimeout.toSeconds(), TimeUnit.SECONDS)) { + try { + f.run() + return + } finally { + if (lock.decrease() <= 0) { + map.remove(key) + } + lock.unlock() + } + } + } catch (e: InterruptedException) { + throw RuntimeException(e) + } + + throw TimeoutException() + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/ReactedEmojiEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/ReactedEmojiEvent.kt new file mode 100644 index 00000000..44ea2fb7 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/ReactedEmojiEvent.kt @@ -0,0 +1,6 @@ +package io.raemian.api.event + +data class ReactedEmojiEvent( + val goalId: Long, + val emojiId: Long, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/event/UpdateCommentReadAtEventHandler.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/ReadEventHandler.kt similarity index 75% rename from backend/application/api/src/main/kotlin/io/raemian/api/comment/event/UpdateCommentReadAtEventHandler.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/event/ReadEventHandler.kt index 8cf6bbe9..a907e7af 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/event/UpdateCommentReadAtEventHandler.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/ReadEventHandler.kt @@ -1,4 +1,4 @@ -package io.raemian.api.comment.event +package io.raemian.api.event import io.raemian.storage.db.core.goal.GoalRepository import org.springframework.context.event.EventListener @@ -6,12 +6,12 @@ import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @Service -class UpdateCommentReadAtEventHandler( +class ReadEventHandler( private val goalRepository: GoalRepository, ) { @Transactional @EventListener - fun updateLastCommentReadTime(event: UpdateLastCommentReadAtEvent) { + fun updateLastCommentReadTime(event: CommentReadEvent) { goalRepository.updateLastCommentReadAtByGoalId( event.goalId, event.commentReadAt, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/RemovedEmojiEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/RemovedEmojiEvent.kt new file mode 100644 index 00000000..fe29f907 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/RemovedEmojiEvent.kt @@ -0,0 +1,6 @@ +package io.raemian.api.event + +data class RemovedEmojiEvent( + val goalId: Long, + val emojiId: Long, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/RunnerLock.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/RunnerLock.kt new file mode 100644 index 00000000..1533548c --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/RunnerLock.kt @@ -0,0 +1,16 @@ +package io.raemian.api.event + +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.locks.ReentrantLock + +class RunnerLock : ReentrantLock() { + private val watingThread = AtomicInteger(0) + + fun increase(): Int { + return watingThread.addAndGet(1) + } + + fun decrease(): Int { + return watingThread.decrementAndGet() + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt index 4874c6e7..ecd96cca 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt @@ -1,11 +1,11 @@ package io.raemian.api.goal +import io.raemian.api.event.CreatedGoalEvent import io.raemian.api.goal.controller.request.CreateGoalRequest import io.raemian.api.goal.controller.request.UpdateGoalRequest import io.raemian.api.goal.controller.response.CreateGoalResponse import io.raemian.api.goal.controller.response.GoalResponse import io.raemian.api.goal.domain.GoalExploreDTO -import io.raemian.api.goal.event.CreateGoalEvent import io.raemian.api.sticker.StickerService import io.raemian.api.support.RaemianLocalDate import io.raemian.api.support.error.MaxGoalCountExceededException @@ -51,11 +51,9 @@ class GoalService( // goal 생성시 count event 발행 applicationEventPublisher.publishEvent( - CreateGoalEvent( - goalId = goal.id!!, - lifeMapId = lifeMap.id!!, - ), + CreatedGoalEvent(goalId = goal.id!!, lifeMapId = lifeMap.id!!), ) + return CreateGoalResponse(goal) } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/event/CreateGoalEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/event/CreateGoalEvent.kt deleted file mode 100644 index fcb34498..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/event/CreateGoalEvent.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.raemian.api.goal.event - -data class CreateGoalEvent( - val goalId: Long, - val lifeMapId: Long, -) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/event/GoalEventHandler.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/event/GoalEventHandler.kt deleted file mode 100644 index f523fa96..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/event/GoalEventHandler.kt +++ /dev/null @@ -1,32 +0,0 @@ -package io.raemian.api.goal.event - -import io.raemian.storage.db.core.goal.GoalRepository -import io.raemian.storage.db.core.lifemap.LifeMapCount -import io.raemian.storage.db.core.lifemap.LifeMapCountRepository -import io.raemian.storage.db.core.lifemap.LifeMapRepository -import jakarta.transaction.Transactional -import org.slf4j.LoggerFactory -import org.springframework.context.event.EventListener -import org.springframework.stereotype.Service - -@Service -class GoalEventHandler( - private val lifeMapCountRepository: LifeMapCountRepository, - private val goalRepository: GoalRepository, - private val lifeMapRepository: LifeMapRepository, -) { - private val log = LoggerFactory.getLogger(javaClass) - - @Transactional - @EventListener - fun addGoalCount(createGoalEvent: CreateGoalEvent) { - // TODO goal 테이블에서 life map 기준으로 전체 count 한 값으로 업데이트 - val mapCount = lifeMapCountRepository.findByLifeMapId(createGoalEvent.lifeMapId) - ?: LifeMapCount.of(createGoalEvent.lifeMapId) - - val added = mapCount.addGoalCount() - - lifeMapCountRepository.save(added) - log.info("life map id: ${createGoalEvent.lifeMapId}, added goal count : ${added.goalCount}") - } -} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheering.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheering.kt index 80503cbd..d4bb7800 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheering.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheering.kt @@ -22,7 +22,7 @@ class Cheering( val id: Long? = null, ) : BaseEntity() { fun addCount(): Cheering { - this.count = this.count + 1 + this.count += 1 return this } } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheeringRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheeringRepository.kt index a4b58b81..73f17675 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheeringRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheeringRepository.kt @@ -1,15 +1,7 @@ package io.raemian.storage.db.core.cheer -import jakarta.persistence.LockModeType import org.springframework.data.jpa.repository.JpaRepository -import org.springframework.data.jpa.repository.Lock -import org.springframework.data.jpa.repository.Query -import org.springframework.data.repository.query.Param interface CheeringRepository : JpaRepository { fun findByLifeMapId(lifeMapId: Long): Cheering? - - @Lock(LockModeType.PESSIMISTIC_WRITE) - @Query("select c from Cheering c where c.lifeMapId = :lifeMapId") - fun findByLifeMapIdForUpdate(@Param("lifeMapId") lifeMapId: Long): Cheering? } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt new file mode 100644 index 00000000..4bd2cb06 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt @@ -0,0 +1,37 @@ +package io.raemian.storage.db.core.emoji + +import io.raemian.storage.db.core.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Index +import jakarta.persistence.Table + +@Entity +@Table(name = "EMOJI_COUNTS", indexes = [Index(name = "IDX_GOAL_ID_AND_EMOJI_ID", columnList = "goalId, emojiId")]) +class EmojiCount( + @Column + var count: Long, + + @Column + val emojiId: Long, + + @Column + val goalId: Long, + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, +) : BaseEntity() { + fun addCount(): EmojiCount { + this.count += 1 + return this + } + + fun minusCount(): EmojiCount { + this.count -= 1 + return this + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt new file mode 100644 index 00000000..cb683295 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt @@ -0,0 +1,7 @@ +package io.raemian.storage.db.core.emoji + +import org.springframework.data.jpa.repository.JpaRepository + +interface EmojiCountRepository : JpaRepository { + fun findByGoalIdAndEmojiId(goalId: Long, emojiId: Long): EmojiCount? +} From 77200b353042dedcea599edae47549f425ae7ebc Mon Sep 17 00:00:00 2001 From: binary_ho Date: Wed, 13 Mar 2024 17:29:14 +0900 Subject: [PATCH 07/60] =?UTF-8?q?[=F0=9F=9A=8C=20Issue]=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EC=9D=B4=EC=8A=88=20=ED=85=9C?= =?UTF-8?q?=ED=94=8C=EB=A6=BF=20=EC=84=B8=ED=8C=85=20(#220)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore : 리팩토링 이슈 템플릿 세팅 * chore : 기타-이슈 기본 생성 제목에 불필요한 띄어쓰기 제거 --- ...0\355\203\200-\354\235\264\354\212\210.md" | 2 +- ...54\355\214\251\355\206\240\353\247\201.md" | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 ".github/ISSUE_TEMPLATE/\360\237\233\240\357\270\217-\353\246\254\355\214\251\355\206\240\353\247\201.md" diff --git "a/.github/ISSUE_TEMPLATE/\360\237\232\214-\352\270\260\355\203\200-\354\235\264\354\212\210.md" "b/.github/ISSUE_TEMPLATE/\360\237\232\214-\352\270\260\355\203\200-\354\235\264\354\212\210.md" index 18e572e2..efa98b35 100644 --- "a/.github/ISSUE_TEMPLATE/\360\237\232\214-\352\270\260\355\203\200-\354\235\264\354\212\210.md" +++ "b/.github/ISSUE_TEMPLATE/\360\237\232\214-\352\270\260\355\203\200-\354\235\264\354\212\210.md" @@ -1,7 +1,7 @@ --- name: "\U0001F68C 기타 이슈" about: 기타 작업을 위한 이슈 템플릿 -title: "[\U0001F68C Issue ] 작업 내용" +title: "[\U0001F68C Issue] 작업 내용" labels: '' assignees: '' diff --git "a/.github/ISSUE_TEMPLATE/\360\237\233\240\357\270\217-\353\246\254\355\214\251\355\206\240\353\247\201.md" "b/.github/ISSUE_TEMPLATE/\360\237\233\240\357\270\217-\353\246\254\355\214\251\355\206\240\353\247\201.md" new file mode 100644 index 00000000..3b7a0111 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\360\237\233\240\357\270\217-\353\246\254\355\214\251\355\206\240\353\247\201.md" @@ -0,0 +1,19 @@ +--- +name: "\U0001F6E0️ 리팩토링" +about: 리팩토링을 위한 이슈 템플릿 +title: "[\U0001F6E0️ Refactor] " +labels: refactor +assignees: '' + +--- + +## 변경할 내용 +- 변경 동기와 내용을 정의해주세요 +- As-Is와 To-Be를 설명해주세요 + +## 할 일 +- [ ] 작업1 +- [ ] 작업2 + +## 작업 완료 예정일 +- 0000.00.00 00시 00분 From 2de704d234df4c30a68cf571c4141e73567cc469 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Wed, 13 Mar 2024 17:29:59 +0900 Subject: [PATCH 08/60] =?UTF-8?q?[=F0=9F=9B=A0=EF=B8=8F=20Refactor]=20Goal?= =?UTF-8?q?=20Emoji=20API=20=EC=9D=91=EB=8B=B5=EC=97=90=20"=EC=9D=B4?= =?UTF-8?q?=EB=AA=A8=EC=A7=80=EB=A5=BC=20=EB=88=84=EB=A5=B8=20=EC=82=AC?= =?UTF-8?q?=EB=9E=8C=20=EC=88=98"=20=EC=B6=94=EA=B0=80=20(#221)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor : Goal에 반응 조회 API에 반응한 사람 수 값 추가 (#218) * test : Goal 반응 조회 API 반응한 사람 수 값이 정확한지 확인하는 테스트 작성 (#218) --- .../response/ReactedEmojisResponse.kt | 11 +++++++ .../api/integration/emoji/EmojiServiceTest.kt | 33 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/response/ReactedEmojisResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/response/ReactedEmojisResponse.kt index c764b5ff..d865e7e6 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/response/ReactedEmojisResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/response/ReactedEmojisResponse.kt @@ -5,6 +5,7 @@ import io.raemian.storage.db.core.user.User data class ReactedEmojisResponse( val totalReactedEmojisCount: Int, + val totalReactUserCount: Int, val latestReactUserNickname: String?, val reactedEmojis: List, ) { @@ -12,10 +13,13 @@ data class ReactedEmojisResponse( fun of(reactedEmojis: List, username: String): ReactedEmojisResponse { val reactedEmojiAndReactUsers = convert(reactedEmojis, username) + val reactedUserCount = countTotalReactUser(reactedEmojiAndReactUsers) val totalEmojisCount = reactedEmojiAndReactUsers.sumOf { it.reactCount } + val latestReactUserNickname = reactedEmojis.lastOrNull()?.reactUser?.nickname return ReactedEmojisResponse( totalReactedEmojisCount = totalEmojisCount, + totalReactUserCount = reactedUserCount, latestReactUserNickname = latestReactUserNickname, reactedEmojis = reactedEmojiAndReactUsers, ) @@ -31,6 +35,13 @@ data class ReactedEmojisResponse( .mapValues { entry -> ReactedEmojiAndReactUsers.of(entry.value, username) } .values .toList() + + private fun countTotalReactUser(reactedEmojiAndReactUsers: List) = + reactedEmojiAndReactUsers + .flatMap { it.reactUsers } + .map { it.username } + .distinct() + .size } data class ReactedEmojiAndReactUsers( diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt index 872ff4ee..23387dfc 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt @@ -226,4 +226,37 @@ class EmojiServiceTest { val reactedEmojis = reactedEmojiRepository.findAllByGoal(GOAL_FIXTURE) assertThat(reactedEmojis.size).isEqualTo(0) } + + @Test + @DisplayName("Goal에 반응한 사람 수를 확인할 수 있다.") + fun totalReactUserCountTest() { + // given + val userFixture3 = User( + email = "dfghcvb2", + username = "binary", + nickname = "binary", + birth = LocalDate.MIN, + image = "", + provider = OAuthProvider.NAVER, + authority = Authority.ROLE_USER, + ) + entityManager.merge(userFixture3) + + val emoji = Emoji("이모지", "url") + val emoji2 = Emoji("이모지", "url") + emojiRepository.saveAll(listOf(emoji, emoji2)) + + val reactedEmoji = ReactedEmoji(GOAL_FIXTURE, emoji, USER_FIXTURE) + val reactedEmoji2 = ReactedEmoji(GOAL_FIXTURE, emoji, USER_FIXTURE2) + val reactedEmoji3 = ReactedEmoji(GOAL_FIXTURE, emoji2, USER_FIXTURE) + val reactedEmoji4 = ReactedEmoji(GOAL_FIXTURE, emoji2, USER_FIXTURE2) + val reactedEmoji5 = ReactedEmoji(GOAL_FIXTURE, emoji2, userFixture3) + reactedEmojiRepository.saveAll(listOf(reactedEmoji, reactedEmoji2, reactedEmoji3, reactedEmoji4, reactedEmoji5)) + + // when + val reactedEmojis = emojiService.findAllReactedEmojisByGoalId(GOAL_FIXTURE.id!!, USER_FIXTURE.username!!) + + // then + assertThat(reactedEmojis.totalReactUserCount).isEqualTo(3) + } } From a138cf7d3e3fbfa824d1ef37262693f785ccddc5 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Wed, 13 Mar 2024 17:38:27 +0900 Subject: [PATCH 09/60] =?UTF-8?q?[=F0=9F=9B=A0=EF=B8=8F=20Refactor]=20IsMy?= =?UTF-8?q?Reaction=EC=9D=84=20=ED=99=95=EC=9D=B8=ED=95=A0=20=EB=95=8C=20U?= =?UTF-8?q?sername=EC=9D=B4=20=EC=95=84=EB=8B=8C=20id=EB=A1=9C=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=ED=95=98=EA=B8=B0=20(#219)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor : IsMyReaction을 확인할 때 Username이 아닌 id로 확인하도록 변경 (#217) * merge with develop --- .../io/raemian/api/emoji/EmojiService.kt | 4 +-- .../api/emoji/controller/EmojiController.kt | 2 +- .../response/ReactedEmojisResponse.kt | 12 +++---- .../api/integration/emoji/EmojiServiceTest.kt | 32 +++++++++++++++++-- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt index dd394c2d..fac50ceb 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt @@ -28,10 +28,10 @@ class EmojiService( .map(EmojiResponse::from) @Transactional(readOnly = true) - fun findAllReactedEmojisByGoalId(goalId: Long, currentUserUsername: String): ReactedEmojisResponse { + fun findAllReactedEmojisByGoalId(goalId: Long, currentUserId: Long): ReactedEmojisResponse { val goal = goalRepository.getReferenceById(goalId) val reactedEmojis = reactedEmojiRepository.findAllByGoal(goal) - return ReactedEmojisResponse.of(reactedEmojis, currentUserUsername) + return ReactedEmojisResponse.of(reactedEmojis, currentUserId) } @Transactional diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/EmojiController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/EmojiController.kt index 4ec1e525..99b36234 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/EmojiController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/EmojiController.kt @@ -32,7 +32,7 @@ class EmojiController( @PathVariable goalId: Long, @AuthenticationPrincipal currentUser: CurrentUser, ): ResponseEntity> { - val response = emojiService.findAllReactedEmojisByGoalId(goalId, currentUser.username) + val response = emojiService.findAllReactedEmojisByGoalId(goalId, currentUser.id) return ResponseEntity.ok(ApiResponse.success(response)) } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/response/ReactedEmojisResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/response/ReactedEmojisResponse.kt index d865e7e6..8864dd2a 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/response/ReactedEmojisResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/response/ReactedEmojisResponse.kt @@ -10,8 +10,8 @@ data class ReactedEmojisResponse( val reactedEmojis: List, ) { companion object { - fun of(reactedEmojis: List, username: String): ReactedEmojisResponse { - val reactedEmojiAndReactUsers = convert(reactedEmojis, username) + fun of(reactedEmojis: List, userId: Long): ReactedEmojisResponse { + val reactedEmojiAndReactUsers = convert(reactedEmojis, userId) val reactedUserCount = countTotalReactUser(reactedEmojiAndReactUsers) val totalEmojisCount = reactedEmojiAndReactUsers.sumOf { it.reactCount } @@ -27,12 +27,12 @@ data class ReactedEmojisResponse( private fun convert( reactedEmojis: List, - username: String, + userId: Long, ): List = reactedEmojis .filter { it.emoji.id != null } .groupBy { it.emoji.id } - .mapValues { entry -> ReactedEmojiAndReactUsers.of(entry.value, username) } + .mapValues { entry -> ReactedEmojiAndReactUsers.of(entry.value, userId) } .values .toList() @@ -53,10 +53,10 @@ data class ReactedEmojisResponse( val reactUsers: List, ) { companion object { - fun of(reactedEmojis: List, username: String): ReactedEmojiAndReactUsers { + fun of(reactedEmojis: List, userId: Long): ReactedEmojiAndReactUsers { val emoji = reactedEmojis.first().emoji val reactUsers = getReactUsers(reactedEmojis) - val isMyReaction = reactUsers.any { it.username == username } + val isMyReaction = reactedEmojis.any { userId == it.reactUser.id } return ReactedEmojiAndReactUsers( id = emoji.id, diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt index 23387dfc..e34d5535 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt @@ -102,7 +102,7 @@ class EmojiServiceTest { } @Test - @DisplayName("Goal에 달린 이모지들을 조회할 수 있다.") + @DisplayName("Goal에 반응한 이모지들을 조회할 수 있다.") fun findGoalReactedEmojisTest() { // given val emoji = Emoji("이모지", "url") @@ -115,13 +115,39 @@ class EmojiServiceTest { reactedEmojiRepository.saveAll(listOf(reactedEmoji, reactedEmoji2, reactedEmoji3)) // when - val reactedEmojis = emojiService.findAllReactedEmojisByGoalId(GOAL_FIXTURE.id!!, USER_FIXTURE.username!!) + val reactedEmojis = emojiService.findAllReactedEmojisByGoalId(GOAL_FIXTURE.id!!, USER_FIXTURE.id!!) // then assertThat(reactedEmojis.totalReactedEmojisCount).isEqualTo(3) println(reactedEmojis) } + @Test + @DisplayName("내가 반응한 이모지를 확인할 수 있다.") + fun isMyReactionTest() { + // given + val emoji = Emoji("이모지", "url") + val emoji2 = Emoji("이모지", "url") + val emoji3 = Emoji("이모지", "url") + emojiRepository.saveAll(listOf(emoji, emoji2, emoji3)) + + val reactedEmoji = ReactedEmoji(GOAL_FIXTURE, emoji, USER_FIXTURE) + val reactedEmoji2 = ReactedEmoji(GOAL_FIXTURE, emoji, USER_FIXTURE2) + val reactedEmoji3 = ReactedEmoji(GOAL_FIXTURE, emoji2, USER_FIXTURE2) + val reactedEmoji4 = ReactedEmoji(GOAL_FIXTURE, emoji3, USER_FIXTURE2) + reactedEmojiRepository.saveAll(listOf(reactedEmoji, reactedEmoji2, reactedEmoji3, reactedEmoji4)) + + // when + val response = emojiService.findAllReactedEmojisByGoalId(GOAL_FIXTURE.id!!, USER_FIXTURE.id!!) + + // then + val reactedEmojis = response.reactedEmojis + println(reactedEmojis) + assertThat(reactedEmojis[0].isMyReaction).isEqualTo(true) + assertThat(reactedEmojis[1].isMyReaction).isEqualTo(false) + assertThat(reactedEmojis[2].isMyReaction).isEqualTo(false) + } + @Test @DisplayName("Goal에 이모지를 반응할 수 있다.") fun reactTest() { @@ -254,7 +280,7 @@ class EmojiServiceTest { reactedEmojiRepository.saveAll(listOf(reactedEmoji, reactedEmoji2, reactedEmoji3, reactedEmoji4, reactedEmoji5)) // when - val reactedEmojis = emojiService.findAllReactedEmojisByGoalId(GOAL_FIXTURE.id!!, USER_FIXTURE.username!!) + val reactedEmojis = emojiService.findAllReactedEmojisByGoalId(GOAL_FIXTURE.id!!, USER_FIXTURE.id!!) // then assertThat(reactedEmojis.totalReactUserCount).isEqualTo(3) From e81cec1906baf96bb554aeca243cba9b7d865188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=9A=B0=EC=84=9D=20=28Woosuk=20Kwon=29?= Date: Wed, 13 Mar 2024 20:59:17 +0900 Subject: [PATCH 10/60] =?UTF-8?q?[=F0=9F=86=98=20Bug]=20authorization=5Fre?= =?UTF-8?q?quest=5Fnot=5Ffound=20=EC=98=A4=EB=A5=98=20(#216)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(#209): spring security authorization_request_not_found 수정 * fix: AuthorizationRequestRepository cookie 기반에서 state parameter 기반으로 변경 * fix: AuthorizationRequestRepository remove 메서드에 명시적인 삭제 추가 --- .../raemian/api/config/WebSecurityConfig.kt | 4 +- .../io/raemian/api/support/CookieUtils.kt | 55 --------------- ...kieOAuth2AuthorizationRequestRepository.kt | 68 ------------------- ...ateOAuth2AuthorizationRequestRepository.kt | 49 +++++++++++++ 4 files changed, 51 insertions(+), 125 deletions(-) delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/CookieUtils.kt delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/HttpCookieOAuth2AuthorizationRequestRepository.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/StateOAuth2AuthorizationRequestRepository.kt diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt index fe3784b6..ed0e4fad 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt @@ -3,7 +3,7 @@ package io.raemian.api.config import io.raemian.api.auth.converter.TokenRequestEntityConverter import io.raemian.api.auth.domain.CurrentUser import io.raemian.api.auth.service.OAuth2UserService -import io.raemian.api.support.HttpCookieOAuth2AuthorizationRequestRepository +import io.raemian.api.support.StateOAuth2AuthorizationRequestRepository import io.raemian.api.support.TokenProvider import jakarta.servlet.http.HttpServletResponse import org.slf4j.LoggerFactory @@ -39,7 +39,7 @@ class WebSecurityConfig( @Value("\${spring.profiles.active:local}") private val profile: String, private val tokenRequestEntityConverter: TokenRequestEntityConverter, - private val httpCookieOAuth2AuthorizationRequestRepository: HttpCookieOAuth2AuthorizationRequestRepository, + private val httpCookieOAuth2AuthorizationRequestRepository: StateOAuth2AuthorizationRequestRepository, ) : SecurityConfigurerAdapter() { private val log = LoggerFactory.getLogger(javaClass) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/CookieUtils.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/CookieUtils.kt deleted file mode 100644 index d049311e..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/CookieUtils.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.raemian.api.support - -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 = request.cookies - if (cookies != null && cookies.isNotEmpty()) { - for (cookie in cookies) { - if (cookie.name == name) { - return Optional.of(cookie) - } - } - } - return Optional.empty() - } - - fun addCookie(response: HttpServletResponse, name: String, value: String, maxAge: Int) { - val cookie = Cookie(name, value) - cookie.path = "/" - cookie.isHttpOnly = true - cookie.maxAge = maxAge - response.addCookie(cookie) - } - - fun deleteCookie(request: HttpServletRequest, response: HttpServletResponse, name: String) { - val cookies = request.cookies - if (cookies != null && cookies.isNotEmpty()) { - for (cookie in cookies) { - if (cookie.name == name) { - cookie.value = "" - cookie.path = "/" - cookie.maxAge = 0 - response.addCookie(cookie) - } - } - } - } - - fun serialize(obj: Any?): String { - return Base64.getUrlEncoder() - .encodeToString( - SerializationUtils.serialize(obj), - ) - } - - fun deserialize(cookie: Cookie, cls: Class): T { - return cls.cast(SerializationUtils.deserialize(Base64.getUrlDecoder().decode(cookie.value))) - } -} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/HttpCookieOAuth2AuthorizationRequestRepository.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/HttpCookieOAuth2AuthorizationRequestRepository.kt deleted file mode 100644 index 995034cc..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/HttpCookieOAuth2AuthorizationRequestRepository.kt +++ /dev/null @@ -1,68 +0,0 @@ -package io.raemian.api.support - -import jakarta.servlet.http.Cookie -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 -import java.time.Duration - -@Component -class HttpCookieOAuth2AuthorizationRequestRepository() : AuthorizationRequestRepository { - - private val AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request" - private val EXPIRE_SECONDS: Int = Duration.ofSeconds(180).toMillis().toInt() - - override fun loadAuthorizationRequest(request: HttpServletRequest): OAuth2AuthorizationRequest? { - val state = this.getStateParameter(request) ?: return null - - val authorizationRequest: OAuth2AuthorizationRequest? = - CookieUtils.getCookie(request, AUTHORIZATION_REQUEST_COOKIE_NAME) - .map { cookie: Cookie -> - CookieUtils.deserialize(cookie, OAuth2AuthorizationRequest::class.java) - }.orElse(null) - - return if (authorizationRequest != null && state == authorizationRequest.state) { - authorizationRequest - } else { - null - } - } - override fun saveAuthorizationRequest( - authorizationRequest: OAuth2AuthorizationRequest?, - request: HttpServletRequest, - response: HttpServletResponse, - ) { - if (authorizationRequest == null) { - removeAuthorizationRequest(request, response) - return - } - - CookieUtils.addCookie( - response, - AUTHORIZATION_REQUEST_COOKIE_NAME, - CookieUtils.serialize(authorizationRequest), - EXPIRE_SECONDS, - ) - } - - override fun removeAuthorizationRequest( - request: HttpServletRequest, - response: HttpServletResponse, - ): OAuth2AuthorizationRequest? { - val authorizationRequest: OAuth2AuthorizationRequest? = this.loadAuthorizationRequest(request) - - if (authorizationRequest != null) { - removeAuthorizationRequestCookies(request, response) - } - - return authorizationRequest - } - - private fun removeAuthorizationRequestCookies(request: HttpServletRequest, response: HttpServletResponse) { - CookieUtils.deleteCookie(request, response, AUTHORIZATION_REQUEST_COOKIE_NAME) - } - - private fun getStateParameter(request: HttpServletRequest): String? = request.getParameter("state") -} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/StateOAuth2AuthorizationRequestRepository.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/StateOAuth2AuthorizationRequestRepository.kt new file mode 100644 index 00000000..42ef3b5b --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/StateOAuth2AuthorizationRequestRepository.kt @@ -0,0 +1,49 @@ +package io.raemian.api.support + +import com.github.benmanes.caffeine.cache.Cache +import com.github.benmanes.caffeine.cache.Caffeine +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 +import java.util.concurrent.TimeUnit + +@Component +class StateOAuth2AuthorizationRequestRepository() : AuthorizationRequestRepository { + private val oauthRequestStorage: Cache = Caffeine.newBuilder() + .expireAfterWrite(60L, TimeUnit.SECONDS) + .build() + + override fun loadAuthorizationRequest(request: HttpServletRequest): OAuth2AuthorizationRequest? { + return oauthRequestStorage.getIfPresent(getStateParameter(request)) + } + + override fun saveAuthorizationRequest( + authorizationRequest: OAuth2AuthorizationRequest?, + request: HttpServletRequest, + response: HttpServletResponse, + ) { + if (authorizationRequest == null) { + removeAuthorizationRequest(request, response) + return + } + + oauthRequestStorage.put(authorizationRequest.state, authorizationRequest) + } + + override fun removeAuthorizationRequest( + request: HttpServletRequest, + response: HttpServletResponse, + ): OAuth2AuthorizationRequest? { + val authorizationRequest = loadAuthorizationRequest(request) + + if (authorizationRequest != null) { + oauthRequestStorage.invalidate(authorizationRequest.state) + } + + return loadAuthorizationRequest(request) + } + + private fun getStateParameter(request: HttpServletRequest): String = request.getParameter("state") +} From 9d131e0e44a1daabcd421ef716442dc912dd45a0 Mon Sep 17 00:00:00 2001 From: wkwon Date: Thu, 14 Mar 2024 00:10:24 +0900 Subject: [PATCH 11/60] =?UTF-8?q?fix:=20AuthorizationRequestRepository=20o?= =?UTF-8?q?authRequestStorage=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C=EC=A0=81=20=EC=82=AD=EC=A0=9C=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 --- .../StateOAuth2AuthorizationRequestRepository.kt | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/StateOAuth2AuthorizationRequestRepository.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/StateOAuth2AuthorizationRequestRepository.kt index 42ef3b5b..16e917a0 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/StateOAuth2AuthorizationRequestRepository.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/StateOAuth2AuthorizationRequestRepository.kt @@ -24,24 +24,15 @@ class StateOAuth2AuthorizationRequestRepository() : AuthorizationRequestReposito request: HttpServletRequest, response: HttpServletResponse, ) { - if (authorizationRequest == null) { - removeAuthorizationRequest(request, response) - return + if (authorizationRequest != null) { + oauthRequestStorage.put(authorizationRequest.state, authorizationRequest) } - - oauthRequestStorage.put(authorizationRequest.state, authorizationRequest) } override fun removeAuthorizationRequest( request: HttpServletRequest, response: HttpServletResponse, ): OAuth2AuthorizationRequest? { - val authorizationRequest = loadAuthorizationRequest(request) - - if (authorizationRequest != null) { - oauthRequestStorage.invalidate(authorizationRequest.state) - } - return loadAuthorizationRequest(request) } From 270751d3d18d8ad6dd5972935e9b0a7b5a09a708 Mon Sep 17 00:00:00 2001 From: ManHyuk Date: Thu, 14 Mar 2024 00:19:26 +0900 Subject: [PATCH 12/60] refact(#171): add emoji count --- .../api/emoji/domain/EmojiCountSubset.kt | 17 ++++++++++++++++ .../io/raemian/api/event/CountEventHandler.kt | 5 ++++- .../kotlin/io/raemian/api/goal/GoalService.kt | 12 +++++++++-- .../api/goal/controller/GoalController.kt | 5 +++-- .../raemian/api/goal/domain/GoalExploreDTO.kt | 20 ++++++++++++++----- .../storage/db/core/emoji/EmojiCount.kt | 9 ++++++--- .../db/core/emoji/EmojiCountRepository.kt | 1 + 7 files changed, 56 insertions(+), 13 deletions(-) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt new file mode 100644 index 00000000..5f9c0e31 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt @@ -0,0 +1,17 @@ +package io.raemian.api.emoji.domain + +import io.raemian.storage.db.core.emoji.EmojiCount + +data class EmojiCountSubset( + val id: Long, + val name: String, + val url: String, + val count: Long +) { + constructor(emojiCount: EmojiCount): this( + id = emojiCount.emoji.id ?: -1, + name = emojiCount.emoji.name, + url = emojiCount.emoji.url, + count = emojiCount.count + ) +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/CountEventHandler.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/CountEventHandler.kt index d8e68b97..4f691300 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/CountEventHandler.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/CountEventHandler.kt @@ -4,6 +4,7 @@ import io.raemian.storage.db.core.cheer.Cheering import io.raemian.storage.db.core.cheer.CheeringRepository import io.raemian.storage.db.core.emoji.EmojiCount import io.raemian.storage.db.core.emoji.EmojiCountRepository +import io.raemian.storage.db.core.emoji.EmojiRepository import io.raemian.storage.db.core.lifemap.LifeMapCount import io.raemian.storage.db.core.lifemap.LifeMapCountRepository import org.springframework.context.event.EventListener @@ -17,6 +18,7 @@ class CountEventHandler( private val lifeMapCountRepository: LifeMapCountRepository, private val emojiCountRepository: EmojiCountRepository, private val exclusiveRunner: ExclusiveRunner, + private val emojiRepository: EmojiRepository ) { @Transactional @EventListener @@ -47,8 +49,9 @@ class CountEventHandler( @EventListener fun addEmojiCount(reactEmojiEvent: ReactedEmojiEvent) { exclusiveRunner.call("emoji:${reactEmojiEvent.goalId}:${reactEmojiEvent.emojiId}", Duration.ofSeconds(10)) { + val emoji = emojiRepository.getReferenceById(reactEmojiEvent.emojiId) val emojiCount = emojiCountRepository.findByGoalIdAndEmojiId(reactEmojiEvent.goalId, reactEmojiEvent.emojiId) - ?: EmojiCount(0, reactEmojiEvent.emojiId, reactEmojiEvent.goalId) + ?: EmojiCount(0, emoji, reactEmojiEvent.goalId) emojiCountRepository.save(emojiCount.addCount()) } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt index ecd96cca..6595e465 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt @@ -11,6 +11,8 @@ import io.raemian.api.support.RaemianLocalDate import io.raemian.api.support.error.MaxGoalCountExceededException import io.raemian.api.support.error.PrivateLifeMapException import io.raemian.api.tag.TagService +import io.raemian.storage.db.core.emoji.EmojiCountRepository +import io.raemian.storage.db.core.emoji.ReactedEmojiRepository import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.lifemap.LifeMap @@ -29,6 +31,8 @@ class GoalService( private val goalRepository: GoalRepository, private val lifeMapRepository: LifeMapRepository, private val applicationEventPublisher: ApplicationEventPublisher, + private val emojiCountRepository: EmojiCountRepository, + private val reactedEmojiRepository: ReactedEmojiRepository ) { @Transactional(readOnly = true) @@ -85,9 +89,13 @@ class GoalService( @Transactional(readOnly = true) fun explore(goalId: Long): List { - val results = goalRepository.explore(goalId) + val explore = goalRepository.explore(goalId) + val goalIds = explore.map { it.goalId } - return results.map { GoalExploreDTO(it) } + val emojiGroup = emojiCountRepository.findAllByGoalIdIn(goalIds).groupBy { it.goalId } + + return explore + .map { GoalExploreDTO.from(it, emojiGroup[it.goalId] ?: listOf()) } } private fun createFirstLifeMap(userId: Long): LifeMap { diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt index c5f0ec44..75c249de 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt @@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import java.net.URI +import kotlin.math.exp fun String.toUri(): URI = URI.create(this) @@ -80,9 +81,9 @@ class GoalController( @AuthenticationPrincipal currentUser: CurrentUser, @RequestParam(required = false, defaultValue = Long.MAX_VALUE.toString()) cursor: Long, ): ResponseEntity> { - val goals = goalService.explore(goalId = cursor) + val explore = goalService.explore(goalId = cursor) return ResponseEntity.ok() - .body(ApiResponse.success(GoalExploreResponse(goals))) + .body(ApiResponse.success(GoalExploreResponse(explore))) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt index 5d4d759d..6c7b5688 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt @@ -1,16 +1,26 @@ package io.raemian.api.goal.domain +import io.raemian.api.emoji.domain.EmojiCountSubset import io.raemian.api.user.domain.UserSubset +import io.raemian.storage.db.core.emoji.EmojiCount import io.raemian.storage.db.core.model.GoalExploreQueryResult data class GoalExploreDTO( val user: UserSubset, val goal: GoalSubset, val count: GoalCountSubset, + val emojis: List + ) { - constructor(result: GoalExploreQueryResult) : this( - goal = GoalSubset(result), - user = UserSubset(result), - count = GoalCountSubset(result), - ) + companion object { + fun from(explore: GoalExploreQueryResult, emojiCounts: List): GoalExploreDTO { + + return GoalExploreDTO( + goal = GoalSubset(explore), + user = UserSubset(explore), + count = GoalCountSubset(explore), + emojis = emojiCounts.map { EmojiCountSubset(it) } + ) + } + } } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt index 4bd2cb06..25319990 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt @@ -7,16 +7,19 @@ import jakarta.persistence.GeneratedValue import jakarta.persistence.GenerationType import jakarta.persistence.Id import jakarta.persistence.Index +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne import jakarta.persistence.Table @Entity -@Table(name = "EMOJI_COUNTS", indexes = [Index(name = "IDX_GOAL_ID_AND_EMOJI_ID", columnList = "goalId, emojiId")]) +@Table(name = "EMOJI_COUNTS", indexes = [Index(name = "IDX_GOAL_ID", columnList = "goalId")]) class EmojiCount( @Column var count: Long, - @Column - val emojiId: Long, + @ManyToOne + @JoinColumn(name = "emoji_id") + val emoji: Emoji, @Column val goalId: Long, diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt index cb683295..c31ef76c 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt @@ -4,4 +4,5 @@ import org.springframework.data.jpa.repository.JpaRepository interface EmojiCountRepository : JpaRepository { fun findByGoalIdAndEmojiId(goalId: Long, emojiId: Long): EmojiCount? + fun findAllByGoalIdIn(ids: List): List } From c89f71f8ed22fe1ed9db5bcaa68044d932ec2229 Mon Sep 17 00:00:00 2001 From: ManHyuk Date: Thu, 14 Mar 2024 01:03:54 +0900 Subject: [PATCH 13/60] =?UTF-8?q?feat(#171):=20=ED=94=BC=EB=93=9C=EC=97=90?= =?UTF-8?q?=20=EC=9D=B4=EB=AA=A8=EC=A7=80=EB=B0=8F=20=EB=82=B4=EA=B0=80=20?= =?UTF-8?q?=EB=88=84=EB=A5=B8=20=EC=9D=B4=EB=AA=A8=EC=A7=80=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 --- .../io/raemian/api/config/WebSecurityConfig.kt | 1 + .../kotlin/io/raemian/api/emoji/EmojiService.kt | 10 ++++++++++ .../raemian/api/emoji/domain/EmojiCountSubset.kt | 6 ++++-- .../kotlin/io/raemian/api/goal/GoalService.kt | 9 +++++---- .../api/goal/controller/GoalController.kt | 7 ++++--- .../io/raemian/api/goal/domain/GoalExploreDTO.kt | 16 ++++++++++++---- .../db/core/emoji/ReactedEmojiRepository.kt | 1 + 7 files changed, 37 insertions(+), 13 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt index ed0e4fad..d5b616c2 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt @@ -69,6 +69,7 @@ class WebSecurityConfig( .requestMatchers(AntPathRequestMatcher("/one-baily-actuator/**")).permitAll() .requestMatchers(AntPathRequestMatcher("/log/**")).permitAll() .requestMatchers(AntPathRequestMatcher("/open/life-map/**")).permitAll() + .requestMatchers(AntPathRequestMatcher("/goal/explore")).permitAll() .requestMatchers( AntPathRequestMatcher("/swagger*/**"), AntPathRequestMatcher("/v3/api-docs/**"), diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt index fac50ceb..5c85ddea 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt @@ -4,6 +4,7 @@ import io.raemian.api.emoji.controller.response.EmojiResponse import io.raemian.api.emoji.controller.response.ReactedEmojisResponse import io.raemian.api.event.ReactedEmojiEvent import io.raemian.api.event.RemovedEmojiEvent +import io.raemian.storage.db.core.emoji.EmojiCountRepository import io.raemian.storage.db.core.emoji.EmojiRepository import io.raemian.storage.db.core.emoji.ReactedEmoji import io.raemian.storage.db.core.emoji.ReactedEmojiRepository @@ -21,6 +22,7 @@ class EmojiService( private val userRepository: UserRepository, private val reactedEmojiRepository: ReactedEmojiRepository, private val applicationEventPublisher: ApplicationEventPublisher, + private val emojiCountRepository: EmojiCountRepository ) { @Transactional(readOnly = true) fun findAll(): List = @@ -59,6 +61,14 @@ class EmojiService( applicationEventPublisher.publishEvent(RemovedEmojiEvent(goalId, emojiId)) } + + @Transactional(readOnly = true) + fun findAllByGoalIds(ids: List, userId: Long?): Map { + return reactedEmojiRepository.findAllByGoalIdIn(ids) + .groupBy { it.goal.id!! } + .mapValues { ReactedEmojisResponse.of(it.value, userId ?: -1) } + + } } inline fun ignoreDuplicatedReactedEmojiException(action: () -> Any) { diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt index 5f9c0e31..94584f43 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt @@ -6,12 +6,14 @@ data class EmojiCountSubset( val id: Long, val name: String, val url: String, - val count: Long + val count: Long, + val isMine: Boolean ) { constructor(emojiCount: EmojiCount): this( id = emojiCount.emoji.id ?: -1, name = emojiCount.emoji.name, url = emojiCount.emoji.url, - count = emojiCount.count + count = emojiCount.count, + isMine = false ) } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt index 6595e465..6a1e5b78 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt @@ -1,5 +1,6 @@ package io.raemian.api.goal +import io.raemian.api.emoji.EmojiService import io.raemian.api.event.CreatedGoalEvent import io.raemian.api.goal.controller.request.CreateGoalRequest import io.raemian.api.goal.controller.request.UpdateGoalRequest @@ -32,7 +33,7 @@ class GoalService( private val lifeMapRepository: LifeMapRepository, private val applicationEventPublisher: ApplicationEventPublisher, private val emojiCountRepository: EmojiCountRepository, - private val reactedEmojiRepository: ReactedEmojiRepository + private val emojiService: EmojiService ) { @Transactional(readOnly = true) @@ -88,14 +89,14 @@ class GoalService( } @Transactional(readOnly = true) - fun explore(goalId: Long): List { + fun explore(goalId: Long, userId: Long?): List { val explore = goalRepository.explore(goalId) val goalIds = explore.map { it.goalId } - val emojiGroup = emojiCountRepository.findAllByGoalIdIn(goalIds).groupBy { it.goalId } + val reactedEmojiMap = emojiService.findAllByGoalIds(goalIds, userId) return explore - .map { GoalExploreDTO.from(it, emojiGroup[it.goalId] ?: listOf()) } + .map { GoalExploreDTO.from(it, reactedEmojiMap[it.goalId]) } } private fun createFirstLifeMap(userId: Long): LifeMap { diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt index 75c249de..94c0e1d3 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt @@ -1,6 +1,7 @@ package io.raemian.api.goal.controller import io.raemian.api.auth.domain.CurrentUser +import io.raemian.api.emoji.EmojiService import io.raemian.api.goal.GoalService import io.raemian.api.goal.controller.request.CreateGoalRequest import io.raemian.api.goal.controller.request.UpdateGoalRequest @@ -21,7 +22,6 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import java.net.URI -import kotlin.math.exp fun String.toUri(): URI = URI.create(this) @@ -29,6 +29,7 @@ fun String.toUri(): URI = URI.create(this) @RequestMapping("/goal") class GoalController( private val goalService: GoalService, + private val emojiService: EmojiService ) { @Operation(summary = "목표 단건 조회 API") @@ -78,10 +79,10 @@ class GoalController( @Operation(summary = "explore goal") @GetMapping("/explore") fun exploreGoals( - @AuthenticationPrincipal currentUser: CurrentUser, + @AuthenticationPrincipal currentUser: CurrentUser?, @RequestParam(required = false, defaultValue = Long.MAX_VALUE.toString()) cursor: Long, ): ResponseEntity> { - val explore = goalService.explore(goalId = cursor) + val explore = goalService.explore(goalId = cursor, userId = currentUser?.id) return ResponseEntity.ok() .body(ApiResponse.success(GoalExploreResponse(explore))) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt index 6c7b5688..f4f014c5 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt @@ -1,5 +1,6 @@ package io.raemian.api.goal.domain +import io.raemian.api.emoji.controller.response.ReactedEmojisResponse import io.raemian.api.emoji.domain.EmojiCountSubset import io.raemian.api.user.domain.UserSubset import io.raemian.storage.db.core.emoji.EmojiCount @@ -10,16 +11,23 @@ data class GoalExploreDTO( val goal: GoalSubset, val count: GoalCountSubset, val emojis: List - ) { companion object { - fun from(explore: GoalExploreQueryResult, emojiCounts: List): GoalExploreDTO { - + fun from(explore: GoalExploreQueryResult, reactedEmojisResponse: ReactedEmojisResponse?): GoalExploreDTO { + val emojis = reactedEmojisResponse?.reactedEmojis?.map { + EmojiCountSubset( + id = it.id ?: -1, + url = it.url, + name = it.name, + count = it.reactCount.toLong(), + isMine = it.isMyReaction + ) + } ?: listOf() return GoalExploreDTO( goal = GoalSubset(explore), user = UserSubset(explore), count = GoalCountSubset(explore), - emojis = emojiCounts.map { EmojiCountSubset(it) } + emojis = emojis ) } } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt index f2975316..e5e8a310 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt @@ -7,6 +7,7 @@ import org.springframework.data.jpa.repository.JpaRepository interface ReactedEmojiRepository : JpaRepository { fun findAllByGoal(goal: Goal): List + fun findAllByGoalIdIn(goalIds: List): List fun deleteByEmojiAndGoalAndReactUser(emoji: Emoji, goal: Goal, reactUser: User) } From 34622be92dbb22a5d994859b08ba80295ac252a9 Mon Sep 17 00:00:00 2001 From: ManHyuk Date: Thu, 14 Mar 2024 01:07:24 +0900 Subject: [PATCH 14/60] chore(#171): ktlint --- .../src/main/kotlin/io/raemian/api/emoji/EmojiService.kt | 3 +-- .../kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt | 6 +++--- .../main/kotlin/io/raemian/api/event/CountEventHandler.kt | 2 +- .../api/src/main/kotlin/io/raemian/api/goal/GoalService.kt | 3 +-- .../io/raemian/api/goal/controller/GoalController.kt | 2 +- .../kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt | 7 +++---- 6 files changed, 10 insertions(+), 13 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt index 5c85ddea..925918ec 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt @@ -22,7 +22,7 @@ class EmojiService( private val userRepository: UserRepository, private val reactedEmojiRepository: ReactedEmojiRepository, private val applicationEventPublisher: ApplicationEventPublisher, - private val emojiCountRepository: EmojiCountRepository + private val emojiCountRepository: EmojiCountRepository, ) { @Transactional(readOnly = true) fun findAll(): List = @@ -67,7 +67,6 @@ class EmojiService( return reactedEmojiRepository.findAllByGoalIdIn(ids) .groupBy { it.goal.id!! } .mapValues { ReactedEmojisResponse.of(it.value, userId ?: -1) } - } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt index 94584f43..cac657a5 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt @@ -7,13 +7,13 @@ data class EmojiCountSubset( val name: String, val url: String, val count: Long, - val isMine: Boolean + val isMine: Boolean, ) { - constructor(emojiCount: EmojiCount): this( + constructor(emojiCount: EmojiCount) : this( id = emojiCount.emoji.id ?: -1, name = emojiCount.emoji.name, url = emojiCount.emoji.url, count = emojiCount.count, - isMine = false + isMine = false, ) } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/CountEventHandler.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/CountEventHandler.kt index 4f691300..e8dac8fd 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/CountEventHandler.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/CountEventHandler.kt @@ -18,7 +18,7 @@ class CountEventHandler( private val lifeMapCountRepository: LifeMapCountRepository, private val emojiCountRepository: EmojiCountRepository, private val exclusiveRunner: ExclusiveRunner, - private val emojiRepository: EmojiRepository + private val emojiRepository: EmojiRepository, ) { @Transactional @EventListener diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt index 6a1e5b78..2b787d21 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt @@ -13,7 +13,6 @@ import io.raemian.api.support.error.MaxGoalCountExceededException import io.raemian.api.support.error.PrivateLifeMapException import io.raemian.api.tag.TagService import io.raemian.storage.db.core.emoji.EmojiCountRepository -import io.raemian.storage.db.core.emoji.ReactedEmojiRepository import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.lifemap.LifeMap @@ -33,7 +32,7 @@ class GoalService( private val lifeMapRepository: LifeMapRepository, private val applicationEventPublisher: ApplicationEventPublisher, private val emojiCountRepository: EmojiCountRepository, - private val emojiService: EmojiService + private val emojiService: EmojiService, ) { @Transactional(readOnly = true) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt index 94c0e1d3..1ba9a3ee 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt @@ -29,7 +29,7 @@ fun String.toUri(): URI = URI.create(this) @RequestMapping("/goal") class GoalController( private val goalService: GoalService, - private val emojiService: EmojiService + private val emojiService: EmojiService, ) { @Operation(summary = "목표 단건 조회 API") diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt index f4f014c5..0d74805f 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt @@ -3,14 +3,13 @@ package io.raemian.api.goal.domain import io.raemian.api.emoji.controller.response.ReactedEmojisResponse import io.raemian.api.emoji.domain.EmojiCountSubset import io.raemian.api.user.domain.UserSubset -import io.raemian.storage.db.core.emoji.EmojiCount import io.raemian.storage.db.core.model.GoalExploreQueryResult data class GoalExploreDTO( val user: UserSubset, val goal: GoalSubset, val count: GoalCountSubset, - val emojis: List + val emojis: List, ) { companion object { fun from(explore: GoalExploreQueryResult, reactedEmojisResponse: ReactedEmojisResponse?): GoalExploreDTO { @@ -20,14 +19,14 @@ data class GoalExploreDTO( url = it.url, name = it.name, count = it.reactCount.toLong(), - isMine = it.isMyReaction + isMine = it.isMyReaction, ) } ?: listOf() return GoalExploreDTO( goal = GoalSubset(explore), user = UserSubset(explore), count = GoalCountSubset(explore), - emojis = emojis + emojis = emojis, ) } } From dbe0de6c34f52b045cadc59cfbd05c774698a67a Mon Sep 17 00:00:00 2001 From: ManHyuk Date: Thu, 14 Mar 2024 01:09:04 +0900 Subject: [PATCH 15/60] =?UTF-8?q?chore(#171):=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=BD=94=EB=93=9C=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 --- .../io/raemian/api/emoji/domain/EmojiCountSubset.kt | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt index cac657a5..6d218b78 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt @@ -8,12 +8,4 @@ data class EmojiCountSubset( val url: String, val count: Long, val isMine: Boolean, -) { - constructor(emojiCount: EmojiCount) : this( - id = emojiCount.emoji.id ?: -1, - name = emojiCount.emoji.name, - url = emojiCount.emoji.url, - count = emojiCount.count, - isMine = false, - ) -} +) From e17668572c3aa57203691479fb6afb8c3a255476 Mon Sep 17 00:00:00 2001 From: ManHyuk Date: Thu, 14 Mar 2024 01:09:54 +0900 Subject: [PATCH 16/60] chore(#171): ktlint --- .../main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt index 6d218b78..30573730 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt @@ -1,7 +1,5 @@ package io.raemian.api.emoji.domain -import io.raemian.storage.db.core.emoji.EmojiCount - data class EmojiCountSubset( val id: Long, val name: String, From cffabfff4f7f7095bc255818ed8b149cabb19440 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Thu, 14 Mar 2024 22:14:59 +0900 Subject: [PATCH 17/60] =?UTF-8?q?[=F0=9F=9B=A0=EF=B8=8F=20Refactor]=20?= =?UTF-8?q?=EB=8C=93=EA=B8=80=20=EC=9D=91=EB=8B=B5=EC=97=90=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EA=B0=AF=EC=88=98=20=EC=B6=94=EA=B0=80=20=20(#225)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor : Goal에 달린 댓글 응답에 댓글 갯수 추가 (#224) * test : Goal에 달린 댓글 응답이 댓글 갯수를 반환하는 것을 확인하는 테스트 코드 작성 (#224) * ktlint formatting --- .../controller/response/CommentsResponse.kt | 7 +++++-- .../api/integration/comment/CommentServiceTest.kt | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/response/CommentsResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/response/CommentsResponse.kt index 72365794..7ed37572 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/response/CommentsResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/response/CommentsResponse.kt @@ -4,13 +4,16 @@ import io.raemian.storage.db.core.comment.Comment import io.raemian.storage.db.core.user.User import java.time.LocalDateTime -data class CommentsResponse(val comments: List) { +data class CommentsResponse( + val comments: List, + val commentCount: Int, +) { companion object { fun from(comments: List, userId: Long): CommentsResponse { val comments = comments .map { CommentResponse.from(it, userId) } .sortedBy { it.writtenAt } - return CommentsResponse(comments) + return CommentsResponse(comments, comments.size) } } diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt index dd87ddad..2f6f2cde 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt @@ -95,6 +95,21 @@ class CommentServiceTest { assertThat(comments[2].content).isEqualTo("comment2") } + @Test + @DisplayName("Goal의 Comment들을 조회할 때, 갯수를 함께 받을 수 있다.") + fun getAllByGoalIdCommentCountTest() { + // given + commentRepository.save(Comment(GOAL_FIXTURE, USER_FIXTURE, "comment0")) + commentRepository.save(Comment(GOAL_FIXTURE, USER_FIXTURE, "comment1")) + commentRepository.save(Comment(GOAL_FIXTURE, USER_FIXTURE, "comment2")) + + // when + val comments = commentService.findAllByGoalId(GOAL_FIXTURE.id!!, USER_FIXTURE.id!!) + + // then + assertThat(comments.commentCount).isEqualTo(3) + } + @Test @DisplayName("유저는 읽지 않은 Comment가 있는지 확인할 수 있다.") fun isNewTest() { From db1c7fcfedeb5df4847d06e6a696779644da6f79 Mon Sep 17 00:00:00 2001 From: ManHyuk Date: Fri, 15 Mar 2024 00:38:31 +0900 Subject: [PATCH 18/60] refact(#171): edit field name --- .../kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt | 4 ++-- .../main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt index 30573730..9401f9e2 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt @@ -4,6 +4,6 @@ data class EmojiCountSubset( val id: Long, val name: String, val url: String, - val count: Long, - val isMine: Boolean, + val reactCount: Long, + val isMyReaction: Boolean, ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt index 0d74805f..46cc2dfe 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt @@ -18,8 +18,8 @@ data class GoalExploreDTO( id = it.id ?: -1, url = it.url, name = it.name, - count = it.reactCount.toLong(), - isMine = it.isMyReaction, + reactCount = it.reactCount.toLong(), + isMyReaction = it.isMyReaction, ) } ?: listOf() return GoalExploreDTO( From fbfbdecbca7fbdb740c21734ecf48e27cc15c20e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=9A=B0=EC=84=9D=20=28Woosuk=20Kwon=29?= Date: Fri, 15 Mar 2024 17:00:01 +0900 Subject: [PATCH 19/60] =?UTF-8?q?[=F0=9F=9B=A0=EF=B8=8F=20Refactor]=20Runn?= =?UTF-8?q?erLock=20=ED=95=84=EB=93=9C=20=EB=B3=80=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=08+=20Security=20Uri=20=EC=83=81=EC=88=98?= =?UTF-8?q?=ED=99=94=20+=20H2=20=EC=9B=B9=20=EC=BD=98=EC=86=94=EC=9A=A9=20?= =?UTF-8?q?WebSecurityCustomizer=20=EC=A0=9C=EA=B1=B0=20(#226)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: RunnerLock 내부 필드 변수명 변경 * refactor: security permitall uri 상수화 --- .../raemian/api/config/WebSecurityConfig.kt | 47 ++++--------------- .../kotlin/io/raemian/api/event/RunnerLock.kt | 6 +-- .../support/constant/WebSecurityConstant.kt | 19 ++++++++ 3 files changed, 31 insertions(+), 41 deletions(-) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/constant/WebSecurityConstant.kt diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt index d5b616c2..ce645a4a 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt @@ -5,18 +5,16 @@ import io.raemian.api.auth.domain.CurrentUser import io.raemian.api.auth.service.OAuth2UserService import io.raemian.api.support.StateOAuth2AuthorizationRequestRepository import io.raemian.api.support.TokenProvider +import io.raemian.api.support.constant.WebSecurityConstant import jakarta.servlet.http.HttpServletResponse import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Value -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.boot.autoconfigure.security.servlet.PathRequest import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.http.MediaType import org.springframework.security.config.annotation.SecurityConfigurerAdapter import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity -import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder @@ -26,7 +24,6 @@ import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCo import org.springframework.security.web.DefaultSecurityFilterChain import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter -import org.springframework.security.web.util.matcher.AntPathRequestMatcher import org.springframework.web.filter.CorsFilter import java.nio.charset.StandardCharsets @@ -52,35 +49,20 @@ class WebSecurityConfig( .httpBasic { it.disable() } .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter::class.java) .exceptionHandling { - it - .authenticationEntryPoint { request, response, authException -> - // 유효한 자격증명을 제공하지 않고 접근하려 할때 401 - response.sendError(HttpServletResponse.SC_UNAUTHORIZED) - } + it.authenticationEntryPoint { request, response, authException -> + // 유효한 자격증명을 제공하지 않고 접근하려 할때 401 + response.sendError(HttpServletResponse.SC_UNAUTHORIZED) + } .accessDeniedHandler { request, response, accessDeniedException -> // 필요한 권한이 없이 접근하려 할때 403 response.sendError(HttpServletResponse.SC_FORBIDDEN) } } .authorizeHttpRequests { - it.requestMatchers(AntPathRequestMatcher("/auth/**")).permitAll() - .requestMatchers(AntPathRequestMatcher("/oauth2/**")).permitAll() - .requestMatchers(AntPathRequestMatcher("/login/**")).permitAll() - .requestMatchers(AntPathRequestMatcher("/one-baily-actuator/**")).permitAll() - .requestMatchers(AntPathRequestMatcher("/log/**")).permitAll() - .requestMatchers(AntPathRequestMatcher("/open/life-map/**")).permitAll() - .requestMatchers(AntPathRequestMatcher("/goal/explore")).permitAll() - .requestMatchers( - AntPathRequestMatcher("/swagger*/**"), - AntPathRequestMatcher("/v3/api-docs/**"), - AntPathRequestMatcher("/swagger-resources/**"), - AntPathRequestMatcher("/webjars/**"), - ).permitAll() - .requestMatchers( - AntPathRequestMatcher("/cheering/squad/**"), - AntPathRequestMatcher("/cheering/count/**"), - ).permitAll() - .anyRequest().authenticated() + it.requestMatchers(*WebSecurityConstant.PUBLIC_URIS) + .permitAll() + .anyRequest() + .authenticated() } .oauth2Login { it.tokenEndpoint { it.accessTokenResponseClient(accessTokenResponseClient()) } @@ -110,17 +92,6 @@ class WebSecurityConfig( return http.build() } - @Bean - @ConditionalOnProperty(name = ["spring.h2.console.enabled"], havingValue = "true") - fun configureH2ConsoleEnable(): WebSecurityCustomizer { - return WebSecurityCustomizer { - it - .ignoring() - .requestMatchers(PathRequest.toH2Console()) - .requestMatchers(AntPathRequestMatcher("/favicon.ico", "**/favicon.ico")) - } - } - @Bean fun getPasswordEncoder(): PasswordEncoder { return BCryptPasswordEncoder() diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/RunnerLock.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/RunnerLock.kt index 1533548c..773230e9 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/RunnerLock.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/RunnerLock.kt @@ -4,13 +4,13 @@ import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.locks.ReentrantLock class RunnerLock : ReentrantLock() { - private val watingThread = AtomicInteger(0) + private val watingThreadCount = AtomicInteger(0) fun increase(): Int { - return watingThread.addAndGet(1) + return watingThreadCount.addAndGet(1) } fun decrease(): Int { - return watingThread.decrementAndGet() + return watingThreadCount.decrementAndGet() } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/constant/WebSecurityConstant.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/constant/WebSecurityConstant.kt new file mode 100644 index 00000000..bb7d52b0 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/constant/WebSecurityConstant.kt @@ -0,0 +1,19 @@ +package io.raemian.api.support.constant + +object WebSecurityConstant { + val PUBLIC_URIS = arrayOf( + "/auth/**", + "/oauth2/**", + "/login/**", + "/one-baily-actuator/**", + "/log/**", + "/open/life-map/**", + "/cheering/squad/**", + "/cheering/count/**", + // for swagger + "/swagger*/**", + "/v3/api-docs/**", + "/swagger-resources/**", + "/webjars/**", + ) +} From 36304ff73cdb88188e0753eff4fe368b5943548e Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sat, 16 Mar 2024 11:53:05 +0900 Subject: [PATCH 20/60] =?UTF-8?q?[=F0=9F=9B=A0=EF=B8=8F=20Refactor]=20?= =?UTF-8?q?=EB=8C=93=EA=B8=80=20=EC=A1=B0=ED=9A=8C=20API=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=EC=97=90=20"=EB=82=B4=20=EC=A7=80=EB=8F=84=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8"=20key-value=20=EC=B6=94=EA=B0=80=20(#230)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor : CommentsResponse에 자신의 목표인지 확인하는 isMyGoal 필드 추가 (#229) * test : CommentsResponse를 통해 자신의 목표인지 확인할 수 있는지 검증하는 테스트 코드 작성 (#229) --- .../io/raemian/api/comment/CommentService.kt | 22 ++++++++++++++----- .../controller/response/CommentsResponse.kt | 5 +++-- .../integration/comment/CommentServiceTest.kt | 16 ++++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/CommentService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/CommentService.kt index 261a21f1..f655208d 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/CommentService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/CommentService.kt @@ -27,13 +27,20 @@ class CommentService( @Transactional(readOnly = true) fun findAllByGoalId(goalId: Long, currentUserId: Long): CommentsResponse { val comments = commentRepository.findAllByGoalId(goalId) - .ifEmpty { return CommentsResponse.from(emptyList(), currentUserId) } + .ifEmpty { + return CommentsResponse.from( + comments = emptyList(), + userId = currentUserId, + isMyGoal = isMyGoal(goalId, currentUserId), + ) + } val goal = comments.first().goal - if (isCurrentUserGoalOwner(currentUserId, goal)) { + val isMyGoal = isMyGoal(goal, currentUserId) + if (isMyGoal) { publishUpdateCommentReadAtEvent(goalId) } - return CommentsResponse.from(comments, currentUserId) + return CommentsResponse.from(comments, currentUserId, isMyGoal) } @Transactional @@ -68,8 +75,13 @@ class CommentService( } } - private fun isCurrentUserGoalOwner(currentUserId: Long, goal: Goal): Boolean = - currentUserId == goal.lifeMap.user.id + private fun isMyGoal(goalId: Long, userId: Long): Boolean { + val goal = goalRepository.getById(goalId) + return isMyGoal(goal, userId) + } + + private fun isMyGoal(goal: Goal, userId: Long): Boolean = + userId == goal.lifeMap.user.id private fun publishUpdateCommentReadAtEvent(goalId: Long) { val event = CommentReadEvent(goalId, LocalDateTime.now()) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/response/CommentsResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/response/CommentsResponse.kt index 7ed37572..9b62e301 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/response/CommentsResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/response/CommentsResponse.kt @@ -7,13 +7,14 @@ import java.time.LocalDateTime data class CommentsResponse( val comments: List, val commentCount: Int, + val isMyGoal: Boolean, ) { companion object { - fun from(comments: List, userId: Long): CommentsResponse { + fun from(comments: List, userId: Long, isMyGoal: Boolean): CommentsResponse { val comments = comments .map { CommentResponse.from(it, userId) } .sortedBy { it.writtenAt } - return CommentsResponse(comments, comments.size) + return CommentsResponse(comments, comments.size, isMyGoal) } } diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt index 2f6f2cde..7bc7673f 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt @@ -110,6 +110,22 @@ class CommentServiceTest { assertThat(comments.commentCount).isEqualTo(3) } + @Test + @DisplayName("Goal의 Comment 조회시, 나의 Goal인지 확인할 수 있다.") + fun getAllByGoalIdIsMyGoalTest() { + // given + commentRepository.save(Comment(GOAL_FIXTURE, USER_FIXTURE, "comment0")) + + // when + val comments = commentService.findAllByGoalId(GOAL_FIXTURE.id!!, USER_FIXTURE.id!!) + val anotherUserId = USER_FIXTURE.id!! + 1 + val comments2 = commentService.findAllByGoalId(GOAL_FIXTURE.id!!, anotherUserId) + + // then + assertThat(comments.isMyGoal).isEqualTo(true) + assertThat(comments2.isMyGoal).isEqualTo(false) + } + @Test @DisplayName("유저는 읽지 않은 Comment가 있는지 확인할 수 있다.") fun isNewTest() { From b289cab1668e1e4c7088dc501cb9bfaf6ad67143 Mon Sep 17 00:00:00 2001 From: ManHyuk Date: Sun, 17 Mar 2024 14:27:12 +0900 Subject: [PATCH 21/60] feat(#232): add title field --- .../src/main/kotlin/io/raemian/api/lifemap/domain/GoalDto.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/GoalDto.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/GoalDto.kt index 13d9bd7a..cf595afb 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/GoalDto.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/GoalDto.kt @@ -5,13 +5,17 @@ import io.raemian.storage.db.core.goal.Goal class GoalDto private constructor( val id: Long?, + val title: String, + @Deprecated("필드 삭제 예정") val deadline: String, val stickerUrl: String, + @Deprecated("필드 삭제 예정") val tagContent: String, ) { constructor(goal: Goal) : this( id = goal.id, + title = goal.title, deadline = goal.deadline.format(), stickerUrl = goal.sticker.url, tagContent = goal.tag.content, From c29cea6980d3bab6055b626af7e4c477fc0c72dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=9A=B0=EC=84=9D=20=28Woosuk=20Kwon=29?= Date: Mon, 18 Mar 2024 07:17:03 +0900 Subject: [PATCH 22/60] =?UTF-8?q?[=F0=9F=9B=A0=EF=B8=8F=20Refactor]=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(1=EB=B2=88,=203=EB=B2=88=20=EC=A0=9C?= =?UTF-8?q?=EC=95=88=EB=A7=8C=20=EB=B3=80=EA=B2=BD)=20(#234)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ionHandler.kt => GlobalExceptionConfig.kt} | 2 +- ...SpringdocConfig.kt => SpringDocsConfig.kt} | 2 +- .../controller/DashboardController.kt | 5 ++- .../controller/response/DashboardResponse.kt | 24 ---------- .../{dto => model}/ActiveUserStatics.kt | 2 +- .../admin/dashboard/model/DashboardResult.kt | 19 ++++++++ .../dashboard/{dto => model}/GoalStatics.kt | 2 +- .../dashboard/{dto => model}/TaskStatics.kt | 2 +- .../dashboard/{dto => model}/UserStatics.kt | 2 +- .../{ => service}/DashboardService.kt | 16 +++---- .../admin/emoji/controller/EmojiController.kt | 10 ++--- .../controller/response/EmojiResponse.kt | 16 ------- .../raemian/admin/emoji/model/EmojiResult.kt | 16 +++++++ .../admin/emoji/{ => service}/EmojiService.kt | 20 ++++----- .../lifemap/controller/LifeMapController.kt | 6 +-- .../GoalResponse.kt => model/GoalResult.kt} | 6 +-- .../LifeMapResult.kt} | 8 ++-- .../lifemap/{ => service}/LifeMapService.kt | 8 ++-- .../controller/DefaultProfileController.kt | 10 ++--- .../DefaultProfileResult.kt} | 8 ++-- .../{ => service}/DefaultProfileService.kt | 20 ++++----- ...cheduler.kt => BusinessMetricScheduler.kt} | 6 +-- .../sticker/controller/StickerController.kt | 10 ++--- .../StickerResult.kt} | 8 ++-- .../sticker/{ => service}/StickerService.kt | 20 ++++----- .../admin/tag/controller/TagController.kt | 12 ++--- .../tag/controller/response/TagResponse.kt | 22 ---------- .../io/raemian/admin/tag/model/TagResult.kt | 19 ++++++++ .../admin/tag/{ => service}/TagService.kt | 20 ++++----- .../admin/user/controller/UserController.kt | 6 +-- .../UserResponse.kt => model/UserResult.kt} | 8 ++-- .../admin/user/{ => service}/UserService.kt | 8 ++-- .../api/auth/controller/AuthController.kt | 2 +- .../converter/TokenRequestEntityConverter.kt | 1 + .../converter/{ => props}/AppleLoginProps.kt | 2 +- .../io/raemian/api/auth/domain/UserInfoDto.kt | 6 --- .../api/auth/{domain => model}/CurrentUser.kt | 2 +- .../raemian/api/auth/model/OauthUserResult.kt | 6 +++ .../{domain/TokenDTO.kt => model/Token.kt} | 4 +- .../raemian/api/auth/service/AuthService.kt | 2 +- .../api/auth/service/OAuth2UserService.kt | 20 ++++----- .../cheer/controller/CheeringController.kt | 10 ++--- .../response/CheeringCountResponse.kt | 17 ------- .../CheererResult.kt} | 10 ++--- .../api/cheer/model/CheeringCountResult.kt | 17 +++++++ .../cheer/{ => service}/CheeringService.kt | 21 ++++----- .../comment/controller/CommentController.kt | 8 ++-- .../CommentsResult.kt} | 8 ++-- .../comment/{ => service}/CommentService.kt | 16 +++---- ...ionHandler.kt => GlobalExceptionConfig.kt} | 8 ++-- .../raemian/api/config/JwtSecurityConfig.kt | 4 +- ...SpringdocConfig.kt => SpringDocsConfig.kt} | 2 +- .../raemian/api/config/WebSecurityConfig.kt | 6 +-- .../api/emoji/controller/EmojiController.kt | 12 ++--- .../controller/response/EmojiResponse.kt | 16 ------- .../{domain => model}/EmojiCountSubset.kt | 2 +- .../io/raemian/api/emoji/model/EmojiResult.kt | 16 +++++++ .../ReactedEmojisResult.kt} | 10 ++--- .../api/emoji/{ => service}/EmojiService.kt | 22 +++++----- .../event/{ => handler}/ReadEventHandler.kt | 3 +- .../api/event/{ => model}/CheeredEvent.kt | 2 +- .../api/event/{ => model}/CommentReadEvent.kt | 2 +- .../event/{ => model}/CountEventHandler.kt | 3 +- .../api/event/{ => model}/CreatedGoalEvent.kt | 2 +- .../event/{ => model}/ReactedEmojiEvent.kt | 2 +- .../event/{ => model}/RemovedEmojiEvent.kt | 2 +- .../api/goal/controller/GoalController.kt | 22 +++++----- .../CreateGoalResult.kt} | 6 +-- .../goal/{domain => model}/GoalCountSubset.kt | 2 +- .../GoalExplorePageResult.kt} | 10 ++--- .../GoalExploreResult.kt} | 14 +++--- .../GoalResponse.kt => model/GoalResult.kt} | 8 ++-- .../api/goal/{domain => model}/GoalSubset.kt | 2 +- .../api/goal/{ => service}/GoalService.kt | 44 +++++++++---------- .../lifemap/controller/LifeMapController.kt | 10 ++--- .../controller/OpenLifeMapController.kt | 8 ++-- .../request}/UpdatePublicRequest.kt | 2 +- .../{domain => model}/CountResponse.kt | 8 ++-- .../api/lifemap/{domain => model}/GoalDto.kt | 4 +- .../LifeMapCountResult.kt} | 4 +- .../{domain => model}/LifeMapResponse.kt | 10 ++--- .../LifeMapDTO.kt => model/LifeMapResult.kt} | 6 +-- .../lifemap/{ => service}/LifeMapService.kt | 22 +++++----- .../io/raemian/api/log/UserLoginLogService.kt | 24 ---------- .../api/log/controller/LogController.kt | 2 +- .../api/log/{ => service}/LogService.kt | 20 ++++++++- .../profile/controller/ProfileController.kt | 6 +-- .../DefaultProfileResult.kt} | 4 +- .../profile/{ => service}/ProfileService.kt | 8 ++-- .../sticker/controller/StickerController.kt | 6 +-- .../StickerResult.kt} | 4 +- .../sticker/{ => service}/StickerService.kt | 8 ++-- .../{error => exception}/CoreApiException.kt | 2 +- .../support/{error => exception}/ErrorInfo.kt | 2 +- .../MaxGoalCountExceededException.kt | 2 +- .../MaxTaskCountExceededException.kt | 2 +- .../PrivateLifeMapException.kt | 2 +- .../extension/LocalDateExtensionFunctions.kt | 12 +++++ .../limiter}/CheeringLimiter.kt | 2 +- .../lock}/ExclusiveRunner.kt | 2 +- .../api/{event => support/lock}/RunnerLock.kt | 2 +- .../api/support/response/ApiResponse.kt | 2 +- .../api/support/{ => security}/JwtFilter.kt | 2 +- ...ateOAuth2AuthorizationRequestRepository.kt | 2 +- .../support/{ => security}/TokenProvider.kt | 10 ++--- .../DeadlineCreator.kt} | 15 ++----- .../api/support/{ => utils}/SecurityUtil.kt | 2 +- .../api/tag/controller/TagController.kt | 6 +-- .../TagResponse.kt => model/TagResult.kt} | 4 +- .../api/tag/{ => service}/TagService.kt | 8 ++-- .../api/task/controller/TaskController.kt | 8 ++-- .../controller/response/CreateTaskResponse.kt | 6 --- .../api/task/model/CreateTaskResult.kt | 6 +++ .../api/task/{ => service}/TaskService.kt | 10 ++--- .../api/user/controller/UserController.kt | 18 ++++---- .../UserDTO.kt => user/model/UserResult.kt} | 8 ++-- .../api/user/{domain => model}/UserSubset.kt | 2 +- .../UserTokenDecryptResult.kt} | 13 +++--- .../raemian/api/user/service/UserService.kt | 18 ++++---- .../integration/comment/CommentServiceTest.kt | 6 +-- .../api/integration/emoji/EmojiServiceTest.kt | 2 +- .../api/integration/goal/GoalServiceTest.kt | 10 ++--- .../integration/lifemap/LifeMapServiceTest.kt | 4 +- .../integration/sticker/StickerServiceTest.kt | 2 +- .../api/integration/tag/TagServiceTest.kt | 2 +- .../api/integration/task/TaskServiceTest.kt | 4 +- .../support/CoreApiExceptionTestSupporter.kt | 4 +- ...ocalDateTest.kt => DeadlineCreatorTest.kt} | 12 ++--- 128 files changed, 538 insertions(+), 549 deletions(-) rename backend/application/admin/src/main/kotlin/io/raemian/admin/config/{GlobalExceptionHandler.kt => GlobalExceptionConfig.kt} (97%) rename backend/application/admin/src/main/kotlin/io/raemian/admin/config/{SpringdocConfig.kt => SpringDocsConfig.kt} (96%) delete mode 100644 backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/controller/response/DashboardResponse.kt rename backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/{dto => model}/ActiveUserStatics.kt (76%) create mode 100644 backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/DashboardResult.kt rename backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/{dto => model}/GoalStatics.kt (64%) rename backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/{dto => model}/TaskStatics.kt (64%) rename backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/{dto => model}/UserStatics.kt (64%) rename backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/{ => service}/DashboardService.kt (88%) delete mode 100644 backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/controller/response/EmojiResponse.kt create mode 100644 backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/model/EmojiResult.kt rename backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/{ => service}/EmojiService.kt (84%) rename backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/{controller/response/GoalResponse.kt => model/GoalResult.kt} (88%) rename backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/{controller/response/LifeMapResponse.kt => model/LifeMapResult.kt} (72%) rename backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/{ => service}/LifeMapService.kt (85%) rename backend/application/admin/src/main/kotlin/io/raemian/admin/profile/{controller/response/DefaultProfileResponse.kt => model/DefaultProfileResult.kt} (57%) rename backend/application/admin/src/main/kotlin/io/raemian/admin/profile/{ => service}/DefaultProfileService.kt (84%) rename backend/application/admin/src/main/kotlin/io/raemian/admin/scheduler/{DashboardScheduler.kt => BusinessMetricScheduler.kt} (87%) rename backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/{controller/response/StickerResponse.kt => model/StickerResult.kt} (57%) rename backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/{ => service}/StickerService.kt (84%) delete mode 100644 backend/application/admin/src/main/kotlin/io/raemian/admin/tag/controller/response/TagResponse.kt create mode 100644 backend/application/admin/src/main/kotlin/io/raemian/admin/tag/model/TagResult.kt rename backend/application/admin/src/main/kotlin/io/raemian/admin/tag/{ => service}/TagService.kt (68%) rename backend/application/admin/src/main/kotlin/io/raemian/admin/user/{controller/response/UserResponse.kt => model/UserResult.kt} (79%) rename backend/application/admin/src/main/kotlin/io/raemian/admin/user/{ => service}/UserService.kt (60%) rename backend/application/api/src/main/kotlin/io/raemian/api/auth/converter/{ => props}/AppleLoginProps.kt (86%) delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/auth/domain/UserInfoDto.kt rename backend/application/api/src/main/kotlin/io/raemian/api/auth/{domain => model}/CurrentUser.kt (96%) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/auth/model/OauthUserResult.kt rename backend/application/api/src/main/kotlin/io/raemian/api/auth/{domain/TokenDTO.kt => model/Token.kt} (69%) delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/response/CheeringCountResponse.kt rename backend/application/api/src/main/kotlin/io/raemian/api/cheer/{controller/response/CheererResponse.kt => model/CheererResult.kt} (50%) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheeringCountResult.kt rename backend/application/api/src/main/kotlin/io/raemian/api/cheer/{ => service}/CheeringService.kt (87%) rename backend/application/api/src/main/kotlin/io/raemian/api/comment/{controller/response/CommentsResponse.kt => model/CommentsResult.kt} (88%) rename backend/application/api/src/main/kotlin/io/raemian/api/comment/{ => service}/CommentService.kt (89%) rename backend/application/api/src/main/kotlin/io/raemian/api/config/{GlobalExceptionHandler.kt => GlobalExceptionConfig.kt} (89%) rename backend/application/api/src/main/kotlin/io/raemian/api/config/{SpringdocConfig.kt => SpringDocsConfig.kt} (98%) delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/response/EmojiResponse.kt rename backend/application/api/src/main/kotlin/io/raemian/api/emoji/{domain => model}/EmojiCountSubset.kt (80%) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/EmojiResult.kt rename backend/application/api/src/main/kotlin/io/raemian/api/emoji/{controller/response/ReactedEmojisResponse.kt => model/ReactedEmojisResult.kt} (94%) rename backend/application/api/src/main/kotlin/io/raemian/api/emoji/{ => service}/EmojiService.kt (83%) rename backend/application/api/src/main/kotlin/io/raemian/api/event/{ => handler}/ReadEventHandler.kt (85%) rename backend/application/api/src/main/kotlin/io/raemian/api/event/{ => model}/CheeredEvent.kt (60%) rename backend/application/api/src/main/kotlin/io/raemian/api/event/{ => model}/CommentReadEvent.kt (77%) rename backend/application/api/src/main/kotlin/io/raemian/api/event/{ => model}/CountEventHandler.kt (97%) rename backend/application/api/src/main/kotlin/io/raemian/api/event/{ => model}/CreatedGoalEvent.kt (69%) rename backend/application/api/src/main/kotlin/io/raemian/api/event/{ => model}/ReactedEmojiEvent.kt (69%) rename backend/application/api/src/main/kotlin/io/raemian/api/event/{ => model}/RemovedEmojiEvent.kt (69%) rename backend/application/api/src/main/kotlin/io/raemian/api/goal/{controller/response/CreateGoalResponse.kt => model/CreateGoalResult.kt} (79%) rename backend/application/api/src/main/kotlin/io/raemian/api/goal/{domain => model}/GoalCountSubset.kt (90%) rename backend/application/api/src/main/kotlin/io/raemian/api/goal/{controller/response/GoalExploreResponse.kt => model/GoalExplorePageResult.kt} (50%) rename backend/application/api/src/main/kotlin/io/raemian/api/goal/{domain/GoalExploreDTO.kt => model/GoalExploreResult.kt} (72%) rename backend/application/api/src/main/kotlin/io/raemian/api/goal/{controller/response/GoalResponse.kt => model/GoalResult.kt} (86%) rename backend/application/api/src/main/kotlin/io/raemian/api/goal/{domain => model}/GoalSubset.kt (94%) rename backend/application/api/src/main/kotlin/io/raemian/api/goal/{ => service}/GoalService.kt (78%) rename backend/application/api/src/main/kotlin/io/raemian/api/lifemap/{domain => controller/request}/UpdatePublicRequest.kt (55%) rename backend/application/api/src/main/kotlin/io/raemian/api/lifemap/{domain => model}/CountResponse.kt (64%) rename backend/application/api/src/main/kotlin/io/raemian/api/lifemap/{domain => model}/GoalDto.kt (85%) rename backend/application/api/src/main/kotlin/io/raemian/api/lifemap/{domain/LifeMapCountDTO.kt => model/LifeMapCountResult.kt} (85%) rename backend/application/api/src/main/kotlin/io/raemian/api/lifemap/{domain => model}/LifeMapResponse.kt (66%) rename backend/application/api/src/main/kotlin/io/raemian/api/lifemap/{domain/LifeMapDTO.kt => model/LifeMapResult.kt} (89%) rename backend/application/api/src/main/kotlin/io/raemian/api/lifemap/{ => service}/LifeMapService.kt (87%) delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/log/UserLoginLogService.kt rename backend/application/api/src/main/kotlin/io/raemian/api/log/{ => service}/LogService.kt (60%) rename backend/application/api/src/main/kotlin/io/raemian/api/profile/{controller/response/DefaultProfileResponse.kt => model/DefaultProfileResult.kt} (74%) rename backend/application/api/src/main/kotlin/io/raemian/api/profile/{ => service}/ProfileService.kt (64%) rename backend/application/api/src/main/kotlin/io/raemian/api/sticker/{controller/response/StickerResponse.kt => model/StickerResult.kt} (74%) rename backend/application/api/src/main/kotlin/io/raemian/api/sticker/{ => service}/StickerService.kt (75%) rename backend/application/api/src/main/kotlin/io/raemian/api/support/{error => exception}/CoreApiException.kt (77%) rename backend/application/api/src/main/kotlin/io/raemian/api/support/{error => exception}/ErrorInfo.kt (97%) rename backend/application/api/src/main/kotlin/io/raemian/api/support/{error => exception}/MaxGoalCountExceededException.kt (72%) rename backend/application/api/src/main/kotlin/io/raemian/api/support/{error => exception}/MaxTaskCountExceededException.kt (72%) rename backend/application/api/src/main/kotlin/io/raemian/api/support/{error => exception}/PrivateLifeMapException.kt (69%) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/extension/LocalDateExtensionFunctions.kt rename backend/application/api/src/main/kotlin/io/raemian/api/{cheer => support/limiter}/CheeringLimiter.kt (95%) rename backend/application/api/src/main/kotlin/io/raemian/api/{event => support/lock}/ExclusiveRunner.kt (96%) rename backend/application/api/src/main/kotlin/io/raemian/api/{event => support/lock}/RunnerLock.kt (90%) rename backend/application/api/src/main/kotlin/io/raemian/api/support/{ => security}/JwtFilter.kt (97%) rename backend/application/api/src/main/kotlin/io/raemian/api/support/{ => security}/StateOAuth2AuthorizationRequestRepository.kt (97%) rename backend/application/api/src/main/kotlin/io/raemian/api/support/{ => security}/TokenProvider.kt (95%) rename backend/application/api/src/main/kotlin/io/raemian/api/support/{RaemianLocalDate.kt => utils/DeadlineCreator.kt} (73%) rename backend/application/api/src/main/kotlin/io/raemian/api/support/{ => utils}/SecurityUtil.kt (93%) rename backend/application/api/src/main/kotlin/io/raemian/api/tag/{controller/response/TagResponse.kt => model/TagResult.kt} (70%) rename backend/application/api/src/main/kotlin/io/raemian/api/tag/{ => service}/TagService.kt (76%) delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/task/controller/response/CreateTaskResponse.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/task/model/CreateTaskResult.kt rename backend/application/api/src/main/kotlin/io/raemian/api/task/{ => service}/TaskService.kt (90%) rename backend/application/api/src/main/kotlin/io/raemian/api/{auth/domain/UserDTO.kt => user/model/UserResult.kt} (85%) rename backend/application/api/src/main/kotlin/io/raemian/api/user/{domain => model}/UserSubset.kt (94%) rename backend/application/api/src/main/kotlin/io/raemian/api/user/{controller/response/UserResponse.kt => model/UserTokenDecryptResult.kt} (67%) rename backend/application/api/src/test/kotlin/io/raemian/api/support/{RaemianLocalDateTest.kt => DeadlineCreatorTest.kt} (85%) diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/config/GlobalExceptionHandler.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/config/GlobalExceptionConfig.kt similarity index 97% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/config/GlobalExceptionHandler.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/config/GlobalExceptionConfig.kt index 268e532c..c5c0ccce 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/config/GlobalExceptionHandler.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/config/GlobalExceptionConfig.kt @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.RestControllerAdvice @RestControllerAdvice -class GlobalExceptionHandler { +class GlobalExceptionConfig { private val log: Logger = LoggerFactory.getLogger(javaClass) @ExceptionHandler(CoreApiException::class) diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/config/SpringdocConfig.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/config/SpringDocsConfig.kt similarity index 96% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/config/SpringdocConfig.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/config/SpringDocsConfig.kt index ea6685e8..843fb8e7 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/config/SpringdocConfig.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/config/SpringDocsConfig.kt @@ -8,7 +8,7 @@ import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration -class SpringdocConfig { +class SpringDocsConfig { @Value("\${springdoc.server.url}") private lateinit var url: String diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/controller/DashboardController.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/controller/DashboardController.kt index 642630f6..6ed61d4d 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/controller/DashboardController.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/controller/DashboardController.kt @@ -1,6 +1,7 @@ package io.raemian.admin.dashboard -import io.raemian.admin.dashboard.controller.response.DashboardResponse +import io.raemian.admin.dashboard.model.DashboardResult +import io.raemian.admin.dashboard.service.DashboardService import io.raemian.admin.support.response.ApiResponse import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity @@ -15,6 +16,6 @@ class DashboardController( ) { @Operation(summary = "대시보드 통계 데이터 조회 API") @GetMapping - fun findAll(): ResponseEntity> = + fun findAll(): ResponseEntity> = ResponseEntity.ok(ApiResponse.success(dashboardService.getDashBoard())) } diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/controller/response/DashboardResponse.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/controller/response/DashboardResponse.kt deleted file mode 100644 index 74e37ffc..00000000 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/controller/response/DashboardResponse.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.raemian.admin.dashboard.controller.response - -import io.raemian.admin.dashboard.dto.ActiveUserStatics -import io.raemian.admin.dashboard.dto.GoalStatics -import io.raemian.admin.dashboard.dto.TaskStatics -import io.raemian.admin.dashboard.dto.UserStatics - -data class DashboardResponse( - val userStatics: UserStatics, - val goalStatics: GoalStatics, - val taskStatics: TaskStatics, - val activeUserStatics: ActiveUserStatics, -) { - companion object { - fun from( - userStatics: UserStatics, - goalStatics: GoalStatics, - taskStatics: TaskStatics, - activeUserStatics: ActiveUserStatics, - ): DashboardResponse { - return DashboardResponse(userStatics, goalStatics, taskStatics, activeUserStatics) - } - } -} diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/dto/ActiveUserStatics.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/ActiveUserStatics.kt similarity index 76% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/dto/ActiveUserStatics.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/ActiveUserStatics.kt index 0ee512b3..ba5e359e 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/dto/ActiveUserStatics.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/ActiveUserStatics.kt @@ -1,4 +1,4 @@ -package io.raemian.admin.dashboard.dto +package io.raemian.admin.dashboard.model data class ActiveUserStatics( val perTodayPercent: Double, diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/DashboardResult.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/DashboardResult.kt new file mode 100644 index 00000000..06e67545 --- /dev/null +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/DashboardResult.kt @@ -0,0 +1,19 @@ +package io.raemian.admin.dashboard.model + +data class DashboardResult( + val userStatics: UserStatics, + val goalStatics: GoalStatics, + val taskStatics: TaskStatics, + val activeUserStatics: ActiveUserStatics, +) { + companion object { + fun from( + userStatics: UserStatics, + goalStatics: GoalStatics, + taskStatics: TaskStatics, + activeUserStatics: ActiveUserStatics, + ): DashboardResult { + return DashboardResult(userStatics, goalStatics, taskStatics, activeUserStatics) + } + } +} diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/dto/GoalStatics.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/GoalStatics.kt similarity index 64% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/dto/GoalStatics.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/GoalStatics.kt index 5b4c389e..f33e57fa 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/dto/GoalStatics.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/GoalStatics.kt @@ -1,4 +1,4 @@ -package io.raemian.admin.dashboard.dto +package io.raemian.admin.dashboard.model data class GoalStatics( val total: Long, diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/dto/TaskStatics.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/TaskStatics.kt similarity index 64% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/dto/TaskStatics.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/TaskStatics.kt index 298bf85c..4133cdca 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/dto/TaskStatics.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/TaskStatics.kt @@ -1,4 +1,4 @@ -package io.raemian.admin.dashboard.dto +package io.raemian.admin.dashboard.model data class TaskStatics( val total: Long, diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/dto/UserStatics.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/UserStatics.kt similarity index 64% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/dto/UserStatics.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/UserStatics.kt index 30770425..3005efe9 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/dto/UserStatics.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/model/UserStatics.kt @@ -1,4 +1,4 @@ -package io.raemian.admin.dashboard.dto +package io.raemian.admin.dashboard.model data class UserStatics( val total: Long, diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/DashboardService.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/service/DashboardService.kt similarity index 88% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/DashboardService.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/service/DashboardService.kt index a4b973fe..78fb3d3b 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/DashboardService.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/dashboard/service/DashboardService.kt @@ -1,10 +1,10 @@ -package io.raemian.admin.dashboard +package io.raemian.admin.dashboard.service -import io.raemian.admin.dashboard.controller.response.DashboardResponse -import io.raemian.admin.dashboard.dto.ActiveUserStatics -import io.raemian.admin.dashboard.dto.GoalStatics -import io.raemian.admin.dashboard.dto.TaskStatics -import io.raemian.admin.dashboard.dto.UserStatics +import io.raemian.admin.dashboard.model.ActiveUserStatics +import io.raemian.admin.dashboard.model.DashboardResult +import io.raemian.admin.dashboard.model.GoalStatics +import io.raemian.admin.dashboard.model.TaskStatics +import io.raemian.admin.dashboard.model.UserStatics import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.log.UserLoginLogRepository @@ -26,8 +26,8 @@ class DashboardService( ) { @Transactional(readOnly = true) - fun getDashBoard(): DashboardResponse { - return DashboardResponse.from( + fun getDashBoard(): DashboardResult { + return DashboardResult.from( getUserStatics(), getGoalStatics(), getTaskStatics(), diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/controller/EmojiController.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/controller/EmojiController.kt index 9232b86a..28c3fc96 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/controller/EmojiController.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/controller/EmojiController.kt @@ -1,9 +1,9 @@ package io.raemian.admin.emoji.controller -import io.raemian.admin.emoji.EmojiService import io.raemian.admin.emoji.controller.request.CreateEmojiRequest import io.raemian.admin.emoji.controller.request.UpdateEmojiRequest -import io.raemian.admin.emoji.controller.response.EmojiResponse +import io.raemian.admin.emoji.model.EmojiResult +import io.raemian.admin.emoji.service.EmojiService import io.raemian.admin.support.response.ApiResponse import io.swagger.v3.oas.annotations.Operation import org.springframework.http.MediaType @@ -30,7 +30,7 @@ class EmojiController( @PostMapping(consumes = arrayOf(MediaType.MULTIPART_FORM_DATA_VALUE), produces = arrayOf(MediaType.APPLICATION_JSON_VALUE)) fun create( @ModelAttribute createEmojiRequest: CreateEmojiRequest, - ): ResponseEntity> { + ): ResponseEntity> { val response = emojiService.create(createEmojiRequest) return ResponseEntity @@ -40,12 +40,12 @@ class EmojiController( @Operation(summary = "이모지 전체 조회 API") @GetMapping - fun findAll(): ResponseEntity>> = + fun findAll(): ResponseEntity>> = ResponseEntity.ok(ApiResponse.success(emojiService.findAll())) @Operation(summary = "이모지 단건 조회 API") @GetMapping("/{emojiId}") - fun find(@PathVariable emojiId: Long): ResponseEntity> = + fun find(@PathVariable emojiId: Long): ResponseEntity> = ResponseEntity.ok(ApiResponse.success(emojiService.find(emojiId))) @Operation(summary = "이모지 수정 API") diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/controller/response/EmojiResponse.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/controller/response/EmojiResponse.kt deleted file mode 100644 index 3f9d8c37..00000000 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/controller/response/EmojiResponse.kt +++ /dev/null @@ -1,16 +0,0 @@ -package io.raemian.admin.emoji.controller.response - -import io.raemian.storage.db.core.emoji.Emoji - -data class EmojiResponse( - val id: Long?, - val name: String, - val url: String, -) { - - companion object { - fun from(emoji: Emoji): EmojiResponse { - return EmojiResponse(emoji.id, emoji.name, emoji.url) - } - } -} diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/model/EmojiResult.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/model/EmojiResult.kt new file mode 100644 index 00000000..cbdc2387 --- /dev/null +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/model/EmojiResult.kt @@ -0,0 +1,16 @@ +package io.raemian.admin.emoji.model + +import io.raemian.storage.db.core.emoji.Emoji + +data class EmojiResult( + val id: Long?, + val name: String, + val url: String, +) { + + companion object { + fun from(emoji: Emoji): EmojiResult { + return EmojiResult(emoji.id, emoji.name, emoji.url) + } + } +} diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/EmojiService.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/service/EmojiService.kt similarity index 84% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/EmojiService.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/service/EmojiService.kt index 87e1184b..7dc91a50 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/EmojiService.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/emoji/service/EmojiService.kt @@ -1,8 +1,8 @@ -package io.raemian.admin.emoji +package io.raemian.admin.emoji.service import io.raemian.admin.emoji.controller.request.CreateEmojiRequest import io.raemian.admin.emoji.controller.request.UpdateEmojiRequest -import io.raemian.admin.emoji.controller.response.EmojiResponse +import io.raemian.admin.emoji.model.EmojiResult import io.raemian.admin.support.error.CoreApiException import io.raemian.admin.support.error.ErrorType import io.raemian.image.enums.FileExtensionType @@ -22,7 +22,7 @@ class EmojiService( @Transactional fun create( createEmojiRequest: CreateEmojiRequest, - ): EmojiResponse { + ): EmojiResult { val fileName = createEmojiRequest.image.originalFilename validateFileName(fileName) @@ -33,22 +33,22 @@ class EmojiService( ) val emoji = Emoji(createEmojiRequest.name, url) - return EmojiResponse.from(emojiRepository.save(emoji)) + return EmojiResult.from(emojiRepository.save(emoji)) } @Transactional(readOnly = true) - fun findAll(): List = - emojiRepository.findAll().map(EmojiResponse::from) + fun findAll(): List = + emojiRepository.findAll().map(EmojiResult::from) @Transactional(readOnly = true) - fun find(emojiId: Long): EmojiResponse = - EmojiResponse.from(emojiRepository.getById(emojiId)) + fun find(emojiId: Long): EmojiResult = + EmojiResult.from(emojiRepository.getById(emojiId)) @Transactional fun update( emojiId: Long, updateEmojiRequest: UpdateEmojiRequest, - ): EmojiResponse { + ): EmojiResult { val newFileName = updateEmojiRequest.image.originalFilename validateFileName(newFileName) @@ -61,7 +61,7 @@ class EmojiService( ) emoji.updateNameAndUrl(updateEmojiRequest.name, url) - return EmojiResponse.from(emojiRepository.save(emoji)) + return EmojiResult.from(emojiRepository.save(emoji)) } @Transactional diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/controller/LifeMapController.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/controller/LifeMapController.kt index a45920ea..8991170d 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/controller/LifeMapController.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/controller/LifeMapController.kt @@ -1,7 +1,7 @@ package io.raemian.admin.lifemap.controller -import io.raemian.admin.lifemap.LifeMapService -import io.raemian.admin.lifemap.controller.response.LifeMapResponse +import io.raemian.admin.lifemap.model.LifeMapResult +import io.raemian.admin.lifemap.service.LifeMapService import io.raemian.admin.support.response.ApiResponse import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity @@ -17,6 +17,6 @@ class LifeMapController( ) { @Operation(summary = "유저 인생 지도 조회 API") @GetMapping - fun findAllByUsername(@RequestParam userId: Long): ResponseEntity> = + fun findAllByUsername(@RequestParam userId: Long): ResponseEntity> = ResponseEntity.ok(ApiResponse.success(lifeMapService.findByUserId(userId))) } diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/controller/response/GoalResponse.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/model/GoalResult.kt similarity index 88% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/controller/response/GoalResponse.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/model/GoalResult.kt index 46b16771..00654cd1 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/controller/response/GoalResponse.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/model/GoalResult.kt @@ -1,10 +1,10 @@ -package io.raemian.admin.lifemap.controller.response +package io.raemian.admin.lifemap.model import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.tag.Tag import io.raemian.storage.db.core.task.Task -data class GoalResponse( +data class GoalResult( val title: String, val description: String, val deadline: String, @@ -19,7 +19,7 @@ data class GoalResponse( goal.deadline.toString(), goal.sticker.url, TagInfo(goal.tag), - goal.tasks.map(::TaskInfo), + goal.tasks.map(GoalResult::TaskInfo), ) data class TagInfo( diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/controller/response/LifeMapResponse.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/model/LifeMapResult.kt similarity index 72% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/controller/response/LifeMapResponse.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/model/LifeMapResult.kt index 70244ff1..66af8e52 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/controller/response/LifeMapResponse.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/model/LifeMapResult.kt @@ -1,11 +1,11 @@ -package io.raemian.admin.lifemap.controller.response +package io.raemian.admin.lifemap.model import io.raemian.storage.db.core.lifemap.LifeMap -data class LifeMapResponse( +data class LifeMapResult( val id: Long, val isPublic: Boolean, - val goals: List, + val goals: List, val goalsCount: Int, val viewCount: Long, val cheeringCount: Long, @@ -13,7 +13,7 @@ data class LifeMapResponse( constructor(lifeMap: LifeMap, lifeMapCount: Long, cheeringCount: Long) : this( id = lifeMap.id!!, isPublic = lifeMap.isPublic, - goals = lifeMap.goals.map(::GoalResponse), + goals = lifeMap.goals.map(::GoalResult), goalsCount = lifeMap.goals.size, viewCount = lifeMapCount, cheeringCount = cheeringCount, diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/LifeMapService.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/service/LifeMapService.kt similarity index 85% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/LifeMapService.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/service/LifeMapService.kt index 47036e0e..9ee8ead7 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/LifeMapService.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/lifemap/service/LifeMapService.kt @@ -1,6 +1,6 @@ -package io.raemian.admin.lifemap +package io.raemian.admin.lifemap.service -import io.raemian.admin.lifemap.controller.response.LifeMapResponse +import io.raemian.admin.lifemap.model.LifeMapResult import io.raemian.storage.db.core.cheer.CheeringRepository import io.raemian.storage.db.core.lifemap.LifeMapCountRepository import io.raemian.storage.db.core.lifemap.LifeMapRepository @@ -14,7 +14,7 @@ class LifeMapService( private val lifeMapCountRepository: LifeMapCountRepository, ) { @Transactional(readOnly = true) - fun findByUserId(userId: Long): LifeMapResponse { + fun findByUserId(userId: Long): LifeMapResult { val lifeMap = lifeMapRepository.findFirstByUserId(userId) ?: throw NoSuchElementException("존재하지 않는 유저입니다. $userId") @@ -24,7 +24,7 @@ class LifeMapService( val cheeringCount = getCheeringCount(lifeMap.id!!) - return LifeMapResponse(lifeMap, viewCount, cheeringCount) + return LifeMapResult(lifeMap, viewCount, cheeringCount) } @Transactional(readOnly = true) diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/profile/controller/DefaultProfileController.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/profile/controller/DefaultProfileController.kt index b29a856a..88f86b52 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/profile/controller/DefaultProfileController.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/profile/controller/DefaultProfileController.kt @@ -1,9 +1,9 @@ package io.raemian.admin.profile.controller -import io.raemian.admin.profile.DefaultProfileService import io.raemian.admin.profile.controller.request.CreateDefaultProfileRequest import io.raemian.admin.profile.controller.request.UpdateDefaultProfileRequest -import io.raemian.admin.profile.controller.response.DefaultProfileResponse +import io.raemian.admin.profile.model.DefaultProfileResult +import io.raemian.admin.profile.service.DefaultProfileService import io.raemian.admin.sticker.controller.toUri import io.raemian.admin.support.response.ApiResponse import io.swagger.v3.oas.annotations.Operation @@ -30,7 +30,7 @@ class DefaultProfileController( @PostMapping(consumes = arrayOf(MediaType.MULTIPART_FORM_DATA_VALUE), produces = arrayOf(MediaType.APPLICATION_JSON_VALUE)) fun create( @ModelAttribute createDefaultProfileRequest: CreateDefaultProfileRequest, - ): ResponseEntity> { + ): ResponseEntity> { val response = defaultProfileService.create(createDefaultProfileRequest) return ResponseEntity @@ -40,12 +40,12 @@ class DefaultProfileController( @Operation(summary = "기본 프로필 전체 조회 API") @GetMapping - fun findAll(): ResponseEntity>> = + fun findAll(): ResponseEntity>> = ResponseEntity.ok(ApiResponse.success(defaultProfileService.findAll())) @Operation(summary = "기본 프로필 단건 조회 API") @GetMapping("/{defaultProflieId}") - fun find(@PathVariable defaultProflieId: Long): ResponseEntity> = + fun find(@PathVariable defaultProflieId: Long): ResponseEntity> = ResponseEntity.ok(ApiResponse.success(defaultProfileService.find(defaultProflieId))) @Operation(summary = "기본 프로필 수정 API") diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/profile/controller/response/DefaultProfileResponse.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/profile/model/DefaultProfileResult.kt similarity index 57% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/profile/controller/response/DefaultProfileResponse.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/profile/model/DefaultProfileResult.kt index ad46d88e..e563f0ee 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/profile/controller/response/DefaultProfileResponse.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/profile/model/DefaultProfileResult.kt @@ -1,8 +1,8 @@ -package io.raemian.admin.profile.controller.response +package io.raemian.admin.profile.model import io.raemian.storage.db.core.profile.DefaultProfile -data class DefaultProfileResponse( +data class DefaultProfileResult( val id: Long?, val name: String, val url: String, @@ -15,8 +15,8 @@ data class DefaultProfileResponse( ) companion object { - fun from(entity: DefaultProfile): DefaultProfileResponse { - return DefaultProfileResponse(entity.id, entity.name, entity.url) + fun from(entity: DefaultProfile): DefaultProfileResult { + return DefaultProfileResult(entity.id, entity.name, entity.url) } } } diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/profile/DefaultProfileService.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/profile/service/DefaultProfileService.kt similarity index 84% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/profile/DefaultProfileService.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/profile/service/DefaultProfileService.kt index ae5e8337..92eea275 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/profile/DefaultProfileService.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/profile/service/DefaultProfileService.kt @@ -1,8 +1,8 @@ -package io.raemian.admin.profile +package io.raemian.admin.profile.service import io.raemian.admin.profile.controller.request.CreateDefaultProfileRequest import io.raemian.admin.profile.controller.request.UpdateDefaultProfileRequest -import io.raemian.admin.profile.controller.response.DefaultProfileResponse +import io.raemian.admin.profile.model.DefaultProfileResult import io.raemian.admin.support.error.CoreApiException import io.raemian.admin.support.error.ErrorType import io.raemian.image.enums.FileExtensionType @@ -22,7 +22,7 @@ class DefaultProfileService( @Transactional fun create( createDefaultProfileRequest: CreateDefaultProfileRequest, - ): DefaultProfileResponse { + ): DefaultProfileResult { val fileName = validateFileName(createDefaultProfileRequest.image.originalFilename) val url = imageRepository.upload( @@ -33,22 +33,22 @@ class DefaultProfileService( val defaultProflie = defaultProfileRepository.save(DefaultProfile(createDefaultProfileRequest.name, url)) - return DefaultProfileResponse.from(defaultProflie) + return DefaultProfileResult.from(defaultProflie) } @Transactional(readOnly = true) - fun findAll(): List = - defaultProfileRepository.findAll().map(::DefaultProfileResponse) + fun findAll(): List = + defaultProfileRepository.findAll().map(::DefaultProfileResult) @Transactional(readOnly = true) - fun find(defaultProfileId: Long): DefaultProfileResponse = - DefaultProfileResponse.from(defaultProfileRepository.getById(defaultProfileId)) + fun find(defaultProfileId: Long): DefaultProfileResult = + DefaultProfileResult.from(defaultProfileRepository.getById(defaultProfileId)) @Transactional fun update( defaultProfileId: Long, updateDefaultProfileRequest: UpdateDefaultProfileRequest, - ): DefaultProfileResponse { + ): DefaultProfileResult { val defaultProfile = defaultProfileRepository.getById(defaultProfileId) val newFileName = validateFileName(updateDefaultProfileRequest.image.originalFilename) @@ -61,7 +61,7 @@ class DefaultProfileService( defaultProfile.updateNameAndUrl(updateDefaultProfileRequest.name, url) - return DefaultProfileResponse.from(defaultProfileRepository.save(defaultProfile)) + return DefaultProfileResult.from(defaultProfileRepository.save(defaultProfile)) } @Transactional diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/scheduler/DashboardScheduler.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/scheduler/BusinessMetricScheduler.kt similarity index 87% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/scheduler/DashboardScheduler.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/scheduler/BusinessMetricScheduler.kt index 908fdb15..f6ebb8db 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/scheduler/DashboardScheduler.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/scheduler/BusinessMetricScheduler.kt @@ -1,18 +1,18 @@ package io.raemian.admin.scheduler -import io.raemian.admin.dashboard.DashboardService +import io.raemian.admin.dashboard.service.DashboardService import io.raemian.infra.logging.logger.SlackLogger import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Component @Component -class DashboardScheduler( +class BusinessMetricScheduler( private val slackLogger: SlackLogger, private val dashboardService: DashboardService, ) { @Scheduled(cron = "0 0 23 * * *") - fun sendDashboard() { + fun sendBusinessMetric() { val dashboard = dashboardService.getDashBoard() slackLogger.sendDashboard( diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/controller/StickerController.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/controller/StickerController.kt index 214586be..30cffb90 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/controller/StickerController.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/controller/StickerController.kt @@ -1,9 +1,9 @@ package io.raemian.admin.sticker.controller -import io.raemian.admin.sticker.StickerService import io.raemian.admin.sticker.controller.request.CreateStickerRequest import io.raemian.admin.sticker.controller.request.UpdateStickerRequest -import io.raemian.admin.sticker.controller.response.StickerResponse +import io.raemian.admin.sticker.model.StickerResult +import io.raemian.admin.sticker.service.StickerService import io.raemian.admin.support.response.ApiResponse import io.swagger.v3.oas.annotations.Operation import org.springframework.http.MediaType @@ -30,7 +30,7 @@ class StickerController( @PostMapping(consumes = arrayOf(MediaType.MULTIPART_FORM_DATA_VALUE), produces = arrayOf(MediaType.APPLICATION_JSON_VALUE)) fun create( @ModelAttribute createStickerRequest: CreateStickerRequest, - ): ResponseEntity> { + ): ResponseEntity> { val response = stickerService.create(createStickerRequest) return ResponseEntity @@ -40,12 +40,12 @@ class StickerController( @Operation(summary = "스티커 전체 조회 API") @GetMapping - fun findAll(): ResponseEntity>> = + fun findAll(): ResponseEntity>> = ResponseEntity.ok(ApiResponse.success(stickerService.findAll())) @Operation(summary = "스티커 단건 조회 API") @GetMapping("/{stickerId}") - fun find(@PathVariable stickerId: Long): ResponseEntity> = + fun find(@PathVariable stickerId: Long): ResponseEntity> = ResponseEntity.ok(ApiResponse.success(stickerService.find(stickerId))) @Operation(summary = "스티커 수정 API") diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/controller/response/StickerResponse.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/model/StickerResult.kt similarity index 57% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/controller/response/StickerResponse.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/model/StickerResult.kt index d1730f96..b073fede 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/controller/response/StickerResponse.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/model/StickerResult.kt @@ -1,8 +1,8 @@ -package io.raemian.admin.sticker.controller.response +package io.raemian.admin.sticker.model import io.raemian.storage.db.core.sticker.Sticker -data class StickerResponse( +data class StickerResult( val id: Long?, val name: String, val url: String, @@ -15,8 +15,8 @@ data class StickerResponse( ) companion object { - fun from(entity: Sticker): StickerResponse { - return StickerResponse(entity.id, entity.name, entity.url) + fun from(entity: Sticker): StickerResult { + return StickerResult(entity.id, entity.name, entity.url) } } } diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/StickerService.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/service/StickerService.kt similarity index 84% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/StickerService.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/service/StickerService.kt index 0244a588..2aabaf00 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/StickerService.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/sticker/service/StickerService.kt @@ -1,8 +1,8 @@ -package io.raemian.admin.sticker +package io.raemian.admin.sticker.service import io.raemian.admin.sticker.controller.request.CreateStickerRequest import io.raemian.admin.sticker.controller.request.UpdateStickerRequest -import io.raemian.admin.sticker.controller.response.StickerResponse +import io.raemian.admin.sticker.model.StickerResult import io.raemian.admin.support.error.CoreApiException import io.raemian.admin.support.error.ErrorType import io.raemian.image.enums.FileExtensionType @@ -22,7 +22,7 @@ class StickerService( @Transactional fun create( createStickerRequest: CreateStickerRequest, - ): StickerResponse { + ): StickerResult { val fileName = validateFileName(createStickerRequest.image.originalFilename) val url = imageRepository.upload( @@ -33,22 +33,22 @@ class StickerService( val sticker = stickerRepository.save(Sticker(createStickerRequest.name, url)) - return StickerResponse.from(sticker) + return StickerResult.from(sticker) } @Transactional(readOnly = true) - fun findAll(): List = - stickerRepository.findAll().map(::StickerResponse) + fun findAll(): List = + stickerRepository.findAll().map(::StickerResult) @Transactional(readOnly = true) - fun find(stickerId: Long): StickerResponse = - StickerResponse.from(stickerRepository.getById(stickerId)) + fun find(stickerId: Long): StickerResult = + StickerResult.from(stickerRepository.getById(stickerId)) @Transactional fun update( stickerId: Long, updateStickerRequest: UpdateStickerRequest, - ): StickerResponse { + ): StickerResult { val sticker = stickerRepository.getById(stickerId) val newFileName = validateFileName(updateStickerRequest.image.originalFilename) @@ -63,7 +63,7 @@ class StickerService( val updatedSticker = stickerRepository.save(sticker) - return StickerResponse.from(updatedSticker) + return StickerResult.from(updatedSticker) } @Transactional diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/tag/controller/TagController.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/tag/controller/TagController.kt index 2b01727c..590a8914 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/tag/controller/TagController.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/tag/controller/TagController.kt @@ -1,10 +1,10 @@ package io.raemian.admin.tag.controller import io.raemian.admin.support.response.ApiResponse -import io.raemian.admin.tag.TagService import io.raemian.admin.tag.controller.request.CreateTagRequest import io.raemian.admin.tag.controller.request.UpdateTagRequest -import io.raemian.admin.tag.controller.response.TagResponse +import io.raemian.admin.tag.model.TagResult +import io.raemian.admin.tag.service.TagService import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.DeleteMapping @@ -29,7 +29,7 @@ class TagController( @PostMapping fun create( @RequestBody createTagRequest: CreateTagRequest, - ): ResponseEntity> { + ): ResponseEntity> { val response = tagService.create(createTagRequest) return ResponseEntity.created("/tag/${response.id}".toUri()) .body(ApiResponse.success(response)) @@ -37,12 +37,12 @@ class TagController( @Operation(summary = "태그 전체 조회 API") @GetMapping - fun findAll(): ResponseEntity>> = + fun findAll(): ResponseEntity>> = ResponseEntity.ok(ApiResponse.success(tagService.findAll())) @Operation(summary = "태그 단건 조회 API") @GetMapping("/{tagId}") - fun find(@PathVariable tagId: Long): ResponseEntity> = + fun find(@PathVariable tagId: Long): ResponseEntity> = ResponseEntity.ok(ApiResponse.success(tagService.find(tagId))) @Operation(summary = "태그 수정 API") @@ -50,7 +50,7 @@ class TagController( fun update( @PathVariable tagId: Long, @RequestBody updateTagRequest: UpdateTagRequest, - ): ResponseEntity> = + ): ResponseEntity> = ResponseEntity.ok().body(ApiResponse.success(tagService.update(tagId, updateTagRequest))) @Operation(summary = "태그 삭제 API") diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/tag/controller/response/TagResponse.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/tag/controller/response/TagResponse.kt deleted file mode 100644 index 778d9c33..00000000 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/tag/controller/response/TagResponse.kt +++ /dev/null @@ -1,22 +0,0 @@ -package io.raemian.admin.tag.controller.response - -import io.raemian.storage.db.core.tag.Tag - -fun from(entity: Tag): TagResponse = - TagResponse(entity.id, entity.content) - -data class TagResponse( - val id: Long?, - val content: String, -) { - - constructor(tag: Tag) : this( - tag.id, - tag.content, - ) - - companion object { - fun from(entity: Tag): TagResponse = - TagResponse(entity.id, entity.content) - } -} diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/tag/model/TagResult.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/tag/model/TagResult.kt new file mode 100644 index 00000000..2ac835dd --- /dev/null +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/tag/model/TagResult.kt @@ -0,0 +1,19 @@ +package io.raemian.admin.tag.model + +import io.raemian.storage.db.core.tag.Tag + +data class TagResult( + val id: Long?, + val content: String, +) { + + constructor(tag: Tag) : this( + tag.id, + tag.content, + ) + + companion object { + fun from(entity: Tag): TagResult = + TagResult(entity.id, entity.content) + } +} diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/tag/TagService.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/tag/service/TagService.kt similarity index 68% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/tag/TagService.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/tag/service/TagService.kt index 70517f42..f89a8ca1 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/tag/TagService.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/tag/service/TagService.kt @@ -1,10 +1,10 @@ -package io.raemian.admin.tag +package io.raemian.admin.tag.service import io.raemian.admin.support.error.CoreApiException import io.raemian.admin.support.error.ErrorType import io.raemian.admin.tag.controller.request.CreateTagRequest import io.raemian.admin.tag.controller.request.UpdateTagRequest -import io.raemian.admin.tag.controller.response.TagResponse +import io.raemian.admin.tag.model.TagResult import io.raemian.storage.db.core.tag.TagRepository import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -15,27 +15,27 @@ class TagService( ) { @Transactional - fun create(createTagRequest: CreateTagRequest): TagResponse { + fun create(createTagRequest: CreateTagRequest): TagResult { if (tagRepository.existsTagsByContent(createTagRequest.content)) { throw CoreApiException(ErrorType.DUPLICATE_TAG_ERROR) } - return TagResponse.from(tagRepository.save(createTagRequest.toEntity())) + return TagResult.from(tagRepository.save(createTagRequest.toEntity())) } @Transactional(readOnly = true) - fun findAll(): List = - tagRepository.findAll().map(::TagResponse) + fun findAll(): List = + tagRepository.findAll().map(::TagResult) @Transactional(readOnly = true) - fun find(tagId: Long): TagResponse = - TagResponse.from(tagRepository.getById(tagId)) + fun find(tagId: Long): TagResult = + TagResult.from(tagRepository.getById(tagId)) @Transactional - fun update(tagId: Long, updateTagRequest: UpdateTagRequest): TagResponse { + fun update(tagId: Long, updateTagRequest: UpdateTagRequest): TagResult { val tags = tagRepository.getById(tagId) tags.updateContent(updateTagRequest.content) - return TagResponse.from(tagRepository.save(tags)) + return TagResult.from(tagRepository.save(tags)) } @Transactional diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/user/controller/UserController.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/user/controller/UserController.kt index 78d18812..17214793 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/user/controller/UserController.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/user/controller/UserController.kt @@ -1,8 +1,8 @@ package io.raemian.admin.user.controller import io.raemian.admin.support.response.ApiResponse -import io.raemian.admin.user.UserService -import io.raemian.admin.user.controller.response.UserResponse +import io.raemian.admin.user.model.UserResult +import io.raemian.admin.user.service.UserService import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping @@ -16,6 +16,6 @@ class UserController( ) { @Operation(summary = "유저 전체 조회 API") @GetMapping - fun findAll(): ResponseEntity>> = + fun findAll(): ResponseEntity>> = ResponseEntity.ok(ApiResponse.success(userService.findAll())) } diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/user/controller/response/UserResponse.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/user/model/UserResult.kt similarity index 79% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/user/controller/response/UserResponse.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/user/model/UserResult.kt index f03ccd45..1bb8055d 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/user/controller/response/UserResponse.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/user/model/UserResult.kt @@ -1,9 +1,9 @@ -package io.raemian.admin.user.controller.response +package io.raemian.admin.user.model import io.raemian.storage.db.core.user.User import java.time.LocalDate -data class UserResponse( +data class UserResult( val id: Long?, val email: String, val username: String?, @@ -15,8 +15,8 @@ data class UserResponse( ) { companion object { - fun from(entity: User): UserResponse { - return UserResponse( + fun from(entity: User): UserResult { + return UserResult( entity.id, entity.email, entity.username, diff --git a/backend/application/admin/src/main/kotlin/io/raemian/admin/user/UserService.kt b/backend/application/admin/src/main/kotlin/io/raemian/admin/user/service/UserService.kt similarity index 60% rename from backend/application/admin/src/main/kotlin/io/raemian/admin/user/UserService.kt rename to backend/application/admin/src/main/kotlin/io/raemian/admin/user/service/UserService.kt index 1b08d1f6..5f7ee53d 100644 --- a/backend/application/admin/src/main/kotlin/io/raemian/admin/user/UserService.kt +++ b/backend/application/admin/src/main/kotlin/io/raemian/admin/user/service/UserService.kt @@ -1,6 +1,6 @@ -package io.raemian.admin.user +package io.raemian.admin.user.service -import io.raemian.admin.user.controller.response.UserResponse +import io.raemian.admin.user.model.UserResult import io.raemian.storage.db.core.user.UserRepository import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -10,6 +10,6 @@ class UserService( private val userRepository: UserRepository, ) { @Transactional(readOnly = true) - fun findAll(): List = - userRepository.findAll().map(UserResponse::from) + fun findAll(): List = + userRepository.findAll().map(UserResult::from) } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/auth/controller/AuthController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/auth/controller/AuthController.kt index d0e71b70..52fb0c01 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/auth/controller/AuthController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/auth/controller/AuthController.kt @@ -1,7 +1,7 @@ package io.raemian.api.auth.controller import io.raemian.api.auth.service.AuthService -import io.raemian.api.lifemap.LifeMapService +import io.raemian.api.lifemap.service.LifeMapService import org.springframework.web.bind.annotation.RestController @RestController diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/auth/converter/TokenRequestEntityConverter.kt b/backend/application/api/src/main/kotlin/io/raemian/api/auth/converter/TokenRequestEntityConverter.kt index d9852de6..4b93c331 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/auth/converter/TokenRequestEntityConverter.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/auth/converter/TokenRequestEntityConverter.kt @@ -3,6 +3,7 @@ package io.raemian.api.auth.converter import com.nimbusds.jose.util.IOUtils import com.nimbusds.jose.util.StandardCharset import io.jsonwebtoken.Jwts +import io.raemian.api.auth.converter.props.AppleLoginProps import org.bouncycastle.asn1.pkcs.PrivateKeyInfo import org.bouncycastle.openssl.PEMParser import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/auth/converter/AppleLoginProps.kt b/backend/application/api/src/main/kotlin/io/raemian/api/auth/converter/props/AppleLoginProps.kt similarity index 86% rename from backend/application/api/src/main/kotlin/io/raemian/api/auth/converter/AppleLoginProps.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/auth/converter/props/AppleLoginProps.kt index 637a8320..eea3ca0c 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/auth/converter/AppleLoginProps.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/auth/converter/props/AppleLoginProps.kt @@ -1,4 +1,4 @@ -package io.raemian.api.auth.converter +package io.raemian.api.auth.converter.props import org.springframework.boot.context.properties.ConfigurationProperties diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/auth/domain/UserInfoDto.kt b/backend/application/api/src/main/kotlin/io/raemian/api/auth/domain/UserInfoDto.kt deleted file mode 100644 index 9e8c9ede..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/auth/domain/UserInfoDto.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.raemian.api.auth.domain - -data class UserInfoDto( - val email: String, - val image: String, -) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/auth/domain/CurrentUser.kt b/backend/application/api/src/main/kotlin/io/raemian/api/auth/model/CurrentUser.kt similarity index 96% rename from backend/application/api/src/main/kotlin/io/raemian/api/auth/domain/CurrentUser.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/auth/model/CurrentUser.kt index 59ca77e9..2b34ecee 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/auth/domain/CurrentUser.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/auth/model/CurrentUser.kt @@ -1,4 +1,4 @@ -package io.raemian.api.auth.domain +package io.raemian.api.auth.model import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/auth/model/OauthUserResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/auth/model/OauthUserResult.kt new file mode 100644 index 00000000..9da020d3 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/auth/model/OauthUserResult.kt @@ -0,0 +1,6 @@ +package io.raemian.api.auth.model + +data class OauthUserResult( + val email: String, + val image: String, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/auth/domain/TokenDTO.kt b/backend/application/api/src/main/kotlin/io/raemian/api/auth/model/Token.kt similarity index 69% rename from backend/application/api/src/main/kotlin/io/raemian/api/auth/domain/TokenDTO.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/auth/model/Token.kt index b08e4b2d..23b67077 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/auth/domain/TokenDTO.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/auth/model/Token.kt @@ -1,6 +1,6 @@ -package io.raemian.api.auth.domain +package io.raemian.api.auth.model -data class TokenDTO( +data class Token( val grantType: String, val accessToken: String, val refreshToken: String, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/auth/service/AuthService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/auth/service/AuthService.kt index 15b43ca4..f237b5ef 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/auth/service/AuthService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/auth/service/AuthService.kt @@ -1,6 +1,6 @@ package io.raemian.api.auth.service -import io.raemian.api.auth.domain.CurrentUser +import io.raemian.api.auth.model.CurrentUser import io.raemian.storage.db.core.user.UserRepository import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetailsService diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/auth/service/OAuth2UserService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/auth/service/OAuth2UserService.kt index 57c02fca..129f3f3e 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/auth/service/OAuth2UserService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/auth/service/OAuth2UserService.kt @@ -3,9 +3,9 @@ package io.raemian.api.auth.service import com.fasterxml.jackson.databind.ObjectMapper import com.nimbusds.jwt.JWTClaimsSet import com.nimbusds.jwt.SignedJWT -import io.raemian.api.auth.domain.CurrentUser -import io.raemian.api.auth.domain.UserInfoDto -import io.raemian.api.log.UserLoginLogService +import io.raemian.api.auth.model.CurrentUser +import io.raemian.api.auth.model.OauthUserResult +import io.raemian.api.log.service.LogService import io.raemian.storage.db.core.lifemap.LifeMap import io.raemian.storage.db.core.lifemap.LifeMapRepository import io.raemian.storage.db.core.user.Authority @@ -23,7 +23,7 @@ import org.springframework.transaction.annotation.Transactional class OAuth2UserService( private val userRepository: UserRepository, private val lifeMapRepository: LifeMapRepository, - private val userLoginLogService: UserLoginLogService, + private val logService: LogService, ) : DefaultOAuth2UserService() { companion object { @@ -60,7 +60,7 @@ class OAuth2UserService( lifeMapRepository.findFirstByUserId(user.id!!) ?: createUserDefaultLifeMap(user) - userLoginLogService.upsertLatestLogin(user.id) + logService.upsertLatestLogin(user.id) return user } @@ -103,14 +103,14 @@ class OAuth2UserService( return payload } - private fun parseUserInfo(provide: OAuthProvider, request: OAuth2UserRequest): UserInfoDto { + private fun parseUserInfo(provide: OAuthProvider, request: OAuth2UserRequest): OauthUserResult { return when (provide) { OAuthProvider.APPLE -> { val idToken = request.additionalParameters["id_token"].toString() val userInfo = decodeIdToken(idToken) - UserInfoDto(email = userInfo.getAsString("email"), image = "") + OauthUserResult(email = userInfo.getAsString("email"), image = "") } OAuthProvider.GOOGLE -> { val oAuth2User: OAuth2User = super.loadUser(request) @@ -119,7 +119,7 @@ class OAuth2UserService( oAuth2User.attributes["email"]?.toString() ?: throw RuntimeException("구글 이메일이없음") val image = oAuth2User.attributes["picture"]?.toString() ?: "" - UserInfoDto(email = email, image = image) + OauthUserResult(email = email, image = image) } OAuthProvider.KAKAO -> { val oAuth2User = super.loadUser(request) @@ -129,7 +129,7 @@ class OAuth2UserService( val email = account["email"] ?: throw RuntimeException("카카오 이메일없음") val image = properties["profile_image"] ?: "" - UserInfoDto(email = email, image = image) + OauthUserResult(email = email, image = image) } OAuthProvider.NAVER -> { val oAuth2User: OAuth2User = super.loadUser(request) @@ -142,7 +142,7 @@ class OAuth2UserService( val email = userInfo["email"] ?: throw RuntimeException("네이버 이메일없음") val image = userInfo["profile_image"] ?: "" - UserInfoDto(email = email, image = image) + OauthUserResult(email = email, image = image) } } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt index a2ded4ce..354d5819 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt @@ -1,10 +1,10 @@ package io.raemian.api.cheer.controller -import io.raemian.api.cheer.CheeringService import io.raemian.api.cheer.controller.request.CheeringRequest import io.raemian.api.cheer.controller.request.CheeringSquadPagingRequest -import io.raemian.api.cheer.controller.response.CheererResponse -import io.raemian.api.cheer.controller.response.CheeringCountResponse +import io.raemian.api.cheer.model.CheererResult +import io.raemian.api.cheer.model.CheeringCountResult +import io.raemian.api.cheer.service.CheeringService import io.raemian.api.support.response.ApiResponse import io.raemian.api.support.response.PageResult import org.springframework.http.ResponseEntity @@ -22,14 +22,14 @@ class CheeringController( ) { @GetMapping("/squad/{lifeMapId}") - fun findCheeringSquad(@PathVariable("lifeMapId") lifeMapId: Long, request: CheeringSquadPagingRequest): ResponseEntity>> { + fun findCheeringSquad(@PathVariable("lifeMapId") lifeMapId: Long, request: CheeringSquadPagingRequest): ResponseEntity>> { val response = cheeringService.findCheeringSquad(lifeMapId, request) return ResponseEntity.ok().body(ApiResponse.success(response)) } @GetMapping("/count/{userName}") - fun getCheeringCount(@PathVariable("userName") userName: String): ResponseEntity> = + fun getCheeringCount(@PathVariable("userName") userName: String): ResponseEntity> = ResponseEntity.ok().body(ApiResponse.success(cheeringService.getCheeringCount(userName))) @PostMapping diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/response/CheeringCountResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/response/CheeringCountResponse.kt deleted file mode 100644 index d08eca2a..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/response/CheeringCountResponse.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.raemian.api.cheer.controller.response - -import io.raemian.storage.db.core.cheer.Cheering - -data class CheeringCountResponse( - val count: Long, -) { - companion object { - fun from(cheering: Cheering?): CheeringCountResponse { - return if (cheering == null) { - CheeringCountResponse(0) - } else { - CheeringCountResponse(cheering.count) - } - } - } -} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/response/CheererResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt similarity index 50% rename from backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/response/CheererResponse.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt index ccb7f594..d022a05d 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/response/CheererResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt @@ -1,9 +1,9 @@ -package io.raemian.api.cheer.controller.response +package io.raemian.api.cheer.model import io.raemian.storage.db.core.cheer.Cheerer import java.time.LocalDateTime -data class CheererResponse( +data class CheererResult( val userId: Long, val userName: String, val userNickName: String, @@ -11,11 +11,11 @@ data class CheererResponse( val cheeringAt: LocalDateTime?, ) { companion object { - fun from(cheerer: Cheerer): CheererResponse { + fun from(cheerer: Cheerer): CheererResult { return if (cheerer.user == null) { - CheererResponse(-1, "", "", "", cheerer.cheeringAt) + CheererResult(-1, "", "", "", cheerer.cheeringAt) } else { - CheererResponse(cheerer.user!!.id!!, cheerer.user!!.username!!, cheerer.user!!.nickname!!, cheerer.user!!.image, cheerer.cheeringAt) + CheererResult(cheerer.user!!.id!!, cheerer.user!!.username!!, cheerer.user!!.nickname!!, cheerer.user!!.image, cheerer.cheeringAt) } } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheeringCountResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheeringCountResult.kt new file mode 100644 index 00000000..52cca5be --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheeringCountResult.kt @@ -0,0 +1,17 @@ +package io.raemian.api.cheer.model + +import io.raemian.storage.db.core.cheer.Cheering + +data class CheeringCountResult( + val count: Long, +) { + companion object { + fun from(cheering: Cheering?): CheeringCountResult { + return if (cheering == null) { + CheeringCountResult(0) + } else { + CheeringCountResult(cheering.count) + } + } + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/CheeringService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt similarity index 87% rename from backend/application/api/src/main/kotlin/io/raemian/api/cheer/CheeringService.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt index a8520ae7..4647b521 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/CheeringService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt @@ -1,12 +1,13 @@ -package io.raemian.api.cheer +package io.raemian.api.cheer.service import io.raemian.api.cheer.controller.request.CheeringRequest import io.raemian.api.cheer.controller.request.CheeringSquadPagingRequest -import io.raemian.api.cheer.controller.response.CheererResponse -import io.raemian.api.cheer.controller.response.CheeringCountResponse -import io.raemian.api.event.CheeredEvent -import io.raemian.api.support.error.CoreApiException -import io.raemian.api.support.error.ErrorInfo +import io.raemian.api.cheer.model.CheererResult +import io.raemian.api.cheer.model.CheeringCountResult +import io.raemian.api.event.model.CheeredEvent +import io.raemian.api.support.exception.CoreApiException +import io.raemian.api.support.exception.ErrorInfo +import io.raemian.api.support.limiter.CheeringLimiter import io.raemian.api.support.response.PageResult import io.raemian.storage.db.core.cheer.Cheerer import io.raemian.storage.db.core.cheer.CheererRepository @@ -41,7 +42,7 @@ class CheeringService( } @Transactional(readOnly = true) - fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPagingRequest): PageResult { + fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPagingRequest): PageResult { val cheering = cheeringRepository.findByLifeMapId(lifeMapId) ?: Cheering(0, lifeMapId) @@ -50,17 +51,17 @@ class CheeringService( val isLastPage = isLastPage(cheeringSquad.size, request.pageSize, lifeMapId, cheeringSquad) - return PageResult.of(cheering.count, cheeringSquad.map(CheererResponse::from), isLastPage) + return PageResult.of(cheering.count, cheeringSquad.map(CheererResult::from), isLastPage) } @Transactional(readOnly = true) - fun getCheeringCount(userName: String): CheeringCountResponse { + fun getCheeringCount(userName: String): CheeringCountResult { val lifeMap = lifeMapRepository.findFirstByUserUsername(userName) ?: throw NoSuchElementException("존재하지 않는 유저명입니다. $userName") val cheering = cheeringRepository.findByLifeMapId(lifeMap.id!!) - return CheeringCountResponse.from(cheering) + return CheeringCountResult.from(cheering) } @Transactional(readOnly = true) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/CommentController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/CommentController.kt index 85542b1b..7dbf5b0d 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/CommentController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/CommentController.kt @@ -1,9 +1,9 @@ package io.raemian.api.comment.controller -import io.raemian.api.auth.domain.CurrentUser -import io.raemian.api.comment.CommentService +import io.raemian.api.auth.model.CurrentUser import io.raemian.api.comment.controller.request.WriteCommentRequest -import io.raemian.api.comment.controller.response.CommentsResponse +import io.raemian.api.comment.model.CommentsResult +import io.raemian.api.comment.service.CommentService import io.raemian.api.support.response.ApiResponse import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity @@ -27,7 +27,7 @@ class CommentController( fun findAllReactedEmojisAtGoal( @PathVariable goalId: Long, @AuthenticationPrincipal currentUser: CurrentUser, - ): ResponseEntity> = + ): ResponseEntity> = ResponseEntity.ok( ApiResponse.success(commentService.findAllByGoalId(goalId, currentUser.id)), ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/response/CommentsResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/model/CommentsResult.kt similarity index 88% rename from backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/response/CommentsResponse.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/comment/model/CommentsResult.kt index 9b62e301..d65559de 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/controller/response/CommentsResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/model/CommentsResult.kt @@ -1,20 +1,20 @@ -package io.raemian.api.comment.controller.response +package io.raemian.api.comment.model import io.raemian.storage.db.core.comment.Comment import io.raemian.storage.db.core.user.User import java.time.LocalDateTime -data class CommentsResponse( +data class CommentsResult( val comments: List, val commentCount: Int, val isMyGoal: Boolean, ) { companion object { - fun from(comments: List, userId: Long, isMyGoal: Boolean): CommentsResponse { + fun from(comments: List, userId: Long, isMyGoal: Boolean): CommentsResult { val comments = comments .map { CommentResponse.from(it, userId) } .sortedBy { it.writtenAt } - return CommentsResponse(comments, comments.size, isMyGoal) + return CommentsResult(comments, comments.size, isMyGoal) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/CommentService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt similarity index 89% rename from backend/application/api/src/main/kotlin/io/raemian/api/comment/CommentService.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt index f655208d..38c0b740 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/CommentService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt @@ -1,10 +1,10 @@ -package io.raemian.api.comment +package io.raemian.api.comment.service import io.raemian.api.comment.controller.request.WriteCommentRequest -import io.raemian.api.comment.controller.response.CommentsResponse -import io.raemian.api.event.CommentReadEvent -import io.raemian.api.support.error.CoreApiException -import io.raemian.api.support.error.ErrorInfo +import io.raemian.api.comment.model.CommentsResult +import io.raemian.api.event.model.CommentReadEvent +import io.raemian.api.support.exception.CoreApiException +import io.raemian.api.support.exception.ErrorInfo import io.raemian.storage.db.core.comment.Comment import io.raemian.storage.db.core.comment.CommentRepository import io.raemian.storage.db.core.goal.Goal @@ -25,10 +25,10 @@ class CommentService( ) { @Transactional(readOnly = true) - fun findAllByGoalId(goalId: Long, currentUserId: Long): CommentsResponse { + fun findAllByGoalId(goalId: Long, currentUserId: Long): CommentsResult { val comments = commentRepository.findAllByGoalId(goalId) .ifEmpty { - return CommentsResponse.from( + return CommentsResult.from( comments = emptyList(), userId = currentUserId, isMyGoal = isMyGoal(goalId, currentUserId), @@ -40,7 +40,7 @@ class CommentService( if (isMyGoal) { publishUpdateCommentReadAtEvent(goalId) } - return CommentsResponse.from(comments, currentUserId, isMyGoal) + return CommentsResult.from(comments, currentUserId, isMyGoal) } @Transactional diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/GlobalExceptionHandler.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/GlobalExceptionConfig.kt similarity index 89% rename from backend/application/api/src/main/kotlin/io/raemian/api/config/GlobalExceptionHandler.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/config/GlobalExceptionConfig.kt index 9f105d5d..732c4ada 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/GlobalExceptionHandler.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/GlobalExceptionConfig.kt @@ -1,8 +1,8 @@ package io.raemian.api.config -import io.raemian.api.log.LogService -import io.raemian.api.support.error.CoreApiException -import io.raemian.api.support.error.ErrorInfo +import io.raemian.api.log.service.LogService +import io.raemian.api.support.exception.CoreApiException +import io.raemian.api.support.exception.ErrorInfo import io.raemian.api.support.response.ApiResponse import jakarta.persistence.EntityNotFoundException import org.slf4j.Logger @@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.RestControllerAdvice @RestControllerAdvice -class GlobalExceptionHandler( +class GlobalExceptionConfig( val logService: LogService, ) { private val log: Logger = LoggerFactory.getLogger(javaClass) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/JwtSecurityConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/JwtSecurityConfig.kt index a0d08fbd..4a0f8cb5 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/JwtSecurityConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/JwtSecurityConfig.kt @@ -1,7 +1,7 @@ package io.raemian.api.config -import io.raemian.api.support.JwtFilter -import io.raemian.api.support.TokenProvider +import io.raemian.api.support.security.JwtFilter +import io.raemian.api.support.security.TokenProvider import org.springframework.security.config.annotation.SecurityConfigurerAdapter import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.web.DefaultSecurityFilterChain diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/SpringdocConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/SpringDocsConfig.kt similarity index 98% rename from backend/application/api/src/main/kotlin/io/raemian/api/config/SpringdocConfig.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/config/SpringDocsConfig.kt index 7b8ecb94..87a4a426 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/SpringdocConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/SpringDocsConfig.kt @@ -11,7 +11,7 @@ import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration -class SpringdocConfig { +class SpringDocsConfig { @Value("\${springdoc.server.url}") private lateinit var url: String diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt index ce645a4a..7f337e68 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt @@ -1,11 +1,11 @@ package io.raemian.api.config import io.raemian.api.auth.converter.TokenRequestEntityConverter -import io.raemian.api.auth.domain.CurrentUser +import io.raemian.api.auth.model.CurrentUser import io.raemian.api.auth.service.OAuth2UserService -import io.raemian.api.support.StateOAuth2AuthorizationRequestRepository -import io.raemian.api.support.TokenProvider import io.raemian.api.support.constant.WebSecurityConstant +import io.raemian.api.support.security.StateOAuth2AuthorizationRequestRepository +import io.raemian.api.support.security.TokenProvider import jakarta.servlet.http.HttpServletResponse import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Value diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/EmojiController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/EmojiController.kt index 99b36234..ef130614 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/EmojiController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/EmojiController.kt @@ -1,9 +1,9 @@ package io.raemian.api.emoji.controller -import io.raemian.api.auth.domain.CurrentUser -import io.raemian.api.emoji.EmojiService -import io.raemian.api.emoji.controller.response.EmojiResponse -import io.raemian.api.emoji.controller.response.ReactedEmojisResponse +import io.raemian.api.auth.model.CurrentUser +import io.raemian.api.emoji.model.EmojiResult +import io.raemian.api.emoji.model.ReactedEmojisResult +import io.raemian.api.emoji.service.EmojiService import io.raemian.api.support.response.ApiResponse import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity @@ -21,7 +21,7 @@ class EmojiController( @Operation(summary = "이모지 전체 조회 API") @GetMapping("/emoji") - fun findAll(): ResponseEntity>> = + fun findAll(): ResponseEntity>> = ResponseEntity.ok( ApiResponse.success(emojiService.findAll()), ) @@ -31,7 +31,7 @@ class EmojiController( fun findAllReactedEmojisAtGoal( @PathVariable goalId: Long, @AuthenticationPrincipal currentUser: CurrentUser, - ): ResponseEntity> { + ): ResponseEntity> { val response = emojiService.findAllReactedEmojisByGoalId(goalId, currentUser.id) return ResponseEntity.ok(ApiResponse.success(response)) } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/response/EmojiResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/response/EmojiResponse.kt deleted file mode 100644 index 5c5b1844..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/response/EmojiResponse.kt +++ /dev/null @@ -1,16 +0,0 @@ -package io.raemian.api.emoji.controller.response - -import io.raemian.storage.db.core.emoji.Emoji - -data class EmojiResponse( - val id: Long?, - val name: String, - val url: String, -) { - - companion object { - fun from(emoji: Emoji): EmojiResponse { - return EmojiResponse(emoji.id, emoji.name, emoji.url) - } - } -} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/EmojiCountSubset.kt similarity index 80% rename from backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/EmojiCountSubset.kt index 9401f9e2..6fbc2dec 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/domain/EmojiCountSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/EmojiCountSubset.kt @@ -1,4 +1,4 @@ -package io.raemian.api.emoji.domain +package io.raemian.api.emoji.model data class EmojiCountSubset( val id: Long, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/EmojiResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/EmojiResult.kt new file mode 100644 index 00000000..bf4e8740 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/EmojiResult.kt @@ -0,0 +1,16 @@ +package io.raemian.api.emoji.model + +import io.raemian.storage.db.core.emoji.Emoji + +data class EmojiResult( + val id: Long?, + val name: String, + val url: String, +) { + + companion object { + fun from(emoji: Emoji): EmojiResult { + return EmojiResult(emoji.id, emoji.name, emoji.url) + } + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/response/ReactedEmojisResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/ReactedEmojisResult.kt similarity index 94% rename from backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/response/ReactedEmojisResponse.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/ReactedEmojisResult.kt index 8864dd2a..44cdcf3d 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/controller/response/ReactedEmojisResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/ReactedEmojisResult.kt @@ -1,23 +1,23 @@ -package io.raemian.api.emoji.controller.response +package io.raemian.api.emoji.model import io.raemian.storage.db.core.emoji.ReactedEmoji import io.raemian.storage.db.core.user.User -data class ReactedEmojisResponse( +data class ReactedEmojisResult( val totalReactedEmojisCount: Int, val totalReactUserCount: Int, val latestReactUserNickname: String?, val reactedEmojis: List, ) { companion object { - fun of(reactedEmojis: List, userId: Long): ReactedEmojisResponse { + fun of(reactedEmojis: List, userId: Long): ReactedEmojisResult { val reactedEmojiAndReactUsers = convert(reactedEmojis, userId) val reactedUserCount = countTotalReactUser(reactedEmojiAndReactUsers) val totalEmojisCount = reactedEmojiAndReactUsers.sumOf { it.reactCount } val latestReactUserNickname = reactedEmojis.lastOrNull()?.reactUser?.nickname - return ReactedEmojisResponse( + return ReactedEmojisResult( totalReactedEmojisCount = totalEmojisCount, totalReactUserCount = reactedUserCount, latestReactUserNickname = latestReactUserNickname, @@ -73,7 +73,7 @@ data class ReactedEmojisResponse( .map { it.reactUser } .filter { it.username != null } .filter { it.nickname != null } - .map(ReactUser::from) + .map(ReactUser.Companion::from) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt similarity index 83% rename from backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt index 925918ec..30e907be 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/EmojiService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt @@ -1,9 +1,9 @@ -package io.raemian.api.emoji +package io.raemian.api.emoji.service -import io.raemian.api.emoji.controller.response.EmojiResponse -import io.raemian.api.emoji.controller.response.ReactedEmojisResponse -import io.raemian.api.event.ReactedEmojiEvent -import io.raemian.api.event.RemovedEmojiEvent +import io.raemian.api.emoji.model.EmojiResult +import io.raemian.api.emoji.model.ReactedEmojisResult +import io.raemian.api.event.model.ReactedEmojiEvent +import io.raemian.api.event.model.RemovedEmojiEvent import io.raemian.storage.db.core.emoji.EmojiCountRepository import io.raemian.storage.db.core.emoji.EmojiRepository import io.raemian.storage.db.core.emoji.ReactedEmoji @@ -25,15 +25,15 @@ class EmojiService( private val emojiCountRepository: EmojiCountRepository, ) { @Transactional(readOnly = true) - fun findAll(): List = + fun findAll(): List = emojiRepository.findAll() - .map(EmojiResponse::from) + .map(EmojiResult::from) @Transactional(readOnly = true) - fun findAllReactedEmojisByGoalId(goalId: Long, currentUserId: Long): ReactedEmojisResponse { + fun findAllReactedEmojisByGoalId(goalId: Long, currentUserId: Long): ReactedEmojisResult { val goal = goalRepository.getReferenceById(goalId) val reactedEmojis = reactedEmojiRepository.findAllByGoal(goal) - return ReactedEmojisResponse.of(reactedEmojis, currentUserId) + return ReactedEmojisResult.of(reactedEmojis, currentUserId) } @Transactional @@ -63,10 +63,10 @@ class EmojiService( } @Transactional(readOnly = true) - fun findAllByGoalIds(ids: List, userId: Long?): Map { + fun findAllByGoalIds(ids: List, userId: Long?): Map { return reactedEmojiRepository.findAllByGoalIdIn(ids) .groupBy { it.goal.id!! } - .mapValues { ReactedEmojisResponse.of(it.value, userId ?: -1) } + .mapValues { ReactedEmojisResult.of(it.value, userId ?: -1) } } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/ReadEventHandler.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/handler/ReadEventHandler.kt similarity index 85% rename from backend/application/api/src/main/kotlin/io/raemian/api/event/ReadEventHandler.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/event/handler/ReadEventHandler.kt index a907e7af..38dec229 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/ReadEventHandler.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/handler/ReadEventHandler.kt @@ -1,5 +1,6 @@ -package io.raemian.api.event +package io.raemian.api.event.handler +import io.raemian.api.event.model.CommentReadEvent import io.raemian.storage.db.core.goal.GoalRepository import org.springframework.context.event.EventListener import org.springframework.stereotype.Service diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/CheeredEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CheeredEvent.kt similarity index 60% rename from backend/application/api/src/main/kotlin/io/raemian/api/event/CheeredEvent.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/event/model/CheeredEvent.kt index b6b9eecf..e554e371 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/CheeredEvent.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CheeredEvent.kt @@ -1,4 +1,4 @@ -package io.raemian.api.event +package io.raemian.api.event.model data class CheeredEvent( val lifeMapId: Long, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/CommentReadEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CommentReadEvent.kt similarity index 77% rename from backend/application/api/src/main/kotlin/io/raemian/api/event/CommentReadEvent.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/event/model/CommentReadEvent.kt index 673613f9..b931eb7b 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/CommentReadEvent.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CommentReadEvent.kt @@ -1,4 +1,4 @@ -package io.raemian.api.event +package io.raemian.api.event.model import java.time.LocalDateTime diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/CountEventHandler.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CountEventHandler.kt similarity index 97% rename from backend/application/api/src/main/kotlin/io/raemian/api/event/CountEventHandler.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/event/model/CountEventHandler.kt index e8dac8fd..5214b19b 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/CountEventHandler.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CountEventHandler.kt @@ -1,5 +1,6 @@ -package io.raemian.api.event +package io.raemian.api.event.model +import io.raemian.api.support.lock.ExclusiveRunner import io.raemian.storage.db.core.cheer.Cheering import io.raemian.storage.db.core.cheer.CheeringRepository import io.raemian.storage.db.core.emoji.EmojiCount diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/CreatedGoalEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CreatedGoalEvent.kt similarity index 69% rename from backend/application/api/src/main/kotlin/io/raemian/api/event/CreatedGoalEvent.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/event/model/CreatedGoalEvent.kt index 23d896e6..5d6b1f7b 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/CreatedGoalEvent.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CreatedGoalEvent.kt @@ -1,4 +1,4 @@ -package io.raemian.api.event +package io.raemian.api.event.model data class CreatedGoalEvent( val goalId: Long, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/ReactedEmojiEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/ReactedEmojiEvent.kt similarity index 69% rename from backend/application/api/src/main/kotlin/io/raemian/api/event/ReactedEmojiEvent.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/event/model/ReactedEmojiEvent.kt index 44ea2fb7..4de0c00b 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/ReactedEmojiEvent.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/ReactedEmojiEvent.kt @@ -1,4 +1,4 @@ -package io.raemian.api.event +package io.raemian.api.event.model data class ReactedEmojiEvent( val goalId: Long, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/RemovedEmojiEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/RemovedEmojiEvent.kt similarity index 69% rename from backend/application/api/src/main/kotlin/io/raemian/api/event/RemovedEmojiEvent.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/event/model/RemovedEmojiEvent.kt index fe29f907..19c027c8 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/RemovedEmojiEvent.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/RemovedEmojiEvent.kt @@ -1,4 +1,4 @@ -package io.raemian.api.event +package io.raemian.api.event.model data class RemovedEmojiEvent( val goalId: Long, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt index 1ba9a3ee..53508bc6 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt @@ -1,13 +1,13 @@ package io.raemian.api.goal.controller -import io.raemian.api.auth.domain.CurrentUser -import io.raemian.api.emoji.EmojiService -import io.raemian.api.goal.GoalService +import io.raemian.api.auth.model.CurrentUser +import io.raemian.api.emoji.service.EmojiService import io.raemian.api.goal.controller.request.CreateGoalRequest import io.raemian.api.goal.controller.request.UpdateGoalRequest -import io.raemian.api.goal.controller.response.CreateGoalResponse -import io.raemian.api.goal.controller.response.GoalExploreResponse -import io.raemian.api.goal.controller.response.GoalResponse +import io.raemian.api.goal.model.CreateGoalResult +import io.raemian.api.goal.model.GoalExplorePageResult +import io.raemian.api.goal.model.GoalResult +import io.raemian.api.goal.service.GoalService import io.raemian.api.support.response.ApiResponse import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity @@ -37,7 +37,7 @@ class GoalController( fun getById( @AuthenticationPrincipal currentUser: CurrentUser, @PathVariable("goalId") goalId: Long, - ): ResponseEntity> = + ): ResponseEntity> = ResponseEntity.ok( ApiResponse.success(goalService.getById(goalId, currentUser.id)), ) @@ -47,7 +47,7 @@ class GoalController( fun create( @AuthenticationPrincipal currentUser: CurrentUser, @RequestBody createGoalRequest: CreateGoalRequest, - ): ResponseEntity> { + ): ResponseEntity> { val response = goalService.create(currentUser.id, createGoalRequest) return ResponseEntity .created("/goal/${response.id}".toUri()) @@ -60,7 +60,7 @@ class GoalController( @AuthenticationPrincipal currentUser: CurrentUser, @PathVariable("goalId") goalId: Long, @RequestBody updateGoalRequest: UpdateGoalRequest, - ): ResponseEntity> { + ): ResponseEntity> { val goalResponse = goalService.update(currentUser.id, goalId, updateGoalRequest) return ResponseEntity .ok(ApiResponse.success(goalResponse)) @@ -81,10 +81,10 @@ class GoalController( fun exploreGoals( @AuthenticationPrincipal currentUser: CurrentUser?, @RequestParam(required = false, defaultValue = Long.MAX_VALUE.toString()) cursor: Long, - ): ResponseEntity> { + ): ResponseEntity> { val explore = goalService.explore(goalId = cursor, userId = currentUser?.id) return ResponseEntity.ok() - .body(ApiResponse.success(GoalExploreResponse(explore))) + .body(ApiResponse.success(GoalExplorePageResult(explore))) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/response/CreateGoalResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/CreateGoalResult.kt similarity index 79% rename from backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/response/CreateGoalResponse.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/goal/model/CreateGoalResult.kt index 73476cfe..09c835c0 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/response/CreateGoalResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/CreateGoalResult.kt @@ -1,9 +1,9 @@ -package io.raemian.api.goal.controller.response +package io.raemian.api.goal.model -import io.raemian.api.support.format +import io.raemian.api.support.extension.format import io.raemian.storage.db.core.goal.Goal -data class CreateGoalResponse( +data class CreateGoalResult( val id: Long, val title: String, val description: String, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalCountSubset.kt similarity index 90% rename from backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalCountSubset.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalCountSubset.kt index 8327e632..15a447bc 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalCountSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalCountSubset.kt @@ -1,4 +1,4 @@ -package io.raemian.api.goal.domain +package io.raemian.api.goal.model import io.raemian.storage.db.core.model.GoalExploreQueryResult diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/response/GoalExploreResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExplorePageResult.kt similarity index 50% rename from backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/response/GoalExploreResponse.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExplorePageResult.kt index 20c7b894..b0e110e7 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/response/GoalExploreResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExplorePageResult.kt @@ -1,13 +1,11 @@ -package io.raemian.api.goal.controller.response +package io.raemian.api.goal.model -import io.raemian.api.goal.domain.GoalExploreDTO - -data class GoalExploreResponse( - val goals: List, +data class GoalExplorePageResult( + val goals: List, val cursor: GoalExploreCursor, ) { - constructor(goals: List) : this( + constructor(goals: List) : this( goals = goals, cursor = GoalExploreCursor(goals.lastOrNull()?.goal?.id ?: Long.MIN_VALUE), ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreResult.kt similarity index 72% rename from backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreResult.kt index 46cc2dfe..b36ab8f5 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalExploreDTO.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreResult.kt @@ -1,18 +1,18 @@ -package io.raemian.api.goal.domain +package io.raemian.api.goal.model -import io.raemian.api.emoji.controller.response.ReactedEmojisResponse -import io.raemian.api.emoji.domain.EmojiCountSubset -import io.raemian.api.user.domain.UserSubset +import io.raemian.api.emoji.model.EmojiCountSubset +import io.raemian.api.emoji.model.ReactedEmojisResult +import io.raemian.api.user.model.UserSubset import io.raemian.storage.db.core.model.GoalExploreQueryResult -data class GoalExploreDTO( +data class GoalExploreResult( val user: UserSubset, val goal: GoalSubset, val count: GoalCountSubset, val emojis: List, ) { companion object { - fun from(explore: GoalExploreQueryResult, reactedEmojisResponse: ReactedEmojisResponse?): GoalExploreDTO { + fun from(explore: GoalExploreQueryResult, reactedEmojisResponse: ReactedEmojisResult?): GoalExploreResult { val emojis = reactedEmojisResponse?.reactedEmojis?.map { EmojiCountSubset( id = it.id ?: -1, @@ -22,7 +22,7 @@ data class GoalExploreDTO( isMyReaction = it.isMyReaction, ) } ?: listOf() - return GoalExploreDTO( + return GoalExploreResult( goal = GoalSubset(explore), user = UserSubset(explore), count = GoalCountSubset(explore), diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/response/GoalResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalResult.kt similarity index 86% rename from backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/response/GoalResponse.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalResult.kt index 4dc4c559..17db97c5 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/response/GoalResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalResult.kt @@ -1,11 +1,11 @@ -package io.raemian.api.goal.controller.response +package io.raemian.api.goal.model -import io.raemian.api.support.format +import io.raemian.api.support.extension.format import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.tag.Tag import io.raemian.storage.db.core.task.Task -data class GoalResponse( +data class GoalResult( val title: String, val description: String, val deadline: String, @@ -22,7 +22,7 @@ data class GoalResponse( stickerUrl = goal.sticker.url, tagInfo = TagInfo(goal.tag), isMyGoal = isMyGoal, - tasks = goal.tasks.map(::TaskInfo), + tasks = goal.tasks.map(GoalResult::TaskInfo), ) data class TagInfo( diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalSubset.kt similarity index 94% rename from backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalSubset.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalSubset.kt index 5af5c753..ef8d9b7d 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/domain/GoalSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalSubset.kt @@ -1,4 +1,4 @@ -package io.raemian.api.goal.domain +package io.raemian.api.goal.model import io.raemian.storage.db.core.model.GoalExploreQueryResult import java.time.LocalDate diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt similarity index 78% rename from backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt index 2b787d21..51280602 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/GoalService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt @@ -1,18 +1,17 @@ -package io.raemian.api.goal +package io.raemian.api.goal.service -import io.raemian.api.emoji.EmojiService -import io.raemian.api.event.CreatedGoalEvent +import io.raemian.api.emoji.service.EmojiService +import io.raemian.api.event.model.CreatedGoalEvent import io.raemian.api.goal.controller.request.CreateGoalRequest import io.raemian.api.goal.controller.request.UpdateGoalRequest -import io.raemian.api.goal.controller.response.CreateGoalResponse -import io.raemian.api.goal.controller.response.GoalResponse -import io.raemian.api.goal.domain.GoalExploreDTO -import io.raemian.api.sticker.StickerService -import io.raemian.api.support.RaemianLocalDate -import io.raemian.api.support.error.MaxGoalCountExceededException -import io.raemian.api.support.error.PrivateLifeMapException -import io.raemian.api.tag.TagService -import io.raemian.storage.db.core.emoji.EmojiCountRepository +import io.raemian.api.goal.model.CreateGoalResult +import io.raemian.api.goal.model.GoalExploreResult +import io.raemian.api.goal.model.GoalResult +import io.raemian.api.sticker.service.StickerService +import io.raemian.api.support.exception.MaxGoalCountExceededException +import io.raemian.api.support.exception.PrivateLifeMapException +import io.raemian.api.support.utils.DeadlineCreator +import io.raemian.api.tag.service.TagService import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.lifemap.LifeMap @@ -31,20 +30,19 @@ class GoalService( private val goalRepository: GoalRepository, private val lifeMapRepository: LifeMapRepository, private val applicationEventPublisher: ApplicationEventPublisher, - private val emojiCountRepository: EmojiCountRepository, private val emojiService: EmojiService, ) { @Transactional(readOnly = true) - fun getById(id: Long, userId: Long): GoalResponse { + fun getById(id: Long, userId: Long): GoalResult { val goal = goalRepository.getById(id) val isMyGoal = goal.lifeMap.user.id == userId validateAnotherUserLifeMapPublic(isMyGoal, goal.lifeMap) - return GoalResponse(goal, isMyGoal) + return GoalResult(goal, isMyGoal) } @Transactional - fun create(userId: Long, createGoalRequest: CreateGoalRequest): CreateGoalResponse { + fun create(userId: Long, createGoalRequest: CreateGoalRequest): CreateGoalResult { val lifeMap = lifeMapRepository.findFirstByUserId(userId) ?: createFirstLifeMap(userId) @@ -58,11 +56,11 @@ class GoalService( CreatedGoalEvent(goalId = goal.id!!, lifeMapId = lifeMap.id!!), ) - return CreateGoalResponse(goal) + return CreateGoalResult(goal) } @Transactional - fun update(userId: Long, goalId: Long, updateGoalRequest: UpdateGoalRequest): GoalResponse { + fun update(userId: Long, goalId: Long, updateGoalRequest: UpdateGoalRequest): GoalResult { val goal = goalRepository.getById(goalId) validateGoalIsUsers(userId, goal) @@ -73,10 +71,10 @@ class GoalService( val updatedStickerGoal = updateTagGoal.takeIf { it.sticker.id == stickerId } ?: updateSticker(updateTagGoal, stickerId) - val deadline = RaemianLocalDate.of(yearOfDeadline, monthOfDeadline) + val deadline = DeadlineCreator.create(yearOfDeadline, monthOfDeadline) val updatedGoal = updatedStickerGoal.update(title, deadline, description) goalRepository.save(updatedGoal) - return GoalResponse(updatedGoal, true) + return GoalResult(updatedGoal, true) } } @@ -88,14 +86,14 @@ class GoalService( } @Transactional(readOnly = true) - fun explore(goalId: Long, userId: Long?): List { + fun explore(goalId: Long, userId: Long?): List { val explore = goalRepository.explore(goalId) val goalIds = explore.map { it.goalId } val reactedEmojiMap = emojiService.findAllByGoalIds(goalIds, userId) return explore - .map { GoalExploreDTO.from(it, reactedEmojiMap[it.goalId]) } + .map { GoalExploreResult.from(it, reactedEmojiMap[it.goalId]) } } private fun createFirstLifeMap(userId: Long): LifeMap { @@ -109,7 +107,7 @@ class GoalService( private fun createGoal(createGoalRequest: CreateGoalRequest, lifeMap: LifeMap): Goal { with(createGoalRequest) { - val deadline = RaemianLocalDate.of(yearOfDeadline, monthOfDeadline) + val deadline = DeadlineCreator.create(yearOfDeadline, monthOfDeadline) val sticker = stickerService.getReferenceById(stickerId) val tag = tagService.getReferenceById(tagId) return Goal( diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt index b3e85cdc..6731945f 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt @@ -1,10 +1,10 @@ package io.raemian.api.lifemap.controller -import io.raemian.api.auth.domain.CurrentUser -import io.raemian.api.cheer.CheeringService -import io.raemian.api.lifemap.LifeMapService -import io.raemian.api.lifemap.domain.LifeMapResponse -import io.raemian.api.lifemap.domain.UpdatePublicRequest +import io.raemian.api.auth.model.CurrentUser +import io.raemian.api.cheer.service.CheeringService +import io.raemian.api.lifemap.controller.request.UpdatePublicRequest +import io.raemian.api.lifemap.model.LifeMapResponse +import io.raemian.api.lifemap.service.LifeMapService import io.raemian.api.support.response.ApiResponse import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt index 3233f85f..422f7a2b 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt @@ -1,9 +1,9 @@ package io.raemian.api.lifemap.controller -import io.raemian.api.auth.domain.CurrentUser -import io.raemian.api.cheer.CheeringService -import io.raemian.api.lifemap.LifeMapService -import io.raemian.api.lifemap.domain.LifeMapResponse +import io.raemian.api.auth.model.CurrentUser +import io.raemian.api.cheer.service.CheeringService +import io.raemian.api.lifemap.model.LifeMapResponse +import io.raemian.api.lifemap.service.LifeMapService import io.raemian.api.support.response.ApiResponse import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/UpdatePublicRequest.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/request/UpdatePublicRequest.kt similarity index 55% rename from backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/UpdatePublicRequest.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/request/UpdatePublicRequest.kt index 9a9abd65..124f6f42 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/UpdatePublicRequest.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/request/UpdatePublicRequest.kt @@ -1,4 +1,4 @@ -package io.raemian.api.lifemap.domain +package io.raemian.api.lifemap.controller.request data class UpdatePublicRequest( val isPublic: Boolean, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/CountResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/CountResponse.kt similarity index 64% rename from backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/CountResponse.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/CountResponse.kt index f07eaa31..d229e841 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/CountResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/CountResponse.kt @@ -1,18 +1,18 @@ -package io.raemian.api.lifemap.domain +package io.raemian.api.lifemap.model -import io.raemian.api.cheer.controller.response.CheeringCountResponse +import io.raemian.api.cheer.model.CheeringCountResult data class CountResponse( val view: Long, val cheering: Long, val history: Long? = null, ) { - constructor(lifeMapCountDTO: LifeMapCountDTO, cheeringCount: Long) : this( + constructor(lifeMapCountDTO: LifeMapCountResult, cheeringCount: Long) : this( view = lifeMapCountDTO.viewCount, cheering = cheeringCount, history = lifeMapCountDTO.historyCount, ) - constructor(viewCount: Long, cheeringCountResponse: CheeringCountResponse) : this( + constructor(viewCount: Long, cheeringCountResponse: CheeringCountResult) : this( view = viewCount, cheering = cheeringCountResponse.count, ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/GoalDto.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/GoalDto.kt similarity index 85% rename from backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/GoalDto.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/GoalDto.kt index cf595afb..e78cec9d 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/GoalDto.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/GoalDto.kt @@ -1,6 +1,6 @@ -package io.raemian.api.lifemap.domain +package io.raemian.api.lifemap.model -import io.raemian.api.support.format +import io.raemian.api.support.extension.format import io.raemian.storage.db.core.goal.Goal class GoalDto private constructor( diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/LifeMapCountDTO.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/LifeMapCountResult.kt similarity index 85% rename from backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/LifeMapCountDTO.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/LifeMapCountResult.kt index 331db797..8cfb19f8 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/LifeMapCountDTO.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/LifeMapCountResult.kt @@ -1,8 +1,8 @@ -package io.raemian.api.lifemap.domain +package io.raemian.api.lifemap.model import io.raemian.storage.db.core.lifemap.LifeMapCount -data class LifeMapCountDTO( +data class LifeMapCountResult( val id: Long, val lifeMapId: Long, val viewCount: Long, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/LifeMapResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/LifeMapResponse.kt similarity index 66% rename from backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/LifeMapResponse.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/LifeMapResponse.kt index b1d8c101..e2859dc5 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/LifeMapResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/LifeMapResponse.kt @@ -1,7 +1,7 @@ -package io.raemian.api.lifemap.domain +package io.raemian.api.lifemap.model -import io.raemian.api.cheer.controller.response.CheeringCountResponse -import io.raemian.api.user.domain.UserSubset +import io.raemian.api.cheer.model.CheeringCountResult +import io.raemian.api.user.model.UserSubset data class LifeMapResponse( val lifeMapId: Long, @@ -11,7 +11,7 @@ data class LifeMapResponse( val user: UserSubset? = null, val count: CountResponse, ) { - constructor(lifeMapDTO: LifeMapDTO, lifeMapCountDTO: LifeMapCountDTO, cheeringCount: Long) : this( + constructor(lifeMapDTO: LifeMapResult, lifeMapCountDTO: LifeMapCountResult, cheeringCount: Long) : this( lifeMapId = lifeMapDTO.lifeMapId, isPublic = lifeMapDTO.isPublic, goals = lifeMapDTO.goals, @@ -20,7 +20,7 @@ data class LifeMapResponse( count = CountResponse(lifeMapCountDTO, cheeringCount), ) - constructor(lifeMapDTO: LifeMapDTO, viewCount: Long, cheeringCountResponse: CheeringCountResponse) : this( + constructor(lifeMapDTO: LifeMapResult, viewCount: Long, cheeringCountResponse: CheeringCountResult) : this( lifeMapId = lifeMapDTO.lifeMapId, isPublic = lifeMapDTO.isPublic, goals = lifeMapDTO.goals, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/LifeMapDTO.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/LifeMapResult.kt similarity index 89% rename from backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/LifeMapDTO.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/LifeMapResult.kt index 505bad13..6a497afd 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/domain/LifeMapDTO.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/LifeMapResult.kt @@ -1,10 +1,10 @@ -package io.raemian.api.lifemap.domain +package io.raemian.api.lifemap.model -import io.raemian.api.user.domain.UserSubset +import io.raemian.api.user.model.UserSubset import io.raemian.storage.db.core.lifemap.LifeMap import io.raemian.storage.db.core.user.User -data class LifeMapDTO( +data class LifeMapResult( val lifeMapId: Long, val isPublic: Boolean, val goals: List, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/LifeMapService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/service/LifeMapService.kt similarity index 87% rename from backend/application/api/src/main/kotlin/io/raemian/api/lifemap/LifeMapService.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/lifemap/service/LifeMapService.kt index ec2f5457..4e9c0726 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/LifeMapService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/service/LifeMapService.kt @@ -1,9 +1,9 @@ -package io.raemian.api.lifemap +package io.raemian.api.lifemap.service -import io.raemian.api.lifemap.domain.LifeMapCountDTO -import io.raemian.api.lifemap.domain.LifeMapDTO -import io.raemian.api.lifemap.domain.UpdatePublicRequest -import io.raemian.api.support.error.PrivateLifeMapException +import io.raemian.api.lifemap.controller.request.UpdatePublicRequest +import io.raemian.api.lifemap.model.LifeMapCountResult +import io.raemian.api.lifemap.model.LifeMapResult +import io.raemian.api.support.exception.PrivateLifeMapException import io.raemian.storage.db.core.lifemap.LifeMap import io.raemian.storage.db.core.lifemap.LifeMapCount import io.raemian.storage.db.core.lifemap.LifeMapCountRepository @@ -23,18 +23,18 @@ class LifeMapService( private val lifeMapHistoryRepository: LifeMapHistoryRepository, ) { @Transactional(readOnly = true) - fun findFirstByUserId(userId: Long): LifeMapDTO { + fun findFirstByUserId(userId: Long): LifeMapResult { val lifeMap = lifeMapRepository.findFirstByUserId(userId) ?: throw NoSuchElementException("존재하지 않는 유저입니다. $userId") // TODO edit immutable lifeMap.goals = lifeMap.sortGoals().toMutableList() - return LifeMapDTO(lifeMap) + return LifeMapResult(lifeMap) } @Transactional(readOnly = true) - fun findFirstByUserName(username: String): LifeMapDTO { + fun findFirstByUserName(username: String): LifeMapResult { val lifeMap = lifeMapRepository.findFirstByUserUsername(username) ?: throw NoSuchElementException("존재하지 않는 유저입니다. $username") @@ -44,7 +44,7 @@ class LifeMapService( lifeMap.goals = lifeMap.sortGoals().toMutableList() val user = userRepository.getById(lifeMap.user.id!!) - return LifeMapDTO(lifeMap, user) + return LifeMapResult(lifeMap, user) } @Transactional @@ -55,10 +55,10 @@ class LifeMapService( } @Transactional - fun getLifeMapCount(lifeMapId: Long): LifeMapCountDTO { + fun getLifeMapCount(lifeMapId: Long): LifeMapCountResult { val lifeMapCount = lifeMapCountRepository.findByLifeMapId(lifeMapId = lifeMapId) ?: lifeMapCountRepository.save(LifeMapCount.of(lifeMapId)) - return LifeMapCountDTO(lifeMapCount) + return LifeMapCountResult(lifeMapCount) } @Transactional(readOnly = true) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/log/UserLoginLogService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/log/UserLoginLogService.kt deleted file mode 100644 index 49ed075f..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/log/UserLoginLogService.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.raemian.api.log - -import io.raemian.storage.db.core.log.UserLoginLog -import io.raemian.storage.db.core.log.UserLoginLogRepository -import org.springframework.scheduling.annotation.Async -import org.springframework.stereotype.Service -import java.time.LocalDateTime - -@Service -class UserLoginLogService( - private val userLoginLogRepository: UserLoginLogRepository, -) { - @Async - fun upsertLatestLogin(userId: Long?) { - if (userId == null) { - return - } - - val userLoginLog = userLoginLogRepository.findByUserId(userId) - ?: UserLoginLog(userId = userId, latestLoginAt = LocalDateTime.now()) - - userLoginLogRepository.save(userLoginLog.updateLatestLogin()) - } -} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/log/controller/LogController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/log/controller/LogController.kt index af5dc130..8156e736 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/log/controller/LogController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/log/controller/LogController.kt @@ -1,7 +1,7 @@ package io.raemian.api.log.controller -import io.raemian.api.log.LogService import io.raemian.api.log.controller.request.CreateSlackErrorLogRequest +import io.raemian.api.log.service.LogService import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/log/LogService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/log/service/LogService.kt similarity index 60% rename from backend/application/api/src/main/kotlin/io/raemian/api/log/LogService.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/log/service/LogService.kt index b3a958d3..92b1a82a 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/log/LogService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/log/service/LogService.kt @@ -1,15 +1,19 @@ -package io.raemian.api.log +package io.raemian.api.log.service import io.raemian.api.log.controller.request.CreateSlackErrorLogRequest import io.raemian.infra.logging.enums.ErrorLocationEnum import io.raemian.infra.logging.logger.SlackLogger +import io.raemian.storage.db.core.log.UserLoginLog +import io.raemian.storage.db.core.log.UserLoginLogRepository +import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Service +import java.time.LocalDateTime @Service class LogService( private val slackLogger: SlackLogger, + private val userLoginLogRepository: UserLoginLogRepository, ) { - private final val EMPTY_VALUE: String = "EMPTY VALUE" fun createSlackErrorLog(createSlackErrorLogRequest: CreateSlackErrorLogRequest) { @@ -29,4 +33,16 @@ class LogService( exception.message ?: EMPTY_VALUE, ) } + + @Async + fun upsertLatestLogin(userId: Long?) { + if (userId == null) { + return + } + + val userLoginLog = userLoginLogRepository.findByUserId(userId) + ?: UserLoginLog(userId, LocalDateTime.now()) + + userLoginLogRepository.save(userLoginLog.updateLatestLogin()) + } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/profile/controller/ProfileController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/profile/controller/ProfileController.kt index 9f7b17f5..f95a33dc 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/profile/controller/ProfileController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/profile/controller/ProfileController.kt @@ -1,7 +1,7 @@ package io.raemian.api.profile.controller -import io.raemian.api.profile.ProfileService -import io.raemian.api.profile.controller.response.DefaultProfileResponse +import io.raemian.api.profile.model.DefaultProfileResult +import io.raemian.api.profile.service.ProfileService import io.raemian.api.support.response.ApiResponse import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity @@ -17,7 +17,7 @@ class ProfileController( @Operation(summary = "기본 프로필 이미지 전체 조회 API") @GetMapping("/default") - fun findAll(): ResponseEntity>> = + fun findAll(): ResponseEntity>> = ResponseEntity.ok( ApiResponse.success(profileService.findAllDefault()), ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/profile/controller/response/DefaultProfileResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/profile/model/DefaultProfileResult.kt similarity index 74% rename from backend/application/api/src/main/kotlin/io/raemian/api/profile/controller/response/DefaultProfileResponse.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/profile/model/DefaultProfileResult.kt index ed62b818..bf79bd22 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/profile/controller/response/DefaultProfileResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/profile/model/DefaultProfileResult.kt @@ -1,8 +1,8 @@ -package io.raemian.api.profile.controller.response +package io.raemian.api.profile.model import io.raemian.storage.db.core.profile.DefaultProfile -data class DefaultProfileResponse( +data class DefaultProfileResult( val id: Long?, val name: String, val url: String, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/profile/ProfileService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/profile/service/ProfileService.kt similarity index 64% rename from backend/application/api/src/main/kotlin/io/raemian/api/profile/ProfileService.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/profile/service/ProfileService.kt index 6a6c555e..62bb5f47 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/profile/ProfileService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/profile/service/ProfileService.kt @@ -1,6 +1,6 @@ -package io.raemian.api.profile +package io.raemian.api.profile.service -import io.raemian.api.profile.controller.response.DefaultProfileResponse +import io.raemian.api.profile.model.DefaultProfileResult import io.raemian.storage.db.core.profile.DefaultProfileRepository import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -10,8 +10,8 @@ class ProfileService( private val defaultProfileRepository: DefaultProfileRepository, ) { @Transactional(readOnly = true) - fun findAllDefault(): List { + fun findAllDefault(): List { return defaultProfileRepository.findAll() - .map(::DefaultProfileResponse) + .map(::DefaultProfileResult) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/sticker/controller/StickerController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/sticker/controller/StickerController.kt index e6915a3f..2577ba3f 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/sticker/controller/StickerController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/sticker/controller/StickerController.kt @@ -1,7 +1,7 @@ package io.raemian.api.sticker.controller -import io.raemian.api.sticker.StickerService -import io.raemian.api.sticker.controller.response.StickerResponse +import io.raemian.api.sticker.model.StickerResult +import io.raemian.api.sticker.service.StickerService import io.raemian.api.support.response.ApiResponse import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity @@ -17,7 +17,7 @@ class StickerController( @Operation(summary = "스티커 전체 조회 API") @GetMapping - fun findAll(): ResponseEntity>> = + fun findAll(): ResponseEntity>> = ResponseEntity.ok( ApiResponse.success(stickerService.findAll()), ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/sticker/controller/response/StickerResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/sticker/model/StickerResult.kt similarity index 74% rename from backend/application/api/src/main/kotlin/io/raemian/api/sticker/controller/response/StickerResponse.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/sticker/model/StickerResult.kt index 36505c9d..8e4b5edf 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/sticker/controller/response/StickerResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/sticker/model/StickerResult.kt @@ -1,8 +1,8 @@ -package io.raemian.api.sticker.controller.response +package io.raemian.api.sticker.model import io.raemian.storage.db.core.sticker.Sticker -data class StickerResponse( +data class StickerResult( val id: Long?, val name: String, val url: String, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/sticker/StickerService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/sticker/service/StickerService.kt similarity index 75% rename from backend/application/api/src/main/kotlin/io/raemian/api/sticker/StickerService.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/sticker/service/StickerService.kt index 97ad368e..6ce06f5d 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/sticker/StickerService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/sticker/service/StickerService.kt @@ -1,6 +1,6 @@ -package io.raemian.api.sticker +package io.raemian.api.sticker.service -import io.raemian.api.sticker.controller.response.StickerResponse +import io.raemian.api.sticker.model.StickerResult import io.raemian.storage.db.core.sticker.Sticker import io.raemian.storage.db.core.sticker.StickerRepository import org.springframework.stereotype.Service @@ -12,9 +12,9 @@ class StickerService( ) { @Transactional(readOnly = true) - fun findAll(): List { + fun findAll(): List { return stickerRepository.findAll() - .map(::StickerResponse) + .map(::StickerResult) } @Transactional(readOnly = true) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/error/CoreApiException.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/exception/CoreApiException.kt similarity index 77% rename from backend/application/api/src/main/kotlin/io/raemian/api/support/error/CoreApiException.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/support/exception/CoreApiException.kt index 83d31891..33403aa4 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/error/CoreApiException.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/exception/CoreApiException.kt @@ -1,4 +1,4 @@ -package io.raemian.api.support.error +package io.raemian.api.support.exception open class CoreApiException( val errorInfo: ErrorInfo, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/error/ErrorInfo.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/exception/ErrorInfo.kt similarity index 97% rename from backend/application/api/src/main/kotlin/io/raemian/api/support/error/ErrorInfo.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/support/exception/ErrorInfo.kt index 2d1405ad..a03019e1 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/error/ErrorInfo.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/exception/ErrorInfo.kt @@ -1,4 +1,4 @@ -package io.raemian.api.support.error +package io.raemian.api.support.exception import org.springframework.boot.logging.LogLevel import org.springframework.http.HttpStatus diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/error/MaxGoalCountExceededException.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/exception/MaxGoalCountExceededException.kt similarity index 72% rename from backend/application/api/src/main/kotlin/io/raemian/api/support/error/MaxGoalCountExceededException.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/support/exception/MaxGoalCountExceededException.kt index 28954201..8bc71f43 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/error/MaxGoalCountExceededException.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/exception/MaxGoalCountExceededException.kt @@ -1,4 +1,4 @@ -package io.raemian.api.support.error +package io.raemian.api.support.exception class MaxGoalCountExceededException : CoreApiException( ErrorInfo.MAX_GOAL_COUNT_EXCEEDED_EXCEPTION, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/error/MaxTaskCountExceededException.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/exception/MaxTaskCountExceededException.kt similarity index 72% rename from backend/application/api/src/main/kotlin/io/raemian/api/support/error/MaxTaskCountExceededException.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/support/exception/MaxTaskCountExceededException.kt index 8c47478d..5fd582b5 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/error/MaxTaskCountExceededException.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/exception/MaxTaskCountExceededException.kt @@ -1,4 +1,4 @@ -package io.raemian.api.support.error +package io.raemian.api.support.exception class MaxTaskCountExceededException : CoreApiException( ErrorInfo.MAX_TASK_COUNT_EXCEEDED_EXCEPTION, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/error/PrivateLifeMapException.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/exception/PrivateLifeMapException.kt similarity index 69% rename from backend/application/api/src/main/kotlin/io/raemian/api/support/error/PrivateLifeMapException.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/support/exception/PrivateLifeMapException.kt index 857fc0ef..d7079763 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/error/PrivateLifeMapException.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/exception/PrivateLifeMapException.kt @@ -1,4 +1,4 @@ -package io.raemian.api.support.error +package io.raemian.api.support.exception class PrivateLifeMapException : CoreApiException( ErrorInfo.PRIVATE_LIFE_MAP_EXCEPTION, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/extension/LocalDateExtensionFunctions.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/extension/LocalDateExtensionFunctions.kt new file mode 100644 index 00000000..1db210b7 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/extension/LocalDateExtensionFunctions.kt @@ -0,0 +1,12 @@ +package io.raemian.api.support.extension + +import java.time.LocalDate + +fun LocalDate.format(): String { + var month = (this.monthValue).toString() + if (month.length == 1) { + month = "0$month" + } + + return "${this.year}.$month" +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/CheeringLimiter.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/limiter/CheeringLimiter.kt similarity index 95% rename from backend/application/api/src/main/kotlin/io/raemian/api/cheer/CheeringLimiter.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/support/limiter/CheeringLimiter.kt index f4bfeccf..5d33d456 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/CheeringLimiter.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/limiter/CheeringLimiter.kt @@ -1,4 +1,4 @@ -package io.raemian.api.cheer +package io.raemian.api.support.limiter import com.github.benmanes.caffeine.cache.Caffeine import org.springframework.stereotype.Component diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/ExclusiveRunner.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/lock/ExclusiveRunner.kt similarity index 96% rename from backend/application/api/src/main/kotlin/io/raemian/api/event/ExclusiveRunner.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/support/lock/ExclusiveRunner.kt index f91d8177..88cc5726 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/ExclusiveRunner.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/lock/ExclusiveRunner.kt @@ -1,4 +1,4 @@ -package io.raemian.api.event +package io.raemian.api.support.lock import org.springframework.stereotype.Component import java.time.Duration diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/RunnerLock.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/lock/RunnerLock.kt similarity index 90% rename from backend/application/api/src/main/kotlin/io/raemian/api/event/RunnerLock.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/support/lock/RunnerLock.kt index 773230e9..ef4001e3 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/RunnerLock.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/lock/RunnerLock.kt @@ -1,4 +1,4 @@ -package io.raemian.api.event +package io.raemian.api.support.lock import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.locks.ReentrantLock diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/ApiResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/ApiResponse.kt index 1e6d214a..b42db7f0 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/ApiResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/ApiResponse.kt @@ -1,6 +1,6 @@ package io.raemian.api.support.response -import io.raemian.api.support.error.ErrorInfo +import io.raemian.api.support.exception.ErrorInfo class ApiResponse private constructor( val result: ResultType, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/JwtFilter.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/JwtFilter.kt similarity index 97% rename from backend/application/api/src/main/kotlin/io/raemian/api/support/JwtFilter.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/support/security/JwtFilter.kt index 868fbe8c..1a5d30a1 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/JwtFilter.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/JwtFilter.kt @@ -1,4 +1,4 @@ -package io.raemian.api.support +package io.raemian.api.support.security import jakarta.servlet.FilterChain import jakarta.servlet.http.HttpServletRequest diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/StateOAuth2AuthorizationRequestRepository.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/StateOAuth2AuthorizationRequestRepository.kt similarity index 97% rename from backend/application/api/src/main/kotlin/io/raemian/api/support/StateOAuth2AuthorizationRequestRepository.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/support/security/StateOAuth2AuthorizationRequestRepository.kt index 16e917a0..0dbeddf3 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/StateOAuth2AuthorizationRequestRepository.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/StateOAuth2AuthorizationRequestRepository.kt @@ -1,4 +1,4 @@ -package io.raemian.api.support +package io.raemian.api.support.security import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Caffeine diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/TokenProvider.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/TokenProvider.kt similarity index 95% rename from backend/application/api/src/main/kotlin/io/raemian/api/support/TokenProvider.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/support/security/TokenProvider.kt index b35b3a8e..f17f8d6d 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/TokenProvider.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/TokenProvider.kt @@ -1,4 +1,4 @@ -package io.raemian.api.support +package io.raemian.api.support.security import io.jsonwebtoken.Claims import io.jsonwebtoken.ExpiredJwtException @@ -9,8 +9,8 @@ import io.jsonwebtoken.UnsupportedJwtException import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.security.Keys import io.jsonwebtoken.security.SecurityException -import io.raemian.api.auth.domain.CurrentUser -import io.raemian.api.auth.domain.TokenDTO +import io.raemian.api.auth.model.CurrentUser +import io.raemian.api.auth.model.Token import org.slf4j.LoggerFactory import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.Authentication @@ -37,7 +37,7 @@ class TokenProvider { private val ACCESS_TOKEN_EXPIRE_TIME = Duration.ofMinutes(300).toMillis() // 300분 private val REFRESH_TOKEN_EXPIRE_TIME = Duration.ofDays(70).toMillis() // 70일 - fun generateTokenDto(currentUser: CurrentUser): TokenDTO { + fun generateTokenDto(currentUser: CurrentUser): Token { val authorities: String = currentUser.authorities .map { obj: GrantedAuthority -> obj.authority } .joinToString(",") @@ -59,7 +59,7 @@ class TokenProvider { .setExpiration(Date(now + REFRESH_TOKEN_EXPIRE_TIME)) .signWith(key, SignatureAlgorithm.HS512) .compact() - return TokenDTO( + return Token( grantType = BEARER_TYPE, accessToken = accessToken, refreshToken = refreshToken, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/RaemianLocalDate.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/utils/DeadlineCreator.kt similarity index 73% rename from backend/application/api/src/main/kotlin/io/raemian/api/support/RaemianLocalDate.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/support/utils/DeadlineCreator.kt index d20b0983..996fd69a 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/RaemianLocalDate.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/utils/DeadlineCreator.kt @@ -1,23 +1,14 @@ -package io.raemian.api.support +package io.raemian.api.support.utils import java.time.LocalDate import java.time.Month import java.time.Year -fun LocalDate.format(): String { - var month = (this.monthValue).toString() - if (month.length == 1) { - month = "0$month" - } - - return "${this.year}.$month" -} - -object RaemianLocalDate { +object DeadlineCreator { private const val DAY_OF_MONTH = 1 - fun of(year: String, month: String): LocalDate { + fun create(year: String, month: String): LocalDate { val parsedYear = parseYear(year) val parsedMonth = parseMonth(month) return LocalDate.of(parsedYear, parsedMonth, DAY_OF_MONTH) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/SecurityUtil.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/utils/SecurityUtil.kt similarity index 93% rename from backend/application/api/src/main/kotlin/io/raemian/api/support/SecurityUtil.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/support/utils/SecurityUtil.kt index b045f6f1..1abc0d37 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/SecurityUtil.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/utils/SecurityUtil.kt @@ -1,4 +1,4 @@ -package io.raemian.api.support +package io.raemian.api.support.utils import org.springframework.security.core.Authentication import org.springframework.security.core.context.SecurityContextHolder diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/tag/controller/TagController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/tag/controller/TagController.kt index f278478e..51ccb4d0 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/tag/controller/TagController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/tag/controller/TagController.kt @@ -1,8 +1,8 @@ package io.raemian.api.tag.controller import io.raemian.api.support.response.ApiResponse -import io.raemian.api.tag.TagService -import io.raemian.api.tag.controller.response.TagResponse +import io.raemian.api.tag.model.TagResult +import io.raemian.api.tag.service.TagService import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping @@ -17,7 +17,7 @@ class TagController( @Operation(summary = "태그 전체 조회 API") @GetMapping - fun findAll(): ResponseEntity>> = + fun findAll(): ResponseEntity>> = ResponseEntity.ok( ApiResponse.success(tagService.findAll()), ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/tag/controller/response/TagResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/tag/model/TagResult.kt similarity index 70% rename from backend/application/api/src/main/kotlin/io/raemian/api/tag/controller/response/TagResponse.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/tag/model/TagResult.kt index e135bf6d..e35eb172 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/tag/controller/response/TagResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/tag/model/TagResult.kt @@ -1,8 +1,8 @@ -package io.raemian.api.tag.controller.response +package io.raemian.api.tag.model import io.raemian.storage.db.core.tag.Tag -data class TagResponse( +data class TagResult( val id: Long?, val content: String, ) { diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/tag/TagService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/tag/service/TagService.kt similarity index 76% rename from backend/application/api/src/main/kotlin/io/raemian/api/tag/TagService.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/tag/service/TagService.kt index c1eff33a..076cfbd7 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/tag/TagService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/tag/service/TagService.kt @@ -1,6 +1,6 @@ -package io.raemian.api.tag +package io.raemian.api.tag.service -import io.raemian.api.tag.controller.response.TagResponse +import io.raemian.api.tag.model.TagResult import io.raemian.storage.db.core.tag.Tag import io.raemian.storage.db.core.tag.TagRepository import org.springframework.stereotype.Service @@ -12,9 +12,9 @@ class TagService( ) { @Transactional(readOnly = true) - fun findAll(): List { + fun findAll(): List { return tagRepository.findAll() - .map(::TagResponse) + .map(::TagResult) } @Transactional(readOnly = true) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/task/controller/TaskController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/task/controller/TaskController.kt index ee74900d..15366cbf 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/task/controller/TaskController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/task/controller/TaskController.kt @@ -1,13 +1,13 @@ package io.raemian.api.task.controller -import io.raemian.api.auth.domain.CurrentUser +import io.raemian.api.auth.model.CurrentUser import io.raemian.api.goal.controller.toUri import io.raemian.api.support.response.ApiResponse -import io.raemian.api.task.TaskService import io.raemian.api.task.controller.request.CreateTaskRequest import io.raemian.api.task.controller.request.RewriteTaskRequest import io.raemian.api.task.controller.request.UpdateTaskCompletionRequest -import io.raemian.api.task.controller.response.CreateTaskResponse +import io.raemian.api.task.model.CreateTaskResult +import io.raemian.api.task.service.TaskService import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity import org.springframework.security.core.annotation.AuthenticationPrincipal @@ -30,7 +30,7 @@ class TaskController( fun create( @AuthenticationPrincipal currentUser: CurrentUser, @RequestBody createTaskRequest: CreateTaskRequest, - ): ResponseEntity> { + ): ResponseEntity> { val response = taskService.create(currentUser.id, createTaskRequest) return ResponseEntity.created("/task/${response.id}".toUri()) .body(ApiResponse.success(response)) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/task/controller/response/CreateTaskResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/task/controller/response/CreateTaskResponse.kt deleted file mode 100644 index 8355d9a5..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/task/controller/response/CreateTaskResponse.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.raemian.api.task.controller.response - -class CreateTaskResponse( - val id: Long, - val description: String, -) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/task/model/CreateTaskResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/task/model/CreateTaskResult.kt new file mode 100644 index 00000000..e4a39b6e --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/task/model/CreateTaskResult.kt @@ -0,0 +1,6 @@ +package io.raemian.api.task.model + +class CreateTaskResult( + val id: Long, + val description: String, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/task/TaskService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/task/service/TaskService.kt similarity index 90% rename from backend/application/api/src/main/kotlin/io/raemian/api/task/TaskService.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/task/service/TaskService.kt index f11b2b19..fa995a0d 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/task/TaskService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/task/service/TaskService.kt @@ -1,10 +1,10 @@ -package io.raemian.api.task +package io.raemian.api.task.service -import io.raemian.api.support.error.MaxTaskCountExceededException +import io.raemian.api.support.exception.MaxTaskCountExceededException import io.raemian.api.task.controller.request.CreateTaskRequest import io.raemian.api.task.controller.request.RewriteTaskRequest import io.raemian.api.task.controller.request.UpdateTaskCompletionRequest -import io.raemian.api.task.controller.response.CreateTaskResponse +import io.raemian.api.task.model.CreateTaskResult import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.task.Task @@ -19,7 +19,7 @@ class TaskService( ) { @Transactional - fun create(currentUserId: Long, createTaskRequest: CreateTaskRequest): CreateTaskResponse { + fun create(currentUserId: Long, createTaskRequest: CreateTaskRequest): CreateTaskResult { val goal = goalRepository.getById(createTaskRequest.goalId) validateCurrentUserIsGoalOwner(currentUserId, goal) @@ -27,7 +27,7 @@ class TaskService( addNewTask(goal, task) taskRepository.save(task) - return CreateTaskResponse(task.id!!, task.description) + return CreateTaskResult(task.id!!, task.description) } @Transactional diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/user/controller/UserController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/user/controller/UserController.kt index eafabae8..6e724622 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/user/controller/UserController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/user/controller/UserController.kt @@ -2,12 +2,12 @@ package io.raemian.api.user.controller import io.raemian.api.auth.controller.request.UpdateUserInfoRequest import io.raemian.api.auth.controller.request.UpdateUserRequest -import io.raemian.api.auth.domain.CurrentUser -import io.raemian.api.auth.domain.UserDTO -import io.raemian.api.lifemap.LifeMapService -import io.raemian.api.support.error.ErrorInfo +import io.raemian.api.auth.model.CurrentUser +import io.raemian.api.lifemap.service.LifeMapService +import io.raemian.api.support.exception.ErrorInfo import io.raemian.api.support.response.ApiResponse -import io.raemian.api.user.controller.response.UserResponse +import io.raemian.api.user.model.UserResult +import io.raemian.api.user.model.UserTokenDecryptResult import io.raemian.api.user.service.UserService import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity @@ -25,10 +25,10 @@ class UserController( ) { @Operation(summary = "토큰 유저 정보 조회 API") @GetMapping("/my") - fun my(@AuthenticationPrincipal currentUser: CurrentUser): ResponseEntity> { + fun my(@AuthenticationPrincipal currentUser: CurrentUser): ResponseEntity> { val user = userService.getUserById(currentUser.id) val lifeMap = lifeMapService.findFirstByUserId(currentUser.id) - val response = UserResponse.of(user, lifeMap) + val response = UserTokenDecryptResult.of(user, lifeMap) return ResponseEntity.ok(ApiResponse.success(response)) } @@ -37,7 +37,7 @@ class UserController( fun updateBaseInfo( @AuthenticationPrincipal currentUser: CurrentUser, @RequestBody updateUserRequest: UpdateUserRequest, - ): ResponseEntity> { + ): ResponseEntity> { val updated = userService.updateNicknameAndBirth( id = currentUser.id, nickname = updateUserRequest.nickname, @@ -52,7 +52,7 @@ class UserController( fun updateFromMy( @AuthenticationPrincipal currentUser: CurrentUser, @RequestBody updateUserInfoRequest: UpdateUserInfoRequest, - ): ResponseEntity> { + ): ResponseEntity> { val user = userService.getUserById(currentUser.id) if (user.username == updateUserInfoRequest.username) { val updated = userService.updateBaseInfo( diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/auth/domain/UserDTO.kt b/backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserResult.kt similarity index 85% rename from backend/application/api/src/main/kotlin/io/raemian/api/auth/domain/UserDTO.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserResult.kt index 894900ba..4fa2eec2 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/auth/domain/UserDTO.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserResult.kt @@ -1,10 +1,10 @@ -package io.raemian.api.auth.domain +package io.raemian.api.user.model import io.raemian.storage.db.core.user.User import java.time.LocalDate import java.time.LocalDateTime -data class UserDTO( +data class UserResult( val id: Long, val email: String, val username: String? = null, @@ -16,8 +16,8 @@ data class UserDTO( val createdAt: LocalDateTime? = null, ) { companion object { - fun of(user: User): UserDTO { - return UserDTO( + fun of(user: User): UserResult { + return UserResult( id = user.id!!, email = user.email, nickname = user.nickname, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/user/domain/UserSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserSubset.kt similarity index 94% rename from backend/application/api/src/main/kotlin/io/raemian/api/user/domain/UserSubset.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserSubset.kt index 4c4ec2f6..a237bbb3 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/user/domain/UserSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserSubset.kt @@ -1,4 +1,4 @@ -package io.raemian.api.user.domain +package io.raemian.api.user.model import io.raemian.storage.db.core.model.GoalExploreQueryResult import io.raemian.storage.db.core.user.User diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/user/controller/response/UserResponse.kt b/backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserTokenDecryptResult.kt similarity index 67% rename from backend/application/api/src/main/kotlin/io/raemian/api/user/controller/response/UserResponse.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserTokenDecryptResult.kt index 60c0e583..54197366 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/user/controller/response/UserResponse.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserTokenDecryptResult.kt @@ -1,23 +1,22 @@ -package io.raemian.api.user.controller.response +package io.raemian.api.user.model -import io.raemian.api.auth.domain.UserDTO -import io.raemian.api.lifemap.domain.LifeMapDTO +import io.raemian.api.lifemap.model.LifeMapResult import java.time.LocalDate import java.time.LocalDateTime -data class UserResponse( +data class UserTokenDecryptResult( val id: Long, val email: String, val username: String?, val nickname: String?, val birth: LocalDate?, val image: String, + val lifeMap: LifeMapResult, val createdAt: LocalDateTime?, - val lifeMap: LifeMapDTO, ) { companion object { - fun of(user: UserDTO, lifeMap: LifeMapDTO): UserResponse { - return UserResponse( + fun of(user: UserResult, lifeMap: LifeMapResult): UserTokenDecryptResult { + return UserTokenDecryptResult( id = user.id, email = user.email, username = user.username, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/user/service/UserService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/user/service/UserService.kt index 893aa985..74aff2e4 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/user/service/UserService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/user/service/UserService.kt @@ -1,6 +1,6 @@ package io.raemian.api.user.service -import io.raemian.api.auth.domain.UserDTO +import io.raemian.api.user.model.UserResult import io.raemian.storage.db.core.user.UserRepository import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -12,12 +12,12 @@ class UserService( ) { @Transactional(readOnly = true) - fun getUserById(id: Long): UserDTO { + fun getUserById(id: Long): UserResult { val user = userRepository.getById(id) - return UserDTO.of(user) + return UserResult.of(user) } - fun updateNicknameAndBirth(id: Long, nickname: String, birth: LocalDate?): UserDTO { + fun updateNicknameAndBirth(id: Long, nickname: String, birth: LocalDate?): UserResult { val user = userRepository.getById(id) val updated = user.updateNicknameAndBirth( @@ -25,10 +25,10 @@ class UserService( birth = birth, ) - return UserDTO.of(userRepository.save(updated)) + return UserResult.of(userRepository.save(updated)) } - fun updateBaseInfo(id: Long, nickname: String, birth: LocalDate?, image: String): UserDTO { + fun updateBaseInfo(id: Long, nickname: String, birth: LocalDate?, image: String): UserResult { val user = userRepository.getById(id) val updated = user.updateNicknameAndBirth( @@ -36,10 +36,10 @@ class UserService( birth = birth, ).updateImage(image) - return UserDTO.of(userRepository.save(updated)) + return UserResult.of(userRepository.save(updated)) } - fun update(id: Long, nickname: String, birth: LocalDate?, username: String, image: String): UserDTO { + fun update(id: Long, nickname: String, birth: LocalDate?, username: String, image: String): UserResult { val user = userRepository.getById(id) val updated = user @@ -50,7 +50,7 @@ class UserService( .updateUsername(username) .updateImage(image) - return UserDTO.of(userRepository.save(updated)) + return UserResult.of(userRepository.save(updated)) } fun delete(id: Long) { diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt index 7bc7673f..2ac0953c 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt @@ -1,10 +1,10 @@ package io.raemian.api.integration.comment -import io.raemian.api.comment.CommentService import io.raemian.api.comment.controller.request.WriteCommentRequest +import io.raemian.api.comment.service.CommentService import io.raemian.api.support.CoreApiExceptionTestSupporter.Companion.assertThrowsCoreApiExceptionExactly -import io.raemian.api.support.error.CoreApiException -import io.raemian.api.support.error.ErrorInfo +import io.raemian.api.support.exception.CoreApiException +import io.raemian.api.support.exception.ErrorInfo import io.raemian.storage.db.core.comment.Comment import io.raemian.storage.db.core.comment.CommentRepository import io.raemian.storage.db.core.goal.Goal diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt index e34d5535..1d6d8d83 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/emoji/EmojiServiceTest.kt @@ -1,6 +1,6 @@ package io.raemian.api.integration.emoji -import io.raemian.api.emoji.EmojiService +import io.raemian.api.emoji.service.EmojiService import io.raemian.storage.db.core.emoji.Emoji import io.raemian.storage.db.core.emoji.EmojiRepository import io.raemian.storage.db.core.emoji.ReactedEmoji diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/goal/GoalServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/goal/GoalServiceTest.kt index 38e52811..8ed646fa 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/goal/GoalServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/goal/GoalServiceTest.kt @@ -1,11 +1,11 @@ package io.raemian.api.integration.goal -import io.raemian.api.goal.GoalService import io.raemian.api.goal.controller.request.CreateGoalRequest import io.raemian.api.goal.controller.request.UpdateGoalRequest -import io.raemian.api.support.RaemianLocalDate -import io.raemian.api.support.error.MaxGoalCountExceededException -import io.raemian.api.support.error.PrivateLifeMapException +import io.raemian.api.goal.service.GoalService +import io.raemian.api.support.exception.MaxGoalCountExceededException +import io.raemian.api.support.exception.PrivateLifeMapException +import io.raemian.api.support.utils.DeadlineCreator import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.lifemap.LifeMap @@ -243,7 +243,7 @@ class GoalServiceTest { val description = "Description" val year = "2000" val month = "05" - val deadline = RaemianLocalDate.of(year, month) + val deadline = DeadlineCreator.create(year, month) val goal = Goal( lifeMap = LIFE_MAP_FIXTURE, diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/lifemap/LifeMapServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/lifemap/LifeMapServiceTest.kt index 2a3be870..6595bf34 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/lifemap/LifeMapServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/lifemap/LifeMapServiceTest.kt @@ -1,7 +1,7 @@ package io.raemian.api.integration.lifemap -import io.raemian.api.lifemap.LifeMapService -import io.raemian.api.lifemap.domain.UpdatePublicRequest +import io.raemian.api.lifemap.controller.request.UpdatePublicRequest +import io.raemian.api.lifemap.service.LifeMapService import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.lifemap.LifeMap import io.raemian.storage.db.core.lifemap.LifeMapRepository diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/sticker/StickerServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/sticker/StickerServiceTest.kt index 8d6fd869..3d94685b 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/sticker/StickerServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/sticker/StickerServiceTest.kt @@ -1,6 +1,6 @@ package io.raemian.api.integration.sticker -import io.raemian.api.sticker.StickerService +import io.raemian.api.sticker.service.StickerService import io.raemian.storage.db.core.sticker.Sticker import io.raemian.storage.db.core.sticker.StickerRepository import org.assertj.core.api.Assertions.assertThat diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/tag/TagServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/tag/TagServiceTest.kt index b8536a80..a92920fa 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/tag/TagServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/tag/TagServiceTest.kt @@ -1,6 +1,6 @@ package io.raemian.api.integration.tag -import io.raemian.api.tag.TagService +import io.raemian.api.tag.service.TagService import io.raemian.storage.db.core.tag.Tag import io.raemian.storage.db.core.tag.TagRepository import org.assertj.core.api.Assertions.assertThat diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/task/TaskServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/task/TaskServiceTest.kt index 359af477..945e0968 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/task/TaskServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/task/TaskServiceTest.kt @@ -1,10 +1,10 @@ package io.raemian.api.integration.task -import io.raemian.api.support.error.MaxTaskCountExceededException -import io.raemian.api.task.TaskService +import io.raemian.api.support.exception.MaxTaskCountExceededException import io.raemian.api.task.controller.request.CreateTaskRequest import io.raemian.api.task.controller.request.RewriteTaskRequest import io.raemian.api.task.controller.request.UpdateTaskCompletionRequest +import io.raemian.api.task.service.TaskService import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.lifemap.LifeMap diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/support/CoreApiExceptionTestSupporter.kt b/backend/application/api/src/test/kotlin/io/raemian/api/support/CoreApiExceptionTestSupporter.kt index 7cb588b5..63ba55ec 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/support/CoreApiExceptionTestSupporter.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/support/CoreApiExceptionTestSupporter.kt @@ -1,7 +1,7 @@ package io.raemian.api.support -import io.raemian.api.support.error.CoreApiException -import io.raemian.api.support.error.ErrorInfo +import io.raemian.api.support.exception.CoreApiException +import io.raemian.api.support.exception.ErrorInfo class CoreApiExceptionTestSupporter { diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/support/RaemianLocalDateTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/support/DeadlineCreatorTest.kt similarity index 85% rename from backend/application/api/src/test/kotlin/io/raemian/api/support/RaemianLocalDateTest.kt rename to backend/application/api/src/test/kotlin/io/raemian/api/support/DeadlineCreatorTest.kt index 1dbcbf8d..54b35c3d 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/support/RaemianLocalDateTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/support/DeadlineCreatorTest.kt @@ -1,5 +1,7 @@ package io.raemian.api.support +import io.raemian.api.support.extension.format +import io.raemian.api.support.utils.DeadlineCreator import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.jupiter.api.DisplayName @@ -8,7 +10,7 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource import java.time.LocalDate -class RaemianLocalDateTest { +class DeadlineCreatorTest { @Test @DisplayName("RaemianLocalDate를 통해 연도와 날짜 string으로 LocalDate를 만들 수 있다.") @@ -18,7 +20,7 @@ class RaemianLocalDateTest { val month = "11" // when - val localDate = RaemianLocalDate.of(year, month) + val localDate = DeadlineCreator.create(year, month) // then assertThat(localDate.year.toString()).isEqualTo(year) @@ -36,8 +38,8 @@ class RaemianLocalDateTest { val month2 = (1..12).random().toString() // when - val localDate1 = RaemianLocalDate.of(year1, month1) - val localDate2 = RaemianLocalDate.of(year2, month2) + val localDate1 = DeadlineCreator.create(year1, month1) + val localDate2 = DeadlineCreator.create(year2, month2) // then assertThat(localDate1.dayOfMonth).isEqualTo(localDate2.dayOfMonth) @@ -51,7 +53,7 @@ class RaemianLocalDateTest { // when // then assertThatThrownBy { - RaemianLocalDate.of(yearAndMonth, yearAndMonth) + DeadlineCreator.create(yearAndMonth, yearAndMonth) }.isInstanceOf(RuntimeException::class.java) } From 89ce00dd81c5a603dfe9c3e8bb14aa626a634d3d Mon Sep 17 00:00:00 2001 From: wkwon Date: Thu, 21 Mar 2024 01:49:23 +0900 Subject: [PATCH 23/60] =?UTF-8?q?refactor(#235):=20pagination=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=ED=99=94=20+=20cheering=20squad=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=20pagination=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cheer/controller/CheeringController.kt | 15 ++++---- .../request/CheeringSquadPageRequest.kt | 6 ++++ .../request/CheeringSquadPagingRequest.kt | 8 ----- .../cheer/model/CheeringSquadPageResult.kt | 8 +++++ .../api/cheer/service/CheeringService.kt | 36 ++++++++----------- .../api/support/response/PageResult.kt | 13 ------- .../raemian/storage/db/core/cheer/Cheerer.kt | 11 ++++-- .../db/core/cheer/CheererRepository.kt | 7 +--- .../raemian/storage/db/core/cheer/Cheering.kt | 2 +- .../storage/db/core/comment/Comment.kt | 2 +- .../db/core/{ => common}/BaseEntity.kt | 2 +- .../common/pagination/CursorExtractable.kt | 5 +++ .../pagination/CursorPaginationResult.kt | 17 +++++++++ .../pagination/CursorPaginationTemplate.kt | 23 ++++++++++++ .../db/core/common/pagination/TriFunction.kt | 5 +++ .../io/raemian/storage/db/core/emoji/Emoji.kt | 2 +- .../storage/db/core/emoji/EmojiCount.kt | 2 +- .../storage/db/core/emoji/ReactedEmoji.kt | 2 +- .../io/raemian/storage/db/core/goal/Goal.kt | 2 +- .../storage/db/core/lifemap/LifeMap.kt | 2 +- .../storage/db/core/profile/DefaultProfile.kt | 2 +- .../storage/db/core/sticker/Sticker.kt | 2 +- .../io/raemian/storage/db/core/tag/Tag.kt | 2 +- .../io/raemian/storage/db/core/task/Task.kt | 2 +- .../io/raemian/storage/db/core/user/User.kt | 2 +- 25 files changed, 107 insertions(+), 73 deletions(-) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPageRequest.kt delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPagingRequest.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheeringSquadPageResult.kt delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/response/PageResult.kt rename backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/{ => common}/BaseEntity.kt (91%) create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationResult.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/TriFunction.kt diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt index 354d5819..5befc299 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt @@ -1,12 +1,11 @@ package io.raemian.api.cheer.controller import io.raemian.api.cheer.controller.request.CheeringRequest -import io.raemian.api.cheer.controller.request.CheeringSquadPagingRequest -import io.raemian.api.cheer.model.CheererResult +import io.raemian.api.cheer.controller.request.CheeringSquadPageRequest import io.raemian.api.cheer.model.CheeringCountResult +import io.raemian.api.cheer.model.CheeringSquadPageResult import io.raemian.api.cheer.service.CheeringService import io.raemian.api.support.response.ApiResponse -import io.raemian.api.support.response.PageResult import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -22,11 +21,11 @@ class CheeringController( ) { @GetMapping("/squad/{lifeMapId}") - fun findCheeringSquad(@PathVariable("lifeMapId") lifeMapId: Long, request: CheeringSquadPagingRequest): ResponseEntity>> { - val response = cheeringService.findCheeringSquad(lifeMapId, request) - - return ResponseEntity.ok().body(ApiResponse.success(response)) - } + fun findCheeringSquad( + @PathVariable("lifeMapId") lifeMapId: Long, + request: CheeringSquadPageRequest, + ): ResponseEntity> = + ResponseEntity.ok().body(ApiResponse.success(cheeringService.findCheeringSquad(lifeMapId, request))) @GetMapping("/count/{userName}") fun getCheeringCount(@PathVariable("userName") userName: String): ResponseEntity> = diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPageRequest.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPageRequest.kt new file mode 100644 index 00000000..167f3131 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPageRequest.kt @@ -0,0 +1,6 @@ +package io.raemian.api.cheer.controller.request + +data class CheeringSquadPageRequest( + val pageSize: Int, + val cursorId: Long?, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPagingRequest.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPagingRequest.kt deleted file mode 100644 index f033e9e4..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPagingRequest.kt +++ /dev/null @@ -1,8 +0,0 @@ -package io.raemian.api.cheer.controller.request - -import java.time.LocalDateTime - -data class CheeringSquadPagingRequest( - val pageSize: Int, - val lastCursorAt: LocalDateTime?, -) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheeringSquadPageResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheeringSquadPageResult.kt new file mode 100644 index 00000000..6b4f9108 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheeringSquadPageResult.kt @@ -0,0 +1,8 @@ +package io.raemian.api.cheer.model + +import io.raemian.storage.db.core.common.pagination.CursorPaginationResult + +data class CheeringSquadPageResult( + val total: Long, + val cheeringSquad: CursorPaginationResult, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt index 4647b521..6d50e3a7 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt @@ -1,22 +1,23 @@ package io.raemian.api.cheer.service import io.raemian.api.cheer.controller.request.CheeringRequest -import io.raemian.api.cheer.controller.request.CheeringSquadPagingRequest +import io.raemian.api.cheer.controller.request.CheeringSquadPageRequest import io.raemian.api.cheer.model.CheererResult import io.raemian.api.cheer.model.CheeringCountResult +import io.raemian.api.cheer.model.CheeringSquadPageResult import io.raemian.api.event.model.CheeredEvent import io.raemian.api.support.exception.CoreApiException import io.raemian.api.support.exception.ErrorInfo import io.raemian.api.support.limiter.CheeringLimiter -import io.raemian.api.support.response.PageResult import io.raemian.storage.db.core.cheer.Cheerer import io.raemian.storage.db.core.cheer.CheererRepository import io.raemian.storage.db.core.cheer.Cheering import io.raemian.storage.db.core.cheer.CheeringRepository +import io.raemian.storage.db.core.common.pagination.CursorPaginationResult +import io.raemian.storage.db.core.common.pagination.CursorPaginationTemplate import io.raemian.storage.db.core.lifemap.LifeMapRepository import io.raemian.storage.db.core.user.UserRepository import org.springframework.context.ApplicationEventPublisher -import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.time.LocalDateTime @@ -42,16 +43,16 @@ class CheeringService( } @Transactional(readOnly = true) - fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPagingRequest): PageResult { + fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPageRequest): CheeringSquadPageResult { val cheering = cheeringRepository.findByLifeMapId(lifeMapId) ?: Cheering(0, lifeMapId) - val cheeringSquad = - findCheeringSquadPage(lifeMapId, request.lastCursorAt, Pageable.ofSize(request.pageSize)) + val cheeringSquad = findCheeringSquadWithCursor(lifeMapId, request) - val isLastPage = isLastPage(cheeringSquad.size, request.pageSize, lifeMapId, cheeringSquad) - - return PageResult.of(cheering.count, cheeringSquad.map(CheererResult::from), isLastPage) + return CheeringSquadPageResult( + cheering.count, + cheeringSquad.transform(CheererResult::from), + ) } @Transactional(readOnly = true) @@ -86,19 +87,10 @@ class CheeringService( ) } - private fun isLastPage(contentSize: Int, pageSize: Int, lifeMapId: Long, cheeringSquad: List): Boolean { - return if (contentSize < pageSize) { - true - } else { - !cheererRepository.existsByLifeMapIdAndCheeringAtLessThanOrderByCheeringAtDesc(lifeMapId, cheeringSquad.last().cheeringAt) - } - } - - private fun findCheeringSquadPage(lifeMapId: Long, cheeringAt: LocalDateTime?, pageable: Pageable): List { - return if (cheeringAt == null) { - cheererRepository.findByLifeMapIdOrderByCheeringAtDesc(lifeMapId, pageable) - } else { - cheererRepository.findByLifeMapIdAndCheeringAtLessThanOrderByCheeringAtDesc(lifeMapId, cheeringAt, pageable) + private fun findCheeringSquadWithCursor(lifeMapId: Long, request: CheeringSquadPageRequest): CursorPaginationResult { + return CursorPaginationTemplate.execute(lifeMapId, request.cursorId ?: Long.MAX_VALUE, request.pageSize) { + id, cursorId, pageable -> + cheererRepository.findAllByLifeMapIdAndIdLessThanOrderByIdDesc(id, cursorId, pageable) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PageResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PageResult.kt deleted file mode 100644 index a0c93c78..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PageResult.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.raemian.api.support.response - -class PageResult private constructor( - val total: Long, - val contents: List, - val isLastPage: Boolean, -) { - companion object { - fun of(total: Long, contents: List, isLastPage: Boolean): PageResult { - return PageResult(total, contents, isLastPage) - } - } -} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheerer.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheerer.kt index 9b72ee08..d3055e02 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheerer.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheerer.kt @@ -1,6 +1,7 @@ package io.raemian.storage.db.core.cheer -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity +import io.raemian.storage.db.core.common.pagination.CursorExtractable import io.raemian.storage.db.core.user.User import jakarta.persistence.Column import jakarta.persistence.Entity @@ -14,7 +15,7 @@ import jakarta.persistence.Table import java.time.LocalDateTime @Entity -@Table(name = "CHEERER", indexes = [Index(name = "IDX_LIFE_MAP_ID_AND_CHEERING_AT", columnList = "lifeMapId, cheeringAt")]) +@Table(name = "CHEERER", indexes = [Index(name = "IDX_LIFE_MAP_ID_AND_ID", columnList = "lifeMapId, id")]) class Cheerer( @Column val lifeMapId: Long, @@ -29,4 +30,8 @@ class Cheerer( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null, -) : BaseEntity() +) : BaseEntity(), CursorExtractable { + override fun getCursorId(): Long? { + return id + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheererRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheererRepository.kt index cb6543af..d0625447 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheererRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheererRepository.kt @@ -2,13 +2,8 @@ package io.raemian.storage.db.core.cheer import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository -import java.time.LocalDateTime interface CheererRepository : JpaRepository { - fun findByLifeMapIdAndCheeringAtLessThanOrderByCheeringAtDesc(lifeMapId: Long, cheeringAt: LocalDateTime, pageable: Pageable): List - - fun findByLifeMapIdOrderByCheeringAtDesc(lifeMapId: Long, pageable: Pageable): List - - fun existsByLifeMapIdAndCheeringAtLessThanOrderByCheeringAtDesc(lifeMapId: Long, cheeringAt: LocalDateTime): Boolean + fun findAllByLifeMapIdAndIdLessThanOrderByIdDesc(lifeMapId: Long, id: Long, pageable: Pageable): List } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheering.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheering.kt index d4bb7800..e8044c56 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheering.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheering.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.cheer -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt index 5ce6ee39..afa505b8 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.comment -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.user.User import jakarta.persistence.Column diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/BaseEntity.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/BaseEntity.kt similarity index 91% rename from backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/BaseEntity.kt rename to backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/BaseEntity.kt index a15e0f07..2a385a8e 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/BaseEntity.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/BaseEntity.kt @@ -1,4 +1,4 @@ -package io.raemian.storage.db.core +package io.raemian.storage.db.core.common import jakarta.persistence.Column import jakarta.persistence.MappedSuperclass diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt new file mode 100644 index 00000000..18d2410f --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt @@ -0,0 +1,5 @@ +package io.raemian.storage.db.core.common.pagination + +interface CursorExtractable { + fun getCursorId(): Long? +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationResult.kt new file mode 100644 index 00000000..628378ec --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationResult.kt @@ -0,0 +1,17 @@ +package io.raemian.storage.db.core.common.pagination + +import java.util.function.Function + +data class CursorPaginationResult internal constructor( + val contents: List, + val nextCursor: Long?, + val isLast: Boolean, +) { + fun transform(transformer: Function): CursorPaginationResult { + return CursorPaginationResult( + contents.stream().map(transformer).toList(), + nextCursor, + isLast, + ) + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt new file mode 100644 index 00000000..705b7000 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt @@ -0,0 +1,23 @@ +package io.raemian.storage.db.core.common.pagination + +import org.springframework.data.domain.Pageable +import kotlin.math.min + +object CursorPaginationTemplate { + fun execute( + id: Long, + cursorId: Long, + size: Int, + query: TriFunction>, + ): CursorPaginationResult { + val data = query.apply(id, cursorId ?: 0, Pageable.ofSize(size + 1)) + val isLast = data.size != size + 1 + val nextCursor = if (isLast) null else data[size].getCursorId() + + return CursorPaginationResult( + data.subList(0, min(data.size, size)), + nextCursor, + isLast, + ) + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/TriFunction.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/TriFunction.kt new file mode 100644 index 00000000..64cfaed1 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/TriFunction.kt @@ -0,0 +1,5 @@ +package io.raemian.storage.db.core.common.pagination + +fun interface TriFunction { + fun apply(t: T, u: U, v: V): R +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/Emoji.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/Emoji.kt index fd75128d..ac6d193a 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/Emoji.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/Emoji.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.emoji -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt index 25319990..0a7c1498 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.emoji -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmoji.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmoji.kt index f9bb6e78..471e306f 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmoji.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmoji.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.emoji -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.user.User import jakarta.persistence.Entity diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt index 9669bc36..c48405b1 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.goal -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import io.raemian.storage.db.core.lifemap.LifeMap import io.raemian.storage.db.core.sticker.Sticker import io.raemian.storage.db.core.tag.Tag diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMap.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMap.kt index 341ba596..ebd82821 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMap.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMap.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.lifemap -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.user.User import jakarta.persistence.CascadeType diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/profile/DefaultProfile.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/profile/DefaultProfile.kt index 948ea3d9..2a338852 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/profile/DefaultProfile.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/profile/DefaultProfile.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.profile -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/sticker/Sticker.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/sticker/Sticker.kt index eb4352cc..eea5b1a2 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/sticker/Sticker.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/sticker/Sticker.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.sticker -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/tag/Tag.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/tag/Tag.kt index c6cc33fb..e9bf18da 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/tag/Tag.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/tag/Tag.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.tag -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/Task.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/Task.kt index cce75144..fd27319b 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/Task.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/Task.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.task -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import io.raemian.storage.db.core.goal.Goal import jakarta.persistence.Column import jakarta.persistence.Entity diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/User.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/User.kt index 261cbb91..8ad5b188 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/User.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/User.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.user -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import io.raemian.storage.db.core.user.enums.OAuthProvider import jakarta.persistence.Column import jakarta.persistence.Entity From cd4a1874587335686707e5ffd478e5412e5ba04a Mon Sep 17 00:00:00 2001 From: binary_ho Date: Thu, 21 Mar 2024 10:22:28 +0900 Subject: [PATCH 24/60] =?UTF-8?q?[=F0=9F=9B=A0=EF=B8=8F=20Refactor]=20?= =?UTF-8?q?=EC=9E=98=EB=AA=BB=20=EC=9E=91=EC=84=B1=EB=90=9C=20=EC=9D=BD?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EB=8C=93=EA=B8=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=BF=BC=EB=A6=AC=20=EC=98=AC=EB=B0=94=EB=A5=B4?= =?UTF-8?q?=EA=B2=8C=20=EC=A0=95=EC=A0=95=20(#237)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor : CommentsResponse에 자신의 목표인지 확인하는 isMyGoal 필드 추가 (#229) * test : CommentsResponse를 통해 자신의 목표인지 확인할 수 있는지 검증하는 테스트 코드 작성 (#229) * refactor : exist new comment 쿼리를 id와 시간으로 검색하도록 변경 (#236) * refactor : 쿼리가 Goal Id를 사용하도록 변경 (#236) * test : 본인의 Goal만 조회하는지 검증하는 테스트 추가 작성 (#236) --- .../api/comment/service/CommentService.kt | 2 +- .../integration/comment/CommentServiceTest.kt | 45 +++++++++++++++++-- .../db/core/comment/CommentRepository.kt | 2 +- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt index 38c0b740..efd32004 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt @@ -46,7 +46,7 @@ class CommentService( @Transactional fun isNewComment(goalId: Long): Boolean { val goal = goalRepository.getById(goalId) - return commentRepository.existsByCreatedAtGreaterThan(goal.lastCommentReadAt) + return commentRepository.existsByGoalIdAndCreatedAtGreaterThan(goal.id!!, goal.lastCommentReadAt) } @Transactional diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt index 2ac0953c..2d990b25 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/comment/CommentServiceTest.kt @@ -136,19 +136,56 @@ class CommentServiceTest { commentRepository.save(comment2) // when - // 처음 저장된 댓글의 시간으로 last comment read at 업데이트 - goalRepository.updateLastCommentReadAtByGoalId(GOAL_FIXTURE.id!!, comment1.createdAt!!) + // 더 빨리 저장된 댓글의 시간으로 last comment read at 업데이트하면 true를 반환한다. + val earlierCreatedAt = + if (comment1.createdAt!!.isBefore(comment2.createdAt)) comment1.createdAt else comment2.createdAt + goalRepository.updateLastCommentReadAtByGoalId(GOAL_FIXTURE.id!!, earlierCreatedAt!!) val result1 = commentService.isNewComment(GOAL_FIXTURE.id!!) - // 세 번째로 저장된 댓글의 시간으로 last comment read at 업데이트 - goalRepository.updateLastCommentReadAtByGoalId(GOAL_FIXTURE.id!!, comment2.createdAt!!) + // 두 번째로 저장된 댓글의 시간으로 last comment read at 업데이트하면 false를 반환한다. + // 마지막으로 저장된 댓글은 다른 Goal의 댓글이므로 영향을 주지 않는다. + val laterCreatedAt = + if (comment1.createdAt!!.isAfter(comment2.createdAt)) comment1.createdAt else comment2.createdAt + goalRepository.updateLastCommentReadAtByGoalId(GOAL_FIXTURE.id!!, laterCreatedAt!!) val result2 = commentService.isNewComment(GOAL_FIXTURE.id!!) + println("lastCommentReadAt : " + goalRepository.getById(GOAL_FIXTURE.id!!).lastCommentReadAt) + println("comement1 : " + comment1.createdAt) + println("comement2 : " + comment2.createdAt) + println("laterCreatedAt : " + laterCreatedAt.toString()) + // then assertThat(result1).isTrue() assertThat(result2).isFalse() } + @Test + @DisplayName("유저는 읽지 않은 Comment가 있는지 확인할 때 자신의 Goal만 확인한다.") + fun isNewCheckMyGoalOnlyTest() { + // given + val anotherGoal = Goal( + lifeMap = LIFE_MAP_FIXTURE, + title = "title", + deadline = LocalDate.MAX, + sticker = STICKER_FIXTURE, + tag = TAG_FIXTURE, + description = "description", + lastCommentReadAt = LocalDateTime.now(), + ) + entityManager.merge(anotherGoal) + + val comment1 = Comment(GOAL_FIXTURE, USER_FIXTURE, "comment1") + val comment2 = Comment(GOAL_FIXTURE, USER_FIXTURE, "comment2") + commentRepository.save(comment1) + commentRepository.save(comment2) + + // when + goalRepository.updateLastCommentReadAtByGoalId(GOAL_FIXTURE.id!!, LocalDateTime.MIN!!) + + // then + assertThat(commentService.isNewComment(anotherGoal.id!!)).isFalse() + } + @Test @DisplayName("유저는 Comment를 작성할 수 있다.") fun writeTest() { diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentRepository.kt index 082d605f..ef5496f6 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentRepository.kt @@ -7,7 +7,7 @@ interface CommentRepository : JpaRepository { fun findAllByGoalId(goalId: Long): List - fun existsByCreatedAtGreaterThan(createdAt: LocalDateTime): Boolean + fun existsByGoalIdAndCreatedAtGreaterThan(goalId: Long, createdAt: LocalDateTime): Boolean override fun getById(id: Long): Comment = findById(id).orElseThrow() { NoSuchElementException("Comment가 없습니다 $id") } From cbe73a0231e99804a5b21ed28e590bcb4ef57b35 Mon Sep 17 00:00:00 2001 From: wkwon Date: Thu, 21 Mar 2024 18:36:57 +0900 Subject: [PATCH 25/60] =?UTF-8?q?feat:=20pagination=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=20response=20=EC=B6=94=EA=B0=80=20+=20cheering=20squad=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EB=A5=BC=20=EC=9C=84=ED=95=9C=20queryReposit?= =?UTF-8?q?ory=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cheer/controller/CheeringController.kt | 5 +- .../request/CheeringSquadPageRequest.kt | 4 +- .../raemian/api/cheer/model/CheererResult.kt | 22 --------- .../cheer/model/CheeringSquadPageResult.kt | 8 --- .../api/cheer/service/CheeringService.kt | 23 +++++---- .../raemian/api/goal/model/GoalCountSubset.kt | 2 +- .../api/goal/model/GoalExploreResult.kt | 2 +- .../io/raemian/api/goal/model/GoalSubset.kt | 2 +- .../api/support/response/PaginationResult.kt | 21 ++++++++ .../io/raemian/api/user/model/UserSubset.kt | 2 +- .../db/core/cheer/CheerJdbcQueryRepository.kt | 49 +++++++++++++++++++ .../raemian/storage/db/core/cheer/Cheerer.kt | 7 +-- .../db/core/cheer/model/CheererQueryResult.kt | 17 +++++++ .../common/pagination/CursorExtractable.kt | 2 +- .../pagination/CursorPaginationTemplate.kt | 5 +- .../storage/db/core/goal/GoalRepository.kt | 4 +- .../core/goal/model/GoalExploreQueryResult.kt | 2 +- 17 files changed, 114 insertions(+), 63 deletions(-) delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheeringSquadPageResult.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt index 5befc299..221b74c7 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt @@ -3,9 +3,10 @@ package io.raemian.api.cheer.controller import io.raemian.api.cheer.controller.request.CheeringRequest import io.raemian.api.cheer.controller.request.CheeringSquadPageRequest import io.raemian.api.cheer.model.CheeringCountResult -import io.raemian.api.cheer.model.CheeringSquadPageResult import io.raemian.api.cheer.service.CheeringService import io.raemian.api.support.response.ApiResponse +import io.raemian.api.support.response.PaginationResult +import io.raemian.storage.db.core.cheer.model.CheererQueryResult import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -24,7 +25,7 @@ class CheeringController( fun findCheeringSquad( @PathVariable("lifeMapId") lifeMapId: Long, request: CheeringSquadPageRequest, - ): ResponseEntity> = + ): ResponseEntity>> = ResponseEntity.ok().body(ApiResponse.success(cheeringService.findCheeringSquad(lifeMapId, request))) @GetMapping("/count/{userName}") diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPageRequest.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPageRequest.kt index 167f3131..b94ee41f 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPageRequest.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPageRequest.kt @@ -1,6 +1,6 @@ package io.raemian.api.cheer.controller.request data class CheeringSquadPageRequest( - val pageSize: Int, - val cursorId: Long?, + val size: Int, + val cursor: Long?, ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt deleted file mode 100644 index d022a05d..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt +++ /dev/null @@ -1,22 +0,0 @@ -package io.raemian.api.cheer.model - -import io.raemian.storage.db.core.cheer.Cheerer -import java.time.LocalDateTime - -data class CheererResult( - val userId: Long, - val userName: String, - val userNickName: String, - val userImageUrl: String, - val cheeringAt: LocalDateTime?, -) { - companion object { - fun from(cheerer: Cheerer): CheererResult { - return if (cheerer.user == null) { - CheererResult(-1, "", "", "", cheerer.cheeringAt) - } else { - CheererResult(cheerer.user!!.id!!, cheerer.user!!.username!!, cheerer.user!!.nickname!!, cheerer.user!!.image, cheerer.cheeringAt) - } - } - } -} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheeringSquadPageResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheeringSquadPageResult.kt deleted file mode 100644 index 6b4f9108..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheeringSquadPageResult.kt +++ /dev/null @@ -1,8 +0,0 @@ -package io.raemian.api.cheer.model - -import io.raemian.storage.db.core.common.pagination.CursorPaginationResult - -data class CheeringSquadPageResult( - val total: Long, - val cheeringSquad: CursorPaginationResult, -) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt index 6d50e3a7..21620b70 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt @@ -2,17 +2,18 @@ package io.raemian.api.cheer.service import io.raemian.api.cheer.controller.request.CheeringRequest import io.raemian.api.cheer.controller.request.CheeringSquadPageRequest -import io.raemian.api.cheer.model.CheererResult import io.raemian.api.cheer.model.CheeringCountResult -import io.raemian.api.cheer.model.CheeringSquadPageResult import io.raemian.api.event.model.CheeredEvent import io.raemian.api.support.exception.CoreApiException import io.raemian.api.support.exception.ErrorInfo import io.raemian.api.support.limiter.CheeringLimiter +import io.raemian.api.support.response.PaginationResult +import io.raemian.storage.db.core.cheer.CheerJdbcQueryRepository import io.raemian.storage.db.core.cheer.Cheerer import io.raemian.storage.db.core.cheer.CheererRepository import io.raemian.storage.db.core.cheer.Cheering import io.raemian.storage.db.core.cheer.CheeringRepository +import io.raemian.storage.db.core.cheer.model.CheererQueryResult import io.raemian.storage.db.core.common.pagination.CursorPaginationResult import io.raemian.storage.db.core.common.pagination.CursorPaginationTemplate import io.raemian.storage.db.core.lifemap.LifeMapRepository @@ -24,11 +25,12 @@ import java.time.LocalDateTime @Service class CheeringService( + private val cheeringLimiter: CheeringLimiter, + private val cheererJdbcQueryRepository: CheerJdbcQueryRepository, private val cheererRepository: CheererRepository, private val cheeringRepository: CheeringRepository, private val lifeMapRepository: LifeMapRepository, private val userRepository: UserRepository, - private val cheeringLimiter: CheeringLimiter, private val applicationEventPublisher: ApplicationEventPublisher, ) { @Transactional @@ -43,16 +45,13 @@ class CheeringService( } @Transactional(readOnly = true) - fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPageRequest): CheeringSquadPageResult { + fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPageRequest): PaginationResult { val cheering = cheeringRepository.findByLifeMapId(lifeMapId) ?: Cheering(0, lifeMapId) val cheeringSquad = findCheeringSquadWithCursor(lifeMapId, request) - return CheeringSquadPageResult( - cheering.count, - cheeringSquad.transform(CheererResult::from), - ) + return PaginationResult.from(cheering.count, cheeringSquad) } @Transactional(readOnly = true) @@ -87,10 +86,10 @@ class CheeringService( ) } - private fun findCheeringSquadWithCursor(lifeMapId: Long, request: CheeringSquadPageRequest): CursorPaginationResult { - return CursorPaginationTemplate.execute(lifeMapId, request.cursorId ?: Long.MAX_VALUE, request.pageSize) { - id, cursorId, pageable -> - cheererRepository.findAllByLifeMapIdAndIdLessThanOrderByIdDesc(id, cursorId, pageable) + private fun findCheeringSquadWithCursor(lifeMapId: Long, request: CheeringSquadPageRequest): CursorPaginationResult { + return CursorPaginationTemplate.execute(lifeMapId, request.cursor ?: Long.MAX_VALUE, request.size) { + id, cursor, pageable -> + cheererJdbcQueryRepository.findAllCheererWithCursor(id, cursor, pageable) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalCountSubset.kt index 15a447bc..406d1299 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalCountSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalCountSubset.kt @@ -1,6 +1,6 @@ package io.raemian.api.goal.model -import io.raemian.storage.db.core.model.GoalExploreQueryResult +import io.raemian.storage.db.core.cheer.GoalExploreQueryResult data class GoalCountSubset( val reaction: Long, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreResult.kt index b36ab8f5..7760d5ea 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreResult.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreResult.kt @@ -3,7 +3,7 @@ package io.raemian.api.goal.model import io.raemian.api.emoji.model.EmojiCountSubset import io.raemian.api.emoji.model.ReactedEmojisResult import io.raemian.api.user.model.UserSubset -import io.raemian.storage.db.core.model.GoalExploreQueryResult +import io.raemian.storage.db.core.cheer.GoalExploreQueryResult data class GoalExploreResult( val user: UserSubset, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalSubset.kt index ef8d9b7d..68dc3d76 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalSubset.kt @@ -1,6 +1,6 @@ package io.raemian.api.goal.model -import io.raemian.storage.db.core.model.GoalExploreQueryResult +import io.raemian.storage.db.core.cheer.GoalExploreQueryResult import java.time.LocalDate import java.time.LocalDateTime diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt new file mode 100644 index 00000000..09d30059 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt @@ -0,0 +1,21 @@ +package io.raemian.api.support.response + +import io.raemian.storage.db.core.common.pagination.CursorPaginationResult + +data class PaginationResult( + val total: Long, + val contents: List, + val isLast: Boolean, + val nextCursor: Long?, +) { + companion object { + fun from(total: Long, result: CursorPaginationResult): PaginationResult { + return PaginationResult( + total, + result.contents, + result.isLast, + result.nextCursor, + ) + } + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserSubset.kt index a237bbb3..48404314 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserSubset.kt @@ -1,6 +1,6 @@ package io.raemian.api.user.model -import io.raemian.storage.db.core.model.GoalExploreQueryResult +import io.raemian.storage.db.core.cheer.GoalExploreQueryResult import io.raemian.storage.db.core.user.User data class UserSubset( diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt new file mode 100644 index 00000000..29f0b8a8 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt @@ -0,0 +1,49 @@ +package io.raemian.storage.db.core.cheer + +import io.raemian.storage.db.core.cheer.model.CheererQueryResult +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate +import org.springframework.stereotype.Repository +import java.time.LocalDateTime + +@Repository +class CheerJdbcQueryRepository( + private val jdbcTemplate: NamedParameterJdbcTemplate, +) { + fun findAllCheererWithCursor(lifeMapId: Long, cursor: Long, size: Int): List { + val sql = + """ + SELECT + c.ID AS CHEERER_ID, + u.ID AS USER_ID, + u.USERNAME AS USERNAME, + u.NICKNAME AS NICKNAME, + u.IMAGE AS IMAGE_URL, + c.CHEERING_AT AS CHEERING_AT + FROM cheerer c + LEFT OUTER JOIN users u ON c.USER_ID = u.ID + WHERE 1 = 1 + AND c.LIFE_MAP_ID = :lifeMapId + AND c.ID <= :cursor + ORDER BY c.ID DESC + LIMIT :size + """.trimIndent() + + val namedParameter = MapSqlParameterSource() + .addValue("lifeMapId", lifeMapId) + .addValue("cursor", cursor) + .addValue("size", size) + + return jdbcTemplate.query(sql, namedParameter) { + rs, rowNum -> + CheererQueryResult( + cheererId = rs.getLong("CHEERER_ID"), + userId = rs.getLong("USER_ID"), + userName = rs.getString("USERNAME"), + userNickName = rs.getString("NICKNAME"), + userImageUrl = rs.getString("IMAGE_URL"), + cheeringAt = rs.getObject("CHEERING_AT", LocalDateTime::class.java), + ) + } + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheerer.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheerer.kt index d3055e02..600e3859 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheerer.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheerer.kt @@ -1,7 +1,6 @@ package io.raemian.storage.db.core.cheer import io.raemian.storage.db.core.common.BaseEntity -import io.raemian.storage.db.core.common.pagination.CursorExtractable import io.raemian.storage.db.core.user.User import jakarta.persistence.Column import jakarta.persistence.Entity @@ -30,8 +29,4 @@ class Cheerer( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null, -) : BaseEntity(), CursorExtractable { - override fun getCursorId(): Long? { - return id - } -} +) : BaseEntity() diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt new file mode 100644 index 00000000..00da471a --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt @@ -0,0 +1,17 @@ +package io.raemian.storage.db.core.cheer.model + +import io.raemian.storage.db.core.common.pagination.CursorExtractable +import java.time.LocalDateTime + +data class CheererQueryResult( + val cheererId: Long, + val userId: Long?, + val userName: String?, + val userNickName: String?, + val userImageUrl: String?, + val cheeringAt: LocalDateTime, +) : CursorExtractable { + override fun getCursorId(): Long { + return cheererId + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt index 18d2410f..645a8325 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt @@ -1,5 +1,5 @@ package io.raemian.storage.db.core.common.pagination interface CursorExtractable { - fun getCursorId(): Long? + fun getCursorId(): Long } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt index 705b7000..57465be6 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt @@ -1,6 +1,5 @@ package io.raemian.storage.db.core.common.pagination -import org.springframework.data.domain.Pageable import kotlin.math.min object CursorPaginationTemplate { @@ -8,9 +7,9 @@ object CursorPaginationTemplate { id: Long, cursorId: Long, size: Int, - query: TriFunction>, + query: TriFunction>, ): CursorPaginationResult { - val data = query.apply(id, cursorId ?: 0, Pageable.ofSize(size + 1)) + val data = query.apply(id, cursorId ?: 0, size + 1) val isLast = data.size != size + 1 val nextCursor = if (isLast) null else data[size].getCursorId() diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt index 0dd670a5..581eb408 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt @@ -1,7 +1,7 @@ package io.raemian.storage.db.core.goal +import io.raemian.storage.db.core.cheer.GoalExploreQueryResult import io.raemian.storage.db.core.lifemap.LifeMap -import io.raemian.storage.db.core.model.GoalExploreQueryResult import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query @@ -19,7 +19,7 @@ interface GoalRepository : JpaRepository { @Query( """ SELECT - new io.raemian.storage.db.core.model.GoalExploreQueryResult( + new io.raemian.storage.db.core.cheer.GoalExploreQueryResult( goal.id, goal.title, goal.description, diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalExploreQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalExploreQueryResult.kt index 1ce004e1..888b6a02 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalExploreQueryResult.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalExploreQueryResult.kt @@ -1,4 +1,4 @@ -package io.raemian.storage.db.core.model +package io.raemian.storage.db.core.cheer import java.time.LocalDate import java.time.LocalDateTime From 4bb778c042be05c2d1016caa5030745b595334b2 Mon Sep 17 00:00:00 2001 From: wkwon Date: Thu, 21 Mar 2024 20:31:01 +0900 Subject: [PATCH 26/60] =?UTF-8?q?feat(#235):=20comment=20count=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=B6=94=EA=B0=80=20+=20comment=20create/?= =?UTF-8?q?delete=20event=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/comment/service/CommentService.kt | 6 ++++ .../{model => handler}/CountEventHandler.kt | 32 ++++++++++++++++- .../api/event/model/CreatedCommentEvent.kt | 5 +++ .../api/event/model/DeletedCommentEvent.kt | 5 +++ .../storage/db/core/comment/CommentCount.kt | 34 +++++++++++++++++++ .../db/core/comment/CommentCountRepository.kt | 9 +++++ .../db/core/emoji/EmojiCountRepository.kt | 1 - .../db/core/emoji/ReactedEmojiRepository.kt | 2 +- .../storage/db/core/goal/GoalRepository.kt | 2 -- .../db/core/lifemap/LifeMapCountRepository.kt | 2 -- .../db/core/lifemap/LifeMapRepository.kt | 2 -- .../storage/db/core/user/UserRepository.kt | 2 -- 12 files changed, 91 insertions(+), 11 deletions(-) rename backend/application/api/src/main/kotlin/io/raemian/api/event/{model => handler}/CountEventHandler.kt (69%) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/event/model/CreatedCommentEvent.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedCommentEvent.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCountRepository.kt diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt index 38c0b740..6d1d822d 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt @@ -3,6 +3,8 @@ package io.raemian.api.comment.service import io.raemian.api.comment.controller.request.WriteCommentRequest import io.raemian.api.comment.model.CommentsResult import io.raemian.api.event.model.CommentReadEvent +import io.raemian.api.event.model.CreatedCommentEvent +import io.raemian.api.event.model.DeletedCommentEvent import io.raemian.api.support.exception.CoreApiException import io.raemian.api.support.exception.ErrorInfo import io.raemian.storage.db.core.comment.Comment @@ -55,6 +57,8 @@ class CommentService( val currentUser = userRepository.getReferenceById(currentUserId) val comment = createComment(goal, currentUser, request.content) commentRepository.save(comment) + + applicationEventPublisher.publishEvent(CreatedCommentEvent(goalId)) } @Transactional @@ -65,6 +69,8 @@ class CommentService( } commentRepository.delete(comment) + + applicationEventPublisher.publishEvent(DeletedCommentEvent(comment.goal.id!!)) } private fun createComment(goal: Goal, currentUser: User, content: String): Comment { diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CountEventHandler.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/handler/CountEventHandler.kt similarity index 69% rename from backend/application/api/src/main/kotlin/io/raemian/api/event/model/CountEventHandler.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/event/handler/CountEventHandler.kt index 5214b19b..59b1b45e 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CountEventHandler.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/handler/CountEventHandler.kt @@ -1,8 +1,16 @@ -package io.raemian.api.event.model +package io.raemian.api.event.handler +import io.raemian.api.event.model.CheeredEvent +import io.raemian.api.event.model.CreatedCommentEvent +import io.raemian.api.event.model.CreatedGoalEvent +import io.raemian.api.event.model.DeletedCommentEvent +import io.raemian.api.event.model.ReactedEmojiEvent +import io.raemian.api.event.model.RemovedEmojiEvent import io.raemian.api.support.lock.ExclusiveRunner import io.raemian.storage.db.core.cheer.Cheering import io.raemian.storage.db.core.cheer.CheeringRepository +import io.raemian.storage.db.core.comment.CommentCount +import io.raemian.storage.db.core.comment.CommentCountRepository import io.raemian.storage.db.core.emoji.EmojiCount import io.raemian.storage.db.core.emoji.EmojiCountRepository import io.raemian.storage.db.core.emoji.EmojiRepository @@ -20,6 +28,7 @@ class CountEventHandler( private val emojiCountRepository: EmojiCountRepository, private val exclusiveRunner: ExclusiveRunner, private val emojiRepository: EmojiRepository, + private val commentCountRepository: CommentCountRepository, ) { @Transactional @EventListener @@ -69,4 +78,25 @@ class CountEventHandler( } } } + + @Transactional + @EventListener + fun addCommentCount(createdCommentEvent: CreatedCommentEvent) { + exclusiveRunner.call("comment:${createdCommentEvent.goalId}", Duration.ofSeconds(10)) { + val commentCount = commentCountRepository.findByGoalId(createdCommentEvent.goalId) + ?: CommentCount(0, createdCommentEvent.goalId) + + commentCountRepository.save(commentCount.addCount()) + } + } + + @Transactional + @EventListener + fun minusCommentCount(deletedCommentEvent: DeletedCommentEvent) { + val commentCount = commentCountRepository.findByGoalId(deletedCommentEvent.goalId) + + if (commentCount != null && 0 < commentCount.count) { + commentCountRepository.save(commentCount.minusCount()) + } + } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CreatedCommentEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CreatedCommentEvent.kt new file mode 100644 index 00000000..bc348b9e --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CreatedCommentEvent.kt @@ -0,0 +1,5 @@ +package io.raemian.api.event.model + +data class CreatedCommentEvent( + val goalId: Long, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedCommentEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedCommentEvent.kt new file mode 100644 index 00000000..22f8ba91 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedCommentEvent.kt @@ -0,0 +1,5 @@ +package io.raemian.api.event.model + +data class DeletedCommentEvent( + val goalId: Long +) diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt new file mode 100644 index 00000000..6f2d265f --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt @@ -0,0 +1,34 @@ +package io.raemian.storage.db.core.comment + +import io.raemian.storage.db.core.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Index +import jakarta.persistence.Table + +@Entity +@Table(name = "COMMENT_COUNTS", indexes = [Index(name = "IDX_GOAL_ID", columnList = "goalId")]) +class CommentCount( + @Column + var count: Long, + + @Column + val goalId: Long, + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, +): BaseEntity() { + fun addCount(): CommentCount { + this.count += 1 + return this + } + + fun minusCount(): CommentCount { + this.count -= 1 + return this + } +} \ No newline at end of file diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCountRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCountRepository.kt new file mode 100644 index 00000000..57aefc3d --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCountRepository.kt @@ -0,0 +1,9 @@ +package io.raemian.storage.db.core.comment + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface CommentCountRepository : JpaRepository { + fun findByGoalId(goalId: Long): CommentCount? +} \ No newline at end of file diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt index c31ef76c..cb683295 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt @@ -4,5 +4,4 @@ import org.springframework.data.jpa.repository.JpaRepository interface EmojiCountRepository : JpaRepository { fun findByGoalIdAndEmojiId(goalId: Long, emojiId: Long): EmojiCount? - fun findAllByGoalIdIn(ids: List): List } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt index e5e8a310..db3f5b10 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt @@ -5,8 +5,8 @@ import io.raemian.storage.db.core.user.User import org.springframework.data.jpa.repository.JpaRepository interface ReactedEmojiRepository : JpaRepository { - fun findAllByGoal(goal: Goal): List + fun findAllByGoalIdIn(goalIds: List): List fun deleteByEmojiAndGoalAndReactUser(emoji: Emoji, goal: Goal, reactUser: User) diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt index 581eb408..e4e669fd 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt @@ -11,8 +11,6 @@ import java.time.LocalDateTime interface GoalRepository : JpaRepository { fun findUserByCreatedAtGreaterThanEqual(createdAt: LocalDateTime): List - fun countGoalByLifeMap(lifeMap: LifeMap): Int - override fun getById(id: Long): Goal = findById(id).orElseThrow() { NoSuchElementException("목표가 없습니다 $id") } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapCountRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapCountRepository.kt index 67626c90..3e715161 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapCountRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapCountRepository.kt @@ -4,6 +4,4 @@ import org.springframework.data.repository.CrudRepository interface LifeMapCountRepository : CrudRepository { fun findByLifeMapId(lifeMapId: Long): LifeMapCount? - - fun findAllByLifeMapIdIn(lifeMapIds: List): List } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapRepository.kt index 411c20c4..29d8d53c 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapRepository.kt @@ -7,6 +7,4 @@ interface LifeMapRepository : JpaRepository { fun findFirstByUserId(userId: Long): LifeMap? fun findFirstByUserUsername(username: String): LifeMap? - - fun findAllByIdInOrderByIdDesc(ids: List): List } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/UserRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/UserRepository.kt index ded10499..e134584b 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/UserRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/UserRepository.kt @@ -10,8 +10,6 @@ interface UserRepository : JpaRepository { fun findByEmail(email: String): User? - fun existsByEmail(email: String): Boolean - fun findUserByCreatedAtGreaterThanEqual(createdAt: LocalDateTime): List fun findByUsername(username: String): User? From 0ceffde8dd62d564beb3802c71d1c330e7a47d01 Mon Sep 17 00:00:00 2001 From: wkwon Date: Thu, 21 Mar 2024 22:50:07 +0900 Subject: [PATCH 27/60] =?UTF-8?q?feat(#235):=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=ED=83=80=EC=9E=84=EB=9D=BC=EC=9D=B8=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=95=20=EC=A1=B0=ED=9A=8C=20API=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/cheer/service/CheeringService.kt | 4 +- .../api/comment/service/CommentService.kt | 8 +++ .../api/emoji/model/EmojiCountSubset.kt | 2 +- .../api/event/model/DeletedCommentEvent.kt | 2 +- .../api/goal/controller/GoalController.kt | 17 ++++- .../controller/request/TimelinePageRequest.kt | 6 ++ ...untSubset.kt => GoalExploreCountSubset.kt} | 2 +- .../api/goal/model/GoalExploreResult.kt | 6 +- .../api/goal/model/GoalTimelineCountSubset.kt | 12 ++++ .../api/goal/model/GoalTimelinePageResult.kt | 37 ++++++++++ .../api/goal/service/GoalQueryService.kt | 68 +++++++++++++++++++ .../raemian/api/goal/service/GoalService.kt | 4 +- .../raemian/api/task/service/TaskService.kt | 8 +++ .../db/core/cheer/CheerJdbcQueryRepository.kt | 2 +- .../storage/db/core/comment/CommentCount.kt | 4 +- .../db/core/comment/CommentCountRepository.kt | 2 +- .../comment/CommentJdbcQueryRepository.kt | 35 ++++++++++ .../model/GoalCommentCountQueryResult.kt | 6 ++ .../db/core/goal/GoalJdbcQueryRepository.kt | 52 ++++++++++++++ .../storage/db/core/goal/GoalRepository.kt | 1 - .../db/core/goal/model/GoalQueryResult.kt | 18 +++++ .../db/core/task/TaskJdbcQueryRepository.kt | 35 ++++++++++ .../task/model/GoalTaskCountQueryResult.kt | 6 ++ 23 files changed, 320 insertions(+), 17 deletions(-) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt rename backend/application/api/src/main/kotlin/io/raemian/api/goal/model/{GoalCountSubset.kt => GoalExploreCountSubset.kt} (90%) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelineCountSubset.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelinePageResult.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt index 21620b70..4f95f498 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt @@ -88,8 +88,8 @@ class CheeringService( private fun findCheeringSquadWithCursor(lifeMapId: Long, request: CheeringSquadPageRequest): CursorPaginationResult { return CursorPaginationTemplate.execute(lifeMapId, request.cursor ?: Long.MAX_VALUE, request.size) { - id, cursor, pageable -> - cheererJdbcQueryRepository.findAllCheererWithCursor(id, cursor, pageable) + id, cursor, size -> + cheererJdbcQueryRepository.findAllByLifeMapWithCursor(id, cursor, size) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt index 6d1d822d..bbe16af0 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt @@ -8,6 +8,7 @@ import io.raemian.api.event.model.DeletedCommentEvent import io.raemian.api.support.exception.CoreApiException import io.raemian.api.support.exception.ErrorInfo import io.raemian.storage.db.core.comment.Comment +import io.raemian.storage.db.core.comment.CommentJdbcQueryRepository import io.raemian.storage.db.core.comment.CommentRepository import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.goal.GoalRepository @@ -21,6 +22,7 @@ import java.time.LocalDateTime @Service class CommentService( private val commentRepository: CommentRepository, + private val commentJdbcQueryRepository: CommentJdbcQueryRepository, private val goalRepository: GoalRepository, private val userRepository: UserRepository, private val applicationEventPublisher: ApplicationEventPublisher, @@ -73,6 +75,12 @@ class CommentService( applicationEventPublisher.publishEvent(DeletedCommentEvent(comment.goal.id!!)) } + @Transactional(readOnly = true) + fun findGoalCommentCountMap(goalIds: List): Map { + val goalCommentCounts = commentJdbcQueryRepository.findAllGoalCommentCountByGoalIdIn(goalIds) + return goalCommentCounts.associate { it.goalId to it.commentCount } + } + private fun createComment(goal: Goal, currentUser: User, content: String): Comment { return try { Comment(goal, currentUser, content) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/EmojiCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/EmojiCountSubset.kt index 6fbc2dec..0f140f1b 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/EmojiCountSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/EmojiCountSubset.kt @@ -4,6 +4,6 @@ data class EmojiCountSubset( val id: Long, val name: String, val url: String, - val reactCount: Long, + val reactCount: Int, val isMyReaction: Boolean, ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedCommentEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedCommentEvent.kt index 22f8ba91..435bf025 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedCommentEvent.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedCommentEvent.kt @@ -1,5 +1,5 @@ package io.raemian.api.event.model data class DeletedCommentEvent( - val goalId: Long + val goalId: Long, ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt index 53508bc6..0341067e 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt @@ -1,14 +1,17 @@ package io.raemian.api.goal.controller import io.raemian.api.auth.model.CurrentUser -import io.raemian.api.emoji.service.EmojiService import io.raemian.api.goal.controller.request.CreateGoalRequest +import io.raemian.api.goal.controller.request.TimelinePageRequest import io.raemian.api.goal.controller.request.UpdateGoalRequest import io.raemian.api.goal.model.CreateGoalResult import io.raemian.api.goal.model.GoalExplorePageResult import io.raemian.api.goal.model.GoalResult +import io.raemian.api.goal.model.GoalTimelinePageResult +import io.raemian.api.goal.service.GoalQueryService import io.raemian.api.goal.service.GoalService import io.raemian.api.support.response.ApiResponse +import io.raemian.api.support.response.PaginationResult import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity import org.springframework.security.core.annotation.AuthenticationPrincipal @@ -29,7 +32,7 @@ fun String.toUri(): URI = URI.create(this) @RequestMapping("/goal") class GoalController( private val goalService: GoalService, - private val emojiService: EmojiService, + private val goalQueryService: GoalQueryService, ) { @Operation(summary = "목표 단건 조회 API") @@ -87,4 +90,14 @@ class GoalController( return ResponseEntity.ok() .body(ApiResponse.success(GoalExplorePageResult(explore))) } + + @Operation(summary = "목표 타임라인 조회 API") + @GetMapping("/timeline") + fun getTimeline( + @AuthenticationPrincipal currentUser: CurrentUser, + request: TimelinePageRequest, + ): ResponseEntity>> { + val goalTimeline = goalQueryService.getTimeline(currentUser.id, request) + return ResponseEntity.ok(ApiResponse.success(goalTimeline)) + } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt new file mode 100644 index 00000000..d1043e77 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt @@ -0,0 +1,6 @@ +package io.raemian.api.goal.controller.request + +data class TimelinePageRequest( + val cursor: Long?, + val size: Int, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreCountSubset.kt similarity index 90% rename from backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalCountSubset.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreCountSubset.kt index 406d1299..8f9e2007 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalCountSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreCountSubset.kt @@ -2,7 +2,7 @@ package io.raemian.api.goal.model import io.raemian.storage.db.core.cheer.GoalExploreQueryResult -data class GoalCountSubset( +data class GoalExploreCountSubset( val reaction: Long, val comment: Long, val task: Long, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreResult.kt index 7760d5ea..18de9ea8 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreResult.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreResult.kt @@ -8,7 +8,7 @@ import io.raemian.storage.db.core.cheer.GoalExploreQueryResult data class GoalExploreResult( val user: UserSubset, val goal: GoalSubset, - val count: GoalCountSubset, + val count: GoalExploreCountSubset, val emojis: List, ) { companion object { @@ -18,14 +18,14 @@ data class GoalExploreResult( id = it.id ?: -1, url = it.url, name = it.name, - reactCount = it.reactCount.toLong(), + reactCount = it.reactCount, isMyReaction = it.isMyReaction, ) } ?: listOf() return GoalExploreResult( goal = GoalSubset(explore), user = UserSubset(explore), - count = GoalCountSubset(explore), + count = GoalExploreCountSubset(explore), emojis = emojis, ) } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelineCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelineCountSubset.kt new file mode 100644 index 00000000..66e6bd45 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelineCountSubset.kt @@ -0,0 +1,12 @@ +package io.raemian.api.goal.model + +data class GoalTimelineCountSubset( + val comment: Int, + val task: Int, +) { + companion object { + fun of(commentCount: Int, taskCount: Int): GoalTimelineCountSubset { + return GoalTimelineCountSubset(commentCount, taskCount) + } + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelinePageResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelinePageResult.kt new file mode 100644 index 00000000..a07df01b --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelinePageResult.kt @@ -0,0 +1,37 @@ +package io.raemian.api.goal.model + +import io.raemian.api.emoji.model.EmojiCountSubset +import io.raemian.api.emoji.model.ReactedEmojisResult +import io.raemian.storage.db.core.goal.model.GoalQueryResult + +data class GoalTimelinePageResult( + val goal: GoalQueryResult, + val count: GoalTimelineCountSubset?, + val emojis: List, +) { + companion object { + fun from(goal: GoalQueryResult, count: GoalTimelineCountSubset?, reactedEmojisResult: ReactedEmojisResult?): GoalTimelinePageResult { + if (reactedEmojisResult == null) { + return GoalTimelinePageResult( + goal, + count, + listOf(), + ) + } + + return GoalTimelinePageResult( + goal, + count, + reactedEmojisResult.reactedEmojis.map { + EmojiCountSubset( + id = it.id ?: -1, + name = it.name, + url = it.url, + reactCount = it.reactCount, + isMyReaction = it.isMyReaction, + ) + }, + ) + } + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt new file mode 100644 index 00000000..0257ad7a --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt @@ -0,0 +1,68 @@ +package io.raemian.api.goal.service + +import io.raemian.api.comment.service.CommentService +import io.raemian.api.emoji.service.EmojiService +import io.raemian.api.goal.controller.request.TimelinePageRequest +import io.raemian.api.goal.model.GoalTimelineCountSubset +import io.raemian.api.goal.model.GoalTimelinePageResult +import io.raemian.api.lifemap.service.LifeMapService +import io.raemian.api.support.response.PaginationResult +import io.raemian.api.task.service.TaskService +import io.raemian.storage.db.core.common.pagination.CursorPaginationResult +import io.raemian.storage.db.core.common.pagination.CursorPaginationTemplate +import io.raemian.storage.db.core.goal.GoalJdbcQueryRepository +import io.raemian.storage.db.core.goal.model.GoalQueryResult +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class GoalQueryService( + private val lifeMapService: LifeMapService, + private val goalJdbcQueryRepository: GoalJdbcQueryRepository, + private val emojiService: EmojiService, + private val taskService: TaskService, + private val commentService: CommentService, +) { + @Transactional(readOnly = true) + fun getTimeline(userId: Long, request: TimelinePageRequest): PaginationResult { + val lifeMap = lifeMapService.findFirstByUserId(userId) + + val goals = findAllGoalWithCursor(lifeMap.lifeMapId, request) + + val goalIds = goals.contents.map { it.goalId } + + val goalTimelineCountMap = findGoalTimelineCountMap(goalIds) + val reactedEmojiMap = emojiService.findAllByGoalIds(goalIds, userId) + + return PaginationResult.from( + lifeMap.goals.size.toLong(), + goals.transform() { + goal -> + GoalTimelinePageResult.from( + goal, + goalTimelineCountMap[goal.goalId], + reactedEmojiMap[goal.goalId], + ) + }, + ) + } + + private fun findGoalTimelineCountMap(goalIds: List): Map { + val commentCountMap = commentService.findGoalCommentCountMap(goalIds) + val taskCountMap = taskService.findGoalTaskCountMap(goalIds) + + return goalIds.associate { + it to GoalTimelineCountSubset.of( + commentCountMap.get(1L) ?: 0, + taskCountMap.get(1L) ?: 0, + ) + } + } + + private fun findAllGoalWithCursor(lifeMapId: Long, request: TimelinePageRequest): CursorPaginationResult { + return CursorPaginationTemplate.execute(lifeMapId, request.cursor ?: Long.MAX_VALUE, request.size) { + id, cursor, size -> + goalJdbcQueryRepository.findAllByLifeMapWithCursor(id, cursor, size) + } + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt index 51280602..af179470 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt @@ -24,13 +24,13 @@ import java.time.LocalDateTime @Service class GoalService( - private val stickerService: StickerService, - private val tagService: TagService, private val userRepository: UserRepository, private val goalRepository: GoalRepository, private val lifeMapRepository: LifeMapRepository, private val applicationEventPublisher: ApplicationEventPublisher, private val emojiService: EmojiService, + private val stickerService: StickerService, + private val tagService: TagService, ) { @Transactional(readOnly = true) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/task/service/TaskService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/task/service/TaskService.kt index fa995a0d..be1d6bcd 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/task/service/TaskService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/task/service/TaskService.kt @@ -8,6 +8,7 @@ import io.raemian.api.task.model.CreateTaskResult import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.task.Task +import io.raemian.storage.db.core.task.TaskJdbcQueryRepository import io.raemian.storage.db.core.task.TaskRepository import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -15,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional @Service class TaskService( val taskRepository: TaskRepository, + val taskJdbcQueryRepository: TaskJdbcQueryRepository, val goalRepository: GoalRepository, ) { @@ -60,6 +62,12 @@ class TaskService( taskRepository.delete(task) } + @Transactional(readOnly = true) + fun findGoalTaskCountMap(goalIds: List): Map { + val goalTaskCounts = taskJdbcQueryRepository.findAllGoalTaskCountByGoalIdIn(goalIds) + return goalTaskCounts.associate { it.goalId to it.taskCount } + } + private fun validateCurrentUserIsGoalOwner(currentUserId: Long, goal: Goal) { if (currentUserId != goal.lifeMap.user.id) { throw SecurityException() diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt index 29f0b8a8..1c0522ff 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt @@ -10,7 +10,7 @@ import java.time.LocalDateTime class CheerJdbcQueryRepository( private val jdbcTemplate: NamedParameterJdbcTemplate, ) { - fun findAllCheererWithCursor(lifeMapId: Long, cursor: Long, size: Int): List { + fun findAllByLifeMapWithCursor(lifeMapId: Long, cursor: Long, size: Int): List { val sql = """ SELECT diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt index 6f2d265f..f67e50f8 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt @@ -21,7 +21,7 @@ class CommentCount( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null, -): BaseEntity() { +) : BaseEntity() { fun addCount(): CommentCount { this.count += 1 return this @@ -31,4 +31,4 @@ class CommentCount( this.count -= 1 return this } -} \ No newline at end of file +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCountRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCountRepository.kt index 57aefc3d..969ce944 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCountRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCountRepository.kt @@ -6,4 +6,4 @@ import org.springframework.stereotype.Repository @Repository interface CommentCountRepository : JpaRepository { fun findByGoalId(goalId: Long): CommentCount? -} \ No newline at end of file +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt new file mode 100644 index 00000000..1174119f --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt @@ -0,0 +1,35 @@ +package io.raemian.storage.db.core.comment + +import io.raemian.storage.db.core.comment.model.GoalCommentCountQueryResult +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate +import org.springframework.stereotype.Repository + +@Repository +class CommentJdbcQueryRepository( + private val jdbcTemplate: NamedParameterJdbcTemplate, +) { + fun findAllGoalCommentCountByGoalIdIn(goalIds: List): List { + val sql = + """ + SELECT + c.GOAL_ID AS GOAL_ID, + COUNT(GOAL_ID) AS COMMENT_COUNT + FROM comment_counts c + WHERE 1 = 1 + AND c.GOAL_ID IN (:goalIds) + GROUP BY c.GOAL_ID + """.trimIndent() + + val namedParameter = MapSqlParameterSource() + .addValue("goalIds", goalIds) + + return jdbcTemplate.query(sql, namedParameter) { + rs, rowNum -> + GoalCommentCountQueryResult( + goalId = rs.getLong("GOAL_ID"), + commentCount = rs.getInt("COMMENT_COUNT"), + ) + } + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt new file mode 100644 index 00000000..7ba1d80a --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt @@ -0,0 +1,6 @@ +package io.raemian.storage.db.core.comment.model + +data class GoalCommentCountQueryResult( + val goalId: Long, + val commentCount: Int, +) diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt new file mode 100644 index 00000000..5054c18e --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt @@ -0,0 +1,52 @@ +package io.raemian.storage.db.core.goal + +import io.raemian.storage.db.core.goal.model.GoalQueryResult +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate +import org.springframework.stereotype.Repository +import java.time.LocalDateTime + +@Repository +class GoalJdbcQueryRepository( + private val jdbcTemplate: NamedParameterJdbcTemplate, +) { + fun findAllByLifeMapWithCursor(lifeMapId: Long, cursor: Long, size: Int): List { + val sql = + """ + SELECT + g.ID AS GOAL_ID, + g.TITLE AS TITLE, + g.DESCRIPTION AS DESCRIPTION, + g.DEADLINE AS DEADLINE, + s.URL AS STICKER_URL, + t.CONTENT AS TAG, + g.CREATED_AT AS CREATED_AT + FROM goals g + LEFT OUTER JOIN tags t ON g.TAG_ID = t.ID + LEFT OUTER JOIN stickers s ON g.STICKER_ID = s.ID + WHERE 1 = 1 + AND g.LIFE_MAP_ID = :lifeMapId + AND g.ID <= :cursor + ORDER BY g.ID DESC + LIMIT :size + """.trimIndent() + + val namedParameter = MapSqlParameterSource() + .addValue("lifeMapId", lifeMapId) + .addValue("cursor", cursor) + .addValue("size", size) + + return jdbcTemplate.query(sql, namedParameter) { + rs, rowNum -> + GoalQueryResult( + goalId = rs.getLong("GOAL_ID"), + title = rs.getString("TITLE"), + description = rs.getString("DESCRIPTION"), + deadline = rs.getObject("DEADLINE", LocalDateTime::class.java), + stickerUrl = rs.getString("STICKER_URL"), + tag = rs.getString("TAG"), + createdAt = rs.getObject("CREATED_AT", LocalDateTime::class.java), + ) + } + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt index e4e669fd..c1d71eb1 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt @@ -1,7 +1,6 @@ package io.raemian.storage.db.core.goal import io.raemian.storage.db.core.cheer.GoalExploreQueryResult -import io.raemian.storage.db.core.lifemap.LifeMap import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt new file mode 100644 index 00000000..c5f0a788 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt @@ -0,0 +1,18 @@ +package io.raemian.storage.db.core.goal.model + +import io.raemian.storage.db.core.common.pagination.CursorExtractable +import java.time.LocalDateTime + +data class GoalQueryResult( + val goalId: Long, + val title: String, + val description: String, + val deadline: LocalDateTime, + val stickerUrl: String, + val tag: String, + val createdAt: LocalDateTime, +) : CursorExtractable { + override fun getCursorId(): Long { + return goalId + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt new file mode 100644 index 00000000..1c9a6458 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt @@ -0,0 +1,35 @@ +package io.raemian.storage.db.core.task + +import io.raemian.storage.db.core.task.model.GoalTaskCountQueryResult +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate +import org.springframework.stereotype.Repository + +@Repository +class TaskJdbcQueryRepository( + private val jdbcTemplate: NamedParameterJdbcTemplate, +) { + fun findAllGoalTaskCountByGoalIdIn(goalIds: List): List { + val sql = + """ + SELECT + t.GOAL_ID AS GOAL_ID, + COUNT(t.GOAL_ID) AS TASK_COUNT + FROM tasks t + WHERE 1 = 1 + AND t.GOAL_ID IN (:goalIds) + GROUP BY t.GOAL_ID + """.trimIndent() + + val namedParameter = MapSqlParameterSource() + .addValue("goalIds", goalIds) + + return jdbcTemplate.query(sql, namedParameter) { + rs, rowNum -> + GoalTaskCountQueryResult( + goalId = rs.getLong("GOAL_ID"), + taskCount = rs.getInt("TASK_COUNT"), + ) + } + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt new file mode 100644 index 00000000..f473cf69 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt @@ -0,0 +1,6 @@ +package io.raemian.storage.db.core.task.model + +data class GoalTaskCountQueryResult( + val goalId: Long, + val taskCount: Int, +) From b9a62b23d66c181abee5414c0a6f844c62fcaefd Mon Sep 17 00:00:00 2001 From: wkwon Date: Thu, 21 Mar 2024 22:53:02 +0900 Subject: [PATCH 28/60] =?UTF-8?q?fix:=20dev=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20callback=20=EC=8B=9C=20=20redirec?= =?UTF-8?q?t=20uri=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/io/raemian/api/config/WebSecurityConfig.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt index 7f337e68..7909c336 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt @@ -78,7 +78,9 @@ class WebSecurityConfig( log.info("x-token access ${tokenDTO.accessToken}") // TODO edit redirect url val redirectUrl = - if (profile == "live") "https://bandiboodi.com/oauth2/token" else "http://localhost:3000/oauth2/token" + if (profile == "live") "https://bandiboodi.com/oauth2/token" + else if (profile == "dev") "https://dev-bandiboodi.vercel.app/oauth/token" + else "http://localhost:3000/oauth2/token" response.sendRedirect("$redirectUrl?token=${tokenDTO.accessToken}&refresh=${tokenDTO.refreshToken}") } it.failureHandler { request, response, exception -> From 0d95f90d826311f807da1600e6f38d1956e10445 Mon Sep 17 00:00:00 2001 From: wkwon Date: Thu, 21 Mar 2024 23:04:03 +0900 Subject: [PATCH 29/60] chore: apply lint --- .../kotlin/io/raemian/api/config/WebSecurityConfig.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt index 7909c336..40209532 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt @@ -78,9 +78,13 @@ class WebSecurityConfig( log.info("x-token access ${tokenDTO.accessToken}") // TODO edit redirect url val redirectUrl = - if (profile == "live") "https://bandiboodi.com/oauth2/token" - else if (profile == "dev") "https://dev-bandiboodi.vercel.app/oauth/token" - else "http://localhost:3000/oauth2/token" + if (profile == "live") { + "https://bandiboodi.com/oauth2/token" + } else if (profile == "dev") { + "https://dev-bandiboodi.vercel.app/oauth/token" + } else { + "http://localhost:3000/oauth2/token" + } response.sendRedirect("$redirectUrl?token=${tokenDTO.accessToken}&refresh=${tokenDTO.refreshToken}") } it.failureHandler { request, response, exception -> From 0c95f367b7f7a5d2b29dbb2b52d7f4996bc61f88 Mon Sep 17 00:00:00 2001 From: wkwon Date: Thu, 21 Mar 2024 23:19:20 +0900 Subject: [PATCH 30/60] =?UTF-8?q?refactor:=20CheererRepository=20=EB=AF=B8?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EB=A9=94=EC=84=9C=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 --- .../io/raemian/storage/db/core/cheer/CheererRepository.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheererRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheererRepository.kt index d0625447..87b31b92 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheererRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheererRepository.kt @@ -1,9 +1,5 @@ package io.raemian.storage.db.core.cheer -import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository -interface CheererRepository : JpaRepository { - - fun findAllByLifeMapIdAndIdLessThanOrderByIdDesc(lifeMapId: Long, id: Long, pageable: Pageable): List -} +interface CheererRepository : JpaRepository From 8efdf1686e2754d614fa7515daf42d4b094a5c1a Mon Sep 17 00:00:00 2001 From: wkwon Date: Thu, 21 Mar 2024 23:27:27 +0900 Subject: [PATCH 31/60] =?UTF-8?q?refactor:=20TriFunction=20@FunctionalInte?= =?UTF-8?q?rface=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/raemian/storage/db/core/common/pagination/TriFunction.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/TriFunction.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/TriFunction.kt index 64cfaed1..14198cc8 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/TriFunction.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/TriFunction.kt @@ -1,5 +1,6 @@ package io.raemian.storage.db.core.common.pagination +@FunctionalInterface fun interface TriFunction { fun apply(t: T, u: U, v: V): R } From a1cce96b3bb09e40c49a9df08e1b421991ed401a Mon Sep 17 00:00:00 2001 From: wkwon Date: Fri, 22 Mar 2024 18:11:34 +0900 Subject: [PATCH 32/60] =?UTF-8?q?refactor(#235):=20PR=20=EC=BD=94=EB=A9=98?= =?UTF-8?q?=ED=8A=B8=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/comment/service/CommentService.kt | 7 +++---- .../api/event/handler/CountEventHandler.kt | 10 ++++------ .../api/goal/model/GoalTimelinePageResult.kt | 16 ++++++++-------- .../raemian/api/goal/service/GoalQueryService.kt | 8 ++++---- .../api/support/response/PaginationResult.kt | 8 ++++---- .../io/raemian/api/task/service/TaskService.kt | 7 +++---- .../db/core/cheer/model/CheererQueryResult.kt | 4 +--- .../storage/db/core/comment/CommentCount.kt | 4 +++- .../core/comment/CommentJdbcQueryRepository.kt | 2 +- .../comment/model/GoalCommentCountQueryResult.kt | 2 +- .../raemian/storage/db/core/emoji/EmojiCount.kt | 4 +++- .../db/core/goal/model/GoalQueryResult.kt | 4 +--- .../db/core/task/TaskJdbcQueryRepository.kt | 2 +- .../core/task/model/GoalTaskCountQueryResult.kt | 2 +- 14 files changed, 38 insertions(+), 42 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt index 90ba2c7d..a111adcd 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt @@ -10,6 +10,7 @@ import io.raemian.api.support.exception.ErrorInfo import io.raemian.storage.db.core.comment.Comment import io.raemian.storage.db.core.comment.CommentJdbcQueryRepository import io.raemian.storage.db.core.comment.CommentRepository +import io.raemian.storage.db.core.comment.model.GoalCommentCountQueryResult import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.user.User @@ -76,10 +77,8 @@ class CommentService( } @Transactional(readOnly = true) - fun findGoalCommentCountMap(goalIds: List): Map { - val goalCommentCounts = commentJdbcQueryRepository.findAllGoalCommentCountByGoalIdIn(goalIds) - return goalCommentCounts.associate { it.goalId to it.commentCount } - } + fun findGoalCommentCounts(goalIds: List): List = + commentJdbcQueryRepository.findAllGoalCommentCountByGoalIdIn(goalIds) private fun createComment(goal: Goal, currentUser: User, content: String): Comment { return try { diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/handler/CountEventHandler.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/handler/CountEventHandler.kt index 59b1b45e..743850f8 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/handler/CountEventHandler.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/handler/CountEventHandler.kt @@ -72,10 +72,9 @@ class CountEventHandler( fun minusEmojiCount(removeEmojiEvent: RemovedEmojiEvent) { exclusiveRunner.call("emoji:${removeEmojiEvent.goalId}:${removeEmojiEvent.emojiId}", Duration.ofSeconds(10)) { val emojiCount = emojiCountRepository.findByGoalIdAndEmojiId(removeEmojiEvent.goalId, removeEmojiEvent.emojiId) + ?: EmojiCount(count = 0, goalId = removeEmojiEvent.goalId, emoji = emojiRepository.getReferenceById(removeEmojiEvent.emojiId)) - if (emojiCount != null && 0 < emojiCount.count) { - emojiCountRepository.save(emojiCount.minusCount()) - } + emojiCountRepository.save(emojiCount.minusCount()) } } @@ -94,9 +93,8 @@ class CountEventHandler( @EventListener fun minusCommentCount(deletedCommentEvent: DeletedCommentEvent) { val commentCount = commentCountRepository.findByGoalId(deletedCommentEvent.goalId) + ?: CommentCount(count = 0, goalId = deletedCommentEvent.goalId) - if (commentCount != null && 0 < commentCount.count) { - commentCountRepository.save(commentCount.minusCount()) - } + commentCountRepository.save(commentCount.minusCount()) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelinePageResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelinePageResult.kt index a07df01b..8e6d0cf3 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelinePageResult.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelinePageResult.kt @@ -6,23 +6,23 @@ import io.raemian.storage.db.core.goal.model.GoalQueryResult data class GoalTimelinePageResult( val goal: GoalQueryResult, - val count: GoalTimelineCountSubset?, + val counts: GoalTimelineCountSubset?, val emojis: List, ) { companion object { - fun from(goal: GoalQueryResult, count: GoalTimelineCountSubset?, reactedEmojisResult: ReactedEmojisResult?): GoalTimelinePageResult { + fun from(goal: GoalQueryResult, counts: GoalTimelineCountSubset?, reactedEmojisResult: ReactedEmojisResult?): GoalTimelinePageResult { if (reactedEmojisResult == null) { return GoalTimelinePageResult( - goal, - count, - listOf(), + goal = goal, + counts = counts, + emojis = emptyList(), ) } return GoalTimelinePageResult( - goal, - count, - reactedEmojisResult.reactedEmojis.map { + goal = goal, + counts = counts, + emojis = reactedEmojisResult.reactedEmojis.map { EmojiCountSubset( id = it.id ?: -1, name = it.name, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt index 0257ad7a..6aafa6bc 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt @@ -48,13 +48,13 @@ class GoalQueryService( } private fun findGoalTimelineCountMap(goalIds: List): Map { - val commentCountMap = commentService.findGoalCommentCountMap(goalIds) - val taskCountMap = taskService.findGoalTaskCountMap(goalIds) + val commentCountMap = commentService.findGoalCommentCounts(goalIds).associate { it.goalId to it.commentCounts } + val taskCountMap = taskService.findGoalTaskCounts(goalIds).associate { it.goalId to it.taskCounts } return goalIds.associate { it to GoalTimelineCountSubset.of( - commentCountMap.get(1L) ?: 0, - taskCountMap.get(1L) ?: 0, + commentCountMap[it] ?: 0, + taskCountMap[it] ?: 0, ) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt index 09d30059..e3ce480a 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt @@ -11,10 +11,10 @@ data class PaginationResult( companion object { fun from(total: Long, result: CursorPaginationResult): PaginationResult { return PaginationResult( - total, - result.contents, - result.isLast, - result.nextCursor, + total = total, + contents = result.contents, + isLast = result.isLast, + nextCursor = result.nextCursor, ) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/task/service/TaskService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/task/service/TaskService.kt index be1d6bcd..923e9c20 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/task/service/TaskService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/task/service/TaskService.kt @@ -10,6 +10,7 @@ import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.task.Task import io.raemian.storage.db.core.task.TaskJdbcQueryRepository import io.raemian.storage.db.core.task.TaskRepository +import io.raemian.storage.db.core.task.model.GoalTaskCountQueryResult import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -63,10 +64,8 @@ class TaskService( } @Transactional(readOnly = true) - fun findGoalTaskCountMap(goalIds: List): Map { - val goalTaskCounts = taskJdbcQueryRepository.findAllGoalTaskCountByGoalIdIn(goalIds) - return goalTaskCounts.associate { it.goalId to it.taskCount } - } + fun findGoalTaskCounts(goalIds: List): List = + taskJdbcQueryRepository.findAllGoalTaskCountByGoalIdIn(goalIds) private fun validateCurrentUserIsGoalOwner(currentUserId: Long, goal: Goal) { if (currentUserId != goal.lifeMap.user.id) { diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt index 00da471a..4075b6d9 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt @@ -11,7 +11,5 @@ data class CheererQueryResult( val userImageUrl: String?, val cheeringAt: LocalDateTime, ) : CursorExtractable { - override fun getCursorId(): Long { - return cheererId - } + override fun getCursorId(): Long = cheererId } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt index f67e50f8..0922f53b 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt @@ -28,7 +28,9 @@ class CommentCount( } fun minusCount(): CommentCount { - this.count -= 1 + if (0 < this.count) { + this.count -= 1 + } return this } } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt index 1174119f..cc553415 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt @@ -28,7 +28,7 @@ class CommentJdbcQueryRepository( rs, rowNum -> GoalCommentCountQueryResult( goalId = rs.getLong("GOAL_ID"), - commentCount = rs.getInt("COMMENT_COUNT"), + commentCounts = rs.getInt("COMMENT_COUNT"), ) } } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt index 7ba1d80a..786d6c83 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt @@ -2,5 +2,5 @@ package io.raemian.storage.db.core.comment.model data class GoalCommentCountQueryResult( val goalId: Long, - val commentCount: Int, + val commentCounts: Int, ) diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt index 0a7c1498..adb048c8 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt @@ -34,7 +34,9 @@ class EmojiCount( } fun minusCount(): EmojiCount { - this.count -= 1 + if (0 < this.count) { + this.count -= 1 + } return this } } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt index c5f0a788..7c525467 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt @@ -12,7 +12,5 @@ data class GoalQueryResult( val tag: String, val createdAt: LocalDateTime, ) : CursorExtractable { - override fun getCursorId(): Long { - return goalId - } + override fun getCursorId(): Long = goalId } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt index 1c9a6458..3e017374 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt @@ -28,7 +28,7 @@ class TaskJdbcQueryRepository( rs, rowNum -> GoalTaskCountQueryResult( goalId = rs.getLong("GOAL_ID"), - taskCount = rs.getInt("TASK_COUNT"), + taskCounts = rs.getInt("TASK_COUNT"), ) } } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt index f473cf69..3a79387c 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt @@ -2,5 +2,5 @@ package io.raemian.storage.db.core.task.model data class GoalTaskCountQueryResult( val goalId: Long, - val taskCount: Int, + val taskCounts: Int, ) From 27590d612a7999a4d4364904bfde2b215b611713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=9A=B0=EC=84=9D=20=28Woosuk=20Kwon=29?= Date: Fri, 22 Mar 2024 18:14:52 +0900 Subject: [PATCH 33/60] =?UTF-8?q?[=F0=9F=8C=8E=20Feature]=20=ED=83=80?= =?UTF-8?q?=EC=9E=84=20=EB=9D=BC=EC=9D=B8=EC=9A=A9=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EA=B0=9C=EB=B0=9C=20(#240)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(#235): pagination 공통화 + cheering squad 공통 pagination 적용 * feat: pagination 공통 response 추가 + cheering squad 조회를 위한 queryRepository 추가 * feat(#235): comment count 엔티티 추가 + comment create/delete event 생성 * feat(#235): 목표 타임라인 페이징 조회 API 작업 * fix: dev 환경 로그인 callback 시 redirect uri 변경 * chore: apply lint * refactor: CheererRepository 미사용 메서드 제거 * refactor: TriFunction @FunctionalInterface 적용 * refactor(#235): PR 코멘트 반영 --- .../cheer/controller/CheeringController.kt | 16 ++--- .../request/CheeringSquadPageRequest.kt | 6 ++ .../request/CheeringSquadPagingRequest.kt | 8 --- .../raemian/api/cheer/model/CheererResult.kt | 22 ------ .../api/cheer/service/CheeringService.kt | 39 ++++------- .../api/comment/service/CommentService.kt | 13 ++++ .../raemian/api/config/WebSecurityConfig.kt | 8 ++- .../api/emoji/model/EmojiCountSubset.kt | 2 +- .../{model => handler}/CountEventHandler.kt | 36 ++++++++-- .../api/event/model/CreatedCommentEvent.kt | 5 ++ .../api/event/model/DeletedCommentEvent.kt | 5 ++ .../api/goal/controller/GoalController.kt | 17 ++++- .../controller/request/TimelinePageRequest.kt | 6 ++ ...untSubset.kt => GoalExploreCountSubset.kt} | 4 +- .../api/goal/model/GoalExploreResult.kt | 8 +-- .../io/raemian/api/goal/model/GoalSubset.kt | 2 +- .../api/goal/model/GoalTimelineCountSubset.kt | 12 ++++ .../api/goal/model/GoalTimelinePageResult.kt | 37 ++++++++++ .../api/goal/service/GoalQueryService.kt | 68 +++++++++++++++++++ .../raemian/api/goal/service/GoalService.kt | 4 +- .../api/support/response/PageResult.kt | 13 ---- .../api/support/response/PaginationResult.kt | 21 ++++++ .../raemian/api/task/service/TaskService.kt | 7 ++ .../io/raemian/api/user/model/UserSubset.kt | 2 +- .../db/core/cheer/CheerJdbcQueryRepository.kt | 49 +++++++++++++ .../raemian/storage/db/core/cheer/Cheerer.kt | 4 +- .../db/core/cheer/CheererRepository.kt | 11 +-- .../raemian/storage/db/core/cheer/Cheering.kt | 2 +- .../db/core/cheer/model/CheererQueryResult.kt | 15 ++++ .../storage/db/core/comment/Comment.kt | 2 +- .../storage/db/core/comment/CommentCount.kt | 36 ++++++++++ .../db/core/comment/CommentCountRepository.kt | 9 +++ .../comment/CommentJdbcQueryRepository.kt | 35 ++++++++++ .../model/GoalCommentCountQueryResult.kt | 6 ++ .../db/core/{ => common}/BaseEntity.kt | 2 +- .../common/pagination/CursorExtractable.kt | 5 ++ .../pagination/CursorPaginationResult.kt | 17 +++++ .../pagination/CursorPaginationTemplate.kt | 22 ++++++ .../db/core/common/pagination/TriFunction.kt | 6 ++ .../io/raemian/storage/db/core/emoji/Emoji.kt | 2 +- .../storage/db/core/emoji/EmojiCount.kt | 6 +- .../db/core/emoji/EmojiCountRepository.kt | 1 - .../storage/db/core/emoji/ReactedEmoji.kt | 2 +- .../db/core/emoji/ReactedEmojiRepository.kt | 2 +- .../io/raemian/storage/db/core/goal/Goal.kt | 2 +- .../db/core/goal/GoalJdbcQueryRepository.kt | 52 ++++++++++++++ .../storage/db/core/goal/GoalRepository.kt | 7 +- .../core/goal/model/GoalExploreQueryResult.kt | 2 +- .../db/core/goal/model/GoalQueryResult.kt | 16 +++++ .../storage/db/core/lifemap/LifeMap.kt | 2 +- .../db/core/lifemap/LifeMapCountRepository.kt | 2 - .../db/core/lifemap/LifeMapRepository.kt | 2 - .../storage/db/core/profile/DefaultProfile.kt | 2 +- .../storage/db/core/sticker/Sticker.kt | 2 +- .../io/raemian/storage/db/core/tag/Tag.kt | 2 +- .../io/raemian/storage/db/core/task/Task.kt | 2 +- .../db/core/task/TaskJdbcQueryRepository.kt | 35 ++++++++++ .../task/model/GoalTaskCountQueryResult.kt | 6 ++ .../io/raemian/storage/db/core/user/User.kt | 2 +- .../storage/db/core/user/UserRepository.kt | 2 - 60 files changed, 600 insertions(+), 133 deletions(-) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPageRequest.kt delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPagingRequest.kt delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt rename backend/application/api/src/main/kotlin/io/raemian/api/event/{model => handler}/CountEventHandler.kt (65%) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/event/model/CreatedCommentEvent.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedCommentEvent.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt rename backend/application/api/src/main/kotlin/io/raemian/api/goal/model/{GoalCountSubset.kt => GoalExploreCountSubset.kt} (74%) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelineCountSubset.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelinePageResult.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt delete mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/response/PageResult.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCountRepository.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt rename backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/{ => common}/BaseEntity.kt (91%) create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationResult.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/TriFunction.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt index 354d5819..221b74c7 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt @@ -1,12 +1,12 @@ package io.raemian.api.cheer.controller import io.raemian.api.cheer.controller.request.CheeringRequest -import io.raemian.api.cheer.controller.request.CheeringSquadPagingRequest -import io.raemian.api.cheer.model.CheererResult +import io.raemian.api.cheer.controller.request.CheeringSquadPageRequest import io.raemian.api.cheer.model.CheeringCountResult import io.raemian.api.cheer.service.CheeringService import io.raemian.api.support.response.ApiResponse -import io.raemian.api.support.response.PageResult +import io.raemian.api.support.response.PaginationResult +import io.raemian.storage.db.core.cheer.model.CheererQueryResult import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -22,11 +22,11 @@ class CheeringController( ) { @GetMapping("/squad/{lifeMapId}") - fun findCheeringSquad(@PathVariable("lifeMapId") lifeMapId: Long, request: CheeringSquadPagingRequest): ResponseEntity>> { - val response = cheeringService.findCheeringSquad(lifeMapId, request) - - return ResponseEntity.ok().body(ApiResponse.success(response)) - } + fun findCheeringSquad( + @PathVariable("lifeMapId") lifeMapId: Long, + request: CheeringSquadPageRequest, + ): ResponseEntity>> = + ResponseEntity.ok().body(ApiResponse.success(cheeringService.findCheeringSquad(lifeMapId, request))) @GetMapping("/count/{userName}") fun getCheeringCount(@PathVariable("userName") userName: String): ResponseEntity> = diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPageRequest.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPageRequest.kt new file mode 100644 index 00000000..b94ee41f --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPageRequest.kt @@ -0,0 +1,6 @@ +package io.raemian.api.cheer.controller.request + +data class CheeringSquadPageRequest( + val size: Int, + val cursor: Long?, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPagingRequest.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPagingRequest.kt deleted file mode 100644 index f033e9e4..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/request/CheeringSquadPagingRequest.kt +++ /dev/null @@ -1,8 +0,0 @@ -package io.raemian.api.cheer.controller.request - -import java.time.LocalDateTime - -data class CheeringSquadPagingRequest( - val pageSize: Int, - val lastCursorAt: LocalDateTime?, -) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt deleted file mode 100644 index d022a05d..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt +++ /dev/null @@ -1,22 +0,0 @@ -package io.raemian.api.cheer.model - -import io.raemian.storage.db.core.cheer.Cheerer -import java.time.LocalDateTime - -data class CheererResult( - val userId: Long, - val userName: String, - val userNickName: String, - val userImageUrl: String, - val cheeringAt: LocalDateTime?, -) { - companion object { - fun from(cheerer: Cheerer): CheererResult { - return if (cheerer.user == null) { - CheererResult(-1, "", "", "", cheerer.cheeringAt) - } else { - CheererResult(cheerer.user!!.id!!, cheerer.user!!.username!!, cheerer.user!!.nickname!!, cheerer.user!!.image, cheerer.cheeringAt) - } - } - } -} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt index 4647b521..4f95f498 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt @@ -1,33 +1,36 @@ package io.raemian.api.cheer.service import io.raemian.api.cheer.controller.request.CheeringRequest -import io.raemian.api.cheer.controller.request.CheeringSquadPagingRequest -import io.raemian.api.cheer.model.CheererResult +import io.raemian.api.cheer.controller.request.CheeringSquadPageRequest import io.raemian.api.cheer.model.CheeringCountResult import io.raemian.api.event.model.CheeredEvent import io.raemian.api.support.exception.CoreApiException import io.raemian.api.support.exception.ErrorInfo import io.raemian.api.support.limiter.CheeringLimiter -import io.raemian.api.support.response.PageResult +import io.raemian.api.support.response.PaginationResult +import io.raemian.storage.db.core.cheer.CheerJdbcQueryRepository import io.raemian.storage.db.core.cheer.Cheerer import io.raemian.storage.db.core.cheer.CheererRepository import io.raemian.storage.db.core.cheer.Cheering import io.raemian.storage.db.core.cheer.CheeringRepository +import io.raemian.storage.db.core.cheer.model.CheererQueryResult +import io.raemian.storage.db.core.common.pagination.CursorPaginationResult +import io.raemian.storage.db.core.common.pagination.CursorPaginationTemplate import io.raemian.storage.db.core.lifemap.LifeMapRepository import io.raemian.storage.db.core.user.UserRepository import org.springframework.context.ApplicationEventPublisher -import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.time.LocalDateTime @Service class CheeringService( + private val cheeringLimiter: CheeringLimiter, + private val cheererJdbcQueryRepository: CheerJdbcQueryRepository, private val cheererRepository: CheererRepository, private val cheeringRepository: CheeringRepository, private val lifeMapRepository: LifeMapRepository, private val userRepository: UserRepository, - private val cheeringLimiter: CheeringLimiter, private val applicationEventPublisher: ApplicationEventPublisher, ) { @Transactional @@ -42,16 +45,13 @@ class CheeringService( } @Transactional(readOnly = true) - fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPagingRequest): PageResult { + fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPageRequest): PaginationResult { val cheering = cheeringRepository.findByLifeMapId(lifeMapId) ?: Cheering(0, lifeMapId) - val cheeringSquad = - findCheeringSquadPage(lifeMapId, request.lastCursorAt, Pageable.ofSize(request.pageSize)) - - val isLastPage = isLastPage(cheeringSquad.size, request.pageSize, lifeMapId, cheeringSquad) + val cheeringSquad = findCheeringSquadWithCursor(lifeMapId, request) - return PageResult.of(cheering.count, cheeringSquad.map(CheererResult::from), isLastPage) + return PaginationResult.from(cheering.count, cheeringSquad) } @Transactional(readOnly = true) @@ -86,19 +86,10 @@ class CheeringService( ) } - private fun isLastPage(contentSize: Int, pageSize: Int, lifeMapId: Long, cheeringSquad: List): Boolean { - return if (contentSize < pageSize) { - true - } else { - !cheererRepository.existsByLifeMapIdAndCheeringAtLessThanOrderByCheeringAtDesc(lifeMapId, cheeringSquad.last().cheeringAt) - } - } - - private fun findCheeringSquadPage(lifeMapId: Long, cheeringAt: LocalDateTime?, pageable: Pageable): List { - return if (cheeringAt == null) { - cheererRepository.findByLifeMapIdOrderByCheeringAtDesc(lifeMapId, pageable) - } else { - cheererRepository.findByLifeMapIdAndCheeringAtLessThanOrderByCheeringAtDesc(lifeMapId, cheeringAt, pageable) + private fun findCheeringSquadWithCursor(lifeMapId: Long, request: CheeringSquadPageRequest): CursorPaginationResult { + return CursorPaginationTemplate.execute(lifeMapId, request.cursor ?: Long.MAX_VALUE, request.size) { + id, cursor, size -> + cheererJdbcQueryRepository.findAllByLifeMapWithCursor(id, cursor, size) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt index efd32004..a111adcd 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt @@ -3,10 +3,14 @@ package io.raemian.api.comment.service import io.raemian.api.comment.controller.request.WriteCommentRequest import io.raemian.api.comment.model.CommentsResult import io.raemian.api.event.model.CommentReadEvent +import io.raemian.api.event.model.CreatedCommentEvent +import io.raemian.api.event.model.DeletedCommentEvent import io.raemian.api.support.exception.CoreApiException import io.raemian.api.support.exception.ErrorInfo import io.raemian.storage.db.core.comment.Comment +import io.raemian.storage.db.core.comment.CommentJdbcQueryRepository import io.raemian.storage.db.core.comment.CommentRepository +import io.raemian.storage.db.core.comment.model.GoalCommentCountQueryResult import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.user.User @@ -19,6 +23,7 @@ import java.time.LocalDateTime @Service class CommentService( private val commentRepository: CommentRepository, + private val commentJdbcQueryRepository: CommentJdbcQueryRepository, private val goalRepository: GoalRepository, private val userRepository: UserRepository, private val applicationEventPublisher: ApplicationEventPublisher, @@ -55,6 +60,8 @@ class CommentService( val currentUser = userRepository.getReferenceById(currentUserId) val comment = createComment(goal, currentUser, request.content) commentRepository.save(comment) + + applicationEventPublisher.publishEvent(CreatedCommentEvent(goalId)) } @Transactional @@ -65,8 +72,14 @@ class CommentService( } commentRepository.delete(comment) + + applicationEventPublisher.publishEvent(DeletedCommentEvent(comment.goal.id!!)) } + @Transactional(readOnly = true) + fun findGoalCommentCounts(goalIds: List): List = + commentJdbcQueryRepository.findAllGoalCommentCountByGoalIdIn(goalIds) + private fun createComment(goal: Goal, currentUser: User, content: String): Comment { return try { Comment(goal, currentUser, content) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt index 7f337e68..40209532 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt @@ -78,7 +78,13 @@ class WebSecurityConfig( log.info("x-token access ${tokenDTO.accessToken}") // TODO edit redirect url val redirectUrl = - if (profile == "live") "https://bandiboodi.com/oauth2/token" else "http://localhost:3000/oauth2/token" + if (profile == "live") { + "https://bandiboodi.com/oauth2/token" + } else if (profile == "dev") { + "https://dev-bandiboodi.vercel.app/oauth/token" + } else { + "http://localhost:3000/oauth2/token" + } response.sendRedirect("$redirectUrl?token=${tokenDTO.accessToken}&refresh=${tokenDTO.refreshToken}") } it.failureHandler { request, response, exception -> diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/EmojiCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/EmojiCountSubset.kt index 6fbc2dec..0f140f1b 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/EmojiCountSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/EmojiCountSubset.kt @@ -4,6 +4,6 @@ data class EmojiCountSubset( val id: Long, val name: String, val url: String, - val reactCount: Long, + val reactCount: Int, val isMyReaction: Boolean, ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CountEventHandler.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/handler/CountEventHandler.kt similarity index 65% rename from backend/application/api/src/main/kotlin/io/raemian/api/event/model/CountEventHandler.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/event/handler/CountEventHandler.kt index 5214b19b..743850f8 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CountEventHandler.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/handler/CountEventHandler.kt @@ -1,8 +1,16 @@ -package io.raemian.api.event.model +package io.raemian.api.event.handler +import io.raemian.api.event.model.CheeredEvent +import io.raemian.api.event.model.CreatedCommentEvent +import io.raemian.api.event.model.CreatedGoalEvent +import io.raemian.api.event.model.DeletedCommentEvent +import io.raemian.api.event.model.ReactedEmojiEvent +import io.raemian.api.event.model.RemovedEmojiEvent import io.raemian.api.support.lock.ExclusiveRunner import io.raemian.storage.db.core.cheer.Cheering import io.raemian.storage.db.core.cheer.CheeringRepository +import io.raemian.storage.db.core.comment.CommentCount +import io.raemian.storage.db.core.comment.CommentCountRepository import io.raemian.storage.db.core.emoji.EmojiCount import io.raemian.storage.db.core.emoji.EmojiCountRepository import io.raemian.storage.db.core.emoji.EmojiRepository @@ -20,6 +28,7 @@ class CountEventHandler( private val emojiCountRepository: EmojiCountRepository, private val exclusiveRunner: ExclusiveRunner, private val emojiRepository: EmojiRepository, + private val commentCountRepository: CommentCountRepository, ) { @Transactional @EventListener @@ -63,10 +72,29 @@ class CountEventHandler( fun minusEmojiCount(removeEmojiEvent: RemovedEmojiEvent) { exclusiveRunner.call("emoji:${removeEmojiEvent.goalId}:${removeEmojiEvent.emojiId}", Duration.ofSeconds(10)) { val emojiCount = emojiCountRepository.findByGoalIdAndEmojiId(removeEmojiEvent.goalId, removeEmojiEvent.emojiId) + ?: EmojiCount(count = 0, goalId = removeEmojiEvent.goalId, emoji = emojiRepository.getReferenceById(removeEmojiEvent.emojiId)) - if (emojiCount != null && 0 < emojiCount.count) { - emojiCountRepository.save(emojiCount.minusCount()) - } + emojiCountRepository.save(emojiCount.minusCount()) } } + + @Transactional + @EventListener + fun addCommentCount(createdCommentEvent: CreatedCommentEvent) { + exclusiveRunner.call("comment:${createdCommentEvent.goalId}", Duration.ofSeconds(10)) { + val commentCount = commentCountRepository.findByGoalId(createdCommentEvent.goalId) + ?: CommentCount(0, createdCommentEvent.goalId) + + commentCountRepository.save(commentCount.addCount()) + } + } + + @Transactional + @EventListener + fun minusCommentCount(deletedCommentEvent: DeletedCommentEvent) { + val commentCount = commentCountRepository.findByGoalId(deletedCommentEvent.goalId) + ?: CommentCount(count = 0, goalId = deletedCommentEvent.goalId) + + commentCountRepository.save(commentCount.minusCount()) + } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CreatedCommentEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CreatedCommentEvent.kt new file mode 100644 index 00000000..bc348b9e --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CreatedCommentEvent.kt @@ -0,0 +1,5 @@ +package io.raemian.api.event.model + +data class CreatedCommentEvent( + val goalId: Long, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedCommentEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedCommentEvent.kt new file mode 100644 index 00000000..435bf025 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedCommentEvent.kt @@ -0,0 +1,5 @@ +package io.raemian.api.event.model + +data class DeletedCommentEvent( + val goalId: Long, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt index 53508bc6..0341067e 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt @@ -1,14 +1,17 @@ package io.raemian.api.goal.controller import io.raemian.api.auth.model.CurrentUser -import io.raemian.api.emoji.service.EmojiService import io.raemian.api.goal.controller.request.CreateGoalRequest +import io.raemian.api.goal.controller.request.TimelinePageRequest import io.raemian.api.goal.controller.request.UpdateGoalRequest import io.raemian.api.goal.model.CreateGoalResult import io.raemian.api.goal.model.GoalExplorePageResult import io.raemian.api.goal.model.GoalResult +import io.raemian.api.goal.model.GoalTimelinePageResult +import io.raemian.api.goal.service.GoalQueryService import io.raemian.api.goal.service.GoalService import io.raemian.api.support.response.ApiResponse +import io.raemian.api.support.response.PaginationResult import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity import org.springframework.security.core.annotation.AuthenticationPrincipal @@ -29,7 +32,7 @@ fun String.toUri(): URI = URI.create(this) @RequestMapping("/goal") class GoalController( private val goalService: GoalService, - private val emojiService: EmojiService, + private val goalQueryService: GoalQueryService, ) { @Operation(summary = "목표 단건 조회 API") @@ -87,4 +90,14 @@ class GoalController( return ResponseEntity.ok() .body(ApiResponse.success(GoalExplorePageResult(explore))) } + + @Operation(summary = "목표 타임라인 조회 API") + @GetMapping("/timeline") + fun getTimeline( + @AuthenticationPrincipal currentUser: CurrentUser, + request: TimelinePageRequest, + ): ResponseEntity>> { + val goalTimeline = goalQueryService.getTimeline(currentUser.id, request) + return ResponseEntity.ok(ApiResponse.success(goalTimeline)) + } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt new file mode 100644 index 00000000..d1043e77 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt @@ -0,0 +1,6 @@ +package io.raemian.api.goal.controller.request + +data class TimelinePageRequest( + val cursor: Long?, + val size: Int, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreCountSubset.kt similarity index 74% rename from backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalCountSubset.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreCountSubset.kt index 15a447bc..8f9e2007 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalCountSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreCountSubset.kt @@ -1,8 +1,8 @@ package io.raemian.api.goal.model -import io.raemian.storage.db.core.model.GoalExploreQueryResult +import io.raemian.storage.db.core.cheer.GoalExploreQueryResult -data class GoalCountSubset( +data class GoalExploreCountSubset( val reaction: Long, val comment: Long, val task: Long, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreResult.kt index b36ab8f5..18de9ea8 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreResult.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreResult.kt @@ -3,12 +3,12 @@ package io.raemian.api.goal.model import io.raemian.api.emoji.model.EmojiCountSubset import io.raemian.api.emoji.model.ReactedEmojisResult import io.raemian.api.user.model.UserSubset -import io.raemian.storage.db.core.model.GoalExploreQueryResult +import io.raemian.storage.db.core.cheer.GoalExploreQueryResult data class GoalExploreResult( val user: UserSubset, val goal: GoalSubset, - val count: GoalCountSubset, + val count: GoalExploreCountSubset, val emojis: List, ) { companion object { @@ -18,14 +18,14 @@ data class GoalExploreResult( id = it.id ?: -1, url = it.url, name = it.name, - reactCount = it.reactCount.toLong(), + reactCount = it.reactCount, isMyReaction = it.isMyReaction, ) } ?: listOf() return GoalExploreResult( goal = GoalSubset(explore), user = UserSubset(explore), - count = GoalCountSubset(explore), + count = GoalExploreCountSubset(explore), emojis = emojis, ) } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalSubset.kt index ef8d9b7d..68dc3d76 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalSubset.kt @@ -1,6 +1,6 @@ package io.raemian.api.goal.model -import io.raemian.storage.db.core.model.GoalExploreQueryResult +import io.raemian.storage.db.core.cheer.GoalExploreQueryResult import java.time.LocalDate import java.time.LocalDateTime diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelineCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelineCountSubset.kt new file mode 100644 index 00000000..66e6bd45 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelineCountSubset.kt @@ -0,0 +1,12 @@ +package io.raemian.api.goal.model + +data class GoalTimelineCountSubset( + val comment: Int, + val task: Int, +) { + companion object { + fun of(commentCount: Int, taskCount: Int): GoalTimelineCountSubset { + return GoalTimelineCountSubset(commentCount, taskCount) + } + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelinePageResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelinePageResult.kt new file mode 100644 index 00000000..8e6d0cf3 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalTimelinePageResult.kt @@ -0,0 +1,37 @@ +package io.raemian.api.goal.model + +import io.raemian.api.emoji.model.EmojiCountSubset +import io.raemian.api.emoji.model.ReactedEmojisResult +import io.raemian.storage.db.core.goal.model.GoalQueryResult + +data class GoalTimelinePageResult( + val goal: GoalQueryResult, + val counts: GoalTimelineCountSubset?, + val emojis: List, +) { + companion object { + fun from(goal: GoalQueryResult, counts: GoalTimelineCountSubset?, reactedEmojisResult: ReactedEmojisResult?): GoalTimelinePageResult { + if (reactedEmojisResult == null) { + return GoalTimelinePageResult( + goal = goal, + counts = counts, + emojis = emptyList(), + ) + } + + return GoalTimelinePageResult( + goal = goal, + counts = counts, + emojis = reactedEmojisResult.reactedEmojis.map { + EmojiCountSubset( + id = it.id ?: -1, + name = it.name, + url = it.url, + reactCount = it.reactCount, + isMyReaction = it.isMyReaction, + ) + }, + ) + } + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt new file mode 100644 index 00000000..6aafa6bc --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt @@ -0,0 +1,68 @@ +package io.raemian.api.goal.service + +import io.raemian.api.comment.service.CommentService +import io.raemian.api.emoji.service.EmojiService +import io.raemian.api.goal.controller.request.TimelinePageRequest +import io.raemian.api.goal.model.GoalTimelineCountSubset +import io.raemian.api.goal.model.GoalTimelinePageResult +import io.raemian.api.lifemap.service.LifeMapService +import io.raemian.api.support.response.PaginationResult +import io.raemian.api.task.service.TaskService +import io.raemian.storage.db.core.common.pagination.CursorPaginationResult +import io.raemian.storage.db.core.common.pagination.CursorPaginationTemplate +import io.raemian.storage.db.core.goal.GoalJdbcQueryRepository +import io.raemian.storage.db.core.goal.model.GoalQueryResult +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class GoalQueryService( + private val lifeMapService: LifeMapService, + private val goalJdbcQueryRepository: GoalJdbcQueryRepository, + private val emojiService: EmojiService, + private val taskService: TaskService, + private val commentService: CommentService, +) { + @Transactional(readOnly = true) + fun getTimeline(userId: Long, request: TimelinePageRequest): PaginationResult { + val lifeMap = lifeMapService.findFirstByUserId(userId) + + val goals = findAllGoalWithCursor(lifeMap.lifeMapId, request) + + val goalIds = goals.contents.map { it.goalId } + + val goalTimelineCountMap = findGoalTimelineCountMap(goalIds) + val reactedEmojiMap = emojiService.findAllByGoalIds(goalIds, userId) + + return PaginationResult.from( + lifeMap.goals.size.toLong(), + goals.transform() { + goal -> + GoalTimelinePageResult.from( + goal, + goalTimelineCountMap[goal.goalId], + reactedEmojiMap[goal.goalId], + ) + }, + ) + } + + private fun findGoalTimelineCountMap(goalIds: List): Map { + val commentCountMap = commentService.findGoalCommentCounts(goalIds).associate { it.goalId to it.commentCounts } + val taskCountMap = taskService.findGoalTaskCounts(goalIds).associate { it.goalId to it.taskCounts } + + return goalIds.associate { + it to GoalTimelineCountSubset.of( + commentCountMap[it] ?: 0, + taskCountMap[it] ?: 0, + ) + } + } + + private fun findAllGoalWithCursor(lifeMapId: Long, request: TimelinePageRequest): CursorPaginationResult { + return CursorPaginationTemplate.execute(lifeMapId, request.cursor ?: Long.MAX_VALUE, request.size) { + id, cursor, size -> + goalJdbcQueryRepository.findAllByLifeMapWithCursor(id, cursor, size) + } + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt index 51280602..af179470 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt @@ -24,13 +24,13 @@ import java.time.LocalDateTime @Service class GoalService( - private val stickerService: StickerService, - private val tagService: TagService, private val userRepository: UserRepository, private val goalRepository: GoalRepository, private val lifeMapRepository: LifeMapRepository, private val applicationEventPublisher: ApplicationEventPublisher, private val emojiService: EmojiService, + private val stickerService: StickerService, + private val tagService: TagService, ) { @Transactional(readOnly = true) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PageResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PageResult.kt deleted file mode 100644 index a0c93c78..00000000 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PageResult.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.raemian.api.support.response - -class PageResult private constructor( - val total: Long, - val contents: List, - val isLastPage: Boolean, -) { - companion object { - fun of(total: Long, contents: List, isLastPage: Boolean): PageResult { - return PageResult(total, contents, isLastPage) - } - } -} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt new file mode 100644 index 00000000..e3ce480a --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt @@ -0,0 +1,21 @@ +package io.raemian.api.support.response + +import io.raemian.storage.db.core.common.pagination.CursorPaginationResult + +data class PaginationResult( + val total: Long, + val contents: List, + val isLast: Boolean, + val nextCursor: Long?, +) { + companion object { + fun from(total: Long, result: CursorPaginationResult): PaginationResult { + return PaginationResult( + total = total, + contents = result.contents, + isLast = result.isLast, + nextCursor = result.nextCursor, + ) + } + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/task/service/TaskService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/task/service/TaskService.kt index fa995a0d..923e9c20 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/task/service/TaskService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/task/service/TaskService.kt @@ -8,13 +8,16 @@ import io.raemian.api.task.model.CreateTaskResult import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.task.Task +import io.raemian.storage.db.core.task.TaskJdbcQueryRepository import io.raemian.storage.db.core.task.TaskRepository +import io.raemian.storage.db.core.task.model.GoalTaskCountQueryResult import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @Service class TaskService( val taskRepository: TaskRepository, + val taskJdbcQueryRepository: TaskJdbcQueryRepository, val goalRepository: GoalRepository, ) { @@ -60,6 +63,10 @@ class TaskService( taskRepository.delete(task) } + @Transactional(readOnly = true) + fun findGoalTaskCounts(goalIds: List): List = + taskJdbcQueryRepository.findAllGoalTaskCountByGoalIdIn(goalIds) + private fun validateCurrentUserIsGoalOwner(currentUserId: Long, goal: Goal) { if (currentUserId != goal.lifeMap.user.id) { throw SecurityException() diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserSubset.kt index a237bbb3..48404314 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/user/model/UserSubset.kt @@ -1,6 +1,6 @@ package io.raemian.api.user.model -import io.raemian.storage.db.core.model.GoalExploreQueryResult +import io.raemian.storage.db.core.cheer.GoalExploreQueryResult import io.raemian.storage.db.core.user.User data class UserSubset( diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt new file mode 100644 index 00000000..1c0522ff --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt @@ -0,0 +1,49 @@ +package io.raemian.storage.db.core.cheer + +import io.raemian.storage.db.core.cheer.model.CheererQueryResult +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate +import org.springframework.stereotype.Repository +import java.time.LocalDateTime + +@Repository +class CheerJdbcQueryRepository( + private val jdbcTemplate: NamedParameterJdbcTemplate, +) { + fun findAllByLifeMapWithCursor(lifeMapId: Long, cursor: Long, size: Int): List { + val sql = + """ + SELECT + c.ID AS CHEERER_ID, + u.ID AS USER_ID, + u.USERNAME AS USERNAME, + u.NICKNAME AS NICKNAME, + u.IMAGE AS IMAGE_URL, + c.CHEERING_AT AS CHEERING_AT + FROM cheerer c + LEFT OUTER JOIN users u ON c.USER_ID = u.ID + WHERE 1 = 1 + AND c.LIFE_MAP_ID = :lifeMapId + AND c.ID <= :cursor + ORDER BY c.ID DESC + LIMIT :size + """.trimIndent() + + val namedParameter = MapSqlParameterSource() + .addValue("lifeMapId", lifeMapId) + .addValue("cursor", cursor) + .addValue("size", size) + + return jdbcTemplate.query(sql, namedParameter) { + rs, rowNum -> + CheererQueryResult( + cheererId = rs.getLong("CHEERER_ID"), + userId = rs.getLong("USER_ID"), + userName = rs.getString("USERNAME"), + userNickName = rs.getString("NICKNAME"), + userImageUrl = rs.getString("IMAGE_URL"), + cheeringAt = rs.getObject("CHEERING_AT", LocalDateTime::class.java), + ) + } + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheerer.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheerer.kt index 9b72ee08..600e3859 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheerer.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheerer.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.cheer -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import io.raemian.storage.db.core.user.User import jakarta.persistence.Column import jakarta.persistence.Entity @@ -14,7 +14,7 @@ import jakarta.persistence.Table import java.time.LocalDateTime @Entity -@Table(name = "CHEERER", indexes = [Index(name = "IDX_LIFE_MAP_ID_AND_CHEERING_AT", columnList = "lifeMapId, cheeringAt")]) +@Table(name = "CHEERER", indexes = [Index(name = "IDX_LIFE_MAP_ID_AND_ID", columnList = "lifeMapId, id")]) class Cheerer( @Column val lifeMapId: Long, diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheererRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheererRepository.kt index cb6543af..87b31b92 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheererRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheererRepository.kt @@ -1,14 +1,5 @@ package io.raemian.storage.db.core.cheer -import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository -import java.time.LocalDateTime -interface CheererRepository : JpaRepository { - - fun findByLifeMapIdAndCheeringAtLessThanOrderByCheeringAtDesc(lifeMapId: Long, cheeringAt: LocalDateTime, pageable: Pageable): List - - fun findByLifeMapIdOrderByCheeringAtDesc(lifeMapId: Long, pageable: Pageable): List - - fun existsByLifeMapIdAndCheeringAtLessThanOrderByCheeringAtDesc(lifeMapId: Long, cheeringAt: LocalDateTime): Boolean -} +interface CheererRepository : JpaRepository diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheering.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheering.kt index d4bb7800..e8044c56 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheering.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/Cheering.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.cheer -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt new file mode 100644 index 00000000..4075b6d9 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt @@ -0,0 +1,15 @@ +package io.raemian.storage.db.core.cheer.model + +import io.raemian.storage.db.core.common.pagination.CursorExtractable +import java.time.LocalDateTime + +data class CheererQueryResult( + val cheererId: Long, + val userId: Long?, + val userName: String?, + val userNickName: String?, + val userImageUrl: String?, + val cheeringAt: LocalDateTime, +) : CursorExtractable { + override fun getCursorId(): Long = cheererId +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt index 5ce6ee39..afa505b8 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.comment -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.user.User import jakarta.persistence.Column diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt new file mode 100644 index 00000000..0922f53b --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCount.kt @@ -0,0 +1,36 @@ +package io.raemian.storage.db.core.comment + +import io.raemian.storage.db.core.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Index +import jakarta.persistence.Table + +@Entity +@Table(name = "COMMENT_COUNTS", indexes = [Index(name = "IDX_GOAL_ID", columnList = "goalId")]) +class CommentCount( + @Column + var count: Long, + + @Column + val goalId: Long, + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, +) : BaseEntity() { + fun addCount(): CommentCount { + this.count += 1 + return this + } + + fun minusCount(): CommentCount { + if (0 < this.count) { + this.count -= 1 + } + return this + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCountRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCountRepository.kt new file mode 100644 index 00000000..969ce944 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentCountRepository.kt @@ -0,0 +1,9 @@ +package io.raemian.storage.db.core.comment + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface CommentCountRepository : JpaRepository { + fun findByGoalId(goalId: Long): CommentCount? +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt new file mode 100644 index 00000000..cc553415 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt @@ -0,0 +1,35 @@ +package io.raemian.storage.db.core.comment + +import io.raemian.storage.db.core.comment.model.GoalCommentCountQueryResult +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate +import org.springframework.stereotype.Repository + +@Repository +class CommentJdbcQueryRepository( + private val jdbcTemplate: NamedParameterJdbcTemplate, +) { + fun findAllGoalCommentCountByGoalIdIn(goalIds: List): List { + val sql = + """ + SELECT + c.GOAL_ID AS GOAL_ID, + COUNT(GOAL_ID) AS COMMENT_COUNT + FROM comment_counts c + WHERE 1 = 1 + AND c.GOAL_ID IN (:goalIds) + GROUP BY c.GOAL_ID + """.trimIndent() + + val namedParameter = MapSqlParameterSource() + .addValue("goalIds", goalIds) + + return jdbcTemplate.query(sql, namedParameter) { + rs, rowNum -> + GoalCommentCountQueryResult( + goalId = rs.getLong("GOAL_ID"), + commentCounts = rs.getInt("COMMENT_COUNT"), + ) + } + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt new file mode 100644 index 00000000..786d6c83 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt @@ -0,0 +1,6 @@ +package io.raemian.storage.db.core.comment.model + +data class GoalCommentCountQueryResult( + val goalId: Long, + val commentCounts: Int, +) diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/BaseEntity.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/BaseEntity.kt similarity index 91% rename from backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/BaseEntity.kt rename to backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/BaseEntity.kt index a15e0f07..2a385a8e 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/BaseEntity.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/BaseEntity.kt @@ -1,4 +1,4 @@ -package io.raemian.storage.db.core +package io.raemian.storage.db.core.common import jakarta.persistence.Column import jakarta.persistence.MappedSuperclass diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt new file mode 100644 index 00000000..645a8325 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt @@ -0,0 +1,5 @@ +package io.raemian.storage.db.core.common.pagination + +interface CursorExtractable { + fun getCursorId(): Long +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationResult.kt new file mode 100644 index 00000000..628378ec --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationResult.kt @@ -0,0 +1,17 @@ +package io.raemian.storage.db.core.common.pagination + +import java.util.function.Function + +data class CursorPaginationResult internal constructor( + val contents: List, + val nextCursor: Long?, + val isLast: Boolean, +) { + fun transform(transformer: Function): CursorPaginationResult { + return CursorPaginationResult( + contents.stream().map(transformer).toList(), + nextCursor, + isLast, + ) + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt new file mode 100644 index 00000000..57465be6 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt @@ -0,0 +1,22 @@ +package io.raemian.storage.db.core.common.pagination + +import kotlin.math.min + +object CursorPaginationTemplate { + fun execute( + id: Long, + cursorId: Long, + size: Int, + query: TriFunction>, + ): CursorPaginationResult { + val data = query.apply(id, cursorId ?: 0, size + 1) + val isLast = data.size != size + 1 + val nextCursor = if (isLast) null else data[size].getCursorId() + + return CursorPaginationResult( + data.subList(0, min(data.size, size)), + nextCursor, + isLast, + ) + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/TriFunction.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/TriFunction.kt new file mode 100644 index 00000000..14198cc8 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/TriFunction.kt @@ -0,0 +1,6 @@ +package io.raemian.storage.db.core.common.pagination + +@FunctionalInterface +fun interface TriFunction { + fun apply(t: T, u: U, v: V): R +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/Emoji.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/Emoji.kt index fd75128d..ac6d193a 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/Emoji.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/Emoji.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.emoji -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt index 25319990..adb048c8 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCount.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.emoji -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue @@ -34,7 +34,9 @@ class EmojiCount( } fun minusCount(): EmojiCount { - this.count -= 1 + if (0 < this.count) { + this.count -= 1 + } return this } } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt index c31ef76c..cb683295 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/EmojiCountRepository.kt @@ -4,5 +4,4 @@ import org.springframework.data.jpa.repository.JpaRepository interface EmojiCountRepository : JpaRepository { fun findByGoalIdAndEmojiId(goalId: Long, emojiId: Long): EmojiCount? - fun findAllByGoalIdIn(ids: List): List } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmoji.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmoji.kt index f9bb6e78..471e306f 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmoji.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmoji.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.emoji -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.user.User import jakarta.persistence.Entity diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt index e5e8a310..db3f5b10 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt @@ -5,8 +5,8 @@ import io.raemian.storage.db.core.user.User import org.springframework.data.jpa.repository.JpaRepository interface ReactedEmojiRepository : JpaRepository { - fun findAllByGoal(goal: Goal): List + fun findAllByGoalIdIn(goalIds: List): List fun deleteByEmojiAndGoalAndReactUser(emoji: Emoji, goal: Goal, reactUser: User) diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt index 9669bc36..c48405b1 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.goal -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import io.raemian.storage.db.core.lifemap.LifeMap import io.raemian.storage.db.core.sticker.Sticker import io.raemian.storage.db.core.tag.Tag diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt new file mode 100644 index 00000000..5054c18e --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt @@ -0,0 +1,52 @@ +package io.raemian.storage.db.core.goal + +import io.raemian.storage.db.core.goal.model.GoalQueryResult +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate +import org.springframework.stereotype.Repository +import java.time.LocalDateTime + +@Repository +class GoalJdbcQueryRepository( + private val jdbcTemplate: NamedParameterJdbcTemplate, +) { + fun findAllByLifeMapWithCursor(lifeMapId: Long, cursor: Long, size: Int): List { + val sql = + """ + SELECT + g.ID AS GOAL_ID, + g.TITLE AS TITLE, + g.DESCRIPTION AS DESCRIPTION, + g.DEADLINE AS DEADLINE, + s.URL AS STICKER_URL, + t.CONTENT AS TAG, + g.CREATED_AT AS CREATED_AT + FROM goals g + LEFT OUTER JOIN tags t ON g.TAG_ID = t.ID + LEFT OUTER JOIN stickers s ON g.STICKER_ID = s.ID + WHERE 1 = 1 + AND g.LIFE_MAP_ID = :lifeMapId + AND g.ID <= :cursor + ORDER BY g.ID DESC + LIMIT :size + """.trimIndent() + + val namedParameter = MapSqlParameterSource() + .addValue("lifeMapId", lifeMapId) + .addValue("cursor", cursor) + .addValue("size", size) + + return jdbcTemplate.query(sql, namedParameter) { + rs, rowNum -> + GoalQueryResult( + goalId = rs.getLong("GOAL_ID"), + title = rs.getString("TITLE"), + description = rs.getString("DESCRIPTION"), + deadline = rs.getObject("DEADLINE", LocalDateTime::class.java), + stickerUrl = rs.getString("STICKER_URL"), + tag = rs.getString("TAG"), + createdAt = rs.getObject("CREATED_AT", LocalDateTime::class.java), + ) + } + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt index 0dd670a5..c1d71eb1 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt @@ -1,7 +1,6 @@ package io.raemian.storage.db.core.goal -import io.raemian.storage.db.core.lifemap.LifeMap -import io.raemian.storage.db.core.model.GoalExploreQueryResult +import io.raemian.storage.db.core.cheer.GoalExploreQueryResult import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query @@ -11,15 +10,13 @@ import java.time.LocalDateTime interface GoalRepository : JpaRepository { fun findUserByCreatedAtGreaterThanEqual(createdAt: LocalDateTime): List - fun countGoalByLifeMap(lifeMap: LifeMap): Int - override fun getById(id: Long): Goal = findById(id).orElseThrow() { NoSuchElementException("목표가 없습니다 $id") } @Query( """ SELECT - new io.raemian.storage.db.core.model.GoalExploreQueryResult( + new io.raemian.storage.db.core.cheer.GoalExploreQueryResult( goal.id, goal.title, goal.description, diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalExploreQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalExploreQueryResult.kt index 1ce004e1..888b6a02 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalExploreQueryResult.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalExploreQueryResult.kt @@ -1,4 +1,4 @@ -package io.raemian.storage.db.core.model +package io.raemian.storage.db.core.cheer import java.time.LocalDate import java.time.LocalDateTime diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt new file mode 100644 index 00000000..7c525467 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt @@ -0,0 +1,16 @@ +package io.raemian.storage.db.core.goal.model + +import io.raemian.storage.db.core.common.pagination.CursorExtractable +import java.time.LocalDateTime + +data class GoalQueryResult( + val goalId: Long, + val title: String, + val description: String, + val deadline: LocalDateTime, + val stickerUrl: String, + val tag: String, + val createdAt: LocalDateTime, +) : CursorExtractable { + override fun getCursorId(): Long = goalId +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMap.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMap.kt index 341ba596..ebd82821 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMap.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMap.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.lifemap -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.user.User import jakarta.persistence.CascadeType diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapCountRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapCountRepository.kt index 67626c90..3e715161 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapCountRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapCountRepository.kt @@ -4,6 +4,4 @@ import org.springframework.data.repository.CrudRepository interface LifeMapCountRepository : CrudRepository { fun findByLifeMapId(lifeMapId: Long): LifeMapCount? - - fun findAllByLifeMapIdIn(lifeMapIds: List): List } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapRepository.kt index 411c20c4..29d8d53c 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapRepository.kt @@ -7,6 +7,4 @@ interface LifeMapRepository : JpaRepository { fun findFirstByUserId(userId: Long): LifeMap? fun findFirstByUserUsername(username: String): LifeMap? - - fun findAllByIdInOrderByIdDesc(ids: List): List } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/profile/DefaultProfile.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/profile/DefaultProfile.kt index 948ea3d9..2a338852 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/profile/DefaultProfile.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/profile/DefaultProfile.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.profile -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/sticker/Sticker.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/sticker/Sticker.kt index eb4352cc..eea5b1a2 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/sticker/Sticker.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/sticker/Sticker.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.sticker -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/tag/Tag.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/tag/Tag.kt index c6cc33fb..e9bf18da 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/tag/Tag.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/tag/Tag.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.tag -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/Task.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/Task.kt index cce75144..fd27319b 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/Task.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/Task.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.task -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import io.raemian.storage.db.core.goal.Goal import jakarta.persistence.Column import jakarta.persistence.Entity diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt new file mode 100644 index 00000000..3e017374 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt @@ -0,0 +1,35 @@ +package io.raemian.storage.db.core.task + +import io.raemian.storage.db.core.task.model.GoalTaskCountQueryResult +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate +import org.springframework.stereotype.Repository + +@Repository +class TaskJdbcQueryRepository( + private val jdbcTemplate: NamedParameterJdbcTemplate, +) { + fun findAllGoalTaskCountByGoalIdIn(goalIds: List): List { + val sql = + """ + SELECT + t.GOAL_ID AS GOAL_ID, + COUNT(t.GOAL_ID) AS TASK_COUNT + FROM tasks t + WHERE 1 = 1 + AND t.GOAL_ID IN (:goalIds) + GROUP BY t.GOAL_ID + """.trimIndent() + + val namedParameter = MapSqlParameterSource() + .addValue("goalIds", goalIds) + + return jdbcTemplate.query(sql, namedParameter) { + rs, rowNum -> + GoalTaskCountQueryResult( + goalId = rs.getLong("GOAL_ID"), + taskCounts = rs.getInt("TASK_COUNT"), + ) + } + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt new file mode 100644 index 00000000..3a79387c --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt @@ -0,0 +1,6 @@ +package io.raemian.storage.db.core.task.model + +data class GoalTaskCountQueryResult( + val goalId: Long, + val taskCounts: Int, +) diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/User.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/User.kt index 261cbb91..8ad5b188 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/User.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/User.kt @@ -1,6 +1,6 @@ package io.raemian.storage.db.core.user -import io.raemian.storage.db.core.BaseEntity +import io.raemian.storage.db.core.common.BaseEntity import io.raemian.storage.db.core.user.enums.OAuthProvider import jakarta.persistence.Column import jakarta.persistence.Entity diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/UserRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/UserRepository.kt index ded10499..e134584b 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/UserRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/user/UserRepository.kt @@ -10,8 +10,6 @@ interface UserRepository : JpaRepository { fun findByEmail(email: String): User? - fun existsByEmail(email: String): Boolean - fun findUserByCreatedAtGreaterThanEqual(createdAt: LocalDateTime): List fun findByUsername(username: String): User? From 16c6b80a9dbba8de4f213e98fe6c51d10698e35e Mon Sep 17 00:00:00 2001 From: wkwon Date: Sat, 23 Mar 2024 13:00:15 +0900 Subject: [PATCH 34/60] =?UTF-8?q?hotfix(#235):=20=ED=83=80=EC=9D=B8=20?= =?UTF-8?q?=ED=83=80=EC=9E=84=EB=9D=BC=EC=9D=B8=20=EC=A1=B0=ED=9A=8C=20API?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cheer/controller/CheeringController.kt | 3 +- .../raemian/api/cheer/model/CheererResult.kt | 26 +++++++++++++++ .../api/cheer/service/CheeringService.kt | 7 ++-- .../api/goal/controller/GoalController.kt | 11 ------- .../api/goal/service/GoalQueryService.kt | 33 ++++++++++++++++--- .../lifemap/controller/LifeMapController.kt | 15 +++++++++ .../controller/OpenLifeMapController.kt | 19 ++++++++++- .../api/lifemap/model/LifeMapResult.kt | 2 +- .../support/constant/WebSecurityConstant.kt | 1 + .../db/core/cheer/CheerJdbcQueryRepository.kt | 3 +- .../comment/CommentJdbcQueryRepository.kt | 2 +- .../model/GoalCommentCountQueryResult.kt | 2 +- .../extension/ResultSetExtensionFunction.kt | 12 +++++++ .../db/core/task/TaskJdbcQueryRepository.kt | 2 +- .../task/model/GoalTaskCountQueryResult.kt | 2 +- 15 files changed, 114 insertions(+), 26 deletions(-) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt create mode 100644 backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/extension/ResultSetExtensionFunction.kt diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt index 221b74c7..624f8ec2 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt @@ -2,6 +2,7 @@ package io.raemian.api.cheer.controller import io.raemian.api.cheer.controller.request.CheeringRequest import io.raemian.api.cheer.controller.request.CheeringSquadPageRequest +import io.raemian.api.cheer.model.CheererResult import io.raemian.api.cheer.model.CheeringCountResult import io.raemian.api.cheer.service.CheeringService import io.raemian.api.support.response.ApiResponse @@ -25,7 +26,7 @@ class CheeringController( fun findCheeringSquad( @PathVariable("lifeMapId") lifeMapId: Long, request: CheeringSquadPageRequest, - ): ResponseEntity>> = + ): ResponseEntity>> = ResponseEntity.ok().body(ApiResponse.success(cheeringService.findCheeringSquad(lifeMapId, request))) @GetMapping("/count/{userName}") diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt new file mode 100644 index 00000000..23162232 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt @@ -0,0 +1,26 @@ +package io.raemian.api.cheer.model + +import io.raemian.storage.db.core.cheer.model.CheererQueryResult +import java.time.LocalDateTime + +data class CheererResult( + val cheererId: Long, + val userId: Long, + val userName: String?, + val userNickName: String?, + val userImageUrl: String?, + val cheeringAt: LocalDateTime, +) { + companion object { + fun from(queryResult: CheererQueryResult): CheererResult { + return CheererResult( + cheererId = queryResult.cheererId, + userId = queryResult.userId ?: -1, + userName = queryResult.userName, + userNickName = queryResult.userNickName, + userImageUrl = queryResult.userImageUrl, + cheeringAt = queryResult.cheeringAt + ) + } + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt index 4f95f498..48a50ff2 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt @@ -2,6 +2,7 @@ package io.raemian.api.cheer.service import io.raemian.api.cheer.controller.request.CheeringRequest import io.raemian.api.cheer.controller.request.CheeringSquadPageRequest +import io.raemian.api.cheer.model.CheererResult import io.raemian.api.cheer.model.CheeringCountResult import io.raemian.api.event.model.CheeredEvent import io.raemian.api.support.exception.CoreApiException @@ -45,13 +46,15 @@ class CheeringService( } @Transactional(readOnly = true) - fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPageRequest): PaginationResult { + fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPageRequest): PaginationResult { val cheering = cheeringRepository.findByLifeMapId(lifeMapId) ?: Cheering(0, lifeMapId) val cheeringSquad = findCheeringSquadWithCursor(lifeMapId, request) - return PaginationResult.from(cheering.count, cheeringSquad) + val filteredCheeringSquad = cheeringSquad.transform(CheererResult::from) + + return PaginationResult.from(cheering.count, filteredCheeringSquad) } @Transactional(readOnly = true) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt index 0341067e..76bc34af 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt @@ -32,7 +32,6 @@ fun String.toUri(): URI = URI.create(this) @RequestMapping("/goal") class GoalController( private val goalService: GoalService, - private val goalQueryService: GoalQueryService, ) { @Operation(summary = "목표 단건 조회 API") @@ -90,14 +89,4 @@ class GoalController( return ResponseEntity.ok() .body(ApiResponse.success(GoalExplorePageResult(explore))) } - - @Operation(summary = "목표 타임라인 조회 API") - @GetMapping("/timeline") - fun getTimeline( - @AuthenticationPrincipal currentUser: CurrentUser, - request: TimelinePageRequest, - ): ResponseEntity>> { - val goalTimeline = goalQueryService.getTimeline(currentUser.id, request) - return ResponseEntity.ok(ApiResponse.success(goalTimeline)) - } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt index 6aafa6bc..164bdb5b 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt @@ -24,10 +24,10 @@ class GoalQueryService( private val commentService: CommentService, ) { @Transactional(readOnly = true) - fun getTimeline(userId: Long, request: TimelinePageRequest): PaginationResult { + fun findAllByUserIdWithCursor(userId: Long, request: TimelinePageRequest): PaginationResult { val lifeMap = lifeMapService.findFirstByUserId(userId) - val goals = findAllGoalWithCursor(lifeMap.lifeMapId, request) + val goals = findAllByLifeMapIdWithCursor(lifeMap.lifeMapId, request) val goalIds = goals.contents.map { it.goalId } @@ -47,9 +47,32 @@ class GoalQueryService( ) } + @Transactional(readOnly = true) + fun findAllByUsernameWithCursor(username: String, request: TimelinePageRequest): PaginationResult { + val lifeMap = lifeMapService.findFirstByUserName(username) + val goals = findAllByLifeMapIdWithCursor(lifeMap.lifeMapId, request) + + val goalIds = goals.contents.map { it.goalId } + + val goalTimelineCountMap = findGoalTimelineCountMap(goalIds) + val reactedEmojiMap = emojiService.findAllByGoalIds(goalIds, lifeMap.user.id) + + return PaginationResult.from( + lifeMap.goals.size.toLong(), + goals.transform() { + goal -> + GoalTimelinePageResult.from( + goal, + goalTimelineCountMap[goal.goalId], + reactedEmojiMap[goal.goalId], + ) + }, + ) + } + private fun findGoalTimelineCountMap(goalIds: List): Map { - val commentCountMap = commentService.findGoalCommentCounts(goalIds).associate { it.goalId to it.commentCounts } - val taskCountMap = taskService.findGoalTaskCounts(goalIds).associate { it.goalId to it.taskCounts } + val commentCountMap = commentService.findGoalCommentCounts(goalIds).associate { it.goalId to it.commentCount } + val taskCountMap = taskService.findGoalTaskCounts(goalIds).associate { it.goalId to it.taskCount } return goalIds.associate { it to GoalTimelineCountSubset.of( @@ -59,7 +82,7 @@ class GoalQueryService( } } - private fun findAllGoalWithCursor(lifeMapId: Long, request: TimelinePageRequest): CursorPaginationResult { + private fun findAllByLifeMapIdWithCursor(lifeMapId: Long, request: TimelinePageRequest): CursorPaginationResult { return CursorPaginationTemplate.execute(lifeMapId, request.cursor ?: Long.MAX_VALUE, request.size) { id, cursor, size -> goalJdbcQueryRepository.findAllByLifeMapWithCursor(id, cursor, size) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt index 6731945f..b6b80228 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt @@ -2,10 +2,14 @@ package io.raemian.api.lifemap.controller import io.raemian.api.auth.model.CurrentUser import io.raemian.api.cheer.service.CheeringService +import io.raemian.api.goal.controller.request.TimelinePageRequest +import io.raemian.api.goal.model.GoalTimelinePageResult +import io.raemian.api.goal.service.GoalQueryService import io.raemian.api.lifemap.controller.request.UpdatePublicRequest import io.raemian.api.lifemap.model.LifeMapResponse import io.raemian.api.lifemap.service.LifeMapService import io.raemian.api.support.response.ApiResponse +import io.raemian.api.support.response.PaginationResult import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity import org.springframework.security.core.annotation.AuthenticationPrincipal @@ -20,6 +24,7 @@ import org.springframework.web.bind.annotation.RestController class LifeMapController( private val lifeMapService: LifeMapService, private val cheeringService: CheeringService, + private val goalQueryService: GoalQueryService, ) { @Operation(summary = "로그인한 유저의 인생 지도 조회 API") @@ -35,6 +40,16 @@ class LifeMapController( .ok(ApiResponse.success(LifeMapResponse(lifeMap, count, cheeringCount))) } + @Operation(summary = "로그인한 유저의 인생 지도 타임 라인 조회 API") + @GetMapping("/timeline") + fun getTimeline( + @AuthenticationPrincipal currentUser: CurrentUser, + request: TimelinePageRequest, + ): ResponseEntity>> { + val goalTimeline = goalQueryService.findAllByUserIdWithCursor(currentUser.id, request) + return ResponseEntity.ok(ApiResponse.success(goalTimeline)) + } + @Operation(summary = "인생 지도 공개 여부를 수정하는 API") @PatchMapping("/publication") fun updatePublic( diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt index 422f7a2b..8541f625 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt @@ -2,24 +2,31 @@ package io.raemian.api.lifemap.controller import io.raemian.api.auth.model.CurrentUser import io.raemian.api.cheer.service.CheeringService +import io.raemian.api.goal.controller.request.TimelinePageRequest +import io.raemian.api.goal.model.GoalTimelinePageResult +import io.raemian.api.goal.service.GoalQueryService import io.raemian.api.lifemap.model.LifeMapResponse import io.raemian.api.lifemap.service.LifeMapService import io.raemian.api.support.response.ApiResponse +import io.raemian.api.support.response.PaginationResult import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity import org.springframework.security.core.annotation.AuthenticationPrincipal 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 @RestController +@RequestMapping("/open/life-map") class OpenLifeMapController( private val lifeMapService: LifeMapService, private val cheeringService: CheeringService, + private val goalQueryService: GoalQueryService, ) { @Operation(summary = "UserName으로 인생 지도 조회 API") - @GetMapping("/open/life-map/{username}") + @GetMapping("/{username}") fun findAllByUserName( @AuthenticationPrincipal currentUser: CurrentUser?, @PathVariable("username") username: String, @@ -37,4 +44,14 @@ class OpenLifeMapController( return ResponseEntity .ok(ApiResponse.success(LifeMapResponse(lifeMap, count, cheeringCount))) } + + @Operation(summary = "로그인한 유저의 인생 지도 타임 라인 조회 API") + @GetMapping("/timeline/{username}") + fun getTimeline( + @PathVariable("username") username: String, + request: TimelinePageRequest, + ): ResponseEntity>> { + val goalTimeline = goalQueryService.findAllByUsernameWithCursor(username, request) + return ResponseEntity.ok(ApiResponse.success(goalTimeline)) + } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/LifeMapResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/LifeMapResult.kt index 6a497afd..4d1e471b 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/LifeMapResult.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/model/LifeMapResult.kt @@ -9,7 +9,7 @@ data class LifeMapResult( val isPublic: Boolean, val goals: List, val goalsCount: Int, - val user: UserSubset? = null, + val user: UserSubset, ) { constructor(lifeMap: LifeMap) : this( diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/constant/WebSecurityConstant.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/constant/WebSecurityConstant.kt index bb7d52b0..293c5e80 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/constant/WebSecurityConstant.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/constant/WebSecurityConstant.kt @@ -3,6 +3,7 @@ package io.raemian.api.support.constant object WebSecurityConstant { val PUBLIC_URIS = arrayOf( "/auth/**", + "/cherring/**", "/oauth2/**", "/login/**", "/one-baily-actuator/**", diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt index 1c0522ff..3f29ccee 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/CheerJdbcQueryRepository.kt @@ -1,6 +1,7 @@ package io.raemian.storage.db.core.cheer import io.raemian.storage.db.core.cheer.model.CheererQueryResult +import io.raemian.storage.db.core.common.extension.getLongOrNull import org.springframework.jdbc.core.namedparam.MapSqlParameterSource import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate import org.springframework.stereotype.Repository @@ -38,7 +39,7 @@ class CheerJdbcQueryRepository( rs, rowNum -> CheererQueryResult( cheererId = rs.getLong("CHEERER_ID"), - userId = rs.getLong("USER_ID"), + userId = rs.getLongOrNull("USER_ID"), userName = rs.getString("USERNAME"), userNickName = rs.getString("NICKNAME"), userImageUrl = rs.getString("IMAGE_URL"), diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt index cc553415..1174119f 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt @@ -28,7 +28,7 @@ class CommentJdbcQueryRepository( rs, rowNum -> GoalCommentCountQueryResult( goalId = rs.getLong("GOAL_ID"), - commentCounts = rs.getInt("COMMENT_COUNT"), + commentCount = rs.getInt("COMMENT_COUNT"), ) } } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt index 786d6c83..7ba1d80a 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/model/GoalCommentCountQueryResult.kt @@ -2,5 +2,5 @@ package io.raemian.storage.db.core.comment.model data class GoalCommentCountQueryResult( val goalId: Long, - val commentCounts: Int, + val commentCount: Int, ) diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/extension/ResultSetExtensionFunction.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/extension/ResultSetExtensionFunction.kt new file mode 100644 index 00000000..e3dacfe7 --- /dev/null +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/extension/ResultSetExtensionFunction.kt @@ -0,0 +1,12 @@ +package io.raemian.storage.db.core.common.extension + +import java.sql.ResultSet + +fun ResultSet.getLongOrNull(columnName: String): Long? { + return wasNull(this, getLong(columnName)) +} + +fun wasNull(resultSet: ResultSet, value: T) = when { + resultSet.wasNull() -> null + else -> value +} \ No newline at end of file diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt index 3e017374..1c9a6458 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/TaskJdbcQueryRepository.kt @@ -28,7 +28,7 @@ class TaskJdbcQueryRepository( rs, rowNum -> GoalTaskCountQueryResult( goalId = rs.getLong("GOAL_ID"), - taskCounts = rs.getInt("TASK_COUNT"), + taskCount = rs.getInt("TASK_COUNT"), ) } } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt index 3a79387c..f473cf69 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/task/model/GoalTaskCountQueryResult.kt @@ -2,5 +2,5 @@ package io.raemian.storage.db.core.task.model data class GoalTaskCountQueryResult( val goalId: Long, - val taskCounts: Int, + val taskCount: Int, ) From d5cebcf3c8b8dfdcb0986d24fe22371aa1587b52 Mon Sep 17 00:00:00 2001 From: wkwon Date: Sat, 23 Mar 2024 13:59:54 +0900 Subject: [PATCH 35/60] chore: apply klint --- .../io/raemian/api/cheer/controller/CheeringController.kt | 1 - .../main/kotlin/io/raemian/api/cheer/model/CheererResult.kt | 2 +- .../kotlin/io/raemian/api/goal/controller/GoalController.kt | 4 ---- .../db/core/common/extension/ResultSetExtensionFunction.kt | 2 +- 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt index 624f8ec2..64e19472 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt @@ -7,7 +7,6 @@ import io.raemian.api.cheer.model.CheeringCountResult import io.raemian.api.cheer.service.CheeringService import io.raemian.api.support.response.ApiResponse import io.raemian.api.support.response.PaginationResult -import io.raemian.storage.db.core.cheer.model.CheererQueryResult import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt index 23162232..e204a219 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/model/CheererResult.kt @@ -19,7 +19,7 @@ data class CheererResult( userName = queryResult.userName, userNickName = queryResult.userNickName, userImageUrl = queryResult.userImageUrl, - cheeringAt = queryResult.cheeringAt + cheeringAt = queryResult.cheeringAt, ) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt index 76bc34af..8c14ab80 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/GoalController.kt @@ -2,16 +2,12 @@ package io.raemian.api.goal.controller import io.raemian.api.auth.model.CurrentUser import io.raemian.api.goal.controller.request.CreateGoalRequest -import io.raemian.api.goal.controller.request.TimelinePageRequest import io.raemian.api.goal.controller.request.UpdateGoalRequest import io.raemian.api.goal.model.CreateGoalResult import io.raemian.api.goal.model.GoalExplorePageResult import io.raemian.api.goal.model.GoalResult -import io.raemian.api.goal.model.GoalTimelinePageResult -import io.raemian.api.goal.service.GoalQueryService import io.raemian.api.goal.service.GoalService import io.raemian.api.support.response.ApiResponse -import io.raemian.api.support.response.PaginationResult import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity import org.springframework.security.core.annotation.AuthenticationPrincipal diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/extension/ResultSetExtensionFunction.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/extension/ResultSetExtensionFunction.kt index e3dacfe7..b09fd7f0 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/extension/ResultSetExtensionFunction.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/extension/ResultSetExtensionFunction.kt @@ -9,4 +9,4 @@ fun ResultSet.getLongOrNull(columnName: String): Long? { fun wasNull(resultSet: ResultSet, value: T) = when { resultSet.wasNull() -> null else -> value -} \ No newline at end of file +} From dd13f20ac7e335ce0dbe3f52574cfe535720335c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=9A=B0=EC=84=9D=20=28Woosuk=20Kwon=29?= Date: Mon, 25 Mar 2024 20:24:45 +0900 Subject: [PATCH 36/60] =?UTF-8?q?[=F0=9F=9B=A0=EF=B8=8F=20Refactor]=20?= =?UTF-8?q?=ED=83=80=EC=9E=84=20=EB=9D=BC=EC=9D=B8=20API=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20(#241)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * delete: 유저 정보를 활용한 타임라인 조회 API 삭제 * refactor: PaginationResult.transform() 사용 방식 lamda 형태로 통일 * refactor: 메서드 로직에 따라 get, find 명칭 구분 * refactor: lamda body 내 생략 가능한 parameter 생략 * refactor: 메서드명 내 부정확한 단어 제거(findGoalTimelineCountMap -> findGoalCountMap) * refactor: associateWith 메서드 활용 * refactor: PaginationResult total값 Long으로 다시 변경 --- .../api/cheer/service/CheeringService.kt | 2 +- .../api/goal/service/GoalQueryService.kt | 45 +++++-------------- .../lifemap/controller/LifeMapController.kt | 15 +------ .../controller/OpenLifeMapController.kt | 2 +- .../api/lifemap/service/LifeMapService.kt | 4 +- .../api/support/response/PaginationResult.kt | 9 ++++ .../api/user/controller/UserController.kt | 2 +- .../integration/lifemap/LifeMapServiceTest.kt | 8 ++-- .../pagination/CursorPaginationTemplate.kt | 2 +- 9 files changed, 30 insertions(+), 59 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt index 48a50ff2..4f99a81c 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt @@ -52,7 +52,7 @@ class CheeringService( val cheeringSquad = findCheeringSquadWithCursor(lifeMapId, request) - val filteredCheeringSquad = cheeringSquad.transform(CheererResult::from) + val filteredCheeringSquad = cheeringSquad.transform { CheererResult.from(it) } return PaginationResult.from(cheering.count, filteredCheeringSquad) } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt index 164bdb5b..5624a1c6 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt @@ -23,59 +23,34 @@ class GoalQueryService( private val taskService: TaskService, private val commentService: CommentService, ) { - @Transactional(readOnly = true) - fun findAllByUserIdWithCursor(userId: Long, request: TimelinePageRequest): PaginationResult { - val lifeMap = lifeMapService.findFirstByUserId(userId) - - val goals = findAllByLifeMapIdWithCursor(lifeMap.lifeMapId, request) - - val goalIds = goals.contents.map { it.goalId } - - val goalTimelineCountMap = findGoalTimelineCountMap(goalIds) - val reactedEmojiMap = emojiService.findAllByGoalIds(goalIds, userId) - - return PaginationResult.from( - lifeMap.goals.size.toLong(), - goals.transform() { - goal -> - GoalTimelinePageResult.from( - goal, - goalTimelineCountMap[goal.goalId], - reactedEmojiMap[goal.goalId], - ) - }, - ) - } - @Transactional(readOnly = true) fun findAllByUsernameWithCursor(username: String, request: TimelinePageRequest): PaginationResult { - val lifeMap = lifeMapService.findFirstByUserName(username) + val lifeMap = lifeMapService.getFirstByUserName(username) val goals = findAllByLifeMapIdWithCursor(lifeMap.lifeMapId, request) val goalIds = goals.contents.map { it.goalId } - val goalTimelineCountMap = findGoalTimelineCountMap(goalIds) + val goalCountMap = findGoalCountMap(goalIds) val reactedEmojiMap = emojiService.findAllByGoalIds(goalIds, lifeMap.user.id) return PaginationResult.from( - lifeMap.goals.size.toLong(), - goals.transform() { - goal -> + lifeMap.goals.size, + goals.transform { GoalTimelinePageResult.from( - goal, - goalTimelineCountMap[goal.goalId], - reactedEmojiMap[goal.goalId], + goal = it, + counts = goalCountMap[it.goalId], + reactedEmojisResult = reactedEmojiMap[it.goalId], ) }, ) } - private fun findGoalTimelineCountMap(goalIds: List): Map { + private fun findGoalCountMap(goalIds: List): Map { val commentCountMap = commentService.findGoalCommentCounts(goalIds).associate { it.goalId to it.commentCount } val taskCountMap = taskService.findGoalTaskCounts(goalIds).associate { it.goalId to it.taskCount } - return goalIds.associate { - it to GoalTimelineCountSubset.of( + return goalIds.associateWith { + GoalTimelineCountSubset.of( commentCountMap[it] ?: 0, taskCountMap[it] ?: 0, ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt index b6b80228..c7e386c7 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt @@ -2,14 +2,11 @@ package io.raemian.api.lifemap.controller import io.raemian.api.auth.model.CurrentUser import io.raemian.api.cheer.service.CheeringService -import io.raemian.api.goal.controller.request.TimelinePageRequest -import io.raemian.api.goal.model.GoalTimelinePageResult import io.raemian.api.goal.service.GoalQueryService import io.raemian.api.lifemap.controller.request.UpdatePublicRequest import io.raemian.api.lifemap.model.LifeMapResponse import io.raemian.api.lifemap.service.LifeMapService import io.raemian.api.support.response.ApiResponse -import io.raemian.api.support.response.PaginationResult import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity import org.springframework.security.core.annotation.AuthenticationPrincipal @@ -32,7 +29,7 @@ class LifeMapController( fun findAllByCurrentUser( @AuthenticationPrincipal currentUser: CurrentUser, ): ResponseEntity> { - val lifeMap = lifeMapService.findFirstByUserId(currentUser.id) + val lifeMap = lifeMapService.getFirstByUserId(currentUser.id) val count = lifeMapService.getLifeMapCount(lifeMap.lifeMapId) val cheeringCount = cheeringService.getCheeringCount(currentUser.id) @@ -40,16 +37,6 @@ class LifeMapController( .ok(ApiResponse.success(LifeMapResponse(lifeMap, count, cheeringCount))) } - @Operation(summary = "로그인한 유저의 인생 지도 타임 라인 조회 API") - @GetMapping("/timeline") - fun getTimeline( - @AuthenticationPrincipal currentUser: CurrentUser, - request: TimelinePageRequest, - ): ResponseEntity>> { - val goalTimeline = goalQueryService.findAllByUserIdWithCursor(currentUser.id, request) - return ResponseEntity.ok(ApiResponse.success(goalTimeline)) - } - @Operation(summary = "인생 지도 공개 여부를 수정하는 API") @PatchMapping("/publication") fun updatePublic( diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt index 8541f625..4ca2e423 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt @@ -31,7 +31,7 @@ class OpenLifeMapController( @AuthenticationPrincipal currentUser: CurrentUser?, @PathVariable("username") username: String, ): ResponseEntity> { - val lifeMap = lifeMapService.findFirstByUserName(username) + val lifeMap = lifeMapService.getFirstByUserName(username) val count = lifeMapService.addViewCount(lifeMap.lifeMapId) val cheeringCount = cheeringService.getCheeringCount(username) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/service/LifeMapService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/service/LifeMapService.kt index 4e9c0726..1b3b2422 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/service/LifeMapService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/service/LifeMapService.kt @@ -23,7 +23,7 @@ class LifeMapService( private val lifeMapHistoryRepository: LifeMapHistoryRepository, ) { @Transactional(readOnly = true) - fun findFirstByUserId(userId: Long): LifeMapResult { + fun getFirstByUserId(userId: Long): LifeMapResult { val lifeMap = lifeMapRepository.findFirstByUserId(userId) ?: throw NoSuchElementException("존재하지 않는 유저입니다. $userId") @@ -34,7 +34,7 @@ class LifeMapService( } @Transactional(readOnly = true) - fun findFirstByUserName(username: String): LifeMapResult { + fun getFirstByUserName(username: String): LifeMapResult { val lifeMap = lifeMapRepository.findFirstByUserUsername(username) ?: throw NoSuchElementException("존재하지 않는 유저입니다. $username") diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt index e3ce480a..5443e336 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt @@ -17,5 +17,14 @@ data class PaginationResult( nextCursor = result.nextCursor, ) } + + fun from(total: Int, result: CursorPaginationResult): PaginationResult { + return PaginationResult( + total = total.toLong(), + contents = result.contents, + isLast = result.isLast, + nextCursor = result.nextCursor, + ) + } } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/user/controller/UserController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/user/controller/UserController.kt index 6e724622..bdcf2358 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/user/controller/UserController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/user/controller/UserController.kt @@ -27,7 +27,7 @@ class UserController( @GetMapping("/my") fun my(@AuthenticationPrincipal currentUser: CurrentUser): ResponseEntity> { val user = userService.getUserById(currentUser.id) - val lifeMap = lifeMapService.findFirstByUserId(currentUser.id) + val lifeMap = lifeMapService.getFirstByUserId(currentUser.id) val response = UserTokenDecryptResult.of(user, lifeMap) return ResponseEntity.ok(ApiResponse.success(response)) } diff --git a/backend/application/api/src/test/kotlin/io/raemian/api/integration/lifemap/LifeMapServiceTest.kt b/backend/application/api/src/test/kotlin/io/raemian/api/integration/lifemap/LifeMapServiceTest.kt index 6595bf34..07847c0f 100644 --- a/backend/application/api/src/test/kotlin/io/raemian/api/integration/lifemap/LifeMapServiceTest.kt +++ b/backend/application/api/src/test/kotlin/io/raemian/api/integration/lifemap/LifeMapServiceTest.kt @@ -92,7 +92,7 @@ class LifeMapServiceTest { lifeMap.addGoal(goal2) // when - val savedLifeMap = lifeMapService.findFirstByUserId(USER_FIXTURE.id!!) + val savedLifeMap = lifeMapService.getFirstByUserId(USER_FIXTURE.id!!) // then assertAll( @@ -134,7 +134,7 @@ class LifeMapServiceTest { lifeMap.addGoal(goal2) // when - val savedLifeMap = lifeMapService.findFirstByUserId(USER_FIXTURE.id!!) + val savedLifeMap = lifeMapService.getFirstByUserId(USER_FIXTURE.id!!) // then assertAll( @@ -157,7 +157,7 @@ class LifeMapServiceTest { // when // then assertThatThrownBy { - lifeMapService.findFirstByUserName(USER_FIXTURE.username!!) + lifeMapService.getFirstByUserName(USER_FIXTURE.username!!) }.isInstanceOf(RuntimeException::class.java) } @@ -192,7 +192,7 @@ class LifeMapServiceTest { lifeMap.addGoal(goal2) // when - val savedLifeMap = lifeMapService.findFirstByUserId(USER_FIXTURE.id!!) + val savedLifeMap = lifeMapService.getFirstByUserId(USER_FIXTURE.id!!) // then var month = (now.monthValue).toString() diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt index 57465be6..b9f780c2 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt @@ -9,7 +9,7 @@ object CursorPaginationTemplate { size: Int, query: TriFunction>, ): CursorPaginationResult { - val data = query.apply(id, cursorId ?: 0, size + 1) + val data = query.apply(id, cursorId, size + 1) val isLast = data.size != size + 1 val nextCursor = if (isLast) null else data[size].getCursorId() From bb317a1b55ea2ee5ed86f7e2562d07b4e4b61bdf Mon Sep 17 00:00:00 2001 From: wkwon Date: Tue, 26 Mar 2024 01:01:33 +0900 Subject: [PATCH 37/60] =?UTF-8?q?hotfix:=20dev=20login=20redirect=20url=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 --- .../src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt index 40209532..57f38a90 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt @@ -81,7 +81,7 @@ class WebSecurityConfig( if (profile == "live") { "https://bandiboodi.com/oauth2/token" } else if (profile == "dev") { - "https://dev-bandiboodi.vercel.app/oauth/token" + "https://dev-bandiboodi.vercel.app/oauth2/token" } else { "http://localhost:3000/oauth2/token" } From 25239566c5da2646a2e015b7c2f519c18f66f4c5 Mon Sep 17 00:00:00 2001 From: wkwon Date: Tue, 26 Mar 2024 01:59:14 +0900 Subject: [PATCH 38/60] =?UTF-8?q?hotfix:=20login=20redirect=20url=20refere?= =?UTF-8?q?r=20=EA=B8=B0=EC=A4=80=20=EB=B6=84=EA=B8=B0=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt index 57f38a90..c2cc5c34 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt @@ -80,7 +80,7 @@ class WebSecurityConfig( val redirectUrl = if (profile == "live") { "https://bandiboodi.com/oauth2/token" - } else if (profile == "dev") { + } else if (profile == "dev" && request.getHeader("referer").contains("dev-bandiboodi.vercel.app")) { "https://dev-bandiboodi.vercel.app/oauth2/token" } else { "http://localhost:3000/oauth2/token" From e72fb1cc45a27c6d712020e01b3b5e4b357b0fcf Mon Sep 17 00:00:00 2001 From: wkwon Date: Tue, 26 Mar 2024 12:40:32 +0900 Subject: [PATCH 39/60] =?UTF-8?q?hotfix:=20login=20redirect=20url=20refere?= =?UTF-8?q?r=20=EA=B8=B0=EC=A4=80=20=EB=B6=84=EA=B8=B0=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/io/raemian/api/config/WebSecurityConfig.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt index c2cc5c34..e14b3711 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt @@ -76,11 +76,13 @@ class WebSecurityConfig( val tokenDTO = tokenProvider.generateTokenDto(user) response.setHeader("x-token", tokenDTO.accessToken) log.info("x-token access ${tokenDTO.accessToken}") + // TODO edit redirect url + val referer = request.getHeader("referer") val redirectUrl = if (profile == "live") { "https://bandiboodi.com/oauth2/token" - } else if (profile == "dev" && request.getHeader("referer").contains("dev-bandiboodi.vercel.app")) { + } else if (profile == "dev" && (referer.contains("dev-bandiboodi") || referer.contains("accounts.google"))) { "https://dev-bandiboodi.vercel.app/oauth2/token" } else { "http://localhost:3000/oauth2/token" From 232e3ddebb1c1d6e0ba400923bbd271c394debe7 Mon Sep 17 00:00:00 2001 From: wkwon Date: Tue, 26 Mar 2024 21:05:51 +0900 Subject: [PATCH 40/60] =?UTF-8?q?hotfix:=20login=20redirect=20url=20refere?= =?UTF-8?q?r=20=EA=B8=B0=EC=A4=80=20=EB=B6=84=EA=B8=B0=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=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/io/raemian/api/config/WebSecurityConfig.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt index e14b3711..5842c947 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt @@ -82,8 +82,6 @@ class WebSecurityConfig( val redirectUrl = if (profile == "live") { "https://bandiboodi.com/oauth2/token" - } else if (profile == "dev" && (referer.contains("dev-bandiboodi") || referer.contains("accounts.google"))) { - "https://dev-bandiboodi.vercel.app/oauth2/token" } else { "http://localhost:3000/oauth2/token" } From dc8a0dcc0c977ffeb713ead228f183a2df8c7210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=9A=B0=EC=84=9D=20=28Woosuk=20Kwon=29?= Date: Wed, 27 Mar 2024 00:13:47 +0900 Subject: [PATCH 41/60] =?UTF-8?q?[=F0=9F=8C=8E=20Feature]=20=ED=83=80?= =?UTF-8?q?=EC=9E=84=20=EB=9D=BC=EC=9D=B8=EC=9A=A9=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EA=B0=9C=EB=B0=9C=20-=20Cursor=20De?= =?UTF-8?q?adline=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#242)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * delete: 유저 정보를 활용한 타임라인 조회 API 삭제 * refactor: PaginationResult.transform() 사용 방식 lamda 형태로 통일 * refactor: 메서드 로직에 따라 get, find 명칭 구분 * refactor: lamda body 내 생략 가능한 parameter 생략 * refactor: 메서드명 내 부정확한 단어 제거(findGoalTimelineCountMap -> findGoalCountMap) * refactor: associateWith 메서드 활용 * refactor: PaginationResult total값 Long으로 다시 변경 * fix: 목표 피드 조회 API deadline 역순으로 정렬 조회 * chore: apply lint * fix: goals 테이블의 index 수정 * refactor: pagination cusorId type Any -> Generic으로 변경 --- .../api/cheer/controller/CheeringController.kt | 2 +- .../io/raemian/api/cheer/service/CheeringService.kt | 4 ++-- .../io/raemian/api/config/WebSecurityConfig.kt | 13 +++++-------- .../goal/controller/request/TimelinePageRequest.kt | 4 +++- .../io/raemian/api/goal/service/GoalQueryService.kt | 8 +++++--- .../api/lifemap/controller/OpenLifeMapController.kt | 3 ++- .../api/support/constant/LocalDateTimeConstant.kt | 7 +++++++ .../api/support/response/PaginationResult.kt | 8 ++++---- .../db/core/cheer/model/CheererQueryResult.kt | 4 ++-- .../db/core/common/pagination/CursorExtractable.kt | 4 ++-- .../common/pagination/CursorPaginationResult.kt | 6 +++--- .../common/pagination/CursorPaginationTemplate.kt | 10 +++++----- .../kotlin/io/raemian/storage/db/core/goal/Goal.kt | 3 ++- .../storage/db/core/goal/GoalJdbcQueryRepository.kt | 6 +++--- .../storage/db/core/goal/model/GoalQueryResult.kt | 4 ++-- 15 files changed, 48 insertions(+), 38 deletions(-) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/constant/LocalDateTimeConstant.kt diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt index 64e19472..44f5bba2 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt @@ -25,7 +25,7 @@ class CheeringController( fun findCheeringSquad( @PathVariable("lifeMapId") lifeMapId: Long, request: CheeringSquadPageRequest, - ): ResponseEntity>> = + ): ResponseEntity>> = ResponseEntity.ok().body(ApiResponse.success(cheeringService.findCheeringSquad(lifeMapId, request))) @GetMapping("/count/{userName}") diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt index 4f99a81c..6421e651 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt @@ -46,7 +46,7 @@ class CheeringService( } @Transactional(readOnly = true) - fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPageRequest): PaginationResult { + fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPageRequest): PaginationResult { val cheering = cheeringRepository.findByLifeMapId(lifeMapId) ?: Cheering(0, lifeMapId) @@ -89,7 +89,7 @@ class CheeringService( ) } - private fun findCheeringSquadWithCursor(lifeMapId: Long, request: CheeringSquadPageRequest): CursorPaginationResult { + private fun findCheeringSquadWithCursor(lifeMapId: Long, request: CheeringSquadPageRequest): CursorPaginationResult { return CursorPaginationTemplate.execute(lifeMapId, request.cursor ?: Long.MAX_VALUE, request.size) { id, cursor, size -> cheererJdbcQueryRepository.findAllByLifeMapWithCursor(id, cursor, size) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt index 5842c947..f7e41c65 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt @@ -52,17 +52,13 @@ class WebSecurityConfig( it.authenticationEntryPoint { request, response, authException -> // 유효한 자격증명을 제공하지 않고 접근하려 할때 401 response.sendError(HttpServletResponse.SC_UNAUTHORIZED) + }.accessDeniedHandler { request, response, accessDeniedException -> + // 필요한 권한이 없이 접근하려 할때 403 + response.sendError(HttpServletResponse.SC_FORBIDDEN) } - .accessDeniedHandler { request, response, accessDeniedException -> - // 필요한 권한이 없이 접근하려 할때 403 - response.sendError(HttpServletResponse.SC_FORBIDDEN) - } } .authorizeHttpRequests { - it.requestMatchers(*WebSecurityConstant.PUBLIC_URIS) - .permitAll() - .anyRequest() - .authenticated() + it.requestMatchers(*WebSecurityConstant.PUBLIC_URIS).permitAll().anyRequest().authenticated() } .oauth2Login { it.tokenEndpoint { it.accessTokenResponseClient(accessTokenResponseClient()) } @@ -85,6 +81,7 @@ class WebSecurityConfig( } else { "http://localhost:3000/oauth2/token" } + response.sendRedirect("$redirectUrl?token=${tokenDTO.accessToken}&refresh=${tokenDTO.refreshToken}") } it.failureHandler { request, response, exception -> diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt index d1043e77..5f8eef6f 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt @@ -1,6 +1,8 @@ package io.raemian.api.goal.controller.request +import java.time.LocalDateTime + data class TimelinePageRequest( - val cursor: Long?, + val cursor: LocalDateTime?, val size: Int, ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt index 5624a1c6..78bf222e 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt @@ -6,6 +6,7 @@ import io.raemian.api.goal.controller.request.TimelinePageRequest import io.raemian.api.goal.model.GoalTimelineCountSubset import io.raemian.api.goal.model.GoalTimelinePageResult import io.raemian.api.lifemap.service.LifeMapService +import io.raemian.api.support.constant.LocalDateTimeConstant import io.raemian.api.support.response.PaginationResult import io.raemian.api.task.service.TaskService import io.raemian.storage.db.core.common.pagination.CursorPaginationResult @@ -14,6 +15,7 @@ import io.raemian.storage.db.core.goal.GoalJdbcQueryRepository import io.raemian.storage.db.core.goal.model.GoalQueryResult import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import java.time.LocalDateTime @Service class GoalQueryService( @@ -24,7 +26,7 @@ class GoalQueryService( private val commentService: CommentService, ) { @Transactional(readOnly = true) - fun findAllByUsernameWithCursor(username: String, request: TimelinePageRequest): PaginationResult { + fun findAllByUsernameWithCursor(username: String, request: TimelinePageRequest): PaginationResult { val lifeMap = lifeMapService.getFirstByUserName(username) val goals = findAllByLifeMapIdWithCursor(lifeMap.lifeMapId, request) @@ -57,8 +59,8 @@ class GoalQueryService( } } - private fun findAllByLifeMapIdWithCursor(lifeMapId: Long, request: TimelinePageRequest): CursorPaginationResult { - return CursorPaginationTemplate.execute(lifeMapId, request.cursor ?: Long.MAX_VALUE, request.size) { + private fun findAllByLifeMapIdWithCursor(lifeMapId: Long, request: TimelinePageRequest): CursorPaginationResult { + return CursorPaginationTemplate.execute(lifeMapId, request.cursor ?: LocalDateTimeConstant.MAX, request.size) { id, cursor, size -> goalJdbcQueryRepository.findAllByLifeMapWithCursor(id, cursor, size) } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt index 4ca2e423..47aba9ff 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt @@ -16,6 +16,7 @@ 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.time.LocalDateTime @RestController @RequestMapping("/open/life-map") @@ -50,7 +51,7 @@ class OpenLifeMapController( fun getTimeline( @PathVariable("username") username: String, request: TimelinePageRequest, - ): ResponseEntity>> { + ): ResponseEntity>> { val goalTimeline = goalQueryService.findAllByUsernameWithCursor(username, request) return ResponseEntity.ok(ApiResponse.success(goalTimeline)) } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/constant/LocalDateTimeConstant.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/constant/LocalDateTimeConstant.kt new file mode 100644 index 00000000..e20a03c7 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/constant/LocalDateTimeConstant.kt @@ -0,0 +1,7 @@ +package io.raemian.api.support.constant + +import java.time.LocalDateTime + +object LocalDateTimeConstant { + val MAX: LocalDateTime = LocalDateTime.of(9999, 12, 31, 23, 59) +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt index 5443e336..3d915fa0 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt @@ -2,14 +2,14 @@ package io.raemian.api.support.response import io.raemian.storage.db.core.common.pagination.CursorPaginationResult -data class PaginationResult( +data class PaginationResult( val total: Long, val contents: List, val isLast: Boolean, - val nextCursor: Long?, + val nextCursor: CursorType?, ) { companion object { - fun from(total: Long, result: CursorPaginationResult): PaginationResult { + fun from(total: Long, result: CursorPaginationResult): PaginationResult { return PaginationResult( total = total, contents = result.contents, @@ -18,7 +18,7 @@ data class PaginationResult( ) } - fun from(total: Int, result: CursorPaginationResult): PaginationResult { + fun from(total: Int, result: CursorPaginationResult): PaginationResult { return PaginationResult( total = total.toLong(), contents = result.contents, diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt index 4075b6d9..e764576f 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt @@ -10,6 +10,6 @@ data class CheererQueryResult( val userNickName: String?, val userImageUrl: String?, val cheeringAt: LocalDateTime, -) : CursorExtractable { - override fun getCursorId(): Long = cheererId +) : CursorExtractable { + override fun cursorId(): Long = cheererId } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt index 645a8325..edcc0a34 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt @@ -1,5 +1,5 @@ package io.raemian.storage.db.core.common.pagination -interface CursorExtractable { - fun getCursorId(): Long +interface CursorExtractable { + fun cursorId(): CursorType } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationResult.kt index 628378ec..f32d561a 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationResult.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationResult.kt @@ -2,12 +2,12 @@ package io.raemian.storage.db.core.common.pagination import java.util.function.Function -data class CursorPaginationResult internal constructor( +data class CursorPaginationResult internal constructor( val contents: List, - val nextCursor: Long?, + val nextCursor: CursorType?, val isLast: Boolean, ) { - fun transform(transformer: Function): CursorPaginationResult { + fun transform(transformer: Function): CursorPaginationResult { return CursorPaginationResult( contents.stream().map(transformer).toList(), nextCursor, diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt index b9f780c2..f0271e76 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt @@ -3,15 +3,15 @@ package io.raemian.storage.db.core.common.pagination import kotlin.math.min object CursorPaginationTemplate { - fun execute( + fun > execute( id: Long, - cursorId: Long, + cursorId: CursorType, size: Int, - query: TriFunction>, - ): CursorPaginationResult { + query: TriFunction>, + ): CursorPaginationResult { val data = query.apply(id, cursorId, size + 1) val isLast = data.size != size + 1 - val nextCursor = if (isLast) null else data[size].getCursorId() + val nextCursor = if (isLast) null else data[size].cursorId() return CursorPaginationResult( data.subList(0, min(data.size, size)), diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt index c48405b1..cda62d6d 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/Goal.kt @@ -12,6 +12,7 @@ import jakarta.persistence.FetchType import jakarta.persistence.GeneratedValue import jakarta.persistence.GenerationType import jakarta.persistence.Id +import jakarta.persistence.Index import jakarta.persistence.JoinColumn import jakarta.persistence.ManyToOne import jakarta.persistence.OneToMany @@ -21,7 +22,7 @@ import java.time.LocalDate import java.time.LocalDateTime @Entity -@Table(name = "GOALS") +@Table(name = "GOALS", indexes = [Index(name = "IDX_LIFE_MAP_ID_AND_DEADLINE", columnList = "life_map_id, deadline desc")]) class Goal( @ManyToOne @JoinColumn(name = "life_map_id", nullable = false) diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt index 5054c18e..bf0054cc 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt @@ -10,7 +10,7 @@ import java.time.LocalDateTime class GoalJdbcQueryRepository( private val jdbcTemplate: NamedParameterJdbcTemplate, ) { - fun findAllByLifeMapWithCursor(lifeMapId: Long, cursor: Long, size: Int): List { + fun findAllByLifeMapWithCursor(lifeMapId: Long, cursor: LocalDateTime, size: Int): List { val sql = """ SELECT @@ -26,8 +26,8 @@ class GoalJdbcQueryRepository( LEFT OUTER JOIN stickers s ON g.STICKER_ID = s.ID WHERE 1 = 1 AND g.LIFE_MAP_ID = :lifeMapId - AND g.ID <= :cursor - ORDER BY g.ID DESC + AND g.DEADLINE <= :cursor + ORDER BY g.DEADLINE DESC LIMIT :size """.trimIndent() diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt index 7c525467..58d40f88 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt @@ -11,6 +11,6 @@ data class GoalQueryResult( val stickerUrl: String, val tag: String, val createdAt: LocalDateTime, -) : CursorExtractable { - override fun getCursorId(): Long = goalId +) : CursorExtractable { + override fun cursorId(): LocalDateTime = deadline } From ca23a69fbce73b63a5f9d8968ae6e98bbfb5af53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=9A=B0=EC=84=9D=20=28Woosuk=20Kwon=29?= Date: Thu, 28 Mar 2024 22:08:27 +0900 Subject: [PATCH 42/60] =?UTF-8?q?[=F0=9F=8C=8E=20Feature]=20=ED=83=80?= =?UTF-8?q?=EC=9E=84=20=EB=9D=BC=EC=9D=B8=EC=9A=A9=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EA=B0=9C=EB=B0=9C=20-=20Pagination?= =?UTF-8?q?=20=EB=B0=A9=EC=8B=9D=20offset=20=EA=B8=B0=EB=B0=98=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#243)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * delete: 유저 정보를 활용한 타임라인 조회 API 삭제 * refactor: PaginationResult.transform() 사용 방식 lamda 형태로 통일 * refactor: 메서드 로직에 따라 get, find 명칭 구분 * refactor: lamda body 내 생략 가능한 parameter 생략 * refactor: 메서드명 내 부정확한 단어 제거(findGoalTimelineCountMap -> findGoalCountMap) * refactor: associateWith 메서드 활용 * refactor: PaginationResult total값 Long으로 다시 변경 * fix: 목표 피드 조회 API deadline 역순으로 정렬 조회 * chore: apply lint * fix: goals 테이블의 index 수정 * refactor: pagination cusorId type Any -> Generic으로 변경 * fix: 타임 라인 목표 조회 API paging 처리 방식 offset으로 변경 * refactor: cursor 기반 pagination 공통 모듈 cursor type long으로 통일 * refactor: pagination 공통 모듈 cursor와 offset 구분 * refactor: 미사용 코드 및 의미 없는 중복 코드 삭제 --- .../cheer/controller/CheeringController.kt | 4 +-- .../api/cheer/service/CheeringService.kt | 10 +++--- .../controller/request/TimelinePageRequest.kt | 6 ++-- .../api/goal/service/GoalQueryService.kt | 31 +++++++------------ .../lifemap/controller/LifeMapController.kt | 2 -- .../controller/OpenLifeMapController.kt | 7 ++--- ...ionResult.kt => CursorPaginationResult.kt} | 14 ++++----- .../response/OffsetPaginationResult.kt | 14 +++++++++ .../db/core/cheer/model/CheererQueryResult.kt | 2 +- .../common/pagination/CursorExtractable.kt | 4 +-- .../pagination/CursorPaginationTemplate.kt | 10 +++--- ...aginationResult.kt => PaginationResult.kt} | 8 ++--- .../db/core/goal/GoalJdbcQueryRepository.kt | 6 ++-- .../storage/db/core/goal/GoalRepository.kt | 3 ++ .../db/core/goal/model/GoalQueryResult.kt | 5 +-- 15 files changed, 64 insertions(+), 62 deletions(-) rename backend/application/api/src/main/kotlin/io/raemian/api/support/response/{PaginationResult.kt => CursorPaginationResult.kt} (52%) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/response/OffsetPaginationResult.kt rename backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/{CursorPaginationResult.kt => PaginationResult.kt} (53%) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt index 44f5bba2..23e4bf32 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/controller/CheeringController.kt @@ -6,7 +6,7 @@ import io.raemian.api.cheer.model.CheererResult import io.raemian.api.cheer.model.CheeringCountResult import io.raemian.api.cheer.service.CheeringService import io.raemian.api.support.response.ApiResponse -import io.raemian.api.support.response.PaginationResult +import io.raemian.api.support.response.CursorPaginationResult import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -25,7 +25,7 @@ class CheeringController( fun findCheeringSquad( @PathVariable("lifeMapId") lifeMapId: Long, request: CheeringSquadPageRequest, - ): ResponseEntity>> = + ): ResponseEntity>> = ResponseEntity.ok().body(ApiResponse.success(cheeringService.findCheeringSquad(lifeMapId, request))) @GetMapping("/count/{userName}") diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt index 6421e651..0a04b452 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt @@ -8,15 +8,15 @@ import io.raemian.api.event.model.CheeredEvent import io.raemian.api.support.exception.CoreApiException import io.raemian.api.support.exception.ErrorInfo import io.raemian.api.support.limiter.CheeringLimiter -import io.raemian.api.support.response.PaginationResult +import io.raemian.api.support.response.CursorPaginationResult import io.raemian.storage.db.core.cheer.CheerJdbcQueryRepository import io.raemian.storage.db.core.cheer.Cheerer import io.raemian.storage.db.core.cheer.CheererRepository import io.raemian.storage.db.core.cheer.Cheering import io.raemian.storage.db.core.cheer.CheeringRepository import io.raemian.storage.db.core.cheer.model.CheererQueryResult -import io.raemian.storage.db.core.common.pagination.CursorPaginationResult import io.raemian.storage.db.core.common.pagination.CursorPaginationTemplate +import io.raemian.storage.db.core.common.pagination.PaginationResult import io.raemian.storage.db.core.lifemap.LifeMapRepository import io.raemian.storage.db.core.user.UserRepository import org.springframework.context.ApplicationEventPublisher @@ -46,7 +46,7 @@ class CheeringService( } @Transactional(readOnly = true) - fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPageRequest): PaginationResult { + fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPageRequest): CursorPaginationResult { val cheering = cheeringRepository.findByLifeMapId(lifeMapId) ?: Cheering(0, lifeMapId) @@ -54,7 +54,7 @@ class CheeringService( val filteredCheeringSquad = cheeringSquad.transform { CheererResult.from(it) } - return PaginationResult.from(cheering.count, filteredCheeringSquad) + return CursorPaginationResult.from(cheering.count, filteredCheeringSquad) } @Transactional(readOnly = true) @@ -89,7 +89,7 @@ class CheeringService( ) } - private fun findCheeringSquadWithCursor(lifeMapId: Long, request: CheeringSquadPageRequest): CursorPaginationResult { + private fun findCheeringSquadWithCursor(lifeMapId: Long, request: CheeringSquadPageRequest): PaginationResult { return CursorPaginationTemplate.execute(lifeMapId, request.cursor ?: Long.MAX_VALUE, request.size) { id, cursor, size -> cheererJdbcQueryRepository.findAllByLifeMapWithCursor(id, cursor, size) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt index 5f8eef6f..4a6e0017 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/controller/request/TimelinePageRequest.kt @@ -1,8 +1,6 @@ package io.raemian.api.goal.controller.request -import java.time.LocalDateTime - data class TimelinePageRequest( - val cursor: LocalDateTime?, - val size: Int, + val page: Int = 0, + val size: Int = 20, ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt index 78bf222e..7dccfd6a 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt @@ -6,38 +6,38 @@ import io.raemian.api.goal.controller.request.TimelinePageRequest import io.raemian.api.goal.model.GoalTimelineCountSubset import io.raemian.api.goal.model.GoalTimelinePageResult import io.raemian.api.lifemap.service.LifeMapService -import io.raemian.api.support.constant.LocalDateTimeConstant -import io.raemian.api.support.response.PaginationResult +import io.raemian.api.support.response.OffsetPaginationResult import io.raemian.api.task.service.TaskService -import io.raemian.storage.db.core.common.pagination.CursorPaginationResult -import io.raemian.storage.db.core.common.pagination.CursorPaginationTemplate import io.raemian.storage.db.core.goal.GoalJdbcQueryRepository -import io.raemian.storage.db.core.goal.model.GoalQueryResult +import io.raemian.storage.db.core.goal.GoalRepository import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional -import java.time.LocalDateTime @Service class GoalQueryService( private val lifeMapService: LifeMapService, private val goalJdbcQueryRepository: GoalJdbcQueryRepository, + private val goalRepository: GoalRepository, private val emojiService: EmojiService, private val taskService: TaskService, private val commentService: CommentService, ) { @Transactional(readOnly = true) - fun findAllByUsernameWithCursor(username: String, request: TimelinePageRequest): PaginationResult { + fun findAllByUsernameWithOffset(username: String, request: TimelinePageRequest): OffsetPaginationResult { val lifeMap = lifeMapService.getFirstByUserName(username) - val goals = findAllByLifeMapIdWithCursor(lifeMap.lifeMapId, request) + val goals = goalJdbcQueryRepository.findAllByLifeMapWithOffset(lifeMap.lifeMapId, request.page, request.size) + val total = goalRepository.countByLifeMapId(lifeMap.lifeMapId) - val goalIds = goals.contents.map { it.goalId } + val goalIds = goals.map { it.goalId } val goalCountMap = findGoalCountMap(goalIds) val reactedEmojiMap = emojiService.findAllByGoalIds(goalIds, lifeMap.user.id) - return PaginationResult.from( - lifeMap.goals.size, - goals.transform { + return OffsetPaginationResult.of( + request.page, + request.size, + total, + goals.map { GoalTimelinePageResult.from( goal = it, counts = goalCountMap[it.goalId], @@ -58,11 +58,4 @@ class GoalQueryService( ) } } - - private fun findAllByLifeMapIdWithCursor(lifeMapId: Long, request: TimelinePageRequest): CursorPaginationResult { - return CursorPaginationTemplate.execute(lifeMapId, request.cursor ?: LocalDateTimeConstant.MAX, request.size) { - id, cursor, size -> - goalJdbcQueryRepository.findAllByLifeMapWithCursor(id, cursor, size) - } - } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt index c7e386c7..fe534279 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt @@ -2,7 +2,6 @@ package io.raemian.api.lifemap.controller import io.raemian.api.auth.model.CurrentUser import io.raemian.api.cheer.service.CheeringService -import io.raemian.api.goal.service.GoalQueryService import io.raemian.api.lifemap.controller.request.UpdatePublicRequest import io.raemian.api.lifemap.model.LifeMapResponse import io.raemian.api.lifemap.service.LifeMapService @@ -21,7 +20,6 @@ import org.springframework.web.bind.annotation.RestController class LifeMapController( private val lifeMapService: LifeMapService, private val cheeringService: CheeringService, - private val goalQueryService: GoalQueryService, ) { @Operation(summary = "로그인한 유저의 인생 지도 조회 API") diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt index 47aba9ff..8f7002c8 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt @@ -8,7 +8,7 @@ import io.raemian.api.goal.service.GoalQueryService import io.raemian.api.lifemap.model.LifeMapResponse import io.raemian.api.lifemap.service.LifeMapService import io.raemian.api.support.response.ApiResponse -import io.raemian.api.support.response.PaginationResult +import io.raemian.api.support.response.OffsetPaginationResult import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity import org.springframework.security.core.annotation.AuthenticationPrincipal @@ -16,7 +16,6 @@ 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.time.LocalDateTime @RestController @RequestMapping("/open/life-map") @@ -51,8 +50,8 @@ class OpenLifeMapController( fun getTimeline( @PathVariable("username") username: String, request: TimelinePageRequest, - ): ResponseEntity>> { - val goalTimeline = goalQueryService.findAllByUsernameWithCursor(username, request) + ): ResponseEntity>> { + val goalTimeline = goalQueryService.findAllByUsernameWithOffset(username, request) return ResponseEntity.ok(ApiResponse.success(goalTimeline)) } } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/CursorPaginationResult.kt similarity index 52% rename from backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/support/response/CursorPaginationResult.kt index 3d915fa0..3671fb59 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/PaginationResult.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/CursorPaginationResult.kt @@ -1,16 +1,16 @@ package io.raemian.api.support.response -import io.raemian.storage.db.core.common.pagination.CursorPaginationResult +import io.raemian.storage.db.core.common.pagination.PaginationResult -data class PaginationResult( +data class CursorPaginationResult( val total: Long, val contents: List, val isLast: Boolean, - val nextCursor: CursorType?, + val nextCursor: Long?, ) { companion object { - fun from(total: Long, result: CursorPaginationResult): PaginationResult { - return PaginationResult( + fun from(total: Long, result: PaginationResult): CursorPaginationResult { + return CursorPaginationResult( total = total, contents = result.contents, isLast = result.isLast, @@ -18,8 +18,8 @@ data class PaginationResult( ) } - fun from(total: Int, result: CursorPaginationResult): PaginationResult { - return PaginationResult( + fun from(total: Int, result: PaginationResult): CursorPaginationResult { + return CursorPaginationResult( total = total.toLong(), contents = result.contents, isLast = result.isLast, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/OffsetPaginationResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/OffsetPaginationResult.kt new file mode 100644 index 00000000..6490cec5 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/OffsetPaginationResult.kt @@ -0,0 +1,14 @@ +package io.raemian.api.support.response + +data class OffsetPaginationResult( + val total: Long, + val page: Int, + val size: Int, + val contents: List, +) { + companion object { + fun of(page: Int, size: Int, total: Long, contents: List): OffsetPaginationResult { + return OffsetPaginationResult(total, page, size, contents) + } + } +} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt index e764576f..286d2512 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/cheer/model/CheererQueryResult.kt @@ -10,6 +10,6 @@ data class CheererQueryResult( val userNickName: String?, val userImageUrl: String?, val cheeringAt: LocalDateTime, -) : CursorExtractable { +) : CursorExtractable { override fun cursorId(): Long = cheererId } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt index edcc0a34..f44f2c8c 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorExtractable.kt @@ -1,5 +1,5 @@ package io.raemian.storage.db.core.common.pagination -interface CursorExtractable { - fun cursorId(): CursorType +interface CursorExtractable { + fun cursorId(): Long } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt index f0271e76..2c9a7e66 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationTemplate.kt @@ -3,17 +3,17 @@ package io.raemian.storage.db.core.common.pagination import kotlin.math.min object CursorPaginationTemplate { - fun > execute( + fun execute( id: Long, - cursorId: CursorType, + cursorId: Long, size: Int, - query: TriFunction>, - ): CursorPaginationResult { + query: TriFunction>, + ): PaginationResult { val data = query.apply(id, cursorId, size + 1) val isLast = data.size != size + 1 val nextCursor = if (isLast) null else data[size].cursorId() - return CursorPaginationResult( + return PaginationResult( data.subList(0, min(data.size, size)), nextCursor, isLast, diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/PaginationResult.kt similarity index 53% rename from backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationResult.kt rename to backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/PaginationResult.kt index f32d561a..b4b5c8de 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/CursorPaginationResult.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/common/pagination/PaginationResult.kt @@ -2,13 +2,13 @@ package io.raemian.storage.db.core.common.pagination import java.util.function.Function -data class CursorPaginationResult internal constructor( +data class PaginationResult internal constructor( val contents: List, - val nextCursor: CursorType?, + val nextCursor: Long?, val isLast: Boolean, ) { - fun transform(transformer: Function): CursorPaginationResult { - return CursorPaginationResult( + fun transform(transformer: Function): PaginationResult { + return PaginationResult( contents.stream().map(transformer).toList(), nextCursor, isLast, diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt index bf0054cc..01101532 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalJdbcQueryRepository.kt @@ -10,7 +10,7 @@ import java.time.LocalDateTime class GoalJdbcQueryRepository( private val jdbcTemplate: NamedParameterJdbcTemplate, ) { - fun findAllByLifeMapWithCursor(lifeMapId: Long, cursor: LocalDateTime, size: Int): List { + fun findAllByLifeMapWithOffset(lifeMapId: Long, page: Int, size: Int): List { val sql = """ SELECT @@ -26,14 +26,14 @@ class GoalJdbcQueryRepository( LEFT OUTER JOIN stickers s ON g.STICKER_ID = s.ID WHERE 1 = 1 AND g.LIFE_MAP_ID = :lifeMapId - AND g.DEADLINE <= :cursor ORDER BY g.DEADLINE DESC LIMIT :size + OFFSET :page """.trimIndent() val namedParameter = MapSqlParameterSource() .addValue("lifeMapId", lifeMapId) - .addValue("cursor", cursor) + .addValue("page", page) .addValue("size", size) return jdbcTemplate.query(sql, namedParameter) { diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt index c1d71eb1..7112dcca 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt @@ -8,6 +8,9 @@ import org.springframework.data.repository.query.Param import java.time.LocalDateTime interface GoalRepository : JpaRepository { + + fun countByLifeMapId(lifeMapId: Long): Long + fun findUserByCreatedAtGreaterThanEqual(createdAt: LocalDateTime): List override fun getById(id: Long): Goal = diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt index 58d40f88..80a1bc46 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalQueryResult.kt @@ -1,6 +1,5 @@ package io.raemian.storage.db.core.goal.model -import io.raemian.storage.db.core.common.pagination.CursorExtractable import java.time.LocalDateTime data class GoalQueryResult( @@ -11,6 +10,4 @@ data class GoalQueryResult( val stickerUrl: String, val tag: String, val createdAt: LocalDateTime, -) : CursorExtractable { - override fun cursorId(): LocalDateTime = deadline -} +) From 1093207c2681d383624b50f84af5dd2b7dcda7a8 Mon Sep 17 00:00:00 2001 From: wkwon Date: Fri, 29 Mar 2024 19:41:12 +0900 Subject: [PATCH 43/60] =?UTF-8?q?hotfix:=20global=20exception=20config=20?= =?UTF-8?q?=EB=82=B4=20NoSuchElementException=20=EC=84=A4=EC=A0=95=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 --- .../main/kotlin/io/raemian/api/config/GlobalExceptionConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/GlobalExceptionConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/GlobalExceptionConfig.kt index 732c4ada..e923729e 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/GlobalExceptionConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/GlobalExceptionConfig.kt @@ -24,7 +24,7 @@ class GlobalExceptionConfig( } @ExceptionHandler(NoSuchElementException::class, EntityNotFoundException::class) - fun handleResourceNotFoundException(e: CoreApiException): ResponseEntity> { + fun handleResourceNotFoundException(e: Exception): ResponseEntity> { log.error("Exception : {}", e.message, e) return ResponseEntity( ApiResponse.error(ErrorInfo.RESOURCE_NOT_FOUND), From 4f570c2ce662a99c5b14d3f85198108039095da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=9A=B0=EC=84=9D=20=28Woosuk=20Kwon=29?= Date: Sun, 31 Mar 2024 20:22:43 +0900 Subject: [PATCH 44/60] =?UTF-8?q?[=F0=9F=9A=8C=20Issue]=20dev=20api=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=9A=94=EC=B2=AD=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=EC=97=90=20=EB=94=B0=EB=9D=BC=20redirect=20url=20?= =?UTF-8?q?=EB=B3=80=ED=99=98=20(#245)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * delete: 유저 정보를 활용한 타임라인 조회 API 삭제 * refactor: PaginationResult.transform() 사용 방식 lamda 형태로 통일 * refactor: 메서드 로직에 따라 get, find 명칭 구분 * refactor: lamda body 내 생략 가능한 parameter 생략 * refactor: 메서드명 내 부정확한 단어 제거(findGoalTimelineCountMap -> findGoalCountMap) * refactor: associateWith 메서드 활용 * refactor: PaginationResult total값 Long으로 다시 변경 * fix: 목표 피드 조회 API deadline 역순으로 정렬 조회 * chore: apply lint * fix: goals 테이블의 index 수정 * refactor: pagination cusorId type Any -> Generic으로 변경 * fix: 타임 라인 목표 조회 API paging 처리 방식 offset으로 변경 * refactor: cursor 기반 pagination 공통 모듈 cursor type long으로 통일 * refactor: pagination 공통 모듈 cursor와 offset 구분 * feat(#244): dev api 로그인 요청 위치에 따라 redirect url 변환 * chore: apply lint * fix: @Value(spring.profiles.active) default값 추가 * refactor: 코드 리뷰 반영(#245) * chore: encrypt하던 데이터 삭제 * chore: encrypted test properties 삭제 * chore: referer 저장/조회 logging 추가 --- .../service}/CheeringLimiter.kt | 2 +- .../api/cheer/service/CheeringService.kt | 1 - .../raemian/api/config/WebSecurityConfig.kt | 27 ++++------------- .../api/support/security/LoginRedirector.kt | 29 +++++++++++++++++++ .../security/LoginRequestRefererStorage.kt | 24 +++++++++++++++ .../api/support/security/ProfileHolder.kt | 18 ++++++++++++ .../api/support/security/ProfileType.kt | 17 +++++++++++ .../api/support/security/RedirectUrlHolder.kt | 10 +++++++ ...ateOAuth2AuthorizationRequestRepository.kt | 14 ++++++++- .../resources/application-security-dev.yml | 5 ++++ .../resources/application-security-live.yml | 5 ++++ .../resources/application-security-local.yml | 5 ++++ .../resources/application-security-test.yml | 7 ++++- 13 files changed, 138 insertions(+), 26 deletions(-) rename backend/application/api/src/main/kotlin/io/raemian/api/{support/limiter => cheer/service}/CheeringLimiter.kt (95%) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/security/LoginRedirector.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/security/LoginRequestRefererStorage.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/security/ProfileHolder.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/security/ProfileType.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/support/security/RedirectUrlHolder.kt diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/limiter/CheeringLimiter.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringLimiter.kt similarity index 95% rename from backend/application/api/src/main/kotlin/io/raemian/api/support/limiter/CheeringLimiter.kt rename to backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringLimiter.kt index 5d33d456..b764f282 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/limiter/CheeringLimiter.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringLimiter.kt @@ -1,4 +1,4 @@ -package io.raemian.api.support.limiter +package io.raemian.api.cheer.service import com.github.benmanes.caffeine.cache.Caffeine import org.springframework.stereotype.Component diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt index 0a04b452..89e0fbbe 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/cheer/service/CheeringService.kt @@ -7,7 +7,6 @@ import io.raemian.api.cheer.model.CheeringCountResult import io.raemian.api.event.model.CheeredEvent import io.raemian.api.support.exception.CoreApiException import io.raemian.api.support.exception.ErrorInfo -import io.raemian.api.support.limiter.CheeringLimiter import io.raemian.api.support.response.CursorPaginationResult import io.raemian.storage.db.core.cheer.CheerJdbcQueryRepository import io.raemian.storage.db.core.cheer.Cheerer diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt index f7e41c65..91492a91 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/config/WebSecurityConfig.kt @@ -4,14 +4,13 @@ import io.raemian.api.auth.converter.TokenRequestEntityConverter import io.raemian.api.auth.model.CurrentUser import io.raemian.api.auth.service.OAuth2UserService import io.raemian.api.support.constant.WebSecurityConstant +import io.raemian.api.support.security.LoginRedirector import io.raemian.api.support.security.StateOAuth2AuthorizationRequestRepository import io.raemian.api.support.security.TokenProvider import jakarta.servlet.http.HttpServletResponse import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.http.MediaType import org.springframework.security.config.annotation.SecurityConfigurerAdapter import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity @@ -25,7 +24,6 @@ import org.springframework.security.web.DefaultSecurityFilterChain import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.web.filter.CorsFilter -import java.nio.charset.StandardCharsets @Configuration @EnableWebSecurity @@ -33,8 +31,7 @@ class WebSecurityConfig( private val corsFilter: CorsFilter, private val tokenProvider: TokenProvider, private val oAuth2UserService: OAuth2UserService, - @Value("\${spring.profiles.active:local}") - private val profile: String, + private val loginRedirector: LoginRedirector, private val tokenRequestEntityConverter: TokenRequestEntityConverter, private val httpCookieOAuth2AuthorizationRequestRepository: StateOAuth2AuthorizationRequestRepository, ) : SecurityConfigurerAdapter() { @@ -66,23 +63,9 @@ class WebSecurityConfig( it.authorizationEndpoint { it.authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository) } it.successHandler { request, response, authentication -> val user = authentication.principal as CurrentUser - response.contentType = MediaType.APPLICATION_JSON_VALUE - response.characterEncoding = StandardCharsets.UTF_8.name() - - val tokenDTO = tokenProvider.generateTokenDto(user) - response.setHeader("x-token", tokenDTO.accessToken) - log.info("x-token access ${tokenDTO.accessToken}") - - // TODO edit redirect url - val referer = request.getHeader("referer") - val redirectUrl = - if (profile == "live") { - "https://bandiboodi.com/oauth2/token" - } else { - "http://localhost:3000/oauth2/token" - } - - response.sendRedirect("$redirectUrl?token=${tokenDTO.accessToken}&refresh=${tokenDTO.refreshToken}") + val token = tokenProvider.generateTokenDto(user) + val redirectUrl = loginRedirector.getUrl(request.getParameter("state"), token) + response.sendRedirect(redirectUrl) } it.failureHandler { request, response, exception -> log.error("x-token error ${exception.message}") diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/security/LoginRedirector.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/LoginRedirector.kt new file mode 100644 index 00000000..d5fef4a7 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/LoginRedirector.kt @@ -0,0 +1,29 @@ +package io.raemian.api.support.security + +import io.raemian.api.auth.model.Token +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Component + +@Component +class LoginRedirector( + val profilePlaceHolder: ProfileHolder, + val redirectUrlHolder: RedirectUrlHolder, + val loginRequestRefererStorage: LoginRequestRefererStorage, +) { + private val log = LoggerFactory.getLogger(javaClass) + + fun getUrl(state: String, token: Token): String { + if (profilePlaceHolder.isLive()) { + return "${redirectUrlHolder.live}?token=${token.accessToken}&refresh=${token.refreshToken}" + } + + val referer = loginRequestRefererStorage.get(state) + log.info("get referer: {}", referer) + + if (profilePlaceHolder.isDev() && referer.contains("dev-bandiboodi")) { + return "${redirectUrlHolder.dev}?token=${token.accessToken}&refresh=${token.refreshToken}" + } + + return "${redirectUrlHolder.local}?token=${token.accessToken}&refresh=${token.refreshToken}" + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/security/LoginRequestRefererStorage.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/LoginRequestRefererStorage.kt new file mode 100644 index 00000000..19669249 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/LoginRequestRefererStorage.kt @@ -0,0 +1,24 @@ +package io.raemian.api.support.security + +import com.github.benmanes.caffeine.cache.Caffeine +import org.springframework.stereotype.Component +import java.util.concurrent.TimeUnit + +@Component +class LoginRequestRefererStorage { + private val timedStorage = Caffeine.newBuilder() + .expireAfterWrite(60L, TimeUnit.SECONDS) + .build() + + fun put(state: String, referer: String?) { + if (referer.isNullOrBlank()) { + return + } else { + timedStorage.put(state, referer) + } + } + + fun get(state: String): String { + return timedStorage.getIfPresent(state) ?: "" + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/security/ProfileHolder.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/ProfileHolder.kt new file mode 100644 index 00000000..6248f6b2 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/ProfileHolder.kt @@ -0,0 +1,18 @@ +package io.raemian.api.support.security + +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Component + +@Component +class ProfileHolder( + @Value("\${spring.profiles.active:local}") + private val profile: String, +) { + fun isLive(): Boolean { + return ProfileType.fromString(profile) == ProfileType.LIVE + } + + fun isDev(): Boolean { + return ProfileType.fromString(profile) == ProfileType.DEV + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/security/ProfileType.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/ProfileType.kt new file mode 100644 index 00000000..0be6fa0d --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/ProfileType.kt @@ -0,0 +1,17 @@ +package io.raemian.api.support.security + +enum class ProfileType( + value: String, +) { + LIVE("live"), + DEV("dev"), + LOCAL("local"), ; + companion object { + fun fromString(value: String): ProfileType = + when (value) { + "live" -> LIVE + "dev" -> DEV + else -> LOCAL + } + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/security/RedirectUrlHolder.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/RedirectUrlHolder.kt new file mode 100644 index 00000000..639d944f --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/RedirectUrlHolder.kt @@ -0,0 +1,10 @@ +package io.raemian.api.support.security + +import org.springframework.boot.context.properties.ConfigurationProperties + +@ConfigurationProperties(prefix = "redirect-url") +data class RedirectUrlHolder( + val live: String, + val dev: String, + val local: String, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/security/StateOAuth2AuthorizationRequestRepository.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/StateOAuth2AuthorizationRequestRepository.kt index 0dbeddf3..9964287f 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/security/StateOAuth2AuthorizationRequestRepository.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/StateOAuth2AuthorizationRequestRepository.kt @@ -4,13 +4,19 @@ import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Caffeine import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse +import org.slf4j.LoggerFactory import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest import org.springframework.stereotype.Component import java.util.concurrent.TimeUnit @Component -class StateOAuth2AuthorizationRequestRepository() : AuthorizationRequestRepository { +class StateOAuth2AuthorizationRequestRepository( + val profilePlaceHolder: ProfileHolder, + val loginRequestRefererStorage: LoginRequestRefererStorage, +) : AuthorizationRequestRepository { + private val log = LoggerFactory.getLogger(javaClass) + private val oauthRequestStorage: Cache = Caffeine.newBuilder() .expireAfterWrite(60L, TimeUnit.SECONDS) .build() @@ -25,6 +31,12 @@ class StateOAuth2AuthorizationRequestRepository() : AuthorizationRequestReposito response: HttpServletResponse, ) { if (authorizationRequest != null) { + /*** for frontend dev test ***/ + if (profilePlaceHolder.isDev()) { + log.info("save referer: {}", request.getParameter("referer")) + loginRequestRefererStorage.put(authorizationRequest.state, request.getParameter("referer")) + } + oauthRequestStorage.put(authorizationRequest.state, authorizationRequest) } } diff --git a/backend/application/api/src/main/resources/application-security-dev.yml b/backend/application/api/src/main/resources/application-security-dev.yml index ed824029..280e92f4 100644 --- a/backend/application/api/src/main/resources/application-security-dev.yml +++ b/backend/application/api/src/main/resources/application-security-dev.yml @@ -13,6 +13,11 @@ apple-login: team-id: ENC(jzk4UvjfcLerYSo05oJ8vNGGxgt4SLuS) key-id: ENC(T/yX80PVdBkgQcFkOs/H2dFgAuBKDeNR) +redirect-url: + live: ENC(n7sXFCoABK2wVGKkMdhVLJEtvfHXtds+NwvpxWntSnYArf3DGN2cFbKlCR9hIACj) + dev: ENC(ZOWsz9hwBfF4yJrxHevHshhkGZqYuS9XyoFFLQQea473KOSJoKg2zMgHOGXkoGGz8jZ3Zc4rnQE=) + local: ENC(LxpM8W1yvAXRpin++amkeEhqGXro90Y2E3tQgD4EzOJnOZfBVKST3TF/0T3CqvZZ) + spring: security: diff --git a/backend/application/api/src/main/resources/application-security-live.yml b/backend/application/api/src/main/resources/application-security-live.yml index d1f149fa..f3505755 100644 --- a/backend/application/api/src/main/resources/application-security-live.yml +++ b/backend/application/api/src/main/resources/application-security-live.yml @@ -12,6 +12,11 @@ apple-login: team-id: ENC(jzk4UvjfcLerYSo05oJ8vNGGxgt4SLuS) key-id: ENC(T/yX80PVdBkgQcFkOs/H2dFgAuBKDeNR) +redirect-url: + live: ENC(n7sXFCoABK2wVGKkMdhVLJEtvfHXtds+NwvpxWntSnYArf3DGN2cFbKlCR9hIACj) + dev: ENC(ZOWsz9hwBfF4yJrxHevHshhkGZqYuS9XyoFFLQQea473KOSJoKg2zMgHOGXkoGGz8jZ3Zc4rnQE=) + local: ENC(LxpM8W1yvAXRpin++amkeEhqGXro90Y2E3tQgD4EzOJnOZfBVKST3TF/0T3CqvZZ) + spring: security: oauth2: diff --git a/backend/application/api/src/main/resources/application-security-local.yml b/backend/application/api/src/main/resources/application-security-local.yml index a5cae646..49b5945b 100644 --- a/backend/application/api/src/main/resources/application-security-local.yml +++ b/backend/application/api/src/main/resources/application-security-local.yml @@ -12,6 +12,11 @@ apple-login: team-id: ENC(jzk4UvjfcLerYSo05oJ8vNGGxgt4SLuS) key-id: ENC(T/yX80PVdBkgQcFkOs/H2dFgAuBKDeNR) +redirect-url: + live: ENC(n7sXFCoABK2wVGKkMdhVLJEtvfHXtds+NwvpxWntSnYArf3DGN2cFbKlCR9hIACj) + dev: ENC(ZOWsz9hwBfF4yJrxHevHshhkGZqYuS9XyoFFLQQea473KOSJoKg2zMgHOGXkoGGz8jZ3Zc4rnQE=) + local: ENC(LxpM8W1yvAXRpin++amkeEhqGXro90Y2E3tQgD4EzOJnOZfBVKST3TF/0T3CqvZZ) + spring: security: diff --git a/backend/application/api/src/main/resources/application-security-test.yml b/backend/application/api/src/main/resources/application-security-test.yml index a28673c4..db55cdf7 100644 --- a/backend/application/api/src/main/resources/application-security-test.yml +++ b/backend/application/api/src/main/resources/application-security-test.yml @@ -13,6 +13,11 @@ apple-login: team-id: EMPTY-VALUE key-id: EMPTY-VALUE +redirect-url: + live: EMPTY-VALUE + dev: EMPTY-VALUE + local: EMPTY-VALUE + spring: security: oauth2: @@ -62,7 +67,7 @@ spring: redirect-uri: EMPTY-VALUE authorizationGrantType: authorization_code clientAuthenticationMethod: POST - clientName: pple + clientName: apple scope: - email - name \ No newline at end of file From faf6b777fb639974b79955d4eebfdd30658588e7 Mon Sep 17 00:00:00 2001 From: wkwon Date: Sun, 31 Mar 2024 20:47:21 +0900 Subject: [PATCH 45/60] =?UTF-8?q?chore:=20logging=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/raemian/api/support/security/LoginRedirector.kt | 1 - .../StateOAuth2AuthorizationRequestRepository.kt | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/security/LoginRedirector.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/LoginRedirector.kt index d5fef4a7..82a156c3 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/security/LoginRedirector.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/LoginRedirector.kt @@ -18,7 +18,6 @@ class LoginRedirector( } val referer = loginRequestRefererStorage.get(state) - log.info("get referer: {}", referer) if (profilePlaceHolder.isDev() && referer.contains("dev-bandiboodi")) { return "${redirectUrlHolder.dev}?token=${token.accessToken}&refresh=${token.refreshToken}" diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/security/StateOAuth2AuthorizationRequestRepository.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/StateOAuth2AuthorizationRequestRepository.kt index 9964287f..949b973a 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/security/StateOAuth2AuthorizationRequestRepository.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/StateOAuth2AuthorizationRequestRepository.kt @@ -22,7 +22,7 @@ class StateOAuth2AuthorizationRequestRepository( .build() override fun loadAuthorizationRequest(request: HttpServletRequest): OAuth2AuthorizationRequest? { - return oauthRequestStorage.getIfPresent(getStateParameter(request)) + return oauthRequestStorage.getIfPresent(getState(request)) } override fun saveAuthorizationRequest( @@ -33,8 +33,7 @@ class StateOAuth2AuthorizationRequestRepository( if (authorizationRequest != null) { /*** for frontend dev test ***/ if (profilePlaceHolder.isDev()) { - log.info("save referer: {}", request.getParameter("referer")) - loginRequestRefererStorage.put(authorizationRequest.state, request.getParameter("referer")) + loginRequestRefererStorage.put(authorizationRequest.state, getReferer(request)) } oauthRequestStorage.put(authorizationRequest.state, authorizationRequest) @@ -48,5 +47,7 @@ class StateOAuth2AuthorizationRequestRepository( return loadAuthorizationRequest(request) } - private fun getStateParameter(request: HttpServletRequest): String = request.getParameter("state") + private fun getState(request: HttpServletRequest): String = request.getParameter("state") + + private fun getReferer(request: HttpServletRequest): String = request.getHeader("referer") } From 6077ae1dd9b55cb786a721080b83ab3d31de54da Mon Sep 17 00:00:00 2001 From: wkwon Date: Sun, 31 Mar 2024 20:55:23 +0900 Subject: [PATCH 46/60] =?UTF-8?q?fix:=20getReferer=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20referer=EA=B0=92=20null=EC=9D=BC=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20Empty=20String=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/StateOAuth2AuthorizationRequestRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/security/StateOAuth2AuthorizationRequestRepository.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/StateOAuth2AuthorizationRequestRepository.kt index 949b973a..6d0262fa 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/security/StateOAuth2AuthorizationRequestRepository.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/security/StateOAuth2AuthorizationRequestRepository.kt @@ -49,5 +49,5 @@ class StateOAuth2AuthorizationRequestRepository( private fun getState(request: HttpServletRequest): String = request.getParameter("state") - private fun getReferer(request: HttpServletRequest): String = request.getHeader("referer") + private fun getReferer(request: HttpServletRequest): String = request.getHeader("referer") ?: "" } From 43b8f31c694575ab934a3cb0e0a890924c8f1b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=9A=B0=EC=84=9D=20=28Woosuk=20Kwon=29?= Date: Wed, 3 Apr 2024 22:54:38 +0900 Subject: [PATCH 47/60] =?UTF-8?q?[=F0=9F=86=98=20Bug]=20=ED=83=80=EC=9E=84?= =?UTF-8?q?=EB=9D=BC=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#248)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: timeline 조회 시 목표 없으면 early return * feat: 유저정보 기반 타임 라인 조회 API 작업 * fix: 목표 삭제 시 LifeMapCount goalCount 필드 -1 처리 * refactor: timeline 조회 api 내 목표 개수 조회 쿼리 변경 --- .../api/event/handler/CountEventHandler.kt | 60 +++++++++++-------- .../api/event/model/CreatedGoalEvent.kt | 1 - .../api/event/model/DeletedGoalEvent.kt | 5 ++ .../api/goal/service/GoalQueryService.kt | 38 +++++++++++- .../raemian/api/goal/service/GoalService.kt | 7 ++- .../lifemap/controller/LifeMapController.kt | 15 +++++ .../controller/OpenLifeMapController.kt | 2 +- .../service/LifeMapCountQueryService.kt | 14 +++++ .../response/OffsetPaginationResult.kt | 6 ++ .../storage/db/core/lifemap/LifeMapCount.kt | 40 +++++-------- 10 files changed, 136 insertions(+), 52 deletions(-) create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedGoalEvent.kt create mode 100644 backend/application/api/src/main/kotlin/io/raemian/api/lifemap/service/LifeMapCountQueryService.kt diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/handler/CountEventHandler.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/handler/CountEventHandler.kt index 743850f8..94a8c23e 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/handler/CountEventHandler.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/handler/CountEventHandler.kt @@ -4,6 +4,7 @@ import io.raemian.api.event.model.CheeredEvent import io.raemian.api.event.model.CreatedCommentEvent import io.raemian.api.event.model.CreatedGoalEvent import io.raemian.api.event.model.DeletedCommentEvent +import io.raemian.api.event.model.DeletedGoalEvent import io.raemian.api.event.model.ReactedEmojiEvent import io.raemian.api.event.model.RemovedEmojiEvent import io.raemian.api.support.lock.ExclusiveRunner @@ -32,10 +33,10 @@ class CountEventHandler( ) { @Transactional @EventListener - fun addCheeringCount(cheeringEvent: CheeredEvent) { - exclusiveRunner.call("cheering:${cheeringEvent.lifeMapId}", Duration.ofSeconds(10)) { - val cheering = cheeringRepository.findByLifeMapId(cheeringEvent.lifeMapId) - ?: Cheering(0, cheeringEvent.lifeMapId) + fun addCheeringCount(event: CheeredEvent) { + exclusiveRunner.call("cheering:${event.lifeMapId}", Duration.ofSeconds(10)) { + val cheering = cheeringRepository.findByLifeMapId(event.lifeMapId) + ?: Cheering(0, event.lifeMapId) cheeringRepository.save(cheering.addCount()) } @@ -43,11 +44,11 @@ class CountEventHandler( @Transactional @EventListener - fun addGoalCount(createGoalEvent: CreatedGoalEvent) { + fun addGoalCount(event: CreatedGoalEvent) { // TODO goal 테이블에서 life map 기준으로 전체 count 한 값으로 업데이트 - exclusiveRunner.call("goal:${createGoalEvent.lifeMapId}:${createGoalEvent.goalId}", Duration.ofSeconds(10)) { - val mapCount = lifeMapCountRepository.findByLifeMapId(createGoalEvent.lifeMapId) - ?: LifeMapCount.of(createGoalEvent.lifeMapId) + exclusiveRunner.call("goal:${event.lifeMapId}", Duration.ofSeconds(10)) { + val mapCount = lifeMapCountRepository.findByLifeMapId(event.lifeMapId) + ?: LifeMapCount.of(event.lifeMapId) val added = mapCount.addGoalCount() @@ -57,11 +58,22 @@ class CountEventHandler( @Transactional @EventListener - fun addEmojiCount(reactEmojiEvent: ReactedEmojiEvent) { - exclusiveRunner.call("emoji:${reactEmojiEvent.goalId}:${reactEmojiEvent.emojiId}", Duration.ofSeconds(10)) { - val emoji = emojiRepository.getReferenceById(reactEmojiEvent.emojiId) - val emojiCount = emojiCountRepository.findByGoalIdAndEmojiId(reactEmojiEvent.goalId, reactEmojiEvent.emojiId) - ?: EmojiCount(0, emoji, reactEmojiEvent.goalId) + fun minusGoalCount(event: DeletedGoalEvent) { + exclusiveRunner.call("goal:${event.lifeMapId}", Duration.ofSeconds(10)) { + val mapCount = lifeMapCountRepository.findByLifeMapId(event.lifeMapId) + ?: LifeMapCount.of(event.lifeMapId) + + lifeMapCountRepository.save(mapCount.minusGoalCount()) + } + } + + @Transactional + @EventListener + fun addEmojiCount(event: ReactedEmojiEvent) { + exclusiveRunner.call("emoji:${event.goalId}:${event.emojiId}", Duration.ofSeconds(10)) { + val emoji = emojiRepository.getReferenceById(event.emojiId) + val emojiCount = emojiCountRepository.findByGoalIdAndEmojiId(event.goalId, event.emojiId) + ?: EmojiCount(0, emoji, event.goalId) emojiCountRepository.save(emojiCount.addCount()) } @@ -69,10 +81,10 @@ class CountEventHandler( @Transactional @EventListener - fun minusEmojiCount(removeEmojiEvent: RemovedEmojiEvent) { - exclusiveRunner.call("emoji:${removeEmojiEvent.goalId}:${removeEmojiEvent.emojiId}", Duration.ofSeconds(10)) { - val emojiCount = emojiCountRepository.findByGoalIdAndEmojiId(removeEmojiEvent.goalId, removeEmojiEvent.emojiId) - ?: EmojiCount(count = 0, goalId = removeEmojiEvent.goalId, emoji = emojiRepository.getReferenceById(removeEmojiEvent.emojiId)) + fun minusEmojiCount(event: RemovedEmojiEvent) { + exclusiveRunner.call("emoji:${event.goalId}:${event.emojiId}", Duration.ofSeconds(10)) { + val emojiCount = emojiCountRepository.findByGoalIdAndEmojiId(event.goalId, event.emojiId) + ?: EmojiCount(count = 0, goalId = event.goalId, emoji = emojiRepository.getReferenceById(event.emojiId)) emojiCountRepository.save(emojiCount.minusCount()) } @@ -80,10 +92,10 @@ class CountEventHandler( @Transactional @EventListener - fun addCommentCount(createdCommentEvent: CreatedCommentEvent) { - exclusiveRunner.call("comment:${createdCommentEvent.goalId}", Duration.ofSeconds(10)) { - val commentCount = commentCountRepository.findByGoalId(createdCommentEvent.goalId) - ?: CommentCount(0, createdCommentEvent.goalId) + fun addCommentCount(event: CreatedCommentEvent) { + exclusiveRunner.call("comment:${event.goalId}", Duration.ofSeconds(10)) { + val commentCount = commentCountRepository.findByGoalId(event.goalId) + ?: CommentCount(0, event.goalId) commentCountRepository.save(commentCount.addCount()) } @@ -91,9 +103,9 @@ class CountEventHandler( @Transactional @EventListener - fun minusCommentCount(deletedCommentEvent: DeletedCommentEvent) { - val commentCount = commentCountRepository.findByGoalId(deletedCommentEvent.goalId) - ?: CommentCount(count = 0, goalId = deletedCommentEvent.goalId) + fun minusCommentCount(event: DeletedCommentEvent) { + val commentCount = commentCountRepository.findByGoalId(event.goalId) + ?: CommentCount(count = 0, goalId = event.goalId) commentCountRepository.save(commentCount.minusCount()) } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CreatedGoalEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CreatedGoalEvent.kt index 5d6b1f7b..f1b31cae 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CreatedGoalEvent.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/CreatedGoalEvent.kt @@ -1,6 +1,5 @@ package io.raemian.api.event.model data class CreatedGoalEvent( - val goalId: Long, val lifeMapId: Long, ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedGoalEvent.kt b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedGoalEvent.kt new file mode 100644 index 00000000..d2e4b9dc --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/event/model/DeletedGoalEvent.kt @@ -0,0 +1,5 @@ +package io.raemian.api.event.model + +data class DeletedGoalEvent( + val lifeMapId: Long, +) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt index 7dccfd6a..90e000ba 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt @@ -5,6 +5,7 @@ import io.raemian.api.emoji.service.EmojiService import io.raemian.api.goal.controller.request.TimelinePageRequest import io.raemian.api.goal.model.GoalTimelineCountSubset import io.raemian.api.goal.model.GoalTimelinePageResult +import io.raemian.api.lifemap.service.LifeMapCountQueryService import io.raemian.api.lifemap.service.LifeMapService import io.raemian.api.support.response.OffsetPaginationResult import io.raemian.api.task.service.TaskService @@ -17,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional class GoalQueryService( private val lifeMapService: LifeMapService, private val goalJdbcQueryRepository: GoalJdbcQueryRepository, + private val lifeMapCountQueryService: LifeMapCountQueryService, private val goalRepository: GoalRepository, private val emojiService: EmojiService, private val taskService: TaskService, @@ -26,7 +28,41 @@ class GoalQueryService( fun findAllByUsernameWithOffset(username: String, request: TimelinePageRequest): OffsetPaginationResult { val lifeMap = lifeMapService.getFirstByUserName(username) val goals = goalJdbcQueryRepository.findAllByLifeMapWithOffset(lifeMap.lifeMapId, request.page, request.size) - val total = goalRepository.countByLifeMapId(lifeMap.lifeMapId) + + if (goals.isEmpty()) { + return OffsetPaginationResult.empty(request.page, request.size) + } + + val total = lifeMapCountQueryService.findGoalCount(lifeMap.lifeMapId) + + val goalIds = goals.map { it.goalId } + + val goalCountMap = findGoalCountMap(goalIds) + val reactedEmojiMap = emojiService.findAllByGoalIds(goalIds, lifeMap.user.id) + + return OffsetPaginationResult.of( + request.page, + request.size, + total, + goals.map { + GoalTimelinePageResult.from( + goal = it, + counts = goalCountMap[it.goalId], + reactedEmojisResult = reactedEmojiMap[it.goalId], + ) + }, + ) + } + + @Transactional(readOnly = true) + fun findAllByUserIdWithOffset(userId: Long, request: TimelinePageRequest): OffsetPaginationResult { + val lifeMap = lifeMapService.getFirstByUserId(userId) + val goals = goalJdbcQueryRepository.findAllByLifeMapWithOffset(lifeMap.lifeMapId, request.page, request.size) + if (goals.isEmpty()) { + return OffsetPaginationResult.empty(request.page, request.size) + } + + val total = lifeMapCountQueryService.findGoalCount(lifeMap.lifeMapId) val goalIds = goals.map { it.goalId } diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt index af179470..7990848c 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt @@ -2,6 +2,7 @@ package io.raemian.api.goal.service import io.raemian.api.emoji.service.EmojiService import io.raemian.api.event.model.CreatedGoalEvent +import io.raemian.api.event.model.DeletedGoalEvent import io.raemian.api.goal.controller.request.CreateGoalRequest import io.raemian.api.goal.controller.request.UpdateGoalRequest import io.raemian.api.goal.model.CreateGoalResult @@ -53,7 +54,7 @@ class GoalService( // goal 생성시 count event 발행 applicationEventPublisher.publishEvent( - CreatedGoalEvent(goalId = goal.id!!, lifeMapId = lifeMap.id!!), + CreatedGoalEvent(lifeMap.id!!), ) return CreateGoalResult(goal) @@ -83,6 +84,10 @@ class GoalService( val goal = goalRepository.getById(goalId) validateGoalIsUsers(userId, goal) goalRepository.delete(goal) + + applicationEventPublisher.publishEvent( + DeletedGoalEvent(goal.lifeMap.id!!), + ) } @Transactional(readOnly = true) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt index fe534279..049d7441 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/LifeMapController.kt @@ -2,10 +2,14 @@ package io.raemian.api.lifemap.controller import io.raemian.api.auth.model.CurrentUser import io.raemian.api.cheer.service.CheeringService +import io.raemian.api.goal.controller.request.TimelinePageRequest +import io.raemian.api.goal.model.GoalTimelinePageResult +import io.raemian.api.goal.service.GoalQueryService import io.raemian.api.lifemap.controller.request.UpdatePublicRequest import io.raemian.api.lifemap.model.LifeMapResponse import io.raemian.api.lifemap.service.LifeMapService import io.raemian.api.support.response.ApiResponse +import io.raemian.api.support.response.OffsetPaginationResult import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity import org.springframework.security.core.annotation.AuthenticationPrincipal @@ -20,6 +24,7 @@ import org.springframework.web.bind.annotation.RestController class LifeMapController( private val lifeMapService: LifeMapService, private val cheeringService: CheeringService, + private val goalQueryService: GoalQueryService, ) { @Operation(summary = "로그인한 유저의 인생 지도 조회 API") @@ -35,6 +40,16 @@ class LifeMapController( .ok(ApiResponse.success(LifeMapResponse(lifeMap, count, cheeringCount))) } + @Operation(summary = "로그인한 유저의 인생 지도 타임 라인 조회 API") + @GetMapping("/timeline") + fun getTimeline( + @AuthenticationPrincipal currentUser: CurrentUser, + request: TimelinePageRequest, + ): ResponseEntity>> { + val goalTimeline = goalQueryService.findAllByUserIdWithOffset(currentUser.id, request) + return ResponseEntity.ok(ApiResponse.success(goalTimeline)) + } + @Operation(summary = "인생 지도 공개 여부를 수정하는 API") @PatchMapping("/publication") fun updatePublic( diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt index 8f7002c8..ece47d13 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/controller/OpenLifeMapController.kt @@ -45,7 +45,7 @@ class OpenLifeMapController( .ok(ApiResponse.success(LifeMapResponse(lifeMap, count, cheeringCount))) } - @Operation(summary = "로그인한 유저의 인생 지도 타임 라인 조회 API") + @Operation(summary = "UserName으로 인생 지도 타임 라인 조회 API") @GetMapping("/timeline/{username}") fun getTimeline( @PathVariable("username") username: String, diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/service/LifeMapCountQueryService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/service/LifeMapCountQueryService.kt new file mode 100644 index 00000000..1a310447 --- /dev/null +++ b/backend/application/api/src/main/kotlin/io/raemian/api/lifemap/service/LifeMapCountQueryService.kt @@ -0,0 +1,14 @@ +package io.raemian.api.lifemap.service + +import io.raemian.storage.db.core.lifemap.LifeMapCountRepository +import org.springframework.stereotype.Service + +@Service +class LifeMapCountQueryService( + private val lifeMapCountRepository: LifeMapCountRepository, +) { + fun findGoalCount(lifeMapId: Long): Long { + val mapCount = lifeMapCountRepository.findByLifeMapId(lifeMapId) ?: return 0 + return mapCount.goalCount + } +} diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/OffsetPaginationResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/OffsetPaginationResult.kt index 6490cec5..c4a44e86 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/support/response/OffsetPaginationResult.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/support/response/OffsetPaginationResult.kt @@ -1,5 +1,7 @@ package io.raemian.api.support.response +import java.util.Collections + data class OffsetPaginationResult( val total: Long, val page: Int, @@ -10,5 +12,9 @@ data class OffsetPaginationResult( fun of(page: Int, size: Int, total: Long, contents: List): OffsetPaginationResult { return OffsetPaginationResult(total, page, size, contents) } + + fun empty(page: Int, size: Int): OffsetPaginationResult { + return OffsetPaginationResult(0L, page, size, Collections.emptyList()) + } } } diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapCount.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapCount.kt index a5483abc..9a57e43e 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapCount.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/lifemap/LifeMapCount.kt @@ -10,9 +10,9 @@ import jakarta.persistence.Table @Table(name = "LIFE_MAP_COUNT") class LifeMapCount( val lifeMapId: Long, - val viewCount: Long, - val historyCount: Long, - val goalCount: Long, + var viewCount: Long, + var historyCount: Long, + var goalCount: Long, @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null, @@ -28,32 +28,24 @@ class LifeMapCount( } } fun addViewCount(): LifeMapCount { - return LifeMapCount( - lifeMapId = lifeMapId, - viewCount = viewCount + 1, - historyCount = historyCount, - goalCount = goalCount, - id = id, - ) + this.viewCount += 1 + return this } fun addHistoryCount(): LifeMapCount { - return LifeMapCount( - lifeMapId = lifeMapId, - viewCount = viewCount, - historyCount = historyCount + 1, - goalCount = goalCount, - id = id, - ) + this.historyCount += 1 + return this } fun addGoalCount(): LifeMapCount { - return LifeMapCount( - lifeMapId = lifeMapId, - viewCount = viewCount, - historyCount = historyCount, - goalCount = goalCount + 1, - id = id, - ) + this.goalCount += 1 + return this + } + + fun minusGoalCount(): LifeMapCount { + if (0 < this.goalCount) { + this.goalCount -= 1 + } + return this } } From 83ea0fa737cf4e9fe0dbf7547c851521df6701ea Mon Sep 17 00:00:00 2001 From: ManHyuk Date: Thu, 4 Apr 2024 22:37:43 +0900 Subject: [PATCH 48/60] feat(#171): edit comment count --- .../api/goal/model/GoalExploreCountSubset.kt | 2 +- .../io/raemian/api/goal/service/GoalService.kt | 8 +++++++- .../raemian/storage/db/core/goal/GoalRepository.kt | 13 ++++++++----- .../db/core/goal/model/GoalExploreQueryResult.kt | 1 + 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreCountSubset.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreCountSubset.kt index 8f9e2007..06e4e6f8 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreCountSubset.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/model/GoalExploreCountSubset.kt @@ -10,7 +10,7 @@ data class GoalExploreCountSubset( ) { constructor(result: GoalExploreQueryResult) : this( reaction = 0, - comment = 0, + comment = result.commentCount, task = 0, goal = result.goalCount, ) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt index 7990848c..135324f7 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt @@ -1,5 +1,6 @@ package io.raemian.api.goal.service +import io.raemian.api.comment.service.CommentService import io.raemian.api.emoji.service.EmojiService import io.raemian.api.event.model.CreatedGoalEvent import io.raemian.api.event.model.DeletedGoalEvent @@ -13,6 +14,7 @@ import io.raemian.api.support.exception.MaxGoalCountExceededException import io.raemian.api.support.exception.PrivateLifeMapException import io.raemian.api.support.utils.DeadlineCreator import io.raemian.api.tag.service.TagService +import io.raemian.storage.db.core.comment.CommentCountRepository import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.lifemap.LifeMap @@ -32,6 +34,7 @@ class GoalService( private val emojiService: EmojiService, private val stickerService: StickerService, private val tagService: TagService, + private val commentService: CommentService ) { @Transactional(readOnly = true) @@ -98,7 +101,10 @@ class GoalService( val reactedEmojiMap = emojiService.findAllByGoalIds(goalIds, userId) return explore - .map { GoalExploreResult.from(it, reactedEmojiMap[it.goalId]) } + .map { GoalExploreResult.from( + explore = it, + reactedEmojisResponse = reactedEmojiMap[it.goalId], + ) } } private fun createFirstLifeMap(userId: Long): LifeMap { diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt index 7112dcca..6d58863f 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/GoalRepository.kt @@ -28,9 +28,10 @@ interface GoalRepository : JpaRepository { goal.tag.content, goal.createdAt, map.id, - count.goalCount, - count.historyCount, - count.viewCount, + lifeMapCount.goalCount, + lifeMapCount.historyCount, + lifeMapCount.viewCount, + commentCount.count, user.id, user.nickname, user.username, @@ -40,11 +41,13 @@ interface GoalRepository : JpaRepository { Goal as goal, LifeMap as map, USERS as user, - LifeMapCount as count + LifeMapCount as lifeMapCount, + CommentCount as commentCount WHERE 1 = 1 AND goal.lifeMap.id = map.id AND map.user.id = user.id - AND map.id = count.lifeMapId + AND map.id = lifeMapCount.lifeMapId + AND goal.id = commentCount.goalId AND map.isPublic = true AND goal.id < :cursor ORDER BY diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalExploreQueryResult.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalExploreQueryResult.kt index 888b6a02..f9d49568 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalExploreQueryResult.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/goal/model/GoalExploreQueryResult.kt @@ -15,6 +15,7 @@ data class GoalExploreQueryResult( val goalCount: Long, val historyCount: Long, val viewCount: Long, + val commentCount: Long, val userId: Long, val nickname: String, val username: String, From abc7ea1a4bee01cf5e5d06f6986071c2bd1912b5 Mon Sep 17 00:00:00 2001 From: ManHyuk Date: Thu, 4 Apr 2024 22:38:13 +0900 Subject: [PATCH 49/60] chore: ktlint --- .../io/raemian/api/goal/service/GoalService.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt index 135324f7..49e29fd1 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt @@ -14,7 +14,6 @@ import io.raemian.api.support.exception.MaxGoalCountExceededException import io.raemian.api.support.exception.PrivateLifeMapException import io.raemian.api.support.utils.DeadlineCreator import io.raemian.api.tag.service.TagService -import io.raemian.storage.db.core.comment.CommentCountRepository import io.raemian.storage.db.core.goal.Goal import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.lifemap.LifeMap @@ -34,7 +33,7 @@ class GoalService( private val emojiService: EmojiService, private val stickerService: StickerService, private val tagService: TagService, - private val commentService: CommentService + private val commentService: CommentService, ) { @Transactional(readOnly = true) @@ -101,10 +100,12 @@ class GoalService( val reactedEmojiMap = emojiService.findAllByGoalIds(goalIds, userId) return explore - .map { GoalExploreResult.from( - explore = it, - reactedEmojisResponse = reactedEmojiMap[it.goalId], - ) } + .map { + GoalExploreResult.from( + explore = it, + reactedEmojisResponse = reactedEmojiMap[it.goalId], + ) + } } private fun createFirstLifeMap(userId: Long): LifeMap { From 78b7956ce7cf5a8c6d6b658c9f953bed3f01ed15 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sun, 7 Apr 2024 10:46:40 +0900 Subject: [PATCH 50/60] =?UTF-8?q?refactor=20:=20Comment=EC=99=80=20Emoji?= =?UTF-8?q?=EC=97=90=20Goal=20Delete=20Casacade=20=EC=86=8D=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#246)=20(#249)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/io/raemian/storage/db/core/comment/Comment.kt | 3 +++ .../kotlin/io/raemian/storage/db/core/emoji/ReactedEmoji.kt | 3 +++ 2 files changed, 6 insertions(+) diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt index afa505b8..ce16caf7 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/Comment.kt @@ -12,12 +12,15 @@ import jakarta.persistence.JoinColumn import jakarta.persistence.ManyToOne import jakarta.persistence.Table import org.hibernate.annotations.Nationalized +import org.hibernate.annotations.OnDelete +import org.hibernate.annotations.OnDeleteAction @Entity @Table(name = "COMMENTS") class Comment( @ManyToOne @JoinColumn(name = "goal_id") + @OnDelete(action = OnDeleteAction.CASCADE) val goal: Goal, @ManyToOne diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmoji.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmoji.kt index 471e306f..59fa7357 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmoji.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmoji.kt @@ -11,6 +11,8 @@ import jakarta.persistence.JoinColumn import jakarta.persistence.ManyToOne import jakarta.persistence.Table import jakarta.persistence.UniqueConstraint +import org.hibernate.annotations.OnDelete +import org.hibernate.annotations.OnDeleteAction @Entity @Table( @@ -22,6 +24,7 @@ import jakarta.persistence.UniqueConstraint class ReactedEmoji( @ManyToOne @JoinColumn(name = "goal_id") + @OnDelete(action = OnDeleteAction.CASCADE) val goal: Goal, @ManyToOne From 91705a194b5f24b5ca9eb3a33a7e8cbd4172f361 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Thu, 11 Apr 2024 23:25:01 +0900 Subject: [PATCH 51/60] =?UTF-8?q?refactor=20:=20=EC=9E=90=EC=8B=A0?= =?UTF-8?q?=EC=9D=98=20Goal=EC=97=90=20=EB=8B=AC=EB=A6=B0=20Comment=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=EB=A5=BC=20=ED=97=88=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20(#251)=20(#252)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/comment/service/CommentService.kt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt index a111adcd..ceb6ed01 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt @@ -51,7 +51,10 @@ class CommentService( @Transactional fun isNewComment(goalId: Long): Boolean { val goal = goalRepository.getById(goalId) - return commentRepository.existsByGoalIdAndCreatedAtGreaterThan(goal.id!!, goal.lastCommentReadAt) + return commentRepository.existsByGoalIdAndCreatedAtGreaterThan( + goalId = goal.id!!, + createdAt = goal.lastCommentReadAt, + ) } @Transactional @@ -67,12 +70,12 @@ class CommentService( @Transactional fun delete(commentId: Long, currentUserId: Long) { val comment = commentRepository.getById(commentId) - if (currentUserId != comment.commenter.id) { + + if (isNotMyComment(comment, currentUserId) && isNotMyGoal(comment.goal, currentUserId)) { throw CoreApiException(ErrorInfo.RESOURCE_DELETE_FORBIDDEN) } commentRepository.delete(comment) - applicationEventPublisher.publishEvent(DeletedCommentEvent(comment.goal.id!!)) } @@ -88,6 +91,11 @@ class CommentService( } } + private fun isNotMyComment( + comment: Comment, + currentUserId: Long, + ) = currentUserId != comment.commenter.id + private fun isMyGoal(goalId: Long, userId: Long): Boolean { val goal = goalRepository.getById(goalId) return isMyGoal(goal, userId) @@ -96,6 +104,8 @@ class CommentService( private fun isMyGoal(goal: Goal, userId: Long): Boolean = userId == goal.lifeMap.user.id + private fun isNotMyGoal(goal: Goal, userId: Long): Boolean = !isMyGoal(goal, userId) + private fun publishUpdateCommentReadAtEvent(goalId: Long) { val event = CommentReadEvent(goalId, LocalDateTime.now()) applicationEventPublisher.publishEvent(event) From 79f4f7e70380845e5f734ef745b03bb1393efadf Mon Sep 17 00:00:00 2001 From: wkwon Date: Fri, 12 Apr 2024 00:27:55 +0900 Subject: [PATCH 52/60] =?UTF-8?q?bug(#254):=20=ED=83=80=EC=9E=84=EB=9D=BC?= =?UTF-8?q?=EC=9D=B8=20=EC=A1=B0=ED=9A=8C=20API=20=EB=82=B4=20=EC=BD=94?= =?UTF-8?q?=EB=A9=98=ED=8A=B8=20=EA=B0=9C=EC=88=98=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=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 --- .../storage/db/core/comment/CommentJdbcQueryRepository.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt index 1174119f..18a3c069 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/comment/CommentJdbcQueryRepository.kt @@ -14,11 +14,10 @@ class CommentJdbcQueryRepository( """ SELECT c.GOAL_ID AS GOAL_ID, - COUNT(GOAL_ID) AS COMMENT_COUNT + c.COUNT AS COMMENT_COUNT FROM comment_counts c WHERE 1 = 1 AND c.GOAL_ID IN (:goalIds) - GROUP BY c.GOAL_ID """.trimIndent() val namedParameter = MapSqlParameterSource() From 6495aff228b0aaec0ed0c7fced6c33ae4274ad44 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sun, 21 Apr 2024 15:15:24 +0900 Subject: [PATCH 53/60] =?UTF-8?q?feat=20:=20=EC=9D=B4=EB=AA=A8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EC=A0=84=EC=97=90=20=EA=B0=99=EC=9D=80=20?= =?UTF-8?q?=EC=9D=B4=EB=AA=A8=EC=A7=80=EA=B0=80=20=EC=A1=B4=EC=9E=AC?= =?UTF-8?q?=ED=95=98=EB=8A=94=EC=A7=80=20=ED=99=95=EC=9D=B8=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?(#253)=20(#256)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../raemian/api/emoji/service/EmojiService.kt | 28 ++++++++++--------- .../db/core/emoji/ReactedEmojiRepository.kt | 3 ++ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt index 30e907be..e83198c3 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt @@ -4,14 +4,12 @@ import io.raemian.api.emoji.model.EmojiResult import io.raemian.api.emoji.model.ReactedEmojisResult import io.raemian.api.event.model.ReactedEmojiEvent import io.raemian.api.event.model.RemovedEmojiEvent -import io.raemian.storage.db.core.emoji.EmojiCountRepository import io.raemian.storage.db.core.emoji.EmojiRepository import io.raemian.storage.db.core.emoji.ReactedEmoji import io.raemian.storage.db.core.emoji.ReactedEmojiRepository import io.raemian.storage.db.core.goal.GoalRepository import io.raemian.storage.db.core.user.UserRepository import org.springframework.context.ApplicationEventPublisher -import org.springframework.dao.DataIntegrityViolationException import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -22,7 +20,6 @@ class EmojiService( private val userRepository: UserRepository, private val reactedEmojiRepository: ReactedEmojiRepository, private val applicationEventPublisher: ApplicationEventPublisher, - private val emojiCountRepository: EmojiCountRepository, ) { @Transactional(readOnly = true) fun findAll(): List = @@ -41,15 +38,26 @@ class EmojiService( val emoji = emojiRepository.getReferenceById(emojiId) val goal = goalRepository.getReferenceById(goalId) val emojiReactUser = userRepository.getReferenceById(emojiReactUserId) - val reactedEmoji = ReactedEmoji(goal, emoji, emojiReactUser) - ignoreDuplicatedReactedEmojiException { + if (isReactedEmojiNotExist( + reactUserId = emojiReactUserId, + goalId = goalId, + emojiId = emojiId, + ) + ) { + val reactedEmoji = ReactedEmoji(goal, emoji, emojiReactUser) reactedEmojiRepository.save(reactedEmoji) + applicationEventPublisher.publishEvent(ReactedEmojiEvent(goalId, emojiId)) } - - applicationEventPublisher.publishEvent(ReactedEmojiEvent(goalId, emojiId)) } + private fun isReactedEmojiNotExist(reactUserId: Long, goalId: Long, emojiId: Long) = + reactedEmojiRepository.existsByReactUserIdAndGoalIdAndEmojiId( + reactUserId = reactUserId, + goalId = goalId, + emojiId = emojiId, + ).not() + @Transactional fun remove(emojiId: Long, goalId: Long, emojiReactUserId: Long) { val emoji = emojiRepository.getReferenceById(emojiId) @@ -69,9 +77,3 @@ class EmojiService( .mapValues { ReactedEmojisResult.of(it.value, userId ?: -1) } } } - -inline fun ignoreDuplicatedReactedEmojiException(action: () -> Any) { - try { - action() - } catch (exception: DataIntegrityViolationException) {} -} diff --git a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt index db3f5b10..77f1db80 100644 --- a/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt +++ b/backend/storage/db-core/src/main/kotlin/io/raemian/storage/db/core/emoji/ReactedEmojiRepository.kt @@ -5,6 +5,9 @@ import io.raemian.storage.db.core.user.User import org.springframework.data.jpa.repository.JpaRepository interface ReactedEmojiRepository : JpaRepository { + + fun existsByReactUserIdAndGoalIdAndEmojiId(reactUserId: Long, goalId: Long, emojiId: Long): Boolean + fun findAllByGoal(goal: Goal): List fun findAllByGoalIdIn(goalIds: List): List From f8c499384b398ab971758f408b2cf39a09ec36de Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sun, 21 Apr 2024 16:12:55 +0900 Subject: [PATCH 54/60] =?UTF-8?q?refactor=20:=20=EB=AA=A8=EB=93=A0=20Emoji?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20API=EC=97=90=EC=84=9C=20=EC=9D=BC?= =?UTF-8?q?=EA=B4=80=EB=90=98=EA=B2=8C=20Emoji=20id=20=EC=88=9C=EC=84=9C?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=95=EB=A0=AC=ED=95=B4=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20(#257)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/io/raemian/api/emoji/model/ReactedEmojisResult.kt | 1 + .../src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/ReactedEmojisResult.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/ReactedEmojisResult.kt index 44cdcf3d..25d3c1f2 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/ReactedEmojisResult.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/model/ReactedEmojisResult.kt @@ -34,6 +34,7 @@ data class ReactedEmojisResult( .groupBy { it.emoji.id } .mapValues { entry -> ReactedEmojiAndReactUsers.of(entry.value, userId) } .values + .sortedBy { it.id } .toList() private fun countTotalReactUser(reactedEmojiAndReactUsers: List) = diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt index e83198c3..bae9d57e 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt @@ -24,6 +24,7 @@ class EmojiService( @Transactional(readOnly = true) fun findAll(): List = emojiRepository.findAll() + .sortedBy { it.id } .map(EmojiResult::from) @Transactional(readOnly = true) From 3c9a3f71f1ed1cc238c4d06f4b37ad7c94dd64f5 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sun, 21 Apr 2024 16:15:36 +0900 Subject: [PATCH 55/60] =?UTF-8?q?refactor=20:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=A0=95=EB=A0=AC=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?-=20MySQL=20Clustered=20Index=EB=A1=9C=20=EC=9D=B8=ED=95=B4=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=20pk=20=EC=A0=95=EB=A0=AC=20=EB=90=98?= =?UTF-8?q?=EC=96=B4=20=EC=9E=88=EB=8B=A4=20(#257)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt index bae9d57e..e83198c3 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/emoji/service/EmojiService.kt @@ -24,7 +24,6 @@ class EmojiService( @Transactional(readOnly = true) fun findAll(): List = emojiRepository.findAll() - .sortedBy { it.id } .map(EmojiResult::from) @Transactional(readOnly = true) From 12e23970a721cb0a54509923dc331733006b9cb9 Mon Sep 17 00:00:00 2001 From: ManHyuk Date: Wed, 24 Apr 2024 09:24:36 +0900 Subject: [PATCH 56/60] =?UTF-8?q?fix=20:=20=ED=94=BC=EB=93=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20(#259)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(#171): 목표 생성시 기본 댓글 개수 추가 * chore(#171): ktlint --- .../main/kotlin/io/raemian/api/goal/service/GoalService.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt index 49e29fd1..1f4b106f 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt @@ -2,6 +2,7 @@ package io.raemian.api.goal.service import io.raemian.api.comment.service.CommentService import io.raemian.api.emoji.service.EmojiService +import io.raemian.api.event.model.CreatedCommentEvent import io.raemian.api.event.model.CreatedGoalEvent import io.raemian.api.event.model.DeletedGoalEvent import io.raemian.api.goal.controller.request.CreateGoalRequest @@ -59,6 +60,11 @@ class GoalService( CreatedGoalEvent(lifeMap.id!!), ) + // goal 생성시 comment count 이벤트 발행 + applicationEventPublisher.publishEvent( + CreatedCommentEvent(goal.id!!), + ) + return CreateGoalResult(goal) } From 208050b9e4a1a4657707a6e95854b2dc16953150 Mon Sep 17 00:00:00 2001 From: ManHyuk Date: Thu, 25 Apr 2024 01:05:18 +0900 Subject: [PATCH 57/60] =?UTF-8?q?feat(#171):=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=EC=B9=B4=EC=9A=B4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/raemian/api/comment/service/CommentService.kt | 9 +++++++++ .../kotlin/io/raemian/api/goal/service/GoalService.kt | 6 ++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt index ceb6ed01..70a584cd 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt @@ -8,6 +8,8 @@ import io.raemian.api.event.model.DeletedCommentEvent import io.raemian.api.support.exception.CoreApiException import io.raemian.api.support.exception.ErrorInfo import io.raemian.storage.db.core.comment.Comment +import io.raemian.storage.db.core.comment.CommentCount +import io.raemian.storage.db.core.comment.CommentCountRepository import io.raemian.storage.db.core.comment.CommentJdbcQueryRepository import io.raemian.storage.db.core.comment.CommentRepository import io.raemian.storage.db.core.comment.model.GoalCommentCountQueryResult @@ -24,6 +26,7 @@ import java.time.LocalDateTime class CommentService( private val commentRepository: CommentRepository, private val commentJdbcQueryRepository: CommentJdbcQueryRepository, + private val commentCountRepository: CommentCountRepository, private val goalRepository: GoalRepository, private val userRepository: UserRepository, private val applicationEventPublisher: ApplicationEventPublisher, @@ -83,6 +86,12 @@ class CommentService( fun findGoalCommentCounts(goalIds: List): List = commentJdbcQueryRepository.findAllGoalCommentCountByGoalIdIn(goalIds) + @Transactional + fun addDefaultCount(goalId: Long) { + val comment = CommentCount(0, goalId) + commentCountRepository.save(comment) + } + private fun createComment(goal: Goal, currentUser: User, content: String): Comment { return try { Comment(goal, currentUser, content) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt index 1f4b106f..19c1e740 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt @@ -60,10 +60,8 @@ class GoalService( CreatedGoalEvent(lifeMap.id!!), ) - // goal 생성시 comment count 이벤트 발행 - applicationEventPublisher.publishEvent( - CreatedCommentEvent(goal.id!!), - ) + // goal 생성시 기본 comment count 생성 + commentService.addDefaultCount(goal.id!!) return CreateGoalResult(goal) } From c0888fde782b4f587731515da272850cd67ed980 Mon Sep 17 00:00:00 2001 From: ManHyuk Date: Thu, 25 Apr 2024 01:06:29 +0900 Subject: [PATCH 58/60] chore: ktlint --- .../src/main/kotlin/io/raemian/api/goal/service/GoalService.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt index 19c1e740..214e71ca 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalService.kt @@ -2,7 +2,6 @@ package io.raemian.api.goal.service import io.raemian.api.comment.service.CommentService import io.raemian.api.emoji.service.EmojiService -import io.raemian.api.event.model.CreatedCommentEvent import io.raemian.api.event.model.CreatedGoalEvent import io.raemian.api.event.model.DeletedGoalEvent import io.raemian.api.goal.controller.request.CreateGoalRequest @@ -61,7 +60,7 @@ class GoalService( ) // goal 생성시 기본 comment count 생성 - commentService.addDefaultCount(goal.id!!) + commentService.addDefaultCount(goal.id!!) return CreateGoalResult(goal) } From 2bc8d4326a5d3b22e4cc566d02b99da262212ad7 Mon Sep 17 00:00:00 2001 From: ManHyuk Date: Thu, 25 Apr 2024 01:19:34 +0900 Subject: [PATCH 59/60] =?UTF-8?q?refact(#171):=20=EB=B3=80=EC=88=98=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 --- .../kotlin/io/raemian/api/comment/service/CommentService.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt index 70a584cd..b6c8d437 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/comment/service/CommentService.kt @@ -88,8 +88,7 @@ class CommentService( @Transactional fun addDefaultCount(goalId: Long) { - val comment = CommentCount(0, goalId) - commentCountRepository.save(comment) + commentCountRepository.save(CommentCount(0, goalId)) } private fun createComment(goal: Goal, currentUser: User, content: String): Comment { From 9ba3988f2e7eb345f13d068f3d4332f953032890 Mon Sep 17 00:00:00 2001 From: wkwon Date: Fri, 26 Apr 2024 22:16:30 +0900 Subject: [PATCH 60/60] =?UTF-8?q?hotfix:=20timeline=20offset=20pagination?= =?UTF-8?q?=20query=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=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/io/raemian/api/goal/service/GoalQueryService.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt index 90e000ba..490a1648 100644 --- a/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt +++ b/backend/application/api/src/main/kotlin/io/raemian/api/goal/service/GoalQueryService.kt @@ -10,7 +10,6 @@ import io.raemian.api.lifemap.service.LifeMapService import io.raemian.api.support.response.OffsetPaginationResult import io.raemian.api.task.service.TaskService import io.raemian.storage.db.core.goal.GoalJdbcQueryRepository -import io.raemian.storage.db.core.goal.GoalRepository import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -19,7 +18,6 @@ class GoalQueryService( private val lifeMapService: LifeMapService, private val goalJdbcQueryRepository: GoalJdbcQueryRepository, private val lifeMapCountQueryService: LifeMapCountQueryService, - private val goalRepository: GoalRepository, private val emojiService: EmojiService, private val taskService: TaskService, private val commentService: CommentService, @@ -27,7 +25,7 @@ class GoalQueryService( @Transactional(readOnly = true) fun findAllByUsernameWithOffset(username: String, request: TimelinePageRequest): OffsetPaginationResult { val lifeMap = lifeMapService.getFirstByUserName(username) - val goals = goalJdbcQueryRepository.findAllByLifeMapWithOffset(lifeMap.lifeMapId, request.page, request.size) + val goals = goalJdbcQueryRepository.findAllByLifeMapWithOffset(lifeMap.lifeMapId, request.page * request.size, request.size) if (goals.isEmpty()) { return OffsetPaginationResult.empty(request.page, request.size) @@ -57,7 +55,7 @@ class GoalQueryService( @Transactional(readOnly = true) fun findAllByUserIdWithOffset(userId: Long, request: TimelinePageRequest): OffsetPaginationResult { val lifeMap = lifeMapService.getFirstByUserId(userId) - val goals = goalJdbcQueryRepository.findAllByLifeMapWithOffset(lifeMap.lifeMapId, request.page, request.size) + val goals = goalJdbcQueryRepository.findAllByLifeMapWithOffset(lifeMap.lifeMapId, request.page * request.size, request.size) if (goals.isEmpty()) { return OffsetPaginationResult.empty(request.page, request.size) }