From c740de4c5296e1beb71f8b8414acc858f01a7888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=A7=80=ED=98=81?= Date: Thu, 7 Sep 2023 10:16:36 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=EB=8B=89=EB=84=A4=EC=9E=84=20Backfill=20?= =?UTF-8?q?=EA=B3=A0=EB=8F=84=ED=99=94=20(#171)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/users/data/UserEntityCallback.kt | 28 +++++++++++++++++ .../service/UserNicknameBackFillSupporter.kt | 31 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 core/src/main/kotlin/users/data/UserEntityCallback.kt create mode 100644 core/src/main/kotlin/users/service/UserNicknameBackFillSupporter.kt diff --git a/core/src/main/kotlin/users/data/UserEntityCallback.kt b/core/src/main/kotlin/users/data/UserEntityCallback.kt new file mode 100644 index 00000000..2dd06241 --- /dev/null +++ b/core/src/main/kotlin/users/data/UserEntityCallback.kt @@ -0,0 +1,28 @@ +package com.wafflestudio.snu4t.users.data + +import com.wafflestudio.snu4t.users.service.UserNicknameBackFillSupporter +import org.bson.Document +import org.reactivestreams.Publisher +import org.slf4j.LoggerFactory +import org.springframework.context.annotation.Lazy +import org.springframework.core.CoroutinesUtils +import org.springframework.data.mongodb.core.mapping.event.ReactiveAfterConvertCallback +import org.springframework.stereotype.Component +import reactor.core.publisher.Mono + +@Component +class UserEntityCallback( + @Lazy private val supporter: UserNicknameBackFillSupporter +) : ReactiveAfterConvertCallback { + + private val log = LoggerFactory.getLogger(javaClass) + + override fun onAfterConvert(entity: User, document: Document, collection: String): Publisher { + if (entity.nickname == null) { + log.info("empty nickname found. user: $entity") + return CoroutinesUtils.deferredToMono(supporter.backFillNickname(entity)) + } + + return Mono.just(entity) + } +} diff --git a/core/src/main/kotlin/users/service/UserNicknameBackFillSupporter.kt b/core/src/main/kotlin/users/service/UserNicknameBackFillSupporter.kt new file mode 100644 index 00000000..ff508cca --- /dev/null +++ b/core/src/main/kotlin/users/service/UserNicknameBackFillSupporter.kt @@ -0,0 +1,31 @@ +package com.wafflestudio.snu4t.users.service + +import com.wafflestudio.snu4t.users.data.User +import com.wafflestudio.snu4t.users.repository.UserRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.async +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Component + +@Component +class UserNicknameBackFillSupporter( + private val userRepository: UserRepository, + private val userNicknameService: UserNicknameService +) { + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + private val log = LoggerFactory.getLogger(javaClass) + + fun backFillNickname(user: User): Deferred = scope.async { + try { + val result = userRepository.save(user.copy(nickname = userNicknameService.generateUniqueRandomNickname())) + log.info("[BACKFILL] updated nickname of USER: $result") + result + } catch (e: Exception) { + log.error("[BACKFILL] failed to update nickname of USER: $user", e) + throw e + } + } +} From 12c032addf3263a53bc8ba2de7c3729f472ee1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=A7=80=ED=98=81?= Date: Fri, 8 Sep 2023 01:13:11 +0900 Subject: [PATCH 2/4] =?UTF-8?q?entityCallback=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=ED=95=98=EB=8A=94=20backfill=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=98=20=EC=BD=94=EB=A3=A8=ED=8B=B4=20=EC=BB=A8?= =?UTF-8?q?=ED=85=8D=EC=8A=A4=ED=8A=B8=EB=A5=BC=20Unconfined=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/users/data/UserEntityCallback.kt | 30 +++++++++++++----- .../service/UserNicknameBackFillSupporter.kt | 31 ------------------- 2 files changed, 22 insertions(+), 39 deletions(-) delete mode 100644 core/src/main/kotlin/users/service/UserNicknameBackFillSupporter.kt diff --git a/core/src/main/kotlin/users/data/UserEntityCallback.kt b/core/src/main/kotlin/users/data/UserEntityCallback.kt index 2dd06241..daaea1b5 100644 --- a/core/src/main/kotlin/users/data/UserEntityCallback.kt +++ b/core/src/main/kotlin/users/data/UserEntityCallback.kt @@ -1,28 +1,42 @@ package com.wafflestudio.snu4t.users.data -import com.wafflestudio.snu4t.users.service.UserNicknameBackFillSupporter +import com.wafflestudio.snu4t.users.repository.UserRepository +import com.wafflestudio.snu4t.users.service.UserNicknameService +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.reactor.mono import org.bson.Document import org.reactivestreams.Publisher import org.slf4j.LoggerFactory import org.springframework.context.annotation.Lazy -import org.springframework.core.CoroutinesUtils import org.springframework.data.mongodb.core.mapping.event.ReactiveAfterConvertCallback import org.springframework.stereotype.Component import reactor.core.publisher.Mono @Component -class UserEntityCallback( - @Lazy private val supporter: UserNicknameBackFillSupporter +class UserEntityCallback @Lazy constructor( + private val userRepository: UserRepository, + private val userNicknameService: UserNicknameService, ) : ReactiveAfterConvertCallback { private val log = LoggerFactory.getLogger(javaClass) override fun onAfterConvert(entity: User, document: Document, collection: String): Publisher { - if (entity.nickname == null) { - log.info("empty nickname found. user: $entity") - return CoroutinesUtils.deferredToMono(supporter.backFillNickname(entity)) + if (entity.nickname != null) { + return Mono.just(entity) } - return Mono.just(entity) + log.info("empty nickname found. user: $entity") + return backFillNickname(entity) + } + + private fun backFillNickname(user: User): Mono = mono(Dispatchers.Unconfined) { + try { + val result = userRepository.save(user.copy(nickname = userNicknameService.generateUniqueRandomNickname())) + log.info("[BACKFILL] updated nickname of USER: $result") + result + } catch (e: Exception) { + log.error("[BACKFILL] failed to update nickname of USER: $user", e) + throw e + } } } diff --git a/core/src/main/kotlin/users/service/UserNicknameBackFillSupporter.kt b/core/src/main/kotlin/users/service/UserNicknameBackFillSupporter.kt deleted file mode 100644 index ff508cca..00000000 --- a/core/src/main/kotlin/users/service/UserNicknameBackFillSupporter.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.wafflestudio.snu4t.users.service - -import com.wafflestudio.snu4t.users.data.User -import com.wafflestudio.snu4t.users.repository.UserRepository -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.async -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Component - -@Component -class UserNicknameBackFillSupporter( - private val userRepository: UserRepository, - private val userNicknameService: UserNicknameService -) { - private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) - private val log = LoggerFactory.getLogger(javaClass) - - fun backFillNickname(user: User): Deferred = scope.async { - try { - val result = userRepository.save(user.copy(nickname = userNicknameService.generateUniqueRandomNickname())) - log.info("[BACKFILL] updated nickname of USER: $result") - result - } catch (e: Exception) { - log.error("[BACKFILL] failed to update nickname of USER: $user", e) - throw e - } - } -} From 67fa9df888d7b57506bfe64676921bce67dce5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=A7=80=ED=98=81?= Date: Sat, 9 Sep 2023 23:56:33 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=EC=9A=B4=EC=98=81=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EC=97=90=EC=84=9C=EB=A7=8C=20config=20=EC=BA=90=EC=8B=B1=20(#1?= =?UTF-8?q?73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ClientConfigService.kt | 23 ++++++++++++++----- core/src/main/kotlin/config/PhaseConfig.kt | 3 +++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/core/src/main/kotlin/clientconfig/service/ClientConfigService.kt b/core/src/main/kotlin/clientconfig/service/ClientConfigService.kt index 2dffcedd..ae622bad 100644 --- a/core/src/main/kotlin/clientconfig/service/ClientConfigService.kt +++ b/core/src/main/kotlin/clientconfig/service/ClientConfigService.kt @@ -9,8 +9,11 @@ import com.wafflestudio.snu4t.clientconfig.repository.findByNameAndVersions import com.wafflestudio.snu4t.common.cache.Cache import com.wafflestudio.snu4t.common.cache.CacheKey import com.wafflestudio.snu4t.common.cache.get +import com.wafflestudio.snu4t.common.client.AppVersion import com.wafflestudio.snu4t.common.client.ClientInfo +import com.wafflestudio.snu4t.common.client.OsType import com.wafflestudio.snu4t.common.exception.ConfigNotFoundException +import com.wafflestudio.snu4t.config.Phase import kotlinx.coroutines.flow.toList import org.springframework.stereotype.Service import java.time.LocalDateTime @@ -20,16 +23,24 @@ class ClientConfigService( private val clientConfigRepository: ClientConfigRepository, private val objectMapper: ObjectMapper, private val cache: Cache, + private val phase: Phase ) { suspend fun getConfigs(clientInfo: ClientInfo): List { val (osType, appVersion) = clientInfo.osType to requireNotNull(clientInfo.appVersion) - return CacheKey.CLIENT_CONFIGS.build(osType, appVersion).let { cacheKey -> - cache.get(cacheKey) { - clientConfigRepository.findAll().toList() - .filter { it.isAdaptable(osType, appVersion) }.distinctBy { it.name } - } - } ?: emptyList() + if (!phase.isProd) { + return getAdaptableConfigs(osType, appVersion) + } + + val cacheKey = CacheKey.CLIENT_CONFIGS.build(osType, appVersion) + return cache.get(cacheKey) { getAdaptableConfigs(osType, appVersion) } ?: emptyList() + } + + private suspend fun getAdaptableConfigs(osType: OsType, appVersion: AppVersion): List { + return clientConfigRepository.findAll() + .toList() + .filter { it.isAdaptable(osType, appVersion) } + .distinctBy { it.name } } suspend fun getConfigsByName(name: String): List { diff --git a/core/src/main/kotlin/config/PhaseConfig.kt b/core/src/main/kotlin/config/PhaseConfig.kt index 084a746c..fa3b5446 100644 --- a/core/src/main/kotlin/config/PhaseConfig.kt +++ b/core/src/main/kotlin/config/PhaseConfig.kt @@ -27,4 +27,7 @@ enum class Phase { LOCAL, TEST ; + + val isProd: Boolean + get() = this == PROD } From 6618d99669e782dfcd70afecb505d8b23eb25766 Mon Sep 17 00:00:00 2001 From: Davin Byeon Date: Sun, 10 Sep 2023 14:23:11 +0900 Subject: [PATCH 4/4] Resolve Friend QA Issues (#174) --- core/src/main/kotlin/common/exception/ErrorType.kt | 7 ++++--- core/src/main/kotlin/common/exception/Snu4tException.kt | 1 + core/src/main/kotlin/common/push/UrlScheme.kt | 1 + core/src/main/kotlin/friend/service/FriendService.kt | 7 +++++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/core/src/main/kotlin/common/exception/ErrorType.kt b/core/src/main/kotlin/common/exception/ErrorType.kt index d5d23ba7..c8183d48 100644 --- a/core/src/main/kotlin/common/exception/ErrorType.kt +++ b/core/src/main/kotlin/common/exception/ErrorType.kt @@ -41,12 +41,13 @@ enum class ErrorType( PRIMARY_TIMETABLE_NOT_FOUND(HttpStatus.NOT_FOUND, 40401, "timetable_id가 유효하지 않습니다", "대표 시간표가 존재하지 않습니다."), NO_USER_FCM_KEY(HttpStatus.NOT_FOUND, 40402, "유저 FCM 키가 존재하지 않습니다."), CONFIG_NOT_FOUND(HttpStatus.NOT_FOUND, 40403, "config가 존재하지 않습니다."), - FRIEND_NOT_FOUND(HttpStatus.NOT_FOUND, 40404, "친구 관계가 존재하지 않습니다."), + FRIEND_NOT_FOUND(HttpStatus.NOT_FOUND, 40404, "친구 관계가 존재하지 않습니다.", "친구 관계가 존재하지 않습니다."), + USER_NOT_FOUND_BY_NICKNAME(HttpStatus.NOT_FOUND, 40405, "해당 닉네임의 유저를 찾을 수 없습니다.", "해당 닉네임의 유저를 찾을 수 없습니다."), DUPLICATE_VACANCY_NOTIFICATION(HttpStatus.CONFLICT, 40900, "빈자리 알림 중복"), DUPLICATE_EMAIL(HttpStatus.CONFLICT, 40901, "이미 사용 중인 이메일입니다."), - DUPLICATE_FRIEND(HttpStatus.CONFLICT, 40902, "이미 친구 관계이거나 친구 요청을 보냈습니다."), - INVALID_FRIEND(HttpStatus.CONFLICT, 40903, "친구 요청을 보낼 수 없는 유저입니다."), + DUPLICATE_FRIEND(HttpStatus.CONFLICT, 40902, "이미 친구 관계이거나 친구 요청을 보냈습니다.", "이미 친구 관계이거나 친구 요청을 보냈습니다."), + INVALID_FRIEND(HttpStatus.CONFLICT, 40903, "친구 요청을 보낼 수 없는 유저입니다.", "친구 요청을 보낼 수 없는 유저입니다."), DYNAMIC_LINK_GENERATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, 50001, "링크 생성 실패", "링크 생성에 실패했습니다. 잠시 후 다시 시도해주세요."), } diff --git a/core/src/main/kotlin/common/exception/Snu4tException.kt b/core/src/main/kotlin/common/exception/Snu4tException.kt index fb1b1a3a..5106b8c9 100644 --- a/core/src/main/kotlin/common/exception/Snu4tException.kt +++ b/core/src/main/kotlin/common/exception/Snu4tException.kt @@ -49,6 +49,7 @@ object PrimaryTimetableNotFoundException : Snu4tException(ErrorType.TIMETABLE_NO object TimetableNotPrimaryException : Snu4tException(ErrorType.DEFAULT_ERROR) object ConfigNotFoundException : Snu4tException(ErrorType.CONFIG_NOT_FOUND) object FriendNotFoundException : Snu4tException(ErrorType.FRIEND_NOT_FOUND) +object UserNotFoundByNicknameException : Snu4tException(ErrorType.USER_NOT_FOUND_BY_NICKNAME) object DuplicateVacancyNotificationException : Snu4tException(ErrorType.DUPLICATE_VACANCY_NOTIFICATION) object DuplicateEmailException : Snu4tException(ErrorType.DUPLICATE_EMAIL) diff --git a/core/src/main/kotlin/common/push/UrlScheme.kt b/core/src/main/kotlin/common/push/UrlScheme.kt index 24797cd0..d2a25665 100644 --- a/core/src/main/kotlin/common/push/UrlScheme.kt +++ b/core/src/main/kotlin/common/push/UrlScheme.kt @@ -9,6 +9,7 @@ enum class UrlScheme( ) { NOTIFICATIONS(Protocol.SNUTT, "notifications"), VACANCY(Protocol.SNUTT, "vacancy"), + FRIENDS(Protocol.SNUTT, "friends"), ; fun compileWith( diff --git a/core/src/main/kotlin/friend/service/FriendService.kt b/core/src/main/kotlin/friend/service/FriendService.kt index f36d5915..0da6d94f 100644 --- a/core/src/main/kotlin/friend/service/FriendService.kt +++ b/core/src/main/kotlin/friend/service/FriendService.kt @@ -4,7 +4,8 @@ import com.wafflestudio.snu4t.common.exception.DuplicateFriendException import com.wafflestudio.snu4t.common.exception.FriendNotFoundException import com.wafflestudio.snu4t.common.exception.InvalidDisplayNameException import com.wafflestudio.snu4t.common.exception.InvalidFriendException -import com.wafflestudio.snu4t.common.exception.UserNotFoundException +import com.wafflestudio.snu4t.common.exception.UserNotFoundByNicknameException +import com.wafflestudio.snu4t.common.push.UrlScheme import com.wafflestudio.snu4t.common.push.dto.PushMessage import com.wafflestudio.snu4t.friend.data.Friend import com.wafflestudio.snu4t.friend.dto.FriendState @@ -66,7 +67,7 @@ class FriendServiceImpl( } override suspend fun requestFriend(fromUserId: String, toUserNickname: String): Unit = coroutineScope { - val toUser = userRepository.findByNicknameAndActiveTrue(toUserNickname) ?: throw UserNotFoundException + val toUser = userRepository.findByNicknameAndActiveTrue(toUserNickname) ?: throw UserNotFoundByNicknameException val toUserId = toUser.id!! if (fromUserId == toUserId) throw InvalidFriendException @@ -87,6 +88,7 @@ class FriendServiceImpl( val pushMessage = PushMessage( title = "친구 요청", body = "'$fromUserNickname'님의 친구 요청을 수락하고 서로의 대표 시간표를 확인해보세요!", + urlScheme = UrlScheme.FRIENDS, ) pushWithNotificationService.sendPushAndNotification(pushMessage, NotificationType.NORMAL, toUserId) } @@ -110,6 +112,7 @@ class FriendServiceImpl( val pushMessage = PushMessage( title = "친구 요청 수락", body = "'$toUserNickname'님과 친구가 되었어요.", + urlScheme = UrlScheme.FRIENDS, ) pushWithNotificationService.sendPushAndNotification(pushMessage, NotificationType.NORMAL, fromUserId) }