Skip to content

Commit

Permalink
[YS-179] feat: 실험자가 작성한 공고 게시글 수정 API 구현 (#51)
Browse files Browse the repository at this point in the history
* feat: add update ExperimentPost API logic

* fix: resolve merge conflicts from dev

* fix: resolve merge conflicts from dev

* refact: add exception case for update post image and fix typo

* refact: refactor update logic for using ExperimentPostGateway and
querydsl

- `querydsl` 이용하여 update 로직 개선
- 기존 유즈케이스에서 처리하던 update 로직Gateway에 위임
- 유즈케이스는 비즈니스 로직에만 집중할 수 있게 리팩터링
- 예외처리 로직 메서드 단위로 분리
  • Loading branch information
chock-cho authored Jan 23, 2025
1 parent 6865107 commit cd6e011
Show file tree
Hide file tree
Showing 27 changed files with 610 additions and 164 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ package com.dobby.backend.application.mapper

import com.dobby.backend.application.usecase.experiment.GetExperimentPostTotalCountByCustomFilterUseCase
import com.dobby.backend.application.usecase.experiment.GetExperimentPostsUseCase
import com.dobby.backend.domain.model.experiment.CustomFilter
import com.dobby.backend.domain.model.experiment.LocationTarget
import com.dobby.backend.domain.model.experiment.Pagination
import com.dobby.backend.domain.model.experiment.StudyTarget
import com.dobby.backend.application.usecase.experiment.UpdateExperimentPostUseCase
import com.dobby.backend.domain.model.experiment.*
import java.time.LocalDateTime

object ExperimentMapper {
fun toDomainFilter(customFilter: GetExperimentPostsUseCase.CustomFilterInput): CustomFilter {
Expand Down Expand Up @@ -52,4 +51,46 @@ object ExperimentMapper {
count = pagination.count
)
}

fun toDomain(input: UpdateExperimentPostUseCase.Input, existingPost: ExperimentPost): ExperimentPost {
return existingPost.copy(
title = input.title ?: existingPost.title,
reward = input.reward ?: existingPost.reward,
startDate = input.startDate ?: existingPost.startDate,
endDate = input.endDate ?: existingPost.endDate,
content = input.content ?: existingPost.content,
count = input.count ?: existingPost.count,
leadResearcher = input.leadResearcher ?: existingPost.leadResearcher,
detailedAddress = input.detailedAddress ?: existingPost.detailedAddress,
matchType = input.matchType ?: existingPost.matchType,
univName = input.univName ?: existingPost.univName,
region = input.region ?: existingPost.region,
area = input.area ?: existingPost.area,
images = input.imageListInfo?.images?.map { imageUrl ->
val existingImage = existingPost.images.find { it.imageUrl == imageUrl }
ExperimentImage(
id = existingImage?.id ?: 0L,
experimentPost = existingPost,
imageUrl = imageUrl
)
}?.toMutableList() ?: existingPost.images,
updatedAt = LocalDateTime.now()
).apply {
input.targetGroupInfo?.let {
this.targetGroup.update(
startAge = it.startAge,
endAge = it.endAge,
genderType = it.genderType,
otherCondition = it.otherCondition
)
}
input.applyMethodInfo?.let {
this.applyMethod.update(
content = it.content,
formUrl = it.formUrl,
phoneNum = it.phoneNum
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.springframework.stereotype.Service
@Service
class ExperimentPostService(
private val createExperimentPostUseCase: CreateExperimentPostUseCase,
private val updateExperimentPostUseCase: UpdateExperimentPostUseCase,
private val getExperimentPostsUseCase: GetExperimentPostsUseCase,
private val getExperimentPostDetailUseCase: GetExperimentPostDetailUseCase,
private val getExperimentPostCountsByRegionUseCase: GetExperimentPostCountsByRegionUseCase,
Expand All @@ -28,6 +29,11 @@ class ExperimentPostService(
return createExperimentPostUseCase.execute(input)
}

@Transactional
fun updateExperimentPost(input: UpdateExperimentPostUseCase.Input): UpdateExperimentPostUseCase.Output{
return updateExperimentPostUseCase.execute(input)
}

@Transactional
fun getExperimentPosts(input: GetExperimentPostsUseCase.Input): List<GetExperimentPostsUseCase.Output> {
validateFilter(input)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import com.dobby.backend.domain.exception.PermissionDeniedException
import com.dobby.backend.domain.gateway.experiment.ExperimentPostGateway
import com.dobby.backend.domain.gateway.member.MemberGateway
import com.dobby.backend.domain.model.experiment.ApplyMethod
import com.dobby.backend.domain.model.experiment.ExperimentImage
import com.dobby.backend.domain.model.experiment.ExperimentPost
import com.dobby.backend.domain.model.experiment.TargetGroup
import com.dobby.backend.domain.model.experiment.ExperimentImage
import com.dobby.backend.domain.model.member.Member
import com.dobby.backend.infrastructure.database.entity.enums.GenderType
import com.dobby.backend.infrastructure.database.entity.enums.MatchType
Expand Down Expand Up @@ -112,7 +112,7 @@ class CreateExperimentPostUseCase(
)
}

experimentPost = experimentPost.withImages(experimentImages)
experimentPost.updateImages(experimentImages)
val savedExperimentPost = experimentPostGateway.save(experimentPost)

return Output(
Expand Down Expand Up @@ -176,7 +176,7 @@ class CreateExperimentPostUseCase(
detailedAddress = input.detailedAddress,
alarmAgree = input.alarmAgree,
recruitStatus = true,
images = listOf(),
images = mutableListOf(),
createdAt = LocalDateTime.now(),
updatedAt = LocalDateTime.now(),
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.dobby.backend.application.usecase.experiment

import com.dobby.backend.application.mapper.ExperimentMapper
import com.dobby.backend.application.usecase.UseCase
import com.dobby.backend.domain.exception.ErrorCode
import com.dobby.backend.domain.exception.ExperimentPostException
import com.dobby.backend.domain.gateway.experiment.ExperimentPostGateway
import com.dobby.backend.domain.model.experiment.ExperimentPost
import com.dobby.backend.infrastructure.database.entity.enums.GenderType
import com.dobby.backend.infrastructure.database.entity.enums.MatchType
import com.dobby.backend.infrastructure.database.entity.enums.TimeSlot
import com.dobby.backend.infrastructure.database.entity.enums.areaInfo.Area
import com.dobby.backend.infrastructure.database.entity.enums.areaInfo.Region
import java.time.LocalDate

class UpdateExperimentPostUseCase (
private val experimentPostGateway: ExperimentPostGateway
) : UseCase<UpdateExperimentPostUseCase.Input, UpdateExperimentPostUseCase.Output> {
data class Input(
val experimentPostId: Long,
val memberId: Long,
val targetGroupInfo: TargetGroupInfo?,
val applyMethodInfo: ApplyMethodInfo?,
val imageListInfo: ImageListInfo?,

val startDate: LocalDate?,
val endDate: LocalDate?,
val matchType: MatchType?,
val count: Int?,
val timeRequired: TimeSlot?,

val leadResearcher: String?,
val univName: String?,
val region: Region?,
val area: Area?,
val detailedAddress : String?,

val reward: String?,
val title: String?,
val content: String?
)

data class TargetGroupInfo(
val startAge: Int?,
val endAge: Int?,
val genderType: GenderType,
val otherCondition: String?
)

data class ApplyMethodInfo(
val content: String,
val formUrl: String?,
val phoneNum: String?
)

data class ImageListInfo(
val images: List<String> = mutableListOf()
)

data class Output(
val postInfo: PostInfo
)

data class PostInfo(
val postId: Long,
val title: String,
val views: Int,
val univName: String?,
val reward: String?,
val durationInfo: DurationInfo?,
)

data class DurationInfo(
val startDate: LocalDate?,
val endDate: LocalDate?
)

override fun execute(input: Input): Output {
val existingPost = validate(input)
val experimentPost = ExperimentMapper.toDomain(input, existingPost)
val updatedPost = experimentPostGateway.updateExperimentPost(experimentPost)

return Output(
postInfo = PostInfo(
postId = updatedPost.id,
title = updatedPost.title,
views = updatedPost.views,
univName = updatedPost.univName,
reward = updatedPost.reward,
durationInfo = DurationInfo(
startDate = updatedPost.startDate,
endDate = updatedPost.endDate
)
)
)
}

private fun validate(input: Input): ExperimentPost {
val existingPost = validateExistingPost(input)
validatePermission(existingPost, input)
validateNotExpired(existingPost)
validateImageCount(input)
return existingPost
}

private fun validateExistingPost(input: Input): ExperimentPost {
return experimentPostGateway.findExperimentPostByMemberIdAndPostId(input.memberId, input.experimentPostId)
?: throw ExperimentPostException(ErrorCode.EXPERIMENT_POST_NOT_FOUND)
}

private fun validatePermission(existingPost: ExperimentPost, input: Input) {
if(existingPost.member.id != input.memberId) throw ExperimentPostException(ErrorCode.PERMISSION_DENIED)
}


private fun validateNotExpired(existingPost: ExperimentPost){
if (!existingPost.recruitStatus) throw ExperimentPostException(ErrorCode.EXPERIMENT_POST_CANNOT_UPDATE_DATE)
}

private fun validateImageCount(input: Input) {
input.imageListInfo?.let {
if(it.images.size > 3) {
throw ExperimentPostException(ErrorCode.EXPERIMENT_POST_IMAGE_SIZE_LIMIT)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ enum class ErrorCode(
EXPERIMENT_POST_SINGLE_SELECTION_ERROR("EP002", "You can only select one Area_ALL selection", HttpStatus.BAD_REQUEST),
EXPERIMENT_POST_AREA_SELECTION_LIMIT("EP003", "You can only select up to 5 Area options in 1 Region.", HttpStatus.BAD_REQUEST),
EXPERIMENT_POST_AREA_INCORRECT("EP004", "Selected Area doesn't belong to correct Region.", HttpStatus.BAD_REQUEST),

EXPERIMENT_POST_IMAGE_SIZE_LIMIT("EP005", "Image can be uploaded maximum 3 images.", HttpStatus.BAD_REQUEST),
EXPERIMENT_POST_RECRUIT_STATUS_ERROR("EP006", "This experiment post has already closed recruitment.", HttpStatus.BAD_REQUEST)
EXPERIMENT_POST_RECRUIT_STATUS_ERROR("EP006", "This experiment post has already closed recruitment.", HttpStatus.BAD_REQUEST),
EXPERIMENT_POST_CANNOT_UPDATE_DATE("EP007", "You cannot update experiment post with past experiment dates.", HttpStatus.BAD_REQUEST),
EXPERIMENT_POST_CANNOT_UPDATE_ALARM("EP008", "The alarmAgree for the experiment post cannot be updated.", HttpStatus.BAD_REQUEST)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ package com.dobby.backend.domain.gateway.experiment
import com.dobby.backend.domain.model.experiment.CustomFilter
import com.dobby.backend.domain.model.experiment.Pagination
import com.dobby.backend.domain.model.experiment.ExperimentPost
import com.dobby.backend.infrastructure.database.entity.enums.MatchType
import com.dobby.backend.infrastructure.database.entity.enums.TimeSlot
import com.dobby.backend.infrastructure.database.entity.enums.areaInfo.Area
import com.dobby.backend.infrastructure.database.entity.enums.areaInfo.Region
import jakarta.persistence.Tuple
import java.time.LocalDate

interface ExperimentPostGateway {
fun save(experimentPost: ExperimentPost): ExperimentPost
fun updateExperimentPost(experimentPost: ExperimentPost): ExperimentPost
fun findExperimentPostsByCustomFilter(customFilter: CustomFilter, pagination: Pagination): List<ExperimentPost>?
fun findById(experimentPostId: Long): ExperimentPost?
fun countExperimentPostsByRegion(region: Region): Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ package com.dobby.backend.domain.model.experiment

data class ApplyMethod(
val id: Long,
val phoneNum: String?,
val formUrl: String?,
val content: String
var phoneNum: String?,
var formUrl: String?,
var content: String
) {
fun update(phoneNum: String?, formUrl: String?, content: String){
this.content = content
this.formUrl = formUrl
this.phoneNum = phoneNum
}
companion object {
fun newApplyMethod(
id: Long,
Expand All @@ -19,4 +24,5 @@ data class ApplyMethod(
content = content
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,25 @@ data class ExperimentPost(
val targetGroup: TargetGroup,
val applyMethod: ApplyMethod,
var views: Int,
val title: String,
val content: String,
var title: String,
var content: String,
var leadResearcher: String,
val reward: String,
val startDate: LocalDate?,
val endDate: LocalDate?,
val timeRequired: TimeSlot?,
val count: Int,
val matchType: MatchType,
val univName: String,
val region: Region,
val area: Area,
val detailedAddress: String?,
var reward: String,
var startDate: LocalDate?,
var endDate: LocalDate?,
var timeRequired: TimeSlot?,
var count: Int,
var matchType: MatchType,
var univName: String,
var region: Region,
var area: Area,
var detailedAddress: String?,
val alarmAgree: Boolean,
var recruitStatus: Boolean,
val images: List<ExperimentImage>,
var images: MutableList<ExperimentImage>,
var createdAt: LocalDateTime,
var updatedAt: LocalDateTime
) {
fun withImages(newImages: List<ExperimentImage>): ExperimentPost {
return this.copy(images = this.images + newImages)
}
fun incrementViews() {
this.views += 1
this.updatedAt = LocalDateTime.now()
Expand All @@ -51,6 +48,54 @@ data class ExperimentPost(
)
}

fun update(
title: String?,
reward: String?,
startDate: LocalDate?,
endDate: LocalDate?,
content: String?,
count: Int?,
leadResearcher: String?,
detailedAddress: String?,
matchType: MatchType?,
univName: String?,
region: Region?,
area: Area?,
imageListInfo: List<String>?
) {
title?.let { this.title = it }
reward?.let { this.reward = it }
startDate?.let { this.startDate = it }
endDate?.let { this.endDate = it }
content?.let { this.content = it }
count?.let { this.count = it }
leadResearcher?.let { this.leadResearcher = it }
detailedAddress?.let { this.detailedAddress = it }
matchType?.let { this.matchType = it }
univName?.let { this.univName = it }
region?.let { this.region = it }
area?.let { this.area = it }

imageListInfo?.let {
val newImages = it.map { imageUrl ->
val existingImage = this.images.find { existing -> existing.imageUrl == imageUrl }
ExperimentImage(
id = existingImage?.id ?: 0L,
experimentPost = this,
imageUrl = imageUrl
)
}
this.updateImages(newImages)
}
}

fun updateImages(newImages: List<ExperimentImage>) {
images.clear()
images.addAll(newImages)
updatedAt = LocalDateTime.now()
}


companion object {
fun newExperimentPost(
id: Long,
Expand Down Expand Up @@ -97,7 +142,7 @@ data class ExperimentPost(
detailedAddress = detailedAddress,
alarmAgree = alarmAgree,
recruitStatus = recruitStatus,
images = images,
images = images.toMutableList(),
createdAt = createdAt,
updatedAt = updatedAt
)
Expand Down
Loading

0 comments on commit cd6e011

Please sign in to comment.