Skip to content

Commit

Permalink
feat: 프로필 조회/수정 api (#58)
Browse files Browse the repository at this point in the history
* feat: 회원 이메일 필드 추가

* feat: 회원 프로필 조회 API 추가

* feat: 회원 프로필 수정 API 추가

* feat: 회원 프로필에 관심, 리뷰 작성 수 포함 하여 응답하도록 수정

* test: API 테스트 추가
  • Loading branch information
TaeyeonRoyce authored Aug 27, 2024
1 parent 601a9d1 commit 46948d3
Show file tree
Hide file tree
Showing 29 changed files with 429 additions and 13 deletions.
2 changes: 1 addition & 1 deletion server-profile-submodule
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,8 @@ class InterestedCelebrityPersistenceAdapter(
): Boolean {
return interestedCelebrityJpaRepository.existsByMemberIdAndCelebrityId(memberId, celebrityId)
}

override fun countByMemberId(memberId: Long): Int {
return interestedCelebrityJpaRepository.countByMemberId(memberId).toInt()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ interface InterestedCelebrityJpaRepository : JpaRepository<InterestedCelebrityJp
memberId: Long,
celebrityId: Long,
): Boolean

fun countByMemberId(memberId: Long): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ interface ReadInterestedCelebritiesPort {
celebrityId: Long,
memberId: Long,
): Boolean

fun countByMemberId(memberId: Long): Int
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ import com.celuveat.restaurant.adapter.out.persistence.entity.RestaurantImageJpa
import com.celuveat.restaurant.adapter.out.persistence.entity.RestaurantImageJpaRepository
import com.celuveat.restaurant.adapter.out.persistence.entity.RestaurantJpaEntity
import com.celuveat.restaurant.adapter.out.persistence.entity.RestaurantJpaRepository
import java.time.LocalDate
import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.context.annotation.Profile
import org.springframework.context.event.EventListener
import org.springframework.stereotype.Component
import java.time.LocalDate

@Suppress("ktlint:standard:max-line-length")
@Component
Expand Down Expand Up @@ -61,6 +61,7 @@ class DummyEntityInitializer(
val member = MemberJpaEntity(
nickname = "celuveat",
profileImageUrl = "https://www.celuveat.com/images-data/jpeg/celuveat-logo.png",
email = "[email protected]",
socialId = "1234567890",
serverType = SocialLoginType.KAKAO,
)
Expand Down
29 changes: 29 additions & 0 deletions src/main/kotlin/com/celuveat/member/adapter/in/rest/MemberApi.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.celuveat.member.adapter.`in`.rest

import com.celuveat.auth.adapter.`in`.rest.Auth
import com.celuveat.auth.adapter.`in`.rest.AuthContext
import com.celuveat.member.adapter.`in`.rest.request.UpdateProfileRequest
import com.celuveat.member.adapter.`in`.rest.response.MemberProfileResponse
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.RequestBody

@Tag(name = "회원 API")
interface MemberApi {

@Operation(summary = "회원 정보 조회")
@SecurityRequirement(name = "JWT")
@GetMapping("/profile")
fun readMember(@Auth auth: AuthContext): MemberProfileResponse

@Operation(summary = "회원 정보 조회")
@SecurityRequirement(name = "JWT")
@PatchMapping("/profile")
fun updateMember(
@Auth auth: AuthContext,
@RequestBody request: UpdateProfileRequest,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.celuveat.member.adapter.`in`.rest

import com.celuveat.auth.adapter.`in`.rest.Auth
import com.celuveat.auth.adapter.`in`.rest.AuthContext
import com.celuveat.member.adapter.`in`.rest.request.UpdateProfileRequest
import com.celuveat.member.adapter.`in`.rest.response.MemberProfileResponse
import com.celuveat.member.application.port.`in`.ReadMemberUseCase
import com.celuveat.member.application.port.`in`.UpdateProfileUseCase
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController


@RequestMapping("/members")
@RestController
class MemberController(
private val readMemberUseCase: ReadMemberUseCase,
private val updateProfileUseCase: UpdateProfileUseCase,
) : MemberApi {

@GetMapping("/profile")
override fun readMember(@Auth auth: AuthContext): MemberProfileResponse {
val memberId = auth.memberId()
val result = readMemberUseCase.readMember(memberId)
return MemberProfileResponse.from(result)
}

@PatchMapping("/profile")
override fun updateMember(
@Auth auth: AuthContext,
@RequestBody request: UpdateProfileRequest,
) {
val memberId = auth.memberId()
val command = request.toCommand(memberId)
updateProfileUseCase.updateProfile(command)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.celuveat.member.adapter.`in`.rest.request

import com.celuveat.member.application.port.`in`.command.UpdateProfileCommand

data class UpdateProfileRequest(
val nickname: String,
val profileImageUrl: String,
) {
fun toCommand(memberId: Long): UpdateProfileCommand {
return UpdateProfileCommand(
memberId = memberId,
nickname = nickname,
profileImageUrl = profileImageUrl,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.celuveat.member.adapter.`in`.rest.response

import com.celuveat.member.application.port.`in`.result.MemberProfileResult
import com.celuveat.member.domain.SocialLoginType
import io.swagger.v3.oas.annotations.media.Schema

data class MemberProfileResponse(
@Schema(
description = "회원 ID",
example = "1",
)
val id: Long = 0,
@Schema(
description = "닉네임",
example = "닉네임",
)
val nickname: String,
@Schema(
description = "프로필 이미지 URL",
example = "https://example.com/profile.jpg",
)
val profileImageUrl: String?,
@Schema(
description = "이메일",
example = "[email protected]"
)
val email: String,
@Schema(
description = "소셜 로그인 타입",
example = "KAKAO",
)
val serverType: SocialLoginType,
@Schema(
description = "소셜 ID",
example = "123",
)
val socialId: String,
@Schema(
description = "관심 등록 수",
example = "1",
)
val interestedCount: Int,
@Schema(
description = "리뷰 수",
example = "1",
)
val reviewCount: Int,
) {
companion object {
fun from(result: MemberProfileResult): MemberProfileResponse {
return MemberProfileResponse(
id = result.id,
nickname = result.nickname,
profileImageUrl = result.profileImageUrl,
email = result.email,
serverType = result.serverType,
socialId = result.socialId,
interestedCount = result.interestedCount,
reviewCount = result.reviewCount,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ data class GoogleMemberInfoResponse(
private val givenName: String,
private val picture: String,
private val locale: String,
private val email: String,
) {
fun toMember(): Member {
return Member(
nickname = name,
profileImageUrl = picture,
email = email,
socialIdentifier = SocialIdentifier(
serverType = SocialLoginType.GOOGLE,
socialId = id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ data class KakaoMemberInfoResponse(
return Member(
nickname = kakaoAccount.profile.nickname,
profileImageUrl = kakaoAccount.profile.profileImageUrl,
email = kakaoAccount.email,
socialIdentifier = SocialIdentifier(
serverType = SocialLoginType.KAKAO,
socialId = id,
Expand All @@ -25,6 +26,7 @@ data class KakaoMemberInfoResponse(

data class KakaoAccount(
val profile: Profile,
val email: String,
)

@JsonNaming(value = SnakeCaseStrategy::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ data class NaverMemberInfoResponse(
return Member(
nickname = response.nickname,
profileImageUrl = response.profileImage,
email = response.email,
socialIdentifier = SocialIdentifier(
serverType = SocialLoginType.NAVER,
socialId = response.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class MemberJpaEntity(
val id: Long = 0,
val nickname: String,
val profileImageUrl: String?,
val email: String,
@Enumerated(EnumType.STRING)
val serverType: SocialLoginType,
val socialId: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@ class MemberPersistenceMapper {
id = member.id,
nickname = member.nickname,
profileImageUrl = member.profileImageUrl,
email = member.email,
serverType = member.socialIdentifier.serverType,
socialId = member.socialIdentifier.socialId,
)
}

fun toDomain(memberJpaEntity: MemberJpaEntity): Member {
fun toDomain(entity: MemberJpaEntity): Member {
return Member(
id = memberJpaEntity.id,
nickname = memberJpaEntity.nickname,
profileImageUrl = memberJpaEntity.profileImageUrl,
id = entity.id,
nickname = entity.nickname,
profileImageUrl = entity.profileImageUrl,
email = entity.email,
socialIdentifier = SocialIdentifier(
serverType = memberJpaEntity.serverType,
socialId = memberJpaEntity.socialId,
serverType = entity.serverType,
socialId = entity.socialId,
),
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.celuveat.member.application

import com.celuveat.celeb.application.port.out.ReadInterestedCelebritiesPort
import com.celuveat.member.application.port.`in`.ReadMemberUseCase
import com.celuveat.member.application.port.`in`.result.MemberProfileResult
import com.celuveat.member.application.port.out.ReadMemberPort
import com.celuveat.restaurant.application.port.out.ReadInterestedRestaurantPort
import com.celuveat.review.application.port.out.ReadReviewPort
import org.springframework.stereotype.Service

@Service
class MemberQueryService(
private val readMemberPort: ReadMemberPort,
private val readInterestedRestaurantPort: ReadInterestedRestaurantPort,
private val readInterestedCelebritiesPort: ReadInterestedCelebritiesPort,
private val readReviewPort: ReadReviewPort,
) : ReadMemberUseCase {
override fun readMember(memberId: Long): MemberProfileResult {
val member = readMemberPort.readById(memberId)
val interestedRestaurantCount = readInterestedRestaurantPort.countByMemberId(memberId)
val interestedCelebrityCount = readInterestedCelebritiesPort.countByMemberId(memberId)
val reviewCount = readReviewPort.countByWriterId(memberId)
return MemberProfileResult.of(
member = member,
interestedCount = interestedRestaurantCount + interestedCelebrityCount,
reviewCount = reviewCount,
)
}
}
22 changes: 22 additions & 0 deletions src/main/kotlin/com/celuveat/member/application/MemberService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.celuveat.member.application

import com.celuveat.member.application.port.`in`.UpdateProfileUseCase
import com.celuveat.member.application.port.`in`.command.UpdateProfileCommand
import com.celuveat.member.application.port.out.ReadMemberPort
import com.celuveat.member.application.port.out.SaveMemberPort
import org.springframework.stereotype.Service

@Service
class MemberService(
private val readMemberPort: ReadMemberPort,
private val saveMemberPort: SaveMemberPort,
) : UpdateProfileUseCase {
override fun updateProfile(command: UpdateProfileCommand) {
val member = readMemberPort.readById(command.memberId)
member.updateProfile(
nickname = command.nickname,
profileImageUrl = command.profileImageUrl,
)
saveMemberPort.save(member)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.celuveat.member.application.port.`in`

import com.celuveat.member.application.port.`in`.result.MemberProfileResult

interface ReadMemberUseCase {
fun readMember(memberId: Long): MemberProfileResult
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.celuveat.member.application.port.`in`

import com.celuveat.member.application.port.`in`.command.UpdateProfileCommand

interface UpdateProfileUseCase {
fun updateProfile(command: UpdateProfileCommand)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.celuveat.member.application.port.`in`.command

data class UpdateProfileCommand(
val memberId: Long,
val nickname: String,
val profileImageUrl: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.celuveat.member.application.port.`in`.result

import com.celuveat.member.domain.Member
import com.celuveat.member.domain.SocialLoginType

data class MemberProfileResult(
val id: Long = 0,
val nickname: String,
val profileImageUrl: String?,
val email: String,
val serverType: SocialLoginType,
val socialId: String,
val interestedCount: Int,
val reviewCount: Int,
) {
companion object {
fun of(
member: Member,
interestedCount: Int,
reviewCount: Int,
): MemberProfileResult {
return MemberProfileResult(
id = member.id,
nickname = member.nickname,
profileImageUrl = member.profileImageUrl,
email = member.email,
serverType = member.socialIdentifier.serverType,
socialId = member.socialIdentifier.socialId,
interestedCount = interestedCount,
reviewCount = reviewCount,
)
}
}
}
Loading

0 comments on commit 46948d3

Please sign in to comment.