Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[YS-124] feat: 참여자 회원 정보 조회 API 구현 #31

Merged
merged 11 commits into from
Jan 14, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ object SignupMapper {
member = member,
basicAddressInfo = toAddressInfo(req.basicAddressInfo),
additionalAddressInfo = req.additionalAddressInfo?.let { toAddressInfo(it) },
preferType = req.preferType,
matchType = req.matchType,
gender = req.gender,
birthDate = req.birthDate
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package com.dobby.backend.application.service

import com.dobby.backend.application.usecase.member.GetResearcherInfoUseCase
import com.dobby.backend.application.usecase.member.CreateParticipantUseCase
import com.dobby.backend.application.usecase.member.CreateResearcherUseCase
import com.dobby.backend.application.usecase.member.VerifyResearcherEmailUseCase
import com.dobby.backend.application.usecase.member.*
import com.dobby.backend.domain.exception.SignupOauthEmailDuplicateException
import com.dobby.backend.domain.gateway.member.MemberGateway
import com.dobby.backend.infrastructure.database.entity.enum.MemberStatus
Expand All @@ -16,7 +13,8 @@ class MemberService(
private val createParticipantUseCase: CreateParticipantUseCase,
private val createResearcherUseCase: CreateResearcherUseCase,
private val verifyResearcherEmailUseCase: VerifyResearcherEmailUseCase,
private val getResearcherInfoUseCase: GetResearcherInfoUseCase
private val getResearcherInfoUseCase: GetResearcherInfoUseCase,
private val getParticipantInfoUseCase: GetParticipantInfoUseCase
) {
@Transactional
fun participantSignup(input: CreateParticipantUseCase.Input): CreateParticipantUseCase.Output {
Expand All @@ -32,7 +30,11 @@ class MemberService(
return createResearcherUseCase.execute(input)
}

fun getDefaultInfo(input: GetResearcherInfoUseCase.Input): GetResearcherInfoUseCase.Output {
fun getResearcherInfo(input: GetResearcherInfoUseCase.Input): GetResearcherInfoUseCase.Output {
return getResearcherInfoUseCase.execute(input)
}

fun getParticipantInfo(input: GetParticipantInfoUseCase.Input): GetParticipantInfoUseCase.Output {
return getParticipantInfoUseCase.execute(input)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class CreateParticipantUseCase (
val birthDate: LocalDate,
var basicAddressInfo: AddressInfo,
var additionalAddressInfo: AddressInfo?,
var preferType: MatchType,
var matchType: MatchType,
)
data class AddressInfo(
val region: Region,
Expand Down Expand Up @@ -89,7 +89,7 @@ class CreateParticipantUseCase (
additionalAddressInfo = input.additionalAddressInfo?.let {
Participant.AddressInfo(region = it.region, area = it.area)
},
preferType = input.preferType
matchType = input.matchType
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.dobby.backend.application.usecase.member

import com.dobby.backend.application.usecase.UseCase
import com.dobby.backend.domain.exception.ParticipantNotFoundException
import com.dobby.backend.domain.gateway.member.MemberGateway
import com.dobby.backend.domain.gateway.member.ParticipantGateway
import com.dobby.backend.domain.model.member.Member
import com.dobby.backend.domain.model.member.Participant
import com.dobby.backend.infrastructure.database.entity.enum.GenderType
import com.dobby.backend.infrastructure.database.entity.enum.MatchType
import java.time.LocalDate

class GetParticipantInfoUseCase(
private val memberGateway: MemberGateway,
private val participantGateway: ParticipantGateway
) : UseCase<GetParticipantInfoUseCase.Input, GetParticipantInfoUseCase.Output>{
data class Input(
val memberId: Long,
)

data class Output(
val member: Member,
val gender: GenderType,
val birthDate: LocalDate,
val basicAddressInfo: Participant.AddressInfo,
val additionalAddressInfo: Participant.AddressInfo?,
val matchType: MatchType?
)

override fun execute(input: Input): Output {
val memberId = input.memberId
val member = memberGateway.getById(memberId)
val participant = participantGateway.findByMemberId(memberId)
?: throw ParticipantNotFoundException()

return Output(
member = member,
gender = participant.gender,
birthDate = participant.birthDate,
basicAddressInfo = participant.basicAddressInfo,
additionalAddressInfo = participant.additionalAddressInfo,
matchType = participant.matchType
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ enum class ErrorCode(
*/
RESEARCHER_NOT_FOUND("RE001", "Researcher Not Found.", HttpStatus.NOT_FOUND),

/**
* Participant error codes
*/
PARTICIPANT_NOT_FOUND("PA001", "Participant Not Found.", HttpStatus.NOT_FOUND),

/**
* Experiment Post error codes
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.dobby.backend.domain.exception

open class ParticipantException (
errorCode: ErrorCode,
) : DomainException(errorCode)
class ParticipantNotFoundException : ParticipantException(ErrorCode.PARTICIPANT_NOT_FOUND)
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import com.dobby.backend.domain.model.member.Participant

interface ParticipantGateway {
fun save(participant: Participant): Participant
fun findByMemberId(memberId: Long): Participant?
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ data class Participant(
val birthDate: LocalDate,
val basicAddressInfo: AddressInfo,
val additionalAddressInfo: AddressInfo?,
val preferType: MatchType?
val matchType: MatchType?
) {

data class AddressInfo(
Expand All @@ -28,15 +28,15 @@ data class Participant(
birthDate: LocalDate,
basicAddressInfo: AddressInfo,
additionalAddressInfo: AddressInfo?,
preferType: MatchType?
matchType: MatchType?
) = Participant(
id = 0,
member = member,
gender = gender,
birthDate = birthDate,
basicAddressInfo = basicAddressInfo,
additionalAddressInfo = additionalAddressInfo,
preferType = preferType
matchType = matchType
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ object ParticipantConverter {
birthDate = entity.birthDate,
basicAddressInfo = entity.basicAddressInfo.toModel(),
additionalAddressInfo = entity.additionalAddressInfo?.toModel(),
preferType = entity.preferType
matchType = entity.matchType
)
}

Expand All @@ -46,7 +46,7 @@ object ParticipantConverter {
member = memberEntity,
gender = participant.gender,
birthDate = participant.birthDate,
preferType = participant.preferType,
matchType = participant.matchType,
basicAddressInfo = participant.basicAddressInfo.toEntity(),
additionalAddressInfo = participant.additionalAddressInfo?.toEntity()
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.dobby.backend.infrastructure.database.entity.member

import com.dobby.backend.domain.model.member.Participant
import com.dobby.backend.infrastructure.database.entity.enum.GenderType
import com.dobby.backend.infrastructure.database.entity.enum.MatchType
import com.dobby.backend.infrastructure.database.entity.enum.RoleType
import com.dobby.backend.infrastructure.database.entity.enum.areaInfo.Area
import com.dobby.backend.infrastructure.database.entity.enum.areaInfo.Region
import jakarta.persistence.*
Expand Down Expand Up @@ -41,10 +41,35 @@ class ParticipantEntity (
)
val additionalAddressInfo: AddressInfo?,

@Column(name = "prefer_type", nullable = true)
@Column(name = "match_type", nullable = true)
@Enumerated(EnumType.STRING)
var preferType: MatchType?,
)
var matchType: MatchType?,
) {

fun toDomain() = Participant(
id = id,
member = member.toDomain(),
gender = gender,
birthDate = birthDate,
basicAddressInfo = basicAddressInfo.toDomain(),
additionalAddressInfo = additionalAddressInfo?.toDomain(),
matchType = matchType
)

companion object {
fun fromDomain(participant: Participant) = with(participant) {
ParticipantEntity(
id = id,
member = MemberEntity.fromDomain(member),
gender = gender,
birthDate = birthDate,
basicAddressInfo = AddressInfo.fromDomain(basicAddressInfo), // 수정: AddressInfo.fromDomain() 사용
additionalAddressInfo = additionalAddressInfo?.let { AddressInfo.fromDomain(it) }, // 수정
matchType = matchType
)
}
}
}

@Embeddable
data class AddressInfo(
Expand All @@ -55,4 +80,16 @@ data class AddressInfo(
@Enumerated(EnumType.STRING)
@Column(name = "area", nullable = false)
val area: Area
)
) {
fun toDomain() = Participant.AddressInfo(
region = region,
area = area
)

companion object {
fun fromDomain(addressInfo: Participant.AddressInfo) = AddressInfo(
region = addressInfo.region,
area = addressInfo.area
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import com.dobby.backend.infrastructure.database.entity.member.ParticipantEntity
import org.springframework.data.jpa.repository.JpaRepository

interface ParticipantRepository: JpaRepository<ParticipantEntity, Long> {
fun findByMemberId(memberId: Long): ParticipantEntity?
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.dobby.backend.infrastructure.gateway.member
import com.dobby.backend.domain.gateway.member.ParticipantGateway
import com.dobby.backend.domain.model.member.Participant
import com.dobby.backend.infrastructure.converter.ParticipantConverter
import com.dobby.backend.infrastructure.database.entity.member.ParticipantEntity
import com.dobby.backend.infrastructure.database.repository.ParticipantRepository
import org.springframework.stereotype.Component

Expand All @@ -15,4 +16,10 @@ class ParticipantGatewayImpl(
val savedEntity = participantRepository.save(entity)
return ParticipantConverter.toModel(savedEntity)
}

override fun findByMemberId(memberId: Long): Participant? {
return participantRepository
.findByMemberId(memberId)
?.let(ParticipantEntity::toDomain)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.dobby.backend.presentation.api.controller
import com.dobby.backend.application.service.ExperimentPostService
import com.dobby.backend.presentation.api.dto.request.expirement.CreateExperimentPostRequest
import com.dobby.backend.presentation.api.dto.response.expirement.CreateExperimentPostResponse
import com.dobby.backend.presentation.api.dto.response.expirement.DefaultInfoResponse
import com.dobby.backend.presentation.api.dto.response.expirement.ExperimentPostApplyMethodResponse
import com.dobby.backend.presentation.api.dto.response.expirement.ExperimentPostCountsResponse
import com.dobby.backend.presentation.api.dto.response.expirement.ExperimentPostDetailResponse
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package com.dobby.backend.presentation.api.controller
import com.dobby.backend.application.service.MemberService
import com.dobby.backend.presentation.api.dto.request.signup.ParticipantSignupRequest
import com.dobby.backend.presentation.api.dto.request.signup.ResearcherSignupRequest
import com.dobby.backend.presentation.api.dto.response.expirement.DefaultInfoResponse
import com.dobby.backend.presentation.api.dto.response.member.ParticipantInfoResponse
import com.dobby.backend.presentation.api.dto.response.member.ResearcherInfoResponse
import com.dobby.backend.presentation.api.dto.response.member.SignupResponse
import com.dobby.backend.presentation.api.mapper.MemberMapper
import io.swagger.v3.oas.annotations.Operation
Expand Down Expand Up @@ -48,14 +49,26 @@ class MemberController(
}

@PreAuthorize("hasRole('RESEARCHER')")
@GetMapping("/default-info/researcher")
@GetMapping("/researchers/me")
@Operation(
summary = "연구자 기본 정보 렌더링",
description = "연구자의 기본 정보 [학교 + 전공 + 랩실 정보 + 이름]를 반환합니다."
)
fun getDefaultInfo(): DefaultInfoResponse {
val input = MemberMapper.toDefaultInfoUseCaseInput()
val output = memberService.getDefaultInfo(input)
return MemberMapper.toDefaultInfoResponse(output)
fun getResearcherInfo(): ResearcherInfoResponse {
val input = MemberMapper.toGetResearcherInfoUseCaseInput()
val output = memberService.getResearcherInfo(input)
return MemberMapper.toResearcherInfoResponse(output)
}

@PreAuthorize("hasRole('PARTICIPANT')")
@GetMapping("/participants/me")
Comment on lines +63 to +64
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 회원의 기본 정보를 보는 URI는 이렇게 명하는 거 어떠신지 궁금합니다!

@Operation(
summary = "참여자 회원 기본 정보 렌더링",
description = "참여자의 기본 정보를 반환합니다."
)
fun getParticipantInfo(): ParticipantInfoResponse {
val input = MemberMapper.toGetParticipantInfoUseCaseInput()
val output = memberService.getParticipantInfo(input)
return MemberMapper.toParticipantInfoResponse(output)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ data class ParticipantSignupRequest(
var additionalAddressInfo: AddressInfo?,

// 선호 실험 진행 방식
var preferType: MatchType,
var matchType: MatchType,
)

data class AddressInfo(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.dobby.backend.presentation.api.dto.response.member

import com.dobby.backend.domain.model.member.Participant
import com.dobby.backend.infrastructure.database.entity.enum.areaInfo.Area
import com.dobby.backend.infrastructure.database.entity.enum.areaInfo.Region
import io.swagger.v3.oas.annotations.media.Schema

@Schema(description = "주소 정보 반환 DTO")
data class AddressInfoResponse(
@Schema(description = "지역")
val region: Region,

@Schema(description = "지역 상세")
val area: Area
) {
companion object {
fun fromDomain(basicAddressInfo: Participant.AddressInfo): AddressInfoResponse {
return AddressInfoResponse(
region = basicAddressInfo.region,
area = basicAddressInfo.area
)
}
}
}
Comment on lines +8 to +24
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

자주 사용하게 될 거 같아 별도의 DTO로 추출했습니다. 수정님도 이 방식이 낫다면 해당 PR에서 AddressInfo를 사용하는 API들은 이 DTO를 재사용하도록 리팩터링하겠습니다!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 좋습니다! 재사용성을 개선한 선택 같아보여요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.dobby.backend.presentation.api.dto.response.member

import com.dobby.backend.infrastructure.database.entity.enum.GenderType
import com.dobby.backend.infrastructure.database.entity.enum.MatchType
import io.swagger.v3.oas.annotations.media.Schema
import java.time.LocalDate

@Schema(description = "공고 등록 시, 자동 입력에 필요한 연구자 정보 반환 DTO")
data class ParticipantInfoResponse(
@Schema(description = "사용자 기본 정보")
val memberInfo: MemberResponse,

@Schema(description = "성별")
val gender: GenderType,

@Schema(description = "생년월일")
val birthDate: LocalDate,

@Schema(description = "기본 주소 정보")
val basicAddressInfo: AddressInfoResponse,

@Schema(description = "추가 주소 정보")
val additionalAddressInfo: AddressInfoResponse?,

@Schema(description = "매칭 선호 타입")
val matchType: MatchType?,
)
Comment on lines +8 to +27
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 회원 정보 조회와 관련된 디자인이나 와이어프레임이 없어, 참여자의 모든 정보를 반환하고 있습니다. 추후 필요 없는 필드가 있다면, 그에 맞게 응답을 간소화하여 최적화해 나가는 것이 좋을 것 같습니다!

또한, 실험자의 경우에도 현재 참여자 응답 구조처럼 memberInfo를 추가하여 일관성 있게 처리한다면, API 응답 형식에서 통일성을 유지할 수 있을 거라 기대하는데 어떠신가요??

Copy link
Member Author

@Ji-soo708 Ji-soo708 Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추가적으로 수정님의 경우에는 매칭 선호 타입을 응답에서 반환할 때, preferType으로 하셨더라고요. 필드명만 봤을 때 matchType이나 preferredMatchType이 더 직관적으로 이해될 거라 기대하는데 이 부분에 대해 수정님의 의견을 듣고 싶습니다. 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 처음에 '실험자'의 입장에서 '선호 타입'이라는 이름을 그대로 채택해서 preferType 으로 네이밍했었던 것 같은데요.
개발 진척도가 어느 정도 된 상태에서 보니 통일성을 위해 matchType 으로 채택하는 게 좋아보여요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 좋습니다! 아무래도 프론트에게는 통일된 응답 필드를 주어야해서 해당 PR에서 preferType이라 선언된 부분이 있다면 matchType으로 수정하겠습니다!

Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.dobby.backend.presentation.api.dto.response.expirement
package com.dobby.backend.presentation.api.dto.response.member

import io.swagger.v3.oas.annotations.media.Schema

@Schema(description = "공고 등록 시, 자동 입력에 필요한 연구자 정보 반환 DTO")
data class DefaultInfoResponse(
data class ResearcherInfoResponse(
@Schema(description = "연구 책임")
val leadResearcher: String,

Expand Down
Loading
Loading