[#22] 프론트엔드 React 전환 및 인증 API 수정 #23

Merged
pricelees merged 9 commits from refactor/#22 into main 2025-07-27 03:39:20 +00:00
7 changed files with 135 additions and 18 deletions
Showing only changes of commit 15126bc884 - Show all commits

View File

@ -7,16 +7,16 @@ import roomescape.member.exception.MemberErrorCode
import roomescape.member.exception.MemberException
import roomescape.member.infrastructure.persistence.MemberEntity
import roomescape.member.infrastructure.persistence.MemberRepository
import roomescape.member.web.MemberRetrieveListResponse
import roomescape.member.web.toRetrieveResponse
import roomescape.member.infrastructure.persistence.Role
import roomescape.member.web.*
@Service
@Transactional(readOnly = true)
class MemberService(
private val memberRepository: MemberRepository
private val memberRepository: MemberRepository
) {
fun findMembers(): MemberRetrieveListResponse = MemberRetrieveListResponse(
members = memberRepository.findAll().map { it.toRetrieveResponse() }
members = memberRepository.findAll().map { it.toRetrieveResponse() }
)
fun findById(memberId: Long): MemberEntity = fetchOrThrow {
@ -27,6 +27,21 @@ class MemberService(
memberRepository.findByEmailAndPassword(email, password)
}
@Transactional
fun create(request: SignupRequest): SignupResponse {
memberRepository.findByEmail(request.email)?.let {
throw MemberException(MemberErrorCode.DUPLICATE_EMAIL)
}
val member = MemberEntity(
name = request.name,
email = request.email,
password = request.password,
role = Role.MEMBER
)
return memberRepository.save(member).toSignupResponse()
}
private fun fetchOrThrow(block: () -> MemberEntity?): MemberEntity {
return block() ?: throw MemberException(MemberErrorCode.MEMBER_NOT_FOUND)
}

View File

@ -5,20 +5,33 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RequestBody
import roomescape.auth.web.support.Admin
import roomescape.common.dto.response.CommonApiResponse
import roomescape.member.web.MemberRetrieveListResponse
import roomescape.member.web.SignupRequest
import roomescape.member.web.SignupResponse
@Tag(name = "2. 회원 API", description = "회원 정보를 관리할 때 사용합니다.")
interface MemberAPI {
@Admin
@Operation(summary = "모든 회원 조회", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(
ApiResponse(
responseCode = "200",
description = "성공",
useReturnTypeSchema = true
)
ApiResponse(
responseCode = "200",
description = "성공",
useReturnTypeSchema = true
)
)
fun findMembers(): ResponseEntity<CommonApiResponse<MemberRetrieveListResponse>>
@Operation(summary = "회원 가입")
@ApiResponses(
ApiResponse(
responseCode = "201",
description = "성공",
useReturnTypeSchema = true
)
)
fun signup(@RequestBody request: SignupRequest): ResponseEntity<CommonApiResponse<SignupResponse>>
}

View File

@ -8,5 +8,6 @@ enum class MemberErrorCode(
override val errorCode: String,
override val message: String
) : ErrorCode {
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "M001", "회원을 찾을 수 없어요.")
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "M001", "회원을 찾을 수 없어요."),
DUPLICATE_EMAIL(HttpStatus.CONFLICT, "M002", "이미 가입된 이메일이에요.")
}

View File

@ -4,4 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository
interface MemberRepository : JpaRepository<MemberEntity, Long> {
fun findByEmailAndPassword(email: String, password: String): MemberEntity?
fun findByEmail(email: String): MemberEntity?
}

View File

@ -2,16 +2,26 @@ package roomescape.member.web
import org.springframework.http.ResponseEntity
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.RestController
import roomescape.common.dto.response.CommonApiResponse
import roomescape.member.business.MemberService
import roomescape.member.docs.MemberAPI
import java.net.URI
@RestController
class MemberController(
private val memberService: MemberService
private val memberService: MemberService
) : MemberAPI {
@PostMapping("/members")
override fun signup(@RequestBody request: SignupRequest): ResponseEntity<CommonApiResponse<SignupResponse>> {
val response: SignupResponse = memberService.create(request)
return ResponseEntity.created(URI.create("/members/${response.id}"))
.body(CommonApiResponse(response))
}
@GetMapping("/members")
override fun findMembers(): ResponseEntity<CommonApiResponse<MemberRetrieveListResponse>> {
val response: MemberRetrieveListResponse = memberService.findMembers()

View File

@ -4,18 +4,34 @@ import io.swagger.v3.oas.annotations.media.Schema
import roomescape.member.infrastructure.persistence.MemberEntity
fun MemberEntity.toRetrieveResponse(): MemberRetrieveResponse = MemberRetrieveResponse(
id = id!!,
name = name
id = id!!,
name = name
)
data class MemberRetrieveResponse(
@Schema(description = "회원 식별자")
val id: Long,
@Schema(description = "회원 식별자")
val id: Long,
@Schema(description = "회원 이름")
val name: String
@Schema(description = "회원 이름")
val name: String
)
data class MemberRetrieveListResponse(
val members: List<MemberRetrieveResponse>
val members: List<MemberRetrieveResponse>
)
data class SignupRequest(
val email: String,
val password: String,
val name: String
)
data class SignupResponse(
val id: Long,
val name: String,
)
fun MemberEntity.toSignupResponse(): SignupResponse = SignupResponse(
id = this.id!!,
name = this.name
)

View File

@ -4,12 +4,16 @@ import io.kotest.assertions.assertSoftly
import io.kotest.matchers.collections.shouldContainAll
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.web.servlet.MockMvc
import roomescape.auth.exception.AuthErrorCode
import roomescape.member.exception.MemberErrorCode
import roomescape.member.infrastructure.persistence.Role
import roomescape.member.web.MemberController
import roomescape.member.web.MemberRetrieveListResponse
import roomescape.member.web.SignupRequest
import roomescape.util.MemberFixture
import roomescape.util.RoomescapeApiTest
import kotlin.random.Random
@ -82,5 +86,61 @@ class MemberControllerTest(
}
}
}
given("POST /members") {
val endpoint = "/members"
val request = SignupRequest(
name = "name",
email = "email@email.com",
password = "password"
)
`when`("같은 이메일이 없으면") {
every {
memberRepository.findByEmail(request.email)
} returns null
every {
memberRepository.save(any())
} returns MemberFixture.create(
id = 1,
name = request.name,
account = request.email,
password = request.password,
role = Role.MEMBER
)
then("id과 이름을 담아 성공 응답") {
runPostTest(
mockMvc = mockMvc,
endpoint = endpoint,
body = request
) {
status { isCreated() }
jsonPath("$.data.name") { value(request.name) }
jsonPath("$.data.id") { value(1) }
}
}
}
`when`("같은 이메일이 있으면") {
every {
memberRepository.findByEmail(request.email)
} returns mockk()
then("에러 응답") {
val expectedError = MemberErrorCode.DUPLICATE_EMAIL
runPostTest(
mockMvc = mockMvc,
endpoint = endpoint,
body = request
) {
status { isEqualTo(expectedError.httpStatus.value()) }
jsonPath("$.code") { value(expectedError.errorCode) }
}
}
}
}
}
}