From 25be1a6adcd66cba16aa168e791c7ede658f1b27 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 18 Aug 2023 19:15:19 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/GetChangeNicknamePriceResponse.kt | 3 + .../sodalive/member/MemberController.kt | 42 ++++++ .../sodalive/member/MemberRepository.kt | 19 +++ .../sodalive/member/MemberService.kt | 140 ++++++++++++++++++ .../sodalive/member/ProfileResponse.kt | 37 +++++ .../sodalive/member/ProfileUpdateRequest.kt | 17 +++ .../member/nickname/NicknameChangeLog.kt | 15 ++ .../nickname/NicknameChangeLogRepository.kt | 7 + .../member/tag/MemberTagRepository.kt | 10 ++ 9 files changed, 290 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/member/GetChangeNicknamePriceResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/member/ProfileResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/member/ProfileUpdateRequest.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/member/nickname/NicknameChangeLog.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/member/nickname/NicknameChangeLogRepository.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/GetChangeNicknamePriceResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/GetChangeNicknamePriceResponse.kt new file mode 100644 index 0000000..f839164 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/GetChangeNicknamePriceResponse.kt @@ -0,0 +1,3 @@ +package kr.co.vividnext.sodalive.member + +data class GetChangeNicknamePriceResponse(val price: Int) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt index a515312..cc3128d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt @@ -22,6 +22,18 @@ import org.springframework.web.multipart.MultipartFile @RestController @RequestMapping("/member") class MemberController(private val service: MemberService) { + @GetMapping("/check/email") + fun checkEmail(@RequestParam email: String) = service.duplicateCheckEmail(email) + + @GetMapping("/check/nickname") + fun checkNickname(@RequestParam nickname: String) = service.duplicateCheckNickname(nickname) + + @PutMapping("/change/nickname") + fun changeNickname( + @RequestBody profileUpdateRequest: ProfileUpdateRequest, + @AuthenticationPrincipal user: User + ) = ApiResponse.ok(service.updateNickname(profileUpdateRequest, user)) + @PostMapping("/signup") fun signUp( @RequestPart("profileImage", required = false) profileImage: MultipartFile? = null, @@ -50,6 +62,16 @@ class MemberController(private val service: MemberService) { ApiResponse.ok(service.logoutAll(member.id!!)) } + @GetMapping + fun getMember( + @RequestParam container: String, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.getMember(member.id!!, container)) + } + @GetMapping("/info") fun getMemberInfo( @RequestParam container: String?, @@ -151,4 +173,24 @@ class MemberController(private val service: MemberService) { @RequestBody signOutRequest: SignOutRequest, @AuthenticationPrincipal user: User ) = ApiResponse.ok(service.signOut(signOutRequest, user), "정상적으로 탈퇴 처리되었습니다.") + + @GetMapping("/change/nickname/price") + fun getChangeNicknamePrice( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + ApiResponse.ok(service.getChangeNicknamePrice(memberId = member.id!!)) + } + + @PutMapping + fun profileUpdate( + @RequestBody profileUpdateRequest: ProfileUpdateRequest, + @AuthenticationPrincipal user: User + ) = ApiResponse.ok(service.profileUpdate(profileUpdateRequest, user)) + + @PostMapping("/image") + fun profileImageUpdate( + @RequestParam("image") multipartFile: MultipartFile, + @AuthenticationPrincipal user: User + ) = ApiResponse.ok(service.profileImageUpdate(multipartFile, user)) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt index 615774c..32a07e6 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt @@ -7,6 +7,7 @@ import kr.co.vividnext.sodalive.member.QMember.member import kr.co.vividnext.sodalive.member.auth.QAuth.auth import kr.co.vividnext.sodalive.member.block.BlockMemberRepository import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollowing +import kr.co.vividnext.sodalive.member.nickname.QNicknameChangeLog.nicknameChangeLog import kr.co.vividnext.sodalive.member.notification.QMemberNotification.memberNotification import kr.co.vividnext.sodalive.message.QMessage.message import org.springframework.data.jpa.repository.JpaRepository @@ -37,6 +38,7 @@ interface MemberQueryRepository { fun getMessageRecipientPushToken(messageId: Long): GetMessageRecipientPushTokenResponse fun getIndividualRecipientPushTokens(recipients: List, isAuth: Boolean): Map>> + fun getChangeNicknamePrice(memberId: Long): GetChangeNicknamePriceResponse } @Repository @@ -210,4 +212,21 @@ class MemberQueryRepositoryImpl( return mapOf("aos" to aosPushTokens, "ios" to iosPushTokens) } + + override fun getChangeNicknamePrice(memberId: Long): GetChangeNicknamePriceResponse { + val changeCount = queryFactory + .select(nicknameChangeLog.id) + .from(nicknameChangeLog) + .where(nicknameChangeLog.member.id.eq(memberId)) + .fetch() + .count() + + return GetChangeNicknamePriceResponse( + price = if (changeCount > 0) { + 1000 + } else { + 0 + } + ) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt index a916159..922d022 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt @@ -3,6 +3,8 @@ package kr.co.vividnext.sodalive.member import com.amazonaws.services.s3.model.ObjectMetadata import com.fasterxml.jackson.databind.ObjectMapper import kr.co.vividnext.sodalive.aws.s3.S3Uploader +import kr.co.vividnext.sodalive.can.payment.CanPaymentService +import kr.co.vividnext.sodalive.can.use.CanUsage import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.jwt.TokenProvider @@ -16,6 +18,8 @@ import kr.co.vividnext.sodalive.member.info.GetMemberInfoResponse import kr.co.vividnext.sodalive.member.login.LoginRequest import kr.co.vividnext.sodalive.member.login.LoginResponse import kr.co.vividnext.sodalive.member.myPage.MyPageResponse +import kr.co.vividnext.sodalive.member.nickname.NicknameChangeLog +import kr.co.vividnext.sodalive.member.nickname.NicknameChangeLogRepository import kr.co.vividnext.sodalive.member.notification.MemberNotificationService import kr.co.vividnext.sodalive.member.notification.UpdateNotificationSettingRequest import kr.co.vividnext.sodalive.member.signUp.SignUpRequest @@ -25,6 +29,8 @@ import kr.co.vividnext.sodalive.member.stipulation.StipulationAgree import kr.co.vividnext.sodalive.member.stipulation.StipulationAgreeRepository import kr.co.vividnext.sodalive.member.stipulation.StipulationIds import kr.co.vividnext.sodalive.member.stipulation.StipulationRepository +import kr.co.vividnext.sodalive.member.tag.MemberCreatorTag +import kr.co.vividnext.sodalive.member.tag.MemberTagRepository import kr.co.vividnext.sodalive.member.token.MemberTokenRepository import kr.co.vividnext.sodalive.utils.generateFileName import org.springframework.beans.factory.annotation.Value @@ -53,7 +59,10 @@ class MemberService( private val creatorFollowingRepository: CreatorFollowingRepository, private val blockMemberRepository: BlockMemberRepository, private val signOutRepository: SignOutRepository, + private val nicknameChangeLogRepository: NicknameChangeLogRepository, + private val memberTagRepository: MemberTagRepository, + private val canPaymentService: CanPaymentService, private val memberNotificationService: MemberNotificationService, private val s3Uploader: S3Uploader, @@ -107,6 +116,13 @@ class MemberService( ) } + fun getMember(id: Long, container: String): ProfileResponse { + val member = repository.findByIdOrNull(id) + ?: throw SodaException("없는 사용자 입니다.") + + return ProfileResponse(member, cloudFrontHost, container) + } + fun getMemberInfo(member: Member, container: String): GetMemberInfoResponse { return GetMemberInfoResponse( can = member.getChargeCan(container) + member.getRewardCan(container), @@ -406,6 +422,130 @@ class MemberService( signOutRepository.save(signOut) } + fun getChangeNicknamePrice(memberId: Long): GetChangeNicknamePriceResponse { + return repository.getChangeNicknamePrice(memberId = memberId) + } + + @Transactional + fun updateNickname(profileUpdateRequest: ProfileUpdateRequest, user: User) { + if (profileUpdateRequest.email != user.username) { + throw SodaException("로그인 정보를 확인해 주세요.") + } + + val member = repository.findByEmail(user.username) ?: throw SodaException("로그인 정보를 확인해주세요.") + + if (profileUpdateRequest.nickname != null) { + validateNickname(profileUpdateRequest.nickname) + repository.findByNickname(profileUpdateRequest.nickname) + ?.let { throw SodaException("이미 사용중인 닉네임 입니다.") } + + val price = repository.getChangeNicknamePrice(memberId = member.id!!).price + if (price > 0) { + canPaymentService.spendCan( + memberId = member.id!!, + needCan = price, + canUsage = CanUsage.CHANGE_NICKNAME, + container = profileUpdateRequest.container + ) + } + + val nicknameChangeLog = NicknameChangeLog(prevNickname = member.nickname) + nicknameChangeLog.member = member + nicknameChangeLogRepository.save(nicknameChangeLog) + + member.nickname = profileUpdateRequest.nickname + } + } + + @Transactional + fun profileUpdate(profileUpdateRequest: ProfileUpdateRequest, user: User): ProfileResponse { + if (profileUpdateRequest.email != user.username) { + throw SodaException("로그인 정보를 확인해 주세요.") + } + + val member = repository.findByEmail(user.username) ?: throw SodaException("로그인 정보를 확인해주세요.") + + if (profileUpdateRequest.modifyPassword != null) { + if (passwordEncoder.matches(profileUpdateRequest.password, member.password)) { + validatePassword(profileUpdateRequest.modifyPassword) + member.password = passwordEncoder.encode(profileUpdateRequest.modifyPassword) + } else { + throw SodaException("비밀번호가 일치하지 않습니다.") + } + } + + if (profileUpdateRequest.gender != null) { + member.gender = profileUpdateRequest.gender + } + + if (profileUpdateRequest.nickname != null) { + validateNickname(profileUpdateRequest.nickname) + repository.findByNickname(profileUpdateRequest.nickname) + ?.let { throw SodaException("이미 사용중인 닉네임 입니다.") } + member.nickname = profileUpdateRequest.nickname + } + + val tags = if (!profileUpdateRequest.removeTags.isNullOrEmpty()) { + member.tags.filter { !profileUpdateRequest.removeTags.contains(it.tag.tag) } + } else { + member.tags + }.toMutableList() + + if (!profileUpdateRequest.insertTags.isNullOrEmpty()) { + val accountCounselorTags = memberTagRepository.findByMember(member).map { it.tag } + profileUpdateRequest.insertTags.forEach { + val tag = memberTagRepository.findByTag(it) + if (tag != null && !accountCounselorTags.contains(tag)) { + tags.add(MemberCreatorTag(member, tag)) + } + } + } + + if (tags != member.tags) { + member.tags.clear() + member.tags.addAll(tags) + } + + if (profileUpdateRequest.introduce != null) { + member.introduce = profileUpdateRequest.introduce + } + + if (profileUpdateRequest.youtubeUrl != null) { + member.youtubeUrl = profileUpdateRequest.youtubeUrl + } + + if (profileUpdateRequest.instagramUrl != null) { + member.instagramUrl = profileUpdateRequest.instagramUrl + } + + if (profileUpdateRequest.websiteUrl != null) { + member.websiteUrl = profileUpdateRequest.websiteUrl + } + + if (profileUpdateRequest.blogUrl != null) { + member.blogUrl = profileUpdateRequest.blogUrl + } + + return ProfileResponse(member, cloudFrontHost, profileUpdateRequest.container) + } + + @Transactional + fun profileImageUpdate(multipartFile: MultipartFile, user: User): String { + val member = repository.findByEmail(user.username) ?: throw SodaException("로그인 정보를 확인해주세요.") + + val metadata = ObjectMetadata() + metadata.contentLength = multipartFile.size + + member.profileImage = s3Uploader.upload( + inputStream = multipartFile.inputStream, + bucket = s3Bucket, + filePath = "profile/${member.id}/${generateFileName(prefix = "${member.id}-profile")}", + metadata = metadata + ) + + return "$cloudFrontHost/${member.profileImage!!}" + } + private fun getOrCreateLock(memberId: Long): ReentrantReadWriteLock { return tokenLocks.computeIfAbsent(memberId) { ReentrantReadWriteLock() } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/ProfileResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/ProfileResponse.kt new file mode 100644 index 0000000..17ad822 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/ProfileResponse.kt @@ -0,0 +1,37 @@ +package kr.co.vividnext.sodalive.member + +data class ProfileResponse( + val userId: Long, + val email: String, + val nickname: String, + val gender: Gender, + val profileUrl: String, + val chargeCan: Int, + val rewardCan: Int, + val youtubeUrl: String?, + val instagramUrl: String?, + val blogUrl: String?, + val websiteUrl: String?, + val introduce: String, + val tags: List +) { + constructor(member: Member, cloudFrontHost: String, container: String) : this( + userId = member.id!!, + email = member.email, + nickname = member.nickname, + gender = member.gender, + profileUrl = if (member.profileImage != null) { + "$cloudFrontHost/${member.profileImage}" + } else { + "$cloudFrontHost/profile/default-profile.png" + }, + chargeCan = member.getChargeCan(container), + rewardCan = member.getRewardCan(container), + youtubeUrl = member.youtubeUrl, + instagramUrl = member.instagramUrl, + websiteUrl = member.websiteUrl, + blogUrl = member.blogUrl, + introduce = member.introduce, + tags = member.tags.asSequence().filter { it.tag.isActive }.map { it.tag.tag }.toList() + ) +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/ProfileUpdateRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/ProfileUpdateRequest.kt new file mode 100644 index 0000000..790db10 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/ProfileUpdateRequest.kt @@ -0,0 +1,17 @@ +package kr.co.vividnext.sodalive.member + +data class ProfileUpdateRequest( + val email: String, + val password: String? = null, + val modifyPassword: String? = null, + val nickname: String? = null, + val gender: Gender? = null, + val insertTags: List? = null, + val removeTags: List? = null, + val introduce: String? = null, + val youtubeUrl: String? = null, + val instagramUrl: String? = null, + val websiteUrl: String? = null, + val blogUrl: String? = null, + val container: String +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/nickname/NicknameChangeLog.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/nickname/NicknameChangeLog.kt new file mode 100644 index 0000000..9bfa631 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/nickname/NicknameChangeLog.kt @@ -0,0 +1,15 @@ +package kr.co.vividnext.sodalive.member.nickname + +import kr.co.vividnext.sodalive.common.BaseEntity +import kr.co.vividnext.sodalive.member.Member +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne + +@Entity +data class NicknameChangeLog(val prevNickname: String) : BaseEntity() { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + var member: Member? = null +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/nickname/NicknameChangeLogRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/nickname/NicknameChangeLogRepository.kt new file mode 100644 index 0000000..2ec5313 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/nickname/NicknameChangeLogRepository.kt @@ -0,0 +1,7 @@ +package kr.co.vividnext.sodalive.member.nickname + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface NicknameChangeLogRepository : JpaRepository diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/tag/MemberTagRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/tag/MemberTagRepository.kt index 078a252..43e3035 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/tag/MemberTagRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/tag/MemberTagRepository.kt @@ -1,7 +1,9 @@ package kr.co.vividnext.sodalive.member.tag import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.tag.QCreatorTag.creatorTag +import kr.co.vividnext.sodalive.member.tag.QMemberCreatorTag.memberCreatorTag import org.springframework.beans.factory.annotation.Value import org.springframework.data.jpa.repository.JpaRepository @@ -11,6 +13,7 @@ interface MemberTagRepository : JpaRepository, MemberTagQueryR interface MemberTagQueryRepository { fun getTags(): List + fun findByMember(member: Member): List } class MemberTagQueryRepositoryImpl( @@ -33,4 +36,11 @@ class MemberTagQueryRepositoryImpl( .orderBy(creatorTag.orders.asc()) .fetch() } + + override fun findByMember(member: Member): List { + return queryFactory + .selectFrom(memberCreatorTag) + .where(memberCreatorTag.member.id.eq(member.id)) + .fetch() + } } From 7b58335a4266b26249ae4dc8be801554f07710a1 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 18 Aug 2023 19:25:58 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=EB=8B=89=EB=84=A4=EC=9E=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=A1=9C=EA=B7=B8=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?-=20updated=5Fat=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/nickname/NicknameChangeLog.kt | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/nickname/NicknameChangeLog.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/nickname/NicknameChangeLog.kt index 9bfa631..6af084e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/nickname/NicknameChangeLog.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/nickname/NicknameChangeLog.kt @@ -1,15 +1,30 @@ package kr.co.vividnext.sodalive.member.nickname -import kr.co.vividnext.sodalive.common.BaseEntity import kr.co.vividnext.sodalive.member.Member +import java.time.LocalDateTime import javax.persistence.Entity import javax.persistence.FetchType +import javax.persistence.GeneratedValue +import javax.persistence.GenerationType +import javax.persistence.Id import javax.persistence.JoinColumn import javax.persistence.ManyToOne +import javax.persistence.PrePersist @Entity -data class NicknameChangeLog(val prevNickname: String) : BaseEntity() { +data class NicknameChangeLog( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + val prevNickname: String, + var createdAt: LocalDateTime? = null +) { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id", nullable = false) var member: Member? = null + + @PrePersist + fun prePersist() { + createdAt = LocalDateTime.now() + } } From 409af8b18c91ac8df9f9d3804d2567b679e2586a Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 18 Aug 2023 21:49:26 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=ED=81=90=EB=A0=88=EC=9D=B4=EC=85=98=20-=20?= =?UTF-8?q?member=20join=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/co/vividnext/sodalive/content/AudioContentRepository.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt index d29aae7..e3da095 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt @@ -340,6 +340,7 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) ) ) .from(audioContent) + .innerJoin(audioContent.member, member) .where(where) .orderBy(audioContent.id.desc()) .fetch()