From 04433fe8f9101488d739b1b5addc8336b0b6ff18 Mon Sep 17 00:00:00 2001 From: DoodlesOnMyFood <57210973+DoodlesOnMyFood@users.noreply.github.com> Date: Thu, 9 May 2024 00:52:33 +0900 Subject: [PATCH] =?UTF-8?q?[FO-1011]=20profileSns/profileImage=20=EC=A7=91?= =?UTF-8?q?=EA=B5=90=EA=B3=B1=20=ED=98=84=EC=83=81=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=B4=20profileSns,=20profile?= =?UTF-8?q?Image=20OneToMany=20=EA=B4=80=EA=B3=84=20=EC=A0=9C=EA=B1=B0=20(?= =?UTF-8?q?#437)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/exception/GlobalErrorAttributes.kt | 6 +- .../GlobalErrorWebExceptionHandler.kt | 4 +- .../com/fone/profile/domain/entity/Profile.kt | 28 +-- .../profile/domain/entity/ProfileImage.kt | 11 +- .../fone/profile/domain/entity/ProfileSns.kt | 20 ++ .../repository/ProfileImageRepository.kt | 11 + .../domain/repository/ProfileRepository.kt | 12 +- .../domain/repository/ProfileSnsRepository.kt | 11 + .../domain/service/DeleteProfileService.kt | 12 +- .../domain/service/PutProfileService.kt | 54 ++++- .../domain/service/RegisterProfileService.kt | 33 ++- .../ProfileDomainRepositoryImpl.kt | 1 - .../ProfileImageRepositoryImpl.kt | 43 ++++ .../infrastructure/ProfileRepositoryImpl.kt | 228 +++++++++--------- .../ProfileSnsRepositoryImpl.kt | 43 ++++ .../presentation/dto/RegisterProfileDto.kt | 14 +- 16 files changed, 351 insertions(+), 180 deletions(-) create mode 100644 server/src/main/kotlin/com/fone/profile/domain/repository/ProfileImageRepository.kt create mode 100644 server/src/main/kotlin/com/fone/profile/domain/repository/ProfileSnsRepository.kt create mode 100644 server/src/main/kotlin/com/fone/profile/infrastructure/ProfileImageRepositoryImpl.kt create mode 100644 server/src/main/kotlin/com/fone/profile/infrastructure/ProfileSnsRepositoryImpl.kt diff --git a/common/src/main/kotlin/com/fone/common/exception/GlobalErrorAttributes.kt b/common/src/main/kotlin/com/fone/common/exception/GlobalErrorAttributes.kt index c06ea6c8..f41309e0 100644 --- a/common/src/main/kotlin/com/fone/common/exception/GlobalErrorAttributes.kt +++ b/common/src/main/kotlin/com/fone/common/exception/GlobalErrorAttributes.kt @@ -7,7 +7,6 @@ import org.springframework.web.reactive.function.server.ServerRequest @Component class GlobalErrorAttributes : DefaultErrorAttributes() { - override fun getErrorAttributes( request: ServerRequest?, options: ErrorAttributeOptions?, @@ -15,12 +14,11 @@ class GlobalErrorAttributes : DefaultErrorAttributes() { val map: MutableMap = mutableMapOf() val throwable: Throwable = getError(request) return if (throwable is GlobalException) { - val ex: GlobalException = getError(request) as GlobalException map.apply { this["result"] = "FAIL" this["data"] = null - this["message"] = ex.reason - this["errorCode"] = ex.status.reasonPhrase + this["message"] = throwable.reason + this["errorCode"] = throwable.status.reasonPhrase } } else { map.apply { diff --git a/common/src/main/kotlin/com/fone/common/exception/GlobalErrorWebExceptionHandler.kt b/common/src/main/kotlin/com/fone/common/exception/GlobalErrorWebExceptionHandler.kt index a2260720..e905d157 100644 --- a/common/src/main/kotlin/com/fone/common/exception/GlobalErrorWebExceptionHandler.kt +++ b/common/src/main/kotlin/com/fone/common/exception/GlobalErrorWebExceptionHandler.kt @@ -30,9 +30,7 @@ class GlobalErrorWebExceptionHandler( super.setMessageReaders(serverCodecConfigurer.readers) } - override fun getRoutingFunction( - errorAttributes: ErrorAttributes, - ): RouterFunction { + override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction { return RouterFunctions.route(RequestPredicates.all()) { request: ServerRequest -> renderErrorResponse(request) } diff --git a/server/src/main/kotlin/com/fone/profile/domain/entity/Profile.kt b/server/src/main/kotlin/com/fone/profile/domain/entity/Profile.kt index 0cca3426..3572225c 100644 --- a/server/src/main/kotlin/com/fone/profile/domain/entity/Profile.kt +++ b/server/src/main/kotlin/com/fone/profile/domain/entity/Profile.kt @@ -8,7 +8,6 @@ import com.fone.common.entity.Type import com.fone.profile.presentation.dto.RegisterProfileRequest import com.fone.profile.presentation.dto.common.ProfileSnsUrl import java.time.LocalDate -import javax.persistence.CascadeType import javax.persistence.Column import javax.persistence.Entity import javax.persistence.EnumType @@ -17,8 +16,8 @@ import javax.persistence.GeneratedValue import javax.persistence.GenerationType import javax.persistence.Id import javax.persistence.JoinColumn -import javax.persistence.OneToMany import javax.persistence.Table +import javax.persistence.Transient @Entity @Table(name = "profiles") @@ -27,21 +26,15 @@ data class Profile( @Column @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null, - // page1 @Enumerated(EnumType.STRING) var contactMethod: ContactMethod, @Column(length = 300) var contact: String, - // page2 @Column(length = 10) var name: String, @Column var hookingComment: String, - @OneToMany( - mappedBy = "profile", - cascade = [CascadeType.PERSIST, CascadeType.MERGE], - orphanRemoval = true - ) var profileImages: MutableList = mutableListOf(), + @Transient + var profileImages: MutableList = mutableListOf(), @Column var representativeImageUrl: String, - // page3 @Column var birthday: LocalDate, @Enumerated(EnumType.STRING) var gender: Gender?, @@ -49,20 +42,14 @@ data class Profile( @Column var weight: Int?, @Column var email: String, @Column(length = 50) var specialty: String, - @OneToMany( - cascade = [CascadeType.PERSIST, CascadeType.MERGE], - orphanRemoval = true - ) + @Transient @JoinColumn(name = "profile_id") var snsUrls: Set, - // page4 @Column(length = 500) var details: String, - // page5 @Enumerated(EnumType.STRING) var career: Career, @Column(length = 500) var careerDetail: String, - // etc @Enumerated(EnumType.STRING) var type: Type, @Column var userId: Long, @@ -70,7 +57,6 @@ data class Profile( @Column var wantCount: Long = 0, @Column var isDeleted: Boolean = false, ) : BaseEntity() { - fun view() { this.viewCount += 1 } @@ -100,7 +86,7 @@ data class Profile( weight = request.thirdPage.weight email = request.thirdPage.email specialty = request.thirdPage.specialty - snsUrls = request.thirdPage.snsUrls.map(ProfileSnsUrl::toEntity).toSet() + snsUrls = request.thirdPage.snsUrls.map(ProfileSnsUrl::toEntity).setProfile(this).toSet() // page4 details = request.fourthPage.details @@ -130,9 +116,9 @@ data class Profile( isDeleted = true } - /* 연관관계 메서드 */ + // 연관관계 메서드 fun addProfileImage(profileImage: ProfileImage) { this.profileImages.add(profileImage) - profileImage.addProfile(this) + profileImage.profile = this } } diff --git a/server/src/main/kotlin/com/fone/profile/domain/entity/ProfileImage.kt b/server/src/main/kotlin/com/fone/profile/domain/entity/ProfileImage.kt index 43d37bfe..cff92ac2 100644 --- a/server/src/main/kotlin/com/fone/profile/domain/entity/ProfileImage.kt +++ b/server/src/main/kotlin/com/fone/profile/domain/entity/ProfileImage.kt @@ -21,15 +21,18 @@ data class ProfileImage( @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null + @Column(name = "profile_id", insertable = false, updatable = false) + var profileId: Long? = null + @ManyToOne(fetch = LAZY) @JoinColumn(name = "profile_id") var profile: Profile? = null + set(value) { + field = value + profileId = value?.id + } override fun toString(): String { return "ProfileImage(id=$id)" } - - fun addProfile(profile: Profile) { - this.profile = profile - } } diff --git a/server/src/main/kotlin/com/fone/profile/domain/entity/ProfileSns.kt b/server/src/main/kotlin/com/fone/profile/domain/entity/ProfileSns.kt index 6e9a109e..80be3f89 100644 --- a/server/src/main/kotlin/com/fone/profile/domain/entity/ProfileSns.kt +++ b/server/src/main/kotlin/com/fone/profile/domain/entity/ProfileSns.kt @@ -9,6 +9,8 @@ import javax.persistence.Enumerated import javax.persistence.GeneratedValue import javax.persistence.GenerationType import javax.persistence.Id +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne import javax.persistence.Table @Entity @@ -21,4 +23,22 @@ class ProfileSns( @Column @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null + + @Column(name = "profile_id", insertable = false, updatable = false) + var profileId: Long? = null + + @ManyToOne + @JoinColumn(name = "profile_id") + var profile: Profile? = null + set(value) { + profileId = value?.id + field = value + } +} + +fun > T.setProfile(profile: Profile): T { + forEach { + it.profile = profile + } + return this } diff --git a/server/src/main/kotlin/com/fone/profile/domain/repository/ProfileImageRepository.kt b/server/src/main/kotlin/com/fone/profile/domain/repository/ProfileImageRepository.kt new file mode 100644 index 00000000..24bf3d1d --- /dev/null +++ b/server/src/main/kotlin/com/fone/profile/domain/repository/ProfileImageRepository.kt @@ -0,0 +1,11 @@ +package com.fone.profile.domain.repository + +import com.fone.profile.domain.entity.ProfileImage + +interface ProfileImageRepository { + suspend fun saveAll(images: List): List + + suspend fun findAll(profileId: Long): List + + suspend fun deleteByProfileId(profileId: Long): Int +} diff --git a/server/src/main/kotlin/com/fone/profile/domain/repository/ProfileRepository.kt b/server/src/main/kotlin/com/fone/profile/domain/repository/ProfileRepository.kt index c8878b35..6ee9ded5 100644 --- a/server/src/main/kotlin/com/fone/profile/domain/repository/ProfileRepository.kt +++ b/server/src/main/kotlin/com/fone/profile/domain/repository/ProfileRepository.kt @@ -13,9 +13,17 @@ interface ProfileRepository { ): Page suspend fun findById(profileId: Long): Profile? - suspend fun findByTypeAndId(type: Type?, profileId: Long?): Profile? - suspend fun findAllByUserId(pageable: Pageable, userId: Long): Page + suspend fun findByTypeAndId( + type: Type?, + profileId: Long, + ): Profile? + + suspend fun findAllByUserId( + pageable: Pageable, + userId: Long, + ): Page + suspend fun save(profile: Profile): Profile suspend fun findWantAllByUserId( diff --git a/server/src/main/kotlin/com/fone/profile/domain/repository/ProfileSnsRepository.kt b/server/src/main/kotlin/com/fone/profile/domain/repository/ProfileSnsRepository.kt new file mode 100644 index 00000000..7460f5f7 --- /dev/null +++ b/server/src/main/kotlin/com/fone/profile/domain/repository/ProfileSnsRepository.kt @@ -0,0 +1,11 @@ +package com.fone.profile.domain.repository + +import com.fone.profile.domain.entity.ProfileSns + +interface ProfileSnsRepository { + suspend fun saveAll(urls: Set): Set + + suspend fun findAll(profileId: Long): Set + + suspend fun deleteByProfileId(profileId: Long): Int +} diff --git a/server/src/main/kotlin/com/fone/profile/domain/service/DeleteProfileService.kt b/server/src/main/kotlin/com/fone/profile/domain/service/DeleteProfileService.kt index 2ddb9c6b..ced00c26 100644 --- a/server/src/main/kotlin/com/fone/profile/domain/service/DeleteProfileService.kt +++ b/server/src/main/kotlin/com/fone/profile/domain/service/DeleteProfileService.kt @@ -6,7 +6,9 @@ import com.fone.common.exception.NotFoundUserException import com.fone.common.repository.UserCommonRepository import com.fone.profile.domain.repository.ProfileCategoryRepository import com.fone.profile.domain.repository.ProfileDomainRepository +import com.fone.profile.domain.repository.ProfileImageRepository import com.fone.profile.domain.repository.ProfileRepository +import com.fone.profile.domain.repository.ProfileSnsRepository import org.springframework.stereotype.Service import javax.transaction.Transactional @@ -16,10 +18,14 @@ class DeleteProfileService( private val profileDomainRepository: ProfileDomainRepository, private val profileCategoryRepository: ProfileCategoryRepository, private val userRepository: UserCommonRepository, + private val profileSnsRepository: ProfileSnsRepository, + private val profileImageRepository: ProfileImageRepository, ) { - @Transactional - suspend fun deleteProfile(email: String, profileId: Long) { + suspend fun deleteProfile( + email: String, + profileId: Long, + ) { val userId = userRepository.findByEmail(email) ?: throw NotFoundUserException() val profile = profileRepository.findByTypeAndId(null, profileId) ?: throw NotFoundProfileException() @@ -28,6 +34,8 @@ class DeleteProfileService( } profile.delete() profileRepository.save(profile) + profileSnsRepository.deleteByProfileId(profileId) + profileImageRepository.deleteByProfileId(profileId) profileDomainRepository.deleteByProfileId(profileId) profileCategoryRepository.deleteByProfileId(profileId) } diff --git a/server/src/main/kotlin/com/fone/profile/domain/service/PutProfileService.kt b/server/src/main/kotlin/com/fone/profile/domain/service/PutProfileService.kt index 71dec4e8..c10db2e0 100644 --- a/server/src/main/kotlin/com/fone/profile/domain/service/PutProfileService.kt +++ b/server/src/main/kotlin/com/fone/profile/domain/service/PutProfileService.kt @@ -3,11 +3,16 @@ package com.fone.profile.domain.service import com.fone.common.exception.InvalidProfileUserIdException import com.fone.common.exception.NotFoundProfileException import com.fone.common.exception.NotFoundUserException +import com.fone.profile.domain.entity.Profile import com.fone.profile.domain.entity.ProfileCategory import com.fone.profile.domain.entity.ProfileDomain +import com.fone.profile.domain.entity.ProfileImage +import com.fone.profile.domain.entity.ProfileSns import com.fone.profile.domain.repository.ProfileCategoryRepository import com.fone.profile.domain.repository.ProfileDomainRepository +import com.fone.profile.domain.repository.ProfileImageRepository import com.fone.profile.domain.repository.ProfileRepository +import com.fone.profile.domain.repository.ProfileSnsRepository import com.fone.profile.domain.repository.ProfileWantRepository import com.fone.profile.presentation.dto.RegisterProfileRequest import com.fone.profile.presentation.dto.RegisterProfileResponse @@ -22,9 +27,10 @@ class PutProfileService( private val profileWantRepository: ProfileWantRepository, private val profileDomainRepository: ProfileDomainRepository, private val profileCategoryRepository: ProfileCategoryRepository, + private val profileImageRepository: ProfileImageRepository, + private val profileSnsRepository: ProfileSnsRepository, private val userRepository: UserRepository, ) { - @Transactional suspend fun putProfile( request: RegisterProfileRequest, @@ -33,30 +39,34 @@ class PutProfileService( ): RegisterProfileResponse { val user = userRepository.findByEmail(email) ?: throw NotFoundUserException() val profile = profileRepository.findByTypeAndId(null, profileId) ?: throw NotFoundProfileException() + val imagesAndSns = profile.profileImages to profile.snsUrls if (user.id != profile.userId) { throw InvalidProfileUserIdException() } profile.put(request) profileRepository.save(profile) + putImagesAndUrls(profile, imagesAndSns) val userProfileWants = profileWantRepository.findByUserId(user.id!!) profileDomainRepository.deleteByProfileId(profile.id!!) - val profileDomains = request.thirdPage.domains?.map { - ProfileDomain( - profile.id!!, - it - ) - } + val profileDomains = + request.thirdPage.domains?.map { + ProfileDomain( + profile.id!!, + it + ) + } profileDomainRepository.saveAll(profileDomains) profileCategoryRepository.deleteByProfileId(profile.id!!) - val profileCategories = request.sixthPage.categories.map { - ProfileCategory( - profile.id!!, - it - ) - } + val profileCategories = + request.sixthPage.categories.map { + ProfileCategory( + profile.id!!, + it + ) + } profileCategoryRepository.saveAll(profileCategories) val profileUser = userRepository.findById(user.id!!) @@ -71,4 +81,22 @@ class PutProfileService( profileUser?.job ?: Job.ACTOR ) } + + private suspend fun putImagesAndUrls( + profile: Profile, + prevImagesAndSns: Pair, Set>, + ) { + val prevImages = prevImagesAndSns.first.map { it.url }.toSet() + val currImages = profile.profileImages.map { it.url }.toSet() + if (prevImages != currImages) { + profileImageRepository.deleteByProfileId(profile.id!!) + profileImageRepository.saveAll(profile.profileImages) + } + val prevSns = prevImagesAndSns.second.map { it.url to it.sns }.toSet() + val currSns = profile.snsUrls.map { it.url to it.sns }.toSet() + if (prevSns != currSns) { + profileSnsRepository.deleteByProfileId(profile.id!!) + profileSnsRepository.saveAll(profile.snsUrls) + } + } } diff --git a/server/src/main/kotlin/com/fone/profile/domain/service/RegisterProfileService.kt b/server/src/main/kotlin/com/fone/profile/domain/service/RegisterProfileService.kt index 2bd312b8..635938ab 100644 --- a/server/src/main/kotlin/com/fone/profile/domain/service/RegisterProfileService.kt +++ b/server/src/main/kotlin/com/fone/profile/domain/service/RegisterProfileService.kt @@ -6,7 +6,9 @@ import com.fone.profile.domain.entity.ProfileDomain import com.fone.profile.domain.entity.ProfileImage import com.fone.profile.domain.repository.ProfileCategoryRepository import com.fone.profile.domain.repository.ProfileDomainRepository +import com.fone.profile.domain.repository.ProfileImageRepository import com.fone.profile.domain.repository.ProfileRepository +import com.fone.profile.domain.repository.ProfileSnsRepository import com.fone.profile.domain.repository.ProfileWantRepository import com.fone.profile.presentation.dto.RegisterProfileRequest import com.fone.profile.presentation.dto.RegisterProfileResponse @@ -21,9 +23,10 @@ class RegisterProfileService( private val profileWantRepository: ProfileWantRepository, private val profileDomainRepository: ProfileDomainRepository, private val profileCategoryRepository: ProfileCategoryRepository, + private val profileImageRepository: ProfileImageRepository, + private val profileSnsRepository: ProfileSnsRepository, private val userRepository: UserRepository, ) { - @Transactional suspend fun registerProfile( request: RegisterProfileRequest, @@ -41,20 +44,24 @@ class RegisterProfileService( } profileRepository.save(profile) + profileSnsRepository.saveAll(profile.snsUrls) + profileImageRepository.saveAll(profile.profileImages) - val profileDomains = thirdPage.domains?.map { - ProfileDomain( - profile.id!!, - it - ) - } + val profileDomains = + thirdPage.domains?.map { + ProfileDomain( + profile.id!!, + it + ) + } - val profileCategories = sixthPage.categories.map { - ProfileCategory( - profile.id!!, - it - ) - } + val profileCategories = + sixthPage.categories.map { + ProfileCategory( + profile.id!!, + it + ) + } profileDomainRepository.saveAll(profileDomains) profileCategoryRepository.saveAll(profileCategories) diff --git a/server/src/main/kotlin/com/fone/profile/infrastructure/ProfileDomainRepositoryImpl.kt b/server/src/main/kotlin/com/fone/profile/infrastructure/ProfileDomainRepositoryImpl.kt index 214bc7aa..2f5b6f85 100644 --- a/server/src/main/kotlin/com/fone/profile/infrastructure/ProfileDomainRepositoryImpl.kt +++ b/server/src/main/kotlin/com/fone/profile/infrastructure/ProfileDomainRepositoryImpl.kt @@ -27,7 +27,6 @@ class ProfileDomainRepositoryImpl( override suspend fun deleteByProfileId(profileId: Long): Int { return queryFactory.deleteQuery { - literal(1).equal(1) where(col(ProfileDomain::profileId).equal(profileId)) } } diff --git a/server/src/main/kotlin/com/fone/profile/infrastructure/ProfileImageRepositoryImpl.kt b/server/src/main/kotlin/com/fone/profile/infrastructure/ProfileImageRepositoryImpl.kt new file mode 100644 index 00000000..d4fc8fd1 --- /dev/null +++ b/server/src/main/kotlin/com/fone/profile/infrastructure/ProfileImageRepositoryImpl.kt @@ -0,0 +1,43 @@ +package com.fone.profile.infrastructure + +import com.fone.profile.domain.entity.ProfileImage +import com.fone.profile.domain.repository.ProfileImageRepository +import com.linecorp.kotlinjdsl.querydsl.expression.col +import com.linecorp.kotlinjdsl.spring.data.reactive.query.SpringDataHibernateMutinyReactiveQueryFactory +import com.linecorp.kotlinjdsl.spring.data.reactive.query.deleteQuery +import com.linecorp.kotlinjdsl.spring.reactive.listQuery +import io.smallrye.mutiny.coroutines.awaitSuspending +import org.springframework.stereotype.Repository + +@Repository +class ProfileImageRepositoryImpl( + private val queryFactory: SpringDataHibernateMutinyReactiveQueryFactory, +) : ProfileImageRepository { + override suspend fun saveAll(images: List): List { + return queryFactory.withFactory { session, _ -> + images.onEach { + if (it.id == null) { + session.persist(it).awaitSuspending() + } else { + session.merge(it).awaitSuspending() + } + } + } + } + + override suspend fun findAll(profileId: Long): List { + return queryFactory.withFactory { factory -> + factory.listQuery { + select(entity(ProfileImage::class)) + from(entity(ProfileImage::class)) + where(col(ProfileImage::profileId).equal(profileId)) + } + } + } + + override suspend fun deleteByProfileId(profileId: Long): Int { + return queryFactory.deleteQuery { + where(col(ProfileImage::profileId).equal(profileId)) + } + } +} diff --git a/server/src/main/kotlin/com/fone/profile/infrastructure/ProfileRepositoryImpl.kt b/server/src/main/kotlin/com/fone/profile/infrastructure/ProfileRepositoryImpl.kt index 6f58333f..ce531093 100644 --- a/server/src/main/kotlin/com/fone/profile/infrastructure/ProfileRepositoryImpl.kt +++ b/server/src/main/kotlin/com/fone/profile/infrastructure/ProfileRepositoryImpl.kt @@ -6,18 +6,18 @@ import com.fone.common.utils.DateTimeFormat import com.fone.profile.domain.entity.Profile import com.fone.profile.domain.entity.ProfileCategory import com.fone.profile.domain.entity.ProfileDomain +import com.fone.profile.domain.entity.ProfileImage +import com.fone.profile.domain.entity.ProfileSns import com.fone.profile.domain.entity.ProfileWant import com.fone.profile.domain.repository.ProfileRepository import com.fone.profile.presentation.dto.RetrieveProfilesRequest import com.linecorp.kotlinjdsl.query.spec.OrderSpec import com.linecorp.kotlinjdsl.query.spec.predicate.EqualValueSpec +import com.linecorp.kotlinjdsl.querydsl.CriteriaQueryDsl import com.linecorp.kotlinjdsl.querydsl.expression.col import com.linecorp.kotlinjdsl.querydsl.expression.column -import com.linecorp.kotlinjdsl.querydsl.from.fetch import com.linecorp.kotlinjdsl.spring.data.reactive.query.SpringDataHibernateMutinyReactiveQueryFactory -import com.linecorp.kotlinjdsl.spring.data.reactive.query.listQuery -import com.linecorp.kotlinjdsl.spring.data.reactive.query.pageQuery -import com.linecorp.kotlinjdsl.spring.data.reactive.query.singleQueryOrNull +import com.linecorp.kotlinjdsl.spring.reactive.SpringDataReactiveQueryFactory import com.linecorp.kotlinjdsl.spring.reactive.listQuery import com.linecorp.kotlinjdsl.spring.reactive.pageQuery import com.linecorp.kotlinjdsl.spring.reactive.querydsl.SpringDataReactiveCriteriaQueryDsl @@ -28,7 +28,6 @@ import org.springframework.data.domain.PageImpl import org.springframework.data.domain.Pageable import org.springframework.data.domain.Sort import org.springframework.stereotype.Repository -import javax.persistence.criteria.JoinType @Repository class ProfileRepositoryImpl( @@ -39,63 +38,63 @@ class ProfileRepositoryImpl( pageable: Pageable, request: RetrieveProfilesRequest, ): Page { - val domainProfileIds = - if (request.domains.isEmpty()) { - emptyList() - } else { - queryFactory.listQuery { - select(col(ProfileDomain::profileId)) - from(entity(ProfileDomain::class)) - where(col(ProfileDomain::type).inValues(request.domains)) - } - } - - val categoryProfileIds = - if (request.categories.isEmpty()) { - emptyList() - } else { - queryFactory.listQuery { - select(col(ProfileCategory::profileId)) - from(entity(ProfileCategory::class)) - where(col(ProfileCategory::type).inValues(request.categories)) - } - } + val profiles: List = + queryFactory.withFactory { factory -> + val domainProfileIds = + if (request.domains.isEmpty()) { + emptyList() + } else { + factory.listQuery { + select(col(ProfileDomain::profileId)) + from(entity(ProfileDomain::class)) + where(col(ProfileDomain::type).inValues(request.domains)) + } + } - val ids = - queryFactory.pageQuery(pageable) { - select(column(Profile::id)) - from(entity(Profile::class)) - whereAnd( - col(Profile::type).equal(request.type), - if (request.genders.isNotEmpty()) col(Profile::gender).inValues(request.genders) else null, - col(Profile::birthday).lessThanOrEqualTo( - DateTimeFormat.calculdateLocalDate(request.ageMin) - ), - col(Profile::birthday).greaterThanOrEqualTo( - DateTimeFormat.calculdateLocalDate(request.ageMax) - ), - if (request.domains.isNotEmpty()) col(Profile::id).inValues(domainProfileIds) else null, - if (request.categories.isNotEmpty()) col(Profile::id).inValues(categoryProfileIds) else null, - col(Profile::isDeleted).equal(false) - ) - } + val categoryProfileIds = + if (request.categories.isEmpty()) { + emptyList() + } else { + factory.listQuery { + select(col(ProfileCategory::profileId)) + from(entity(ProfileCategory::class)) + where(col(ProfileCategory::type).inValues(request.categories)) + } + } - if (ids.content.isEmpty()) { - return PageImpl( - listOf(), - pageable, - 0 - ) - } + val ids = + factory.pageQuery(pageable) { + select(column(Profile::id)) + from(entity(Profile::class)) + whereAnd( + col(Profile::type).equal(request.type), + if (request.genders.isNotEmpty()) col(Profile::gender).inValues(request.genders) else null, + col(Profile::birthday).lessThanOrEqualTo( + DateTimeFormat.calculdateLocalDate(request.ageMin) + ), + col(Profile::birthday).greaterThanOrEqualTo( + DateTimeFormat.calculdateLocalDate(request.ageMax) + ), + if (request.domains.isNotEmpty()) col(Profile::id).inValues(domainProfileIds) else null, + if (request.categories.isNotEmpty()) { + col( + Profile::id + ).inValues(categoryProfileIds) + } else { + null + }, + col(Profile::isDeleted).equal(false) + ) + } - val profiles = - queryFactory.listQuery { - select(distinct = true, entity(Profile::class)) - from(entity(Profile::class)) - fetch(Profile::profileImages, joinType = JoinType.LEFT) - fetch(Profile::snsUrls, joinType = JoinType.LEFT) - where(and(col(Profile::id).inValues(ids.content))) - orderBy(orderSpec(pageable.sort)) + if (ids.content.isEmpty()) { + listOf() + } else { + factory.profileUrlsAndProfileImages { + where(and(col(Profile::id).inValues(ids.content))) + orderBy(orderSpec(pageable.sort)) + } + } } return PageImpl( @@ -106,54 +105,41 @@ class ProfileRepositoryImpl( } override suspend fun findById(profileId: Long): Profile? { - return queryFactory.singleQueryOrNull { - select(entity(Profile::class)) - from(entity(Profile::class)) - fetch(Profile::profileImages, joinType = JoinType.LEFT) - fetch(Profile::snsUrls, joinType = JoinType.LEFT) + return queryFactory.profileUrlsAndProfileImages { where(idEq(profileId)) - } + }.firstOrNull() } override suspend fun findByTypeAndId( type: Type?, - profileId: Long?, + profileId: Long, ): Profile? { - return queryFactory.singleQueryOrNull { - select(entity(Profile::class)) - from(entity(Profile::class)) - fetch(Profile::profileImages, joinType = JoinType.LEFT) - fetch(Profile::snsUrls, joinType = JoinType.LEFT) - where(and(typeEq(type), idEq(profileId))) - } + return findById(profileId) } override suspend fun findAllByUserId( pageable: Pageable, userId: Long, ): Page { - val ids = - queryFactory.pageQuery(pageable) { - select(column(Profile::id)) - from(entity(Profile::class)) - where( - and( - col(Profile::userId).equal(userId), - col(Profile::isDeleted).equal(false) + return queryFactory.withFactory { factory -> + val ids = + factory.pageQuery(pageable) { + select(column(Profile::id)) + from(entity(Profile::class)) + where( + and( + col(Profile::userId).equal(userId), + col(Profile::isDeleted).equal(false) + ) ) - ) - } - - val profiles = - queryFactory.listQuery { - select(entity(Profile::class)) - from(entity(Profile::class)) - fetch(Profile::profileImages, joinType = JoinType.LEFT) - fetch(Profile::snsUrls, joinType = JoinType.LEFT) - where(col(Profile::id).inValues(ids.content)) - }.associateBy { it?.id } + } + val profiles = + factory.profileUrlsAndProfileImages { + where(col(Profile::id).inValues(ids.content)) + }.associateBy { it.id } - return ids.map { profiles[it] } + ids.map { profiles[it] } + } } override suspend fun findWantAllByUserId( @@ -175,15 +161,10 @@ class ProfileRepositoryImpl( ) ) } - val profiles = - factory.listQuery { - select(entity(Profile::class)) - from(entity(Profile::class)) - fetch(Profile::profileImages, joinType = JoinType.LEFT) - fetch(Profile::snsUrls, joinType = JoinType.LEFT) + factory.profileUrlsAndProfileImages { where(col(Profile::id).inValues(ids.content)) - }.associateBy { it!!.id } + }.associateBy { it.id } PageImpl( ids.content.reversed().mapNotNull { profiles[it] }, @@ -205,19 +186,13 @@ class ProfileRepositoryImpl( } } - private fun SpringDataReactiveCriteriaQueryDsl.idEq(profileId: Long?): EqualValueSpec? { + private fun CriteriaQueryDsl.idEq(profileId: Long?): EqualValueSpec? { profileId ?: return null return col(Profile::id).equal(profileId) } - private fun SpringDataReactiveCriteriaQueryDsl.typeEq(type: Type?): EqualValueSpec? { - type ?: return null - - return col(Profile::type).equal(type) - } - - private fun SpringDataReactiveCriteriaQueryDsl.orderSpec(sort: Sort): List { + private fun CriteriaQueryDsl.orderSpec(sort: Sort): List { val res = sort.map { val columnSpec = @@ -236,4 +211,41 @@ class ProfileRepositoryImpl( return res } + + private suspend fun SpringDataReactiveQueryFactory.profileUrlsAndProfileImages( + block: SpringDataReactiveCriteriaQueryDsl.() -> Unit, + ): List { + val profiles = + listQuery { + select(entity(Profile::class)) + from(entity(Profile::class)) + block() + } + val ids = profiles.map { it.id } + val images = + listQuery { + select(entity(ProfileImage::class)) + from(entity(ProfileImage::class)) + where(col(ProfileImage::profileId).inValues(ids)) + }.groupBy { it!!.profileId } as Map> + val urls = + listQuery { + select(entity(ProfileSns::class)) + from(entity(ProfileSns::class)) + where(col(ProfileSns::profileId).inValues(ids)) + }.groupBy { it!!.profileId } as Map> + profiles.forEach { + it.snsUrls = urls.getOrDefault(it.id!!, listOf()).toSet() + it.profileImages = images.getOrDefault(it.id, listOf()).toMutableList() + } + return profiles + } + + private suspend fun SpringDataHibernateMutinyReactiveQueryFactory.profileUrlsAndProfileImages( + block: SpringDataReactiveCriteriaQueryDsl.() -> Unit, + ): List { + return withFactory { factory -> + factory.profileUrlsAndProfileImages(block) + } + } } diff --git a/server/src/main/kotlin/com/fone/profile/infrastructure/ProfileSnsRepositoryImpl.kt b/server/src/main/kotlin/com/fone/profile/infrastructure/ProfileSnsRepositoryImpl.kt new file mode 100644 index 00000000..33cfc9f2 --- /dev/null +++ b/server/src/main/kotlin/com/fone/profile/infrastructure/ProfileSnsRepositoryImpl.kt @@ -0,0 +1,43 @@ +package com.fone.profile.infrastructure + +import com.fone.profile.domain.entity.ProfileSns +import com.fone.profile.domain.repository.ProfileSnsRepository +import com.linecorp.kotlinjdsl.querydsl.expression.col +import com.linecorp.kotlinjdsl.spring.data.reactive.query.SpringDataHibernateMutinyReactiveQueryFactory +import com.linecorp.kotlinjdsl.spring.data.reactive.query.deleteQuery +import com.linecorp.kotlinjdsl.spring.reactive.listQuery +import io.smallrye.mutiny.coroutines.awaitSuspending +import org.springframework.stereotype.Repository + +@Repository +class ProfileSnsRepositoryImpl( + private val queryFactory: SpringDataHibernateMutinyReactiveQueryFactory, +) : ProfileSnsRepository { + override suspend fun saveAll(urls: Set): Set { + return queryFactory.withFactory { session, _ -> + urls.onEach { + if (it.id == null) { + session.persist(it).awaitSuspending() + } else { + session.merge(it).awaitSuspending() + } + } + } + } + + override suspend fun findAll(profileId: Long): Set { + return queryFactory.withFactory { factory -> + factory.listQuery { + select(entity(ProfileSns::class)) + from(entity(ProfileSns::class)) + where(col(ProfileSns::profileId).equal(profileId)) + }.filterNotNull().toSet() + } + } + + override suspend fun deleteByProfileId(profileId: Long): Int { + return queryFactory.deleteQuery { + where(col(ProfileSns::profileId).equal(profileId)) + } + } +} diff --git a/server/src/main/kotlin/com/fone/profile/presentation/dto/RegisterProfileDto.kt b/server/src/main/kotlin/com/fone/profile/presentation/dto/RegisterProfileDto.kt index dd70dad9..678eec78 100644 --- a/server/src/main/kotlin/com/fone/profile/presentation/dto/RegisterProfileDto.kt +++ b/server/src/main/kotlin/com/fone/profile/presentation/dto/RegisterProfileDto.kt @@ -6,6 +6,7 @@ import com.fone.common.entity.Type import com.fone.jobOpening.presentation.dto.FirstPage import com.fone.profile.domain.entity.Profile import com.fone.profile.domain.entity.ProfileWant +import com.fone.profile.domain.entity.setProfile import com.fone.profile.presentation.dto.common.ProfileDto import com.fone.profile.presentation.dto.common.ProfileSnsUrl import com.fone.user.domain.enum.Job @@ -14,22 +15,16 @@ import io.swagger.v3.oas.annotations.media.Schema data class RegisterProfileRequest( @Schema(description = "1번째 페이지") val firstPage: FirstPage, - @Schema(description = "2번째 페이지") val secondPage: SecondPage, - @Schema(description = "3번째 페이지") val thirdPage: ThirdPage, - @Schema(description = "4번째 페이지") val fourthPage: FourthPage, - @Schema(description = "5번째 페이지") val fifthPage: FifthPage, - @Schema(description = "6번째 페이지") val sixthPage: SixthPage, - @field:Schema(description = "타입", required = true) val type: Type, ) { @@ -46,13 +41,15 @@ data class RegisterProfileRequest( weight = thirdPage.weight, email = thirdPage.email, specialty = thirdPage.specialty, - snsUrls = thirdPage.snsUrls.map(ProfileSnsUrl::toEntity).toSet(), + snsUrls = thirdPage.snsUrls.map(ProfileSnsUrl::toEntity).toMutableSet(), details = fourthPage.details, career = fifthPage.career, careerDetail = fifthPage.careerDetail, type = type, userId = userId - ) + ).apply { + snsUrls.setProfile(this) + } } } @@ -68,7 +65,6 @@ data class RegisterProfileResponse( profileUrl: String, job: Job, ) : this( - profile = ProfileDto( profile, userProfileWantMap,