diff --git a/api/src/main/kotlin/com/mashup/dojo/CoinController.kt b/api/src/main/kotlin/com/mashup/dojo/CoinController.kt index 2727218..54f0fdb 100644 --- a/api/src/main/kotlin/com/mashup/dojo/CoinController.kt +++ b/api/src/main/kotlin/com/mashup/dojo/CoinController.kt @@ -58,6 +58,30 @@ class CoinController( return coinUseCase.earnCoin(CoinUseCase.EarnCoinCommand(memberId, amount)) .let { DojoApiResponse.success(it) } } + + @PostMapping("/admin/update") + @Operation( + summary = "관리자가 직접 특정 사용자에게 잼을 제공하는 API", + description = "특정 사용자에게 잼을 제공하는 API (관리자 전용)", + responses = [ + ApiResponse(responseCode = "200", description = "관리자 보상 제공 성공") + ] + ) + fun provideCoinByEvent( + @RequestParam fullName: String, + @RequestParam amount: Long, + @RequestParam(required = false) platform: String?, + ): DojoApiResponse { + val currentMemberId = MemberPrincipalContextHolder.current().id + return coinUseCase.earnCoinByEvent( + CoinUseCase.EarnCoinByEventCommand( + currentMemberId = currentMemberId, + fullName = fullName, + platform = platform, + coinAmount = amount + ) + ).let { DojoApiResponse.success(it) } + } } private const val DEFAULT_COMPLETE_PICK_OFFER_AMOUNT = 200L diff --git a/common/src/main/kotlin/com/mashup/dojo/DojoExceptionType.kt b/common/src/main/kotlin/com/mashup/dojo/DojoExceptionType.kt index dc47298..7c3c4a7 100644 --- a/common/src/main/kotlin/com/mashup/dojo/DojoExceptionType.kt +++ b/common/src/main/kotlin/com/mashup/dojo/DojoExceptionType.kt @@ -17,6 +17,7 @@ enum class DojoExceptionType( ARGUMENT_NOT_VALID("Method Argument Not Valid. Check argument validation.", "C009_ARGUMENT_NOT_VALID", 400), INVALID_MEMBER_GENDER("The gender does not exist.", "C011_INVALID_MEMBER_GENDER", 400), INVALID_MEMBER_PLATFORM("The platform does not exist.", "C010_INVALID_MEMBER_PLATFORM", 400), + DUPLICATED_MEMBER("DUPLICATED_MEMBER", "C014_DUPLICATED_MEMBER", 400), MEMBER_NOT_FOUND("Member not found", "C012_MEMBER_NOT_FOUND", 400), IMAGE_NOT_FOUND("Image not found", "C013_IMAGE_NOT_FOUND", 400), diff --git a/entity/src/main/kotlin/com/mashup/dojo/MemberRepository.kt b/entity/src/main/kotlin/com/mashup/dojo/MemberRepository.kt index 30b1cad..2fb187f 100644 --- a/entity/src/main/kotlin/com/mashup/dojo/MemberRepository.kt +++ b/entity/src/main/kotlin/com/mashup/dojo/MemberRepository.kt @@ -25,6 +25,13 @@ interface MemberQueryRepository { memberId: String, keyword: String, ): List + + fun findSingleMemberByFullName(fullName: String): MemberEntity + + fun findSingleMemberByFullNameAndPlatform( + fullName: String, + platform: String, + ): MemberEntity } class MemberQueryRepositoryImpl( @@ -41,4 +48,46 @@ class MemberQueryRepositoryImpl( .where(member.id.notIn(memberId), member.fullName.contains(keyword)) .fetch() } + + override fun findSingleMemberByFullName(fullName: String): MemberEntity { + val member = QMemberEntity.memberEntity + + val results = + jpaQueryFactory + .select(member) + .from(member) + .where(member.fullName.eq(fullName)) + .fetch() + + when { + results.size > 1 -> throw DojoException.of(DojoExceptionType.DUPLICATED_MEMBER) + results.isEmpty() -> throw DojoException.of(DojoExceptionType.MEMBER_NOT_FOUND) + } + + return results.first() + } + + override fun findSingleMemberByFullNameAndPlatform( + fullName: String, + platform: String, + ): MemberEntity { + val member = QMemberEntity.memberEntity + + val results = + jpaQueryFactory + .select(member) + .from(member) + .where( + member.platform.eq(platform), + member.fullName.eq(fullName) + ) + .fetch() + + when { + results.size > 1 -> throw DojoException.of(DojoExceptionType.DUPLICATED_MEMBER) + results.isEmpty() -> throw DojoException.of(DojoExceptionType.MEMBER_NOT_FOUND) + } + + return results.first() + } } diff --git a/service/src/main/kotlin/com/mashup/dojo/service/MemberService.kt b/service/src/main/kotlin/com/mashup/dojo/service/MemberService.kt index 724ba4a..9c0ccea 100644 --- a/service/src/main/kotlin/com/mashup/dojo/service/MemberService.kt +++ b/service/src/main/kotlin/com/mashup/dojo/service/MemberService.kt @@ -31,6 +31,13 @@ interface MemberService { fun findAllByIds(memberIds: List): List + fun findByFullNameAndPlatform( + fullName: String, + platform: MemberPlatform, + ): Member + + fun findMemberByFullName(fullName: String): Member + data class CreateMember( val fullName: String, val profileImageId: ImageId?, @@ -112,6 +119,17 @@ class DefaultMemberService( return memberRepository.findAllById(memberIds.map { it.value }).map { it.toMember() } } + override fun findByFullNameAndPlatform( + fullName: String, + platform: MemberPlatform, + ): Member { + return memberRepository.findSingleMemberByFullNameAndPlatform(fullName, platform.name).toMember() + } + + override fun findMemberByFullName(fullName: String): Member { + return memberRepository.findSingleMemberByFullName(fullName).toMember() + } + private fun mockMember(memberId: MemberId) = Member( memberId, "임준형", "ㅈ", ImageId("123456"), MemberPlatform.SPRING, 14, MemberGender.MALE, LocalDateTime.now(), LocalDateTime.now() diff --git a/service/src/main/kotlin/com/mashup/dojo/usecase/CoinUseCase.kt b/service/src/main/kotlin/com/mashup/dojo/usecase/CoinUseCase.kt index 87bc581..294997c 100644 --- a/service/src/main/kotlin/com/mashup/dojo/usecase/CoinUseCase.kt +++ b/service/src/main/kotlin/com/mashup/dojo/usecase/CoinUseCase.kt @@ -6,8 +6,11 @@ import com.mashup.dojo.domain.Coin import com.mashup.dojo.domain.CoinUseDetail import com.mashup.dojo.domain.CoinUseDetailId import com.mashup.dojo.domain.CoinUseType +import com.mashup.dojo.domain.Member import com.mashup.dojo.domain.MemberId +import com.mashup.dojo.domain.MemberPlatform import com.mashup.dojo.service.CoinService +import com.mashup.dojo.service.MemberService import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @@ -16,14 +19,19 @@ interface CoinUseCase { data class EarnCoinCommand(val memberId: MemberId, val coinAmount: Long) + data class EarnCoinByEventCommand(val currentMemberId: MemberId, val fullName: String, val platform: String?, val coinAmount: Long) + fun getCurrentCoin(command: GetCurrentCoinCommand): Coin fun earnCoin(command: EarnCoinCommand): CoinUseDetailId + + fun earnCoinByEvent(command: EarnCoinByEventCommand): CoinUseDetailId } @Component class DefaultCoinUseCase( private val coinService: CoinService, + private val membersService: MemberService, ) : CoinUseCase { override fun getCurrentCoin(command: CoinUseCase.GetCurrentCoinCommand): Coin { return coinService.getCoin(command.memberId) ?: throw DojoException.of(DojoExceptionType.NOT_EXIST, "유저의 코인정보가 없습니다") @@ -36,4 +44,36 @@ class DefaultCoinUseCase( val updatedCoin = coin.earnCoin(command.coinAmount) return coinService.updateCoin(CoinUseType.EARNED, CoinUseDetail.REASON_COMPLETE_PICK, command.coinAmount.toInt(), updatedCoin) } + + @Transactional + override fun earnCoinByEvent(command: CoinUseCase.EarnCoinByEventCommand): CoinUseDetailId { + validAdmin(command.currentMemberId) + val findMember = findMember(command.fullName, command.platform) + return earnCoin(CoinUseCase.EarnCoinCommand(findMember.id, command.coinAmount)) + } + + // todo 추후 Role을 넣어서 Security에서 관리하도록하면 좋을듯합니다. + private fun validAdmin(currentMemberId: MemberId) { + val currentMember = + membersService.findMemberById(currentMemberId) + ?: throw DojoException.of(DojoExceptionType.MEMBER_NOT_FOUND) + + val dojo = listOf("한정민", "오예원", "박세원", "임준형", "이현재", "황태규", "최민석", "낭은영", "오시연") + + if (currentMember.fullName !in dojo) { + throw DojoException.of(DojoExceptionType.AUTHENTICATION_FAILURE, "You Are Not Dojo") + } + } + + private fun findMember( + fullName: String, + inputPlatform: String?, + ): Member { + if (inputPlatform.isNullOrEmpty()) { + return membersService.findMemberByFullName(fullName) + } else { + val platform = MemberPlatform.findByValue(inputPlatform) + return membersService.findByFullNameAndPlatform(fullName, platform) + } + } }