diff --git a/build.gradle.kts b/build.gradle.kts index c1fef92..1bc117c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,7 @@ plugins { kotlin("jvm") version "1.9.24" kotlin("plugin.spring") version "1.9.24" kotlin("plugin.jpa") version "1.9.24" + kotlin("kapt") version "1.5.31" } group = "com.yedongsoon" @@ -37,6 +38,24 @@ dependencies { implementation("org.springdoc:springdoc-openapi-starter-webflux-ui:2.1.0") implementation("org.springframework.boot:spring-boot-starter-data-redis-reactive") implementation("io.projectreactor.kotlin:reactor-kotlin-extensions") + implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta") + implementation("com.querydsl:querydsl-apt:5.0.0:jakarta") + kapt("com.querydsl:querydsl-apt:5.0.0:jakarta") + +} + +kapt { + arguments { + arg("querydsl.entityAccessors", "true") + arg("querydsl.useFields", "false") // 필드 대신 getter/setter를 사용하는 옵션 + } +} + +// QueryDSL로 생성된 QClass의 경로 설정 +sourceSets { + main { + java.srcDir("build/generated/source/kapt/main") + } } tasks.withType { diff --git a/src/main/kotlin/com/yedongsoon/example_project/application/exception/ScheduleException.kt b/src/main/kotlin/com/yedongsoon/example_project/application/exception/ScheduleException.kt index 6fed386..073de83 100644 --- a/src/main/kotlin/com/yedongsoon/example_project/application/exception/ScheduleException.kt +++ b/src/main/kotlin/com/yedongsoon/example_project/application/exception/ScheduleException.kt @@ -3,3 +3,4 @@ package com.yedongsoon.example_project.application.exception import org.springframework.http.HttpStatus class ScheduleNotFoundException(message: String) : AbstractException(HttpStatus.NOT_FOUND, message) +class ScheduleDuplicatedException(message: String) : AbstractException(HttpStatus.BAD_REQUEST, message) diff --git a/src/main/kotlin/com/yedongsoon/example_project/domain/schedule/ScheduleCommandService.kt b/src/main/kotlin/com/yedongsoon/example_project/domain/schedule/ScheduleCommandService.kt index 87f0e4a..3d7a72a 100644 --- a/src/main/kotlin/com/yedongsoon/example_project/domain/schedule/ScheduleCommandService.kt +++ b/src/main/kotlin/com/yedongsoon/example_project/domain/schedule/ScheduleCommandService.kt @@ -1,5 +1,7 @@ package com.yedongsoon.example_project.domain.schedule +import com.yedongsoon.example_project.application.couple.CoupleQueryService +import com.yedongsoon.example_project.application.exception.ScheduleDuplicatedException import com.yedongsoon.example_project.application.exception.ScheduleNotFoundException import com.yedongsoon.example_project.domain.schedule.model.ScheduleCreateCommand import org.springframework.data.repository.findByIdOrNull @@ -7,10 +9,15 @@ import org.springframework.stereotype.Service @Service class ScheduleCommandService( - private val scheduleRepository: ScheduleRepository + private val scheduleRepository: ScheduleRepository, + private val coupleQueryService: CoupleQueryService, ) { // 일정 등록 fun createSchedule(command: ScheduleCreateCommand) { + val partnerNo = coupleQueryService.getLover(command.accountNo).no + if (scheduleRepository.existsByDuplicateSchedule(command.scheduleStartAt, command.scheduleEndAt, command.accountNo, partnerNo)) { + throw ScheduleDuplicatedException("시간대에 겹치는 일정이 존재합니다") + } scheduleRepository.save(Schedule.create(command)) } diff --git a/src/main/kotlin/com/yedongsoon/example_project/domain/schedule/ScheduleRepository.kt b/src/main/kotlin/com/yedongsoon/example_project/domain/schedule/ScheduleRepository.kt index a1c7dd3..3a35f72 100644 --- a/src/main/kotlin/com/yedongsoon/example_project/domain/schedule/ScheduleRepository.kt +++ b/src/main/kotlin/com/yedongsoon/example_project/domain/schedule/ScheduleRepository.kt @@ -3,7 +3,7 @@ package com.yedongsoon.example_project.domain.schedule import org.springframework.data.jpa.repository.JpaRepository import java.time.LocalDate -interface ScheduleRepository : JpaRepository { +interface ScheduleRepository : JpaRepository, ScheduleRepositoryCustom { // 특정 일정 조회 fun findByAccountNoAndScheduleAtAndIsCommonIsFalse(accountNo: Int, scheduleAt: LocalDate): List fun findByAccountNoInAndScheduleAtAndIsCommonIsTrue(accountNos: List, scheduleAt: LocalDate): List diff --git a/src/main/kotlin/com/yedongsoon/example_project/domain/schedule/ScheduleRepositoryCustom.kt b/src/main/kotlin/com/yedongsoon/example_project/domain/schedule/ScheduleRepositoryCustom.kt new file mode 100644 index 0000000..e8a6ab2 --- /dev/null +++ b/src/main/kotlin/com/yedongsoon/example_project/domain/schedule/ScheduleRepositoryCustom.kt @@ -0,0 +1,13 @@ +package com.yedongsoon.example_project.domain.schedule + +import java.time.LocalDateTime + +interface ScheduleRepositoryCustom { + + fun existsByDuplicateSchedule( + scheduleEndAt: LocalDateTime, + scheduleStartAt: LocalDateTime, + accountNo: Int, + partnerNo: Int, + ): Boolean +} \ No newline at end of file diff --git a/src/main/kotlin/com/yedongsoon/example_project/infrastructure/config/QuerydslSupport.kt b/src/main/kotlin/com/yedongsoon/example_project/infrastructure/config/QuerydslSupport.kt new file mode 100644 index 0000000..bf49263 --- /dev/null +++ b/src/main/kotlin/com/yedongsoon/example_project/infrastructure/config/QuerydslSupport.kt @@ -0,0 +1,20 @@ +package com.yedongsoon.example_project.infrastructure.config + +import com.querydsl.jpa.impl.JPAQueryFactory +import org.springframework.data.jpa.repository.support.Querydsl +import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport +import org.springframework.stereotype.Repository +import kotlin.properties.Delegates +import kotlin.reflect.KClass + +@Repository +abstract class QuerydslSupport(clazz: Class<*>) : QuerydslRepositorySupport(clazz) { + private var queryFactory: JPAQueryFactory by Delegates.notNull() + + constructor(kClass: KClass<*>) : this(kClass.java) + + + override fun getQuerydsl(): Querydsl { + return super.getQuerydsl() ?: throw IllegalStateException("Querydsl is null") + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/yedongsoon/example_project/infrastructure/jpa/ScheduleRepositoryImpl.kt b/src/main/kotlin/com/yedongsoon/example_project/infrastructure/jpa/ScheduleRepositoryImpl.kt new file mode 100644 index 0000000..d2b72d4 --- /dev/null +++ b/src/main/kotlin/com/yedongsoon/example_project/infrastructure/jpa/ScheduleRepositoryImpl.kt @@ -0,0 +1,31 @@ +package com.yedongsoon.example_project.infrastructure.jpa + +import com.querydsl.core.BooleanBuilder +import com.yedongsoon.example_project.domain.schedule.QSchedule +import com.yedongsoon.example_project.domain.schedule.ScheduleRepositoryCustom +import com.yedongsoon.example_project.infrastructure.config.QuerydslSupport +import org.springframework.stereotype.Repository +import java.time.LocalDateTime + +@Repository +class ScheduleRepositoryImpl : QuerydslSupport(QSchedule::class.java), ScheduleRepositoryCustom { + override fun existsByDuplicateSchedule(scheduleEndAt: LocalDateTime, scheduleStartAt: LocalDateTime, accountNo: Int, partnerNo: Int): Boolean { + val schedule = QSchedule.schedule + val conditionBuilder = BooleanBuilder() + conditionBuilder.and(schedule.isDeleted.isFalse) + conditionBuilder.and(schedule.scheduleStartAt.loe(scheduleStartAt)) + conditionBuilder.and(schedule.scheduleEndAt.goe(scheduleEndAt)) + conditionBuilder.and(schedule.accountNo.eq(accountNo)) + + val partnerConditionBuilder = BooleanBuilder() + partnerConditionBuilder.and(schedule.isDeleted.isFalse) + partnerConditionBuilder.and(schedule.scheduleStartAt.loe(scheduleStartAt)) + partnerConditionBuilder.and(schedule.scheduleEndAt.goe(scheduleEndAt)) + partnerConditionBuilder.and(schedule.accountNo.eq(partnerNo)) + partnerConditionBuilder.and(schedule.isCommon.isTrue) + val result = from(schedule) + .where(conditionBuilder.or(partnerConditionBuilder)) + .fetchCount() + return result != 0L + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/yedongsoon/example_project/presentation/handler/ScheduleHandler.kt b/src/main/kotlin/com/yedongsoon/example_project/presentation/handler/ScheduleHandler.kt index 53792e2..bb90622 100644 --- a/src/main/kotlin/com/yedongsoon/example_project/presentation/handler/ScheduleHandler.kt +++ b/src/main/kotlin/com/yedongsoon/example_project/presentation/handler/ScheduleHandler.kt @@ -26,7 +26,7 @@ class ScheduleHandler( @Operation(summary = "스케줄 생성", description = "새로운 스케줄을 생성합니다.") @ApiResponses(value = [ ApiResponse(responseCode = "204", description = "스케줄 생성 성공"), - ApiResponse(responseCode = "400", description = "잘못된 요청 데이터") + ApiResponse(responseCode = "400", description = "중복된 요청 데이터") ]) suspend fun createSchedule(request: ServerRequest): ServerResponse = withContext(Dispatchers.IO) { val memberHeader = request.extractMemberCodeHeader()