diff --git a/src/main/kotlin/com/doongjun/commitmon/api/CommitmonController.kt b/src/main/kotlin/com/doongjun/commitmon/api/CommitmonController.kt new file mode 100644 index 0000000..188f7f2 --- /dev/null +++ b/src/main/kotlin/com/doongjun/commitmon/api/CommitmonController.kt @@ -0,0 +1,15 @@ +package com.doongjun.commitmon.api + +import com.doongjun.commitmon.api.data.CommitmonResponse +import io.swagger.v3.oas.annotations.Operation +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/v1/commitmons") +class CommitmonController { + @Operation(summary = "커밋몬 목록 조회") + @GetMapping + fun getAll(): CommitmonResponse = CommitmonResponse.of() +} diff --git a/src/main/kotlin/com/doongjun/commitmon/api/MeController.kt b/src/main/kotlin/com/doongjun/commitmon/api/MeController.kt new file mode 100644 index 0000000..db4face --- /dev/null +++ b/src/main/kotlin/com/doongjun/commitmon/api/MeController.kt @@ -0,0 +1,50 @@ +package com.doongjun.commitmon.api + +import com.doongjun.commitmon.api.data.ChangeCommitmonRequest +import com.doongjun.commitmon.api.data.MeDetailResponse +import com.doongjun.commitmon.api.data.MeResponse +import com.doongjun.commitmon.app.UserFetchType +import com.doongjun.commitmon.app.UserService +import com.doongjun.commitmon.extension.currentUserId +import io.swagger.v3.oas.annotations.Operation +import jakarta.validation.Valid +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/v1/me") +class MeController( + private val userService: UserService, +) { + @Operation(summary = "로그인 유저 조회") + @GetMapping + fun get(): MeResponse = + MeResponse.from( + user = userService.getSimple(currentUserId!!), + ) + + @Operation(summary = "로그인 유저 조회 (팔로워, 팔로잉 포함)") + @GetMapping("/detail") + fun getDetail(): MeDetailResponse = + MeDetailResponse.from( + user = + userService.get( + id = currentUserId!!, + userFetchType = UserFetchType.ALL, + ), + ) + + @Operation(summary = "로그인 유저 커밋몬 변경") + @PostMapping("/commitmon/change") + fun changeCommitmon( + @Valid @RequestBody request: ChangeCommitmonRequest, + ) { + userService.changeCommitmon( + id = currentUserId!!, + commitmon = request.commitmon!!, + ) + } +} diff --git a/src/main/kotlin/com/doongjun/commitmon/api/data/ChangeCommitmonRequest.kt b/src/main/kotlin/com/doongjun/commitmon/api/data/ChangeCommitmonRequest.kt new file mode 100644 index 0000000..2117997 --- /dev/null +++ b/src/main/kotlin/com/doongjun/commitmon/api/data/ChangeCommitmonRequest.kt @@ -0,0 +1,9 @@ +package com.doongjun.commitmon.api.data + +import com.doongjun.commitmon.domain.Commitmon +import jakarta.validation.constraints.NotNull + +data class ChangeCommitmonRequest( + @field:NotNull + val commitmon: Commitmon?, +) diff --git a/src/main/kotlin/com/doongjun/commitmon/api/data/CommitmonResponse.kt b/src/main/kotlin/com/doongjun/commitmon/api/data/CommitmonResponse.kt new file mode 100644 index 0000000..4d47758 --- /dev/null +++ b/src/main/kotlin/com/doongjun/commitmon/api/data/CommitmonResponse.kt @@ -0,0 +1,33 @@ +package com.doongjun.commitmon.api.data + +import com.doongjun.commitmon.domain.Commitmon +import com.doongjun.commitmon.domain.CommitmonLevel + +data class CommitmonResponse( + val commitmons: List, +) { + data class CommitmonData( + val commitmon: Commitmon, + val name: String, + val nameKo: String, + val level: CommitmonLevel, + val seed: Int, + val imageUrl: String, + ) + + companion object { + private val commitmons = + Commitmon.entries.filter { it != Commitmon.EGG }.map { + CommitmonData( + commitmon = it, + name = it.name.lowercase(), + nameKo = it.nameKo, + level = it.level, + seed = it.seed ?: 0, + imageUrl = "https://s3-commitmon.s3.ap-northeast-2.amazonaws.com/static/${it.assetName}.png", + ) + } + + fun of(): CommitmonResponse = CommitmonResponse(commitmons) + } +} diff --git a/src/main/kotlin/com/doongjun/commitmon/api/data/MeDetailResponse.kt b/src/main/kotlin/com/doongjun/commitmon/api/data/MeDetailResponse.kt new file mode 100644 index 0000000..3a88437 --- /dev/null +++ b/src/main/kotlin/com/doongjun/commitmon/api/data/MeDetailResponse.kt @@ -0,0 +1,28 @@ +package com.doongjun.commitmon.api.data + +import com.doongjun.commitmon.app.data.GetSimpleUserDto +import com.doongjun.commitmon.app.data.GetUserDto +import com.doongjun.commitmon.domain.Commitmon + +data class MeDetailResponse( + val id: Long, + val name: String, + val totalCommitCount: Long, + val commitmon: Commitmon, + val exp: Int = 0, + val followers: List, + val following: List, +) { + companion object { + fun from(user: GetUserDto): MeDetailResponse = + MeDetailResponse( + id = user.id, + name = user.name, + totalCommitCount = user.totalCommitCount, + commitmon = user.commitmon, + exp = user.exp, + followers = user.followers, + following = user.following, + ) + } +} diff --git a/src/main/kotlin/com/doongjun/commitmon/api/data/MeResponse.kt b/src/main/kotlin/com/doongjun/commitmon/api/data/MeResponse.kt new file mode 100644 index 0000000..9b3f9c5 --- /dev/null +++ b/src/main/kotlin/com/doongjun/commitmon/api/data/MeResponse.kt @@ -0,0 +1,23 @@ +package com.doongjun.commitmon.api.data + +import com.doongjun.commitmon.app.data.GetSimpleUserDto +import com.doongjun.commitmon.domain.Commitmon + +data class MeResponse( + val id: Long, + val name: String, + val totalCommitCount: Long, + val commitmon: Commitmon, + val exp: Int = 0, +) { + companion object { + fun from(user: GetSimpleUserDto): MeResponse = + MeResponse( + id = user.id, + name = user.name, + totalCommitCount = user.totalCommitCount, + commitmon = user.commitmon, + exp = user.exp, + ) + } +} diff --git a/src/main/kotlin/com/doongjun/commitmon/app/UserService.kt b/src/main/kotlin/com/doongjun/commitmon/app/UserService.kt index f007bde..6dc69d5 100644 --- a/src/main/kotlin/com/doongjun/commitmon/app/UserService.kt +++ b/src/main/kotlin/com/doongjun/commitmon/app/UserService.kt @@ -1,8 +1,10 @@ package com.doongjun.commitmon.app import com.doongjun.commitmon.app.data.CreateUserDto +import com.doongjun.commitmon.app.data.GetSimpleUserDto import com.doongjun.commitmon.app.data.GetUserDto import com.doongjun.commitmon.app.data.UpdateUserDto +import com.doongjun.commitmon.domain.Commitmon import com.doongjun.commitmon.domain.User import com.doongjun.commitmon.domain.UserRepository import org.springframework.cache.annotation.Cacheable @@ -18,13 +20,18 @@ class UserService( @Transactional(readOnly = true) fun existsByName(name: String) = userRepository.existsByName(name) + @Transactional(readOnly = true) + fun getSimple(id: Long): GetSimpleUserDto = + userRepository.findByIdOrNull(id)?.let { user -> GetSimpleUserDto.from(user) } + ?: throw NoSuchElementException("Failed to fetch user by id: $id") + @Transactional(readOnly = true) fun get( id: Long, userFetchType: UserFetchType, ): GetUserDto = userRepository.findByIdOrNull(id)?.let { user -> GetUserDto.from(user, userFetchType) } - ?: throw IllegalArgumentException("Failed to fetch user by id: $id") + ?: throw NoSuchElementException("Failed to fetch user by id: $id") @Cacheable(value = ["userInfo"], key = "#name + '-' + #userFetchType.title") @Transactional(readOnly = true) @@ -33,7 +40,7 @@ class UserService( userFetchType: UserFetchType, ): GetUserDto = userRepository.findByName(name)?.let { user -> GetUserDto.from(user, userFetchType) } - ?: throw IllegalArgumentException("Failed to fetch user by name: $name") + ?: throw NoSuchElementException("Failed to fetch user by name: $name") fun create(dto: CreateUserDto): Long { val user = @@ -54,7 +61,7 @@ class UserService( ) { val user = userRepository.findByIdOrNull(id) - ?: throw IllegalArgumentException("Failed to fetch user by id: $id") + ?: throw NoSuchElementException("Failed to fetch user by id: $id") user.update( totalCommitCount = dto.totalCommitCount, @@ -64,4 +71,17 @@ class UserService( userRepository.save(user) } + + fun changeCommitmon( + id: Long, + commitmon: Commitmon, + ) { + val user = + userRepository.findByIdOrNull(id) + ?: throw NoSuchElementException("Failed to fetch user by id: $id") + + user.changeCommitmon(commitmon) + + userRepository.save(user) + } } diff --git a/src/main/kotlin/com/doongjun/commitmon/app/data/GetUserDto.kt b/src/main/kotlin/com/doongjun/commitmon/app/data/GetUserDto.kt index b1de78a..19a987b 100644 --- a/src/main/kotlin/com/doongjun/commitmon/app/data/GetUserDto.kt +++ b/src/main/kotlin/com/doongjun/commitmon/app/data/GetUserDto.kt @@ -4,8 +4,10 @@ import com.doongjun.commitmon.app.UserFetchType import com.doongjun.commitmon.core.NoArgs import com.doongjun.commitmon.domain.Commitmon import com.doongjun.commitmon.domain.User +import com.fasterxml.jackson.annotation.JsonIgnoreProperties @NoArgs +@JsonIgnoreProperties(value = ["followerIds", "followingIds", "followers", "following"]) data class GetUserDto( val id: Long, val name: String, @@ -13,7 +15,16 @@ data class GetUserDto( val commitmon: Commitmon, val exp: Int = 0, val fetchedUsers: List, + private val followerIds: List, + private val followingIds: List, ) { + val followers: List by lazy { + fetchedUsers.filter { it.id in followerIds } + } + val following: List by lazy { + fetchedUsers.filter { it.id in followingIds } + } + fun toSimple(): GetSimpleUserDto = GetSimpleUserDto( id = id, @@ -39,12 +50,15 @@ data class GetUserDto( UserFetchType.ALL -> listOf(user.followers, user.following) .flatten() + .distinct() .map { GetSimpleUserDto.from(it) } UserFetchType.MUTUAL -> user.mutualFollowers .map { GetSimpleUserDto.from(it) } UserFetchType.SOLO -> emptyList() }, + followerIds = user.followers.map { it.id }, + followingIds = user.following.map { it.id }, ) } } diff --git a/src/main/kotlin/com/doongjun/commitmon/config/security/JwtFilter.kt b/src/main/kotlin/com/doongjun/commitmon/config/security/JwtFilter.kt index 7c8d13a..2fb6a4a 100644 --- a/src/main/kotlin/com/doongjun/commitmon/config/security/JwtFilter.kt +++ b/src/main/kotlin/com/doongjun/commitmon/config/security/JwtFilter.kt @@ -5,6 +5,7 @@ import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import org.springframework.http.HttpHeaders import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.web.authentication.WebAuthenticationDetailsSource import org.springframework.security.web.context.RequestAttributeSecurityContextRepository @@ -29,6 +30,7 @@ class JwtFilter( UsernamePasswordAuthenticationToken( userId, null, + listOf(SimpleGrantedAuthority("ROLE_USER")), ) authenticationToken.details = WebAuthenticationDetailsSource().buildDetails(request) diff --git a/src/main/kotlin/com/doongjun/commitmon/config/security/SecurityConfig.kt b/src/main/kotlin/com/doongjun/commitmon/config/security/SecurityConfig.kt index 0900ce2..edea41c 100644 --- a/src/main/kotlin/com/doongjun/commitmon/config/security/SecurityConfig.kt +++ b/src/main/kotlin/com/doongjun/commitmon/config/security/SecurityConfig.kt @@ -28,6 +28,8 @@ class SecurityConfig( .permitAll() .requestMatchers("/api/v1/account/**") .permitAll() + .requestMatchers("/api/v1/commitmons/**") + .permitAll() .anyRequest() .authenticated() }.exceptionHandling { exception -> diff --git a/src/main/kotlin/com/doongjun/commitmon/core/error/GlobalExceptionHandler.kt b/src/main/kotlin/com/doongjun/commitmon/core/error/GlobalExceptionHandler.kt index 43a4a71..4ddf371 100644 --- a/src/main/kotlin/com/doongjun/commitmon/core/error/GlobalExceptionHandler.kt +++ b/src/main/kotlin/com/doongjun/commitmon/core/error/GlobalExceptionHandler.kt @@ -79,7 +79,7 @@ class GlobalExceptionHandler { @ExceptionHandler(AccountExpiredException::class) protected fun handleAccountExpiredException(e: AccountExpiredException): ResponseEntity { - log.error("AccessDeniedException", e) + log.error("AccountExpiredException", e) val response = ErrorResponse.of(ErrorCode.UNAUTHORIZED) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response) } @@ -90,4 +90,11 @@ class GlobalExceptionHandler { val response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE) return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response) } + + @ExceptionHandler(NoSuchElementException::class) + protected fun handleNoSuchElementException(e: NoSuchElementException): ResponseEntity { + log.error("NoSuchElementException", e) + val response = ErrorResponse.of(ErrorCode.NOT_FOUND) + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response) + } } diff --git a/src/main/kotlin/com/doongjun/commitmon/core/error/response/ErrorCode.kt b/src/main/kotlin/com/doongjun/commitmon/core/error/response/ErrorCode.kt index 2fcccbb..03bcada 100644 --- a/src/main/kotlin/com/doongjun/commitmon/core/error/response/ErrorCode.kt +++ b/src/main/kotlin/com/doongjun/commitmon/core/error/response/ErrorCode.kt @@ -8,6 +8,7 @@ enum class ErrorCode( INVALID_INPUT_VALUE(400, "A001", "Invalid input value."), METHOD_NOT_ALLOWED(405, "A002", "Invalid input value."), INTERNAL_SERVER_ERROR(500, "A003", "Server Error."), + NOT_FOUND(404, "A004", "Not Found."), INVALID_TYPE_VALUE(400, "A005", "Invalid type value."), ACCESS_DENIED(403, "A006", "Access is denied."), UNAUTHORIZED(401, "A007", "Unauthorized."), diff --git a/src/main/kotlin/com/doongjun/commitmon/domain/Commitmon.kt b/src/main/kotlin/com/doongjun/commitmon/domain/Commitmon.kt index a482a1e..714638c 100644 --- a/src/main/kotlin/com/doongjun/commitmon/domain/Commitmon.kt +++ b/src/main/kotlin/com/doongjun/commitmon/domain/Commitmon.kt @@ -19,7 +19,7 @@ enum class Commitmon( METALGREYMON( CommitmonLevel.PERFECT, listOf(EGG, BOTAMON, KOROMON, AGUMON, GREYMON), - assetName = "metalGreymon", + assetName = "metalgreymon", nameKo = "메탈그레이몬", seed = 1, ), @@ -33,7 +33,7 @@ enum class Commitmon( PUNIMON(CommitmonLevel.BABY, listOf(EGG), assetName = "punimon", nameKo = "푸니몬", seed = 2), TSUNOMON(CommitmonLevel.IN_TRAINING, listOf(EGG, PUNIMON), assetName = "tsunomon", nameKo = "뿔몬", seed = 2), - GABUMON(CommitmonLevel.ROOKIE, listOf(EGG, PUNIMON, TSUNOMON), assetName = "gabumon", nameKo = "가루몬", seed = 2), + GABUMON(CommitmonLevel.ROOKIE, listOf(EGG, PUNIMON, TSUNOMON), assetName = "gabumon", nameKo = "파피몬", seed = 2), GARURUMON(CommitmonLevel.CHAMPION, listOf(EGG, PUNIMON, TSUNOMON, GABUMON), assetName = "garurumon", nameKo = "가루몬", seed = 2), WEREGARURUMON( CommitmonLevel.PERFECT, diff --git a/src/main/kotlin/com/doongjun/commitmon/domain/CommitmonLevel.kt b/src/main/kotlin/com/doongjun/commitmon/domain/CommitmonLevel.kt index 0f07317..c1b0fe9 100644 --- a/src/main/kotlin/com/doongjun/commitmon/domain/CommitmonLevel.kt +++ b/src/main/kotlin/com/doongjun/commitmon/domain/CommitmonLevel.kt @@ -2,7 +2,7 @@ package com.doongjun.commitmon.domain enum class CommitmonLevel( val exp: Long, - private val order: Int, + val order: Int, ) { EGG(0, 0), // 알 BABY(100, 1), // 유아기 diff --git a/src/main/kotlin/com/doongjun/commitmon/domain/User.kt b/src/main/kotlin/com/doongjun/commitmon/domain/User.kt index 5a76865..7c8acec 100644 --- a/src/main/kotlin/com/doongjun/commitmon/domain/User.kt +++ b/src/main/kotlin/com/doongjun/commitmon/domain/User.kt @@ -34,6 +34,9 @@ class User( var commitmon: Commitmon = Commitmon.randomCommitmon(CommitmonLevel.fromExp(totalCommitCount)) protected set + @Column(name = "auto_level_up", nullable = false) + var autoLevelUp: Boolean = true + @OneToMany(mappedBy = "follower", fetch = LAZY, cascade = [ALL], orphanRemoval = true) protected val mutableFollowers: MutableSet = toFollowers(followers).toMutableSet() val followers: List get() = mutableFollowers.map { it.following } @@ -59,12 +62,25 @@ class User( following: List, ) { this.totalCommitCount = totalCommitCount - syncCommitmonLevel(CommitmonLevel.fromExp(totalCommitCount)) updateFollowers(followers) updateFollowing(following) + + if (this.autoLevelUp) { + autoLevelUpCommitmon(CommitmonLevel.fromExp(totalCommitCount)) + } + } + + fun changeCommitmon(commitmon: Commitmon) { + val currentLevel = CommitmonLevel.fromExp(totalCommitCount) + if (commitmon.level.order > currentLevel.order) { + throw IllegalArgumentException("Cannot change to higher level") + } + + this.commitmon = commitmon + this.autoLevelUp = commitmon.level == currentLevel } - private fun syncCommitmonLevel(level: CommitmonLevel) { + private fun autoLevelUpCommitmon(level: CommitmonLevel) { if (this.commitmon.level != level) { this.commitmon = Commitmon.randomLevelTreeCommitmon(level, this.commitmon) } diff --git a/src/main/kotlin/com/doongjun/commitmon/extension/SecurityExtension.kt b/src/main/kotlin/com/doongjun/commitmon/extension/SecurityExtension.kt new file mode 100644 index 0000000..8018bc2 --- /dev/null +++ b/src/main/kotlin/com/doongjun/commitmon/extension/SecurityExtension.kt @@ -0,0 +1,5 @@ +package com.doongjun.commitmon.extension + +import org.springframework.security.core.context.SecurityContextHolder + +val currentUserId get() = SecurityContextHolder.getContext().authentication?.principal as Long? diff --git a/src/main/kotlin/com/doongjun/commitmon/infra/GithubGraphqlApi.kt b/src/main/kotlin/com/doongjun/commitmon/infra/GithubGraphqlApi.kt index ea84adf..77e0335 100644 --- a/src/main/kotlin/com/doongjun/commitmon/infra/GithubGraphqlApi.kt +++ b/src/main/kotlin/com/doongjun/commitmon/infra/GithubGraphqlApi.kt @@ -6,6 +6,7 @@ import com.doongjun.commitmon.infra.data.UserFollowInfoResponse import com.doongjun.commitmon.infra.data.UserFollowInfoVariables import com.doongjun.commitmon.infra.data.UserFollowersResponse import com.doongjun.commitmon.infra.data.UserFollowingResponse +import org.slf4j.LoggerFactory import org.springframework.core.ParameterizedTypeReference import org.springframework.core.io.ClassPathResource import org.springframework.stereotype.Component @@ -16,6 +17,8 @@ import java.nio.charset.Charset class GithubGraphqlApi( private val githubGraphqlWebClient: WebClient, ) { + private val log = LoggerFactory.getLogger(javaClass) + companion object { private val userFollowInfoQuery = ClassPathResource( @@ -53,10 +56,13 @@ class GithubGraphqlApi( .bodyValue(requestBody) .retrieve() .bodyToMono(object : ParameterizedTypeReference>() {}) - .onErrorMap { error -> throw IllegalArgumentException("Failed to fetch user follow info: $error") } - .block()!! + .onErrorMap { error -> + log.error(error.message) + throw IllegalArgumentException("Failed to fetch user follow info: $error") + }.block()!! if (isError(response)) { + log.error("Failed to fetch user follow info: ${response.errors}") throw IllegalArgumentException("Failed to fetch user follow info: ${response.errors}") } @@ -85,10 +91,13 @@ class GithubGraphqlApi( .bodyValue(requestBody) .retrieve() .bodyToMono(object : ParameterizedTypeReference>() {}) - .onErrorMap { error -> throw IllegalArgumentException("Failed to fetch user followers: $error") } - .block()!! + .onErrorMap { error -> + log.error(error.message) + throw IllegalArgumentException("Failed to fetch user followers: $error") + }.block()!! if (isError(response)) { + log.error("Failed to fetch user followers: ${response.errors}") throw IllegalArgumentException("Failed to fetch user followers: ${response.errors}") } @@ -117,10 +126,13 @@ class GithubGraphqlApi( .bodyValue(requestBody) .retrieve() .bodyToMono(object : ParameterizedTypeReference>() {}) - .onErrorMap { error -> throw IllegalArgumentException("Failed to fetch user following: $error") } - .block()!! + .onErrorMap { error -> + log.error(error.message) + throw IllegalArgumentException("Failed to fetch user following: $error") + }.block()!! if (isError(response)) { + log.error("Failed to fetch user following: ${response.errors}") throw IllegalArgumentException("Failed to fetch user following: ${response.errors}") } diff --git a/src/main/kotlin/com/doongjun/commitmon/infra/GithubOAuth2Api.kt b/src/main/kotlin/com/doongjun/commitmon/infra/GithubOAuth2Api.kt index b3439b6..a92794b 100644 --- a/src/main/kotlin/com/doongjun/commitmon/infra/GithubOAuth2Api.kt +++ b/src/main/kotlin/com/doongjun/commitmon/infra/GithubOAuth2Api.kt @@ -1,6 +1,7 @@ package com.doongjun.commitmon.infra import com.doongjun.commitmon.infra.data.OAuthLoginResponse +import org.slf4j.LoggerFactory import org.springframework.stereotype.Component import org.springframework.web.reactive.function.client.WebClient @@ -8,6 +9,8 @@ import org.springframework.web.reactive.function.client.WebClient class GithubOAuth2Api( private val githubOAuth2WebClient: WebClient, ) { + private val log = LoggerFactory.getLogger(javaClass) + fun fetchAccessToken( code: String, clientId: String, @@ -24,6 +27,8 @@ class GithubOAuth2Api( .build() }.retrieve() .bodyToMono(OAuthLoginResponse::class.java) - .onErrorMap { error -> throw IllegalArgumentException("Failed to fetch access token: $error") } - .block()!! + .onErrorMap { error -> + log.error(error.message) + throw IllegalArgumentException("Failed to fetch access token: $error") + }.block()!! } diff --git a/src/main/kotlin/com/doongjun/commitmon/infra/GithubRestApi.kt b/src/main/kotlin/com/doongjun/commitmon/infra/GithubRestApi.kt index 9d4e688..04b36ed 100644 --- a/src/main/kotlin/com/doongjun/commitmon/infra/GithubRestApi.kt +++ b/src/main/kotlin/com/doongjun/commitmon/infra/GithubRestApi.kt @@ -2,6 +2,7 @@ package com.doongjun.commitmon.infra import com.doongjun.commitmon.infra.data.UserCommitSearchResponse import com.doongjun.commitmon.infra.data.UserInfoResponse +import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component import org.springframework.web.reactive.function.client.WebClient @@ -12,6 +13,8 @@ class GithubRestApi( @Value("\${app.github.token}") private val githubToken: String, ) { + private val log = LoggerFactory.getLogger(javaClass) + fun fetchUserInfo(userToken: String): String = githubRestWebClient .get() @@ -23,8 +26,10 @@ class GithubRestApi( headers.add("Authorization", "Bearer $userToken") }.retrieve() .bodyToMono(UserInfoResponse::class.java) - .onErrorMap { error -> throw IllegalArgumentException("Failed to fetch user: $error") } - .block()!! + .onErrorMap { error -> + log.error(error.message) + throw IllegalArgumentException("Failed to fetch user: $error") + }.block()!! .login fun fetchUserCommitSearchInfo(username: String): UserCommitSearchResponse = @@ -40,6 +45,8 @@ class GithubRestApi( headers.add("Authorization", "Bearer $githubToken") }.retrieve() .bodyToMono(UserCommitSearchResponse::class.java) - .onErrorMap { error -> throw IllegalArgumentException("Failed to fetch user commit count: $error") } - .block()!! + .onErrorMap { error -> + log.error(error.message) + throw IllegalArgumentException("Failed to fetch user commit count: $error") + }.block()!! } diff --git a/src/test/kotlin/com/doongjun/commitmon/app/UserServiceTest.kt b/src/test/kotlin/com/doongjun/commitmon/app/UserServiceTest.kt index 6392301..d879935 100644 --- a/src/test/kotlin/com/doongjun/commitmon/app/UserServiceTest.kt +++ b/src/test/kotlin/com/doongjun/commitmon/app/UserServiceTest.kt @@ -2,11 +2,13 @@ package com.doongjun.commitmon.app import com.doongjun.commitmon.app.data.CreateUserDto import com.doongjun.commitmon.app.data.UpdateUserDto +import com.doongjun.commitmon.domain.Commitmon import com.doongjun.commitmon.domain.CommitmonLevel import com.doongjun.commitmon.domain.User import com.doongjun.commitmon.domain.UserRepository import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import org.springframework.beans.factory.annotation.Autowired import org.springframework.data.repository.findByIdOrNull @@ -163,4 +165,81 @@ class UserServiceTest : BaseAppTest() { assertThat(findUser?.followers).containsExactlyInAnyOrder(anotherUser1, anotherUser2) assertThat(findUser?.following).containsExactlyInAnyOrder(anotherUser2, anotherUser3) } + + @Test + fun update_ThenNotBeLeveledUp_Test() { + // given + val name = "doongjun" + val user = userRepository.save(User(name = name, totalCommitCount = 200L)) + user.changeCommitmon(Commitmon.EGG) + val dto = + UpdateUserDto( + totalCommitCount = 1600L, + followerNames = emptyList(), + followingNames = emptyList(), + ) + clear() + + // when + userService.update(user.id, dto) + clear() + + // then + val findUser = userRepository.findByIdOrNull(user.id) + assertThat(findUser?.totalCommitCount).isEqualTo(dto.totalCommitCount) + assertThat(findUser?.commitmon?.level).isEqualTo(CommitmonLevel.EGG) + assertThat(findUser?.autoLevelUp).isFalse() + } + + @Test + fun changeCommitmon_ThenAutoLevelUpIsTrue_Test() { + // given + val name = "doongjun" + val user = userRepository.save(User(name = name, totalCommitCount = 200L)) + val commitmon = Commitmon.PUKAMON + clear() + + // when + userService.changeCommitmon(user.id, commitmon) + clear() + + // then + val findUser = userRepository.findByIdOrNull(user.id) + assertThat(findUser?.commitmon).isEqualTo(commitmon) + assertThat(findUser?.autoLevelUp).isTrue() + } + + @Test + fun changeCommitmon_ThenAutoLevelUpIsFalse_Test() { + // given + val name = "doongjun" + val user = userRepository.save(User(name = name, totalCommitCount = 200L)) + val commitmon = Commitmon.PICHIMON + clear() + + // when + userService.changeCommitmon(user.id, commitmon) + clear() + + // then + val findUser = userRepository.findByIdOrNull(user.id) + assertThat(findUser?.commitmon).isEqualTo(commitmon) + assertThat(findUser?.autoLevelUp).isFalse() + } + + @Test + fun changeCommitmon_ThenThrowException_Test() { + // given + val name = "doongjun" + val user = userRepository.save(User(name = name, totalCommitCount = 200L)) + val commitmon = Commitmon.WARGREYMON + clear() + + // when, then + val exception = + assertThrows { + userService.changeCommitmon(user.id, commitmon) + } + assertThat(exception.message).isEqualTo("Cannot change to higher level") + } } diff --git a/src/test/kotlin/com/doongjun/commitmon/domain/UserTest.kt b/src/test/kotlin/com/doongjun/commitmon/domain/UserTest.kt index 97d198a..f524144 100644 --- a/src/test/kotlin/com/doongjun/commitmon/domain/UserTest.kt +++ b/src/test/kotlin/com/doongjun/commitmon/domain/UserTest.kt @@ -2,6 +2,7 @@ package com.doongjun.commitmon.domain import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows class UserTest { private val name = "doongjun" @@ -117,6 +118,108 @@ class UserTest { assertThat(user.mutualFollowers).containsExactlyElementsOf(listOf(mutualFollower)) } + @Test + fun update_ThenBeLeveledUp_Test() { + // given + val user = + User( + name = name, + totalCommitCount = 200L, + ) + user.changeCommitmon(Commitmon.KOROMON) + + val updateTotalCommitCount = 800L + // when + user.update( + totalCommitCount = updateTotalCommitCount, + followers = emptyList(), + following = emptyList(), + ) + + // then + assertThat(user.id).isNotNull() + assertThat(user.totalCommitCount).isEqualTo(updateTotalCommitCount) + assertThat(user.commitmon).isEqualTo(Commitmon.GREYMON) + assertThat(user.commitmon.level).isEqualTo(CommitmonLevel.CHAMPION) + assertThat(user.autoLevelUp).isTrue() + } + + @Test + fun update_ThenNotBeLeveledUp_Test() { + // given + val user = + User( + name = name, + totalCommitCount = 200L, + ) + user.changeCommitmon(Commitmon.BOTAMON) + + val updateTotalCommitCount = 400L + // when + user.update( + totalCommitCount = updateTotalCommitCount, + followers = emptyList(), + following = emptyList(), + ) + + // then + assertThat(user.id).isNotNull() + assertThat(user.totalCommitCount).isEqualTo(updateTotalCommitCount) + assertThat(user.commitmon.level).isEqualTo(CommitmonLevel.BABY) + assertThat(user.autoLevelUp).isFalse() + } + + @Test + fun changeCommitmon_ThenSameLevel_Test() { + // given + val user = + User( + name = name, + totalCommitCount = 200L, + ) + + // when + user.changeCommitmon(Commitmon.KOROMON) + + // then + assertThat(user.commitmon).isEqualTo(Commitmon.KOROMON) + assertThat(user.autoLevelUp).isTrue() + } + + @Test + fun changeCommitmon_ThenLowerLevel_Test() { + // given + val user = + User( + name = name, + totalCommitCount = 200L, + ) + + // when + user.changeCommitmon(Commitmon.EGG) + + // then + assertThat(user.commitmon).isEqualTo(Commitmon.EGG) + assertThat(user.autoLevelUp).isFalse() + } + + @Test + fun changeCommitmon_ThenHigherLevel_Test() { + // given + val user = + User( + name = name, + totalCommitCount = 200L, + ) + + // when, then + val exception = + assertThrows { + user.changeCommitmon(Commitmon.ATLURKABUTERIMON) + } + assertThat(exception.message).isEqualTo("Cannot change to higher level") + } + @Test fun exp_Then0_Test() { // given diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index b88725a..e091e44 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -27,6 +27,16 @@ logging: org.hibernate.orm.jdbc.bind: TRACE app: + auth: + jwt: + base64-secret: 12345 + expired-ms: 1000 + refresh-token: + expired-ms: 1000 github: token: 12345 - base-url: https://api.github.com \ No newline at end of file + base-url: https://api.github.com + oauth2: + base-url: https://github.com/login/oauth + client-id: 12345 + client-secret: 12345 \ No newline at end of file