Skip to content

Commit

Permalink
Merge pull request #1 from HappyScrolls/feat/addSchedule
Browse files Browse the repository at this point in the history
feat: 일정 등록 구현
  • Loading branch information
chs98412 authored Sep 23, 2024
2 parents a760df4 + 32af75c commit 4cdbb7c
Show file tree
Hide file tree
Showing 16 changed files with 400 additions and 3 deletions.
22 changes: 22 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## ⚠️ 관련 이슈
> ex) #이슈번호, #이슈번호
## 🚧 작업 내용
> 작업한 내용을 간략히 설명
### 📸 스크린샷 및 참고 자료 (선택)


## ✅ PR 유형
- [ ] 새로운 기능 추가
- [ ] 버그 수정
- [ ] UI 디자인 변경
- [ ] 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
- [ ] 코드 리팩토링
- [ ] 주석 추가 및 수정
- [ ] 문서 수정
- [ ] 테스트 추가, 테스트 리팩토링
- [ ] 빌드 부분 혹은 패키지 매니저 수정
- [ ] 파일 혹은 폴더명 수정
- [ ] 파일 혹은 폴더 삭제

Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.yedongsoon.example_project.application.couple

import com.yedongsoon.example_project.application.couple.model.CoupleDetailResponse
import com.yedongsoon.example_project.application.couple.model.CouplePartnerResponse

interface CoupleService {
suspend fun getCoupleDetail(memberHeader: String): CoupleDetailResponse
suspend fun getCouplePartnerInfo(memberHeader: String) : CouplePartnerResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.yedongsoon.example_project.application.couple.model

data class CouplePartnerResponse(
val no : Int,
val accountId : String,
val name : String,
val email : String,
val profilePhoto : String? = null
)
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package com.yedongsoon.example_project.application.example

import com.yedongsoon.example_project.application.couple.CoupleService
import com.yedongsoon.example_project.application.couple.model.CouplePartnerResponse
import com.yedongsoon.example_project.application.exception.ExampleNotFoundException
import com.yedongsoon.example_project.domain.example.Example
import com.yedongsoon.example_project.domain.example.ExampleRepository
import com.yedongsoon.example_project.domain.schedule.Schedule
import com.yedongsoon.example_project.infrastructure.couple.CoupleAdapter
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service

@Service
class ExampleQueryService(
private val exampleRepository: ExampleRepository,
private val coupleService: CoupleService,
private val exampleRepository: ExampleRepository,
private val coupleService: CoupleService,
) {
private val logger = LoggerFactory.getLogger(CoupleAdapter::class.java)
suspend fun getExampleInfo(exampleInfoNo: Int, memberHeader: String): Example {
Expand All @@ -21,4 +23,6 @@ class ExampleQueryService(
return exampleRepository.findByNo(exampleInfoNo) ?: throw ExampleNotFoundException("없음")

}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.yedongsoon.example_project.application.schedule

import com.yedongsoon.example_project.application.schedule.model.ScheduleCreateCommand
import com.yedongsoon.example_project.domain.schedule.Schedule
import com.yedongsoon.example_project.domain.schedule.ScheduleRepository
import org.springframework.stereotype.Service

@Service
class ScheduleCommandService(
private val scheduleRepository: ScheduleRepository
) {
// 일정 등록
fun createSchedule(command: ScheduleCreateCommand) {
scheduleRepository.save(Schedule.create(command))
}

// 일정 삭제
fun deleteSchedule(scheduleNo : Int){
scheduleRepository.deleteById(scheduleNo)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.yedongsoon.example_project.application.schedule

import com.yedongsoon.example_project.application.couple.CoupleService
import com.yedongsoon.example_project.domain.schedule.Schedule
import com.yedongsoon.example_project.domain.schedule.ScheduleRepository
import com.yedongsoon.example_project.presentation.handler.model.ScheduleDetailResponse
import org.springframework.stereotype.Service
import java.time.LocalDate

@Service
class ScheduleQueryService(
private val scheduleRepository: ScheduleRepository,
) {

// 특정 날짜 일정 조회
fun getScheduleByDate(accountNo: Int, searchDate: LocalDate): List<ScheduleDetailResponse> {
val schedules: List<Schedule> = scheduleRepository.findByAccountNoAndScheduleAt(accountNo, searchDate)

// 조회한 일정들 -> ScheduleDetailResponse
return schedules.map { schedule -> ScheduleDetailResponse.from(schedule) }
}

// 일정 상세 조회
fun getScheduleByScheduleNo(accountNo: Int, scheduleNo: Int): Schedule {
return scheduleRepository.findByAccountNoAndScheduleNo(accountNo, scheduleNo)
}

// 일정 존재 여부
fun existsByScheduleNo(scheduleNo: Int): Boolean{
return scheduleRepository.existsById(scheduleNo)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.yedongsoon.example_project.application.schedule.model

import java.time.LocalDate
import java.time.LocalDateTime

data class ScheduleCreateCommand(
val accountNo: Int,
val scheduleName: String,
val scheduleLocation: String,
val scheduleWith: String,
val groupGenderType: String,
val scheduleStartAt: LocalDateTime,
val scheduleEndAt: LocalDateTime,
val scheduleAt: LocalDate,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.yedongsoon.example_project.domain.schedule

import com.yedongsoon.example_project.application.schedule.model.ScheduleCreateCommand
import jakarta.persistence.*
import java.time.LocalDate
import java.time.LocalDateTime

@Entity
@Table(name = "schedule")
class Schedule(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "schedule_no")
val scheduleNo: Int = 0,

@Column(name = "account_no")
val accountNo: Int,

@Column(name = "schedule_name")
var scheduleName: String,

@Column(name = "schedule_location")
var scheduleLocation: String,

@Column(name = "schedule_with")
var scheduleWith: String,

@Column(name = "group_gender_type")
var groupGenderType: String,

@Column(name = "schedule_start_at")
var scheduleStartAt: LocalDateTime,

@Column(name = "schedule_end_at")
var scheduleEndAt: LocalDateTime,

@Column(name = "schedule_at")
var scheduleAt: LocalDate
) {
companion object {
// 일정 등록 (command -> Schedule)
fun create(command: ScheduleCreateCommand) = Schedule(
accountNo = command.accountNo,
scheduleName = command.scheduleName,
scheduleLocation = command.scheduleLocation,
scheduleWith = command.scheduleWith,
groupGenderType = command.groupGenderType,
scheduleStartAt = command.scheduleStartAt,
scheduleEndAt = command.scheduleEndAt,
scheduleAt = command.scheduleAt
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.yedongsoon.example_project.domain.schedule

import org.springframework.data.jpa.repository.JpaRepository
import java.time.LocalDate

interface ScheduleRepository : JpaRepository<Schedule, Int> {
// 특정 일정 조회
fun findByAccountNoAndScheduleAt(accountNo: Int, scheduleAt: LocalDate): List<Schedule>

// 일정 상세 조회
fun findByAccountNoAndScheduleNo(accountNo: Int, scheduleNo : Int) : Schedule

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.yedongsoon.example_project.infrastructure.couple

import com.yedongsoon.example_project.application.couple.CoupleService
import com.yedongsoon.example_project.application.couple.model.CoupleDetailResponse
import com.yedongsoon.example_project.application.couple.model.CouplePartnerResponse
import kotlinx.coroutines.reactor.awaitSingle
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
Expand Down Expand Up @@ -33,4 +34,20 @@ class CoupleAdapter(private val webClient: WebClient
}
}

// 내 상대방 조회
override suspend fun getCouplePartnerInfo(memberHeader :String):CouplePartnerResponse{
return try {
webClient.get()
.uri("$coupleServiceUrl/couple/lover")
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.header("Member-Code", memberHeader)
.retrieve()
.bodyToMono<CouplePartnerResponse>()
.awaitSingle()
} catch (e: Exception) {
logger.error("Error while fetching couple partner details for Member-Code: $memberHeader", e)
throw e
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,16 @@ fun ServerRequest.localDateQueryParam(parameter: String): LocalDate {

fun ServerRequest.extractRawMemberCodeHeader(): String {
return headers().header("Member-Code").firstOrNull() ?: throw ExampleBadRequestException("헤더 없음")
}

// Path Variable
fun ServerRequest.intPathVariable(variable: String): Int {
return pathVariable(variable).toIntOrNull()
?: throw IllegalArgumentException("Invalid or missing '$variable' path variable")
}

fun ServerRequest.localDatePathVariable(variable: String): LocalDate {
return pathVariable(variable).let {
LocalDate.parse(it, DateTimeFormatter.ofPattern("yyyy-MM-dd"))
} ?: throw IllegalArgumentException("Invalid or missing '$variable' path variable")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.yedongsoon.example_project.presentation.handler

import com.yedongsoon.example_project.application.couple.CoupleService
import com.yedongsoon.example_project.application.schedule.ScheduleCommandService
import com.yedongsoon.example_project.application.schedule.ScheduleQueryService
import com.yedongsoon.example_project.presentation.extension.extractMemberCodeHeader
import com.yedongsoon.example_project.presentation.extension.intPathVariable
import com.yedongsoon.example_project.presentation.extension.extractRawMemberCodeHeader
import com.yedongsoon.example_project.presentation.extension.intQueryParam
import com.yedongsoon.example_project.presentation.extension.localDateQueryParam
import com.yedongsoon.example_project.presentation.handler.model.ExampleDetailResponse
import com.yedongsoon.example_project.presentation.handler.model.ScheduleCreateRequest
import com.yedongsoon.example_project.presentation.handler.model.ScheduleDetailResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.apache.commons.logging.Log
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.server.*
import java.lang.IllegalArgumentException
import java.time.LocalDate

@Service
class ScheduleHandler(
private val scheduleCommandService: ScheduleCommandService,
private val scheduleQueryService: ScheduleQueryService,
private val coupleService: CoupleService
) {
// 일정 등록
suspend fun createSchedule(request: ServerRequest): ServerResponse = withContext(Dispatchers.IO) {
val memberHeader = request.extractMemberCodeHeader()
// 요청 body -> ScheduleCreateRequest -> ScheduleCreateCommand
val command = request.awaitBodyOrNull<ScheduleCreateRequest>()
?.toCommand(memberHeader.no)
?: throw IllegalArgumentException() // TODO : 커스텀 예외 변경 필요

scheduleCommandService.createSchedule(command)
ServerResponse.noContent().buildAndAwait()
}

// 특정 날짜 일정 조회
suspend fun readSchedules(request: ServerRequest): ServerResponse = withContext(Dispatchers.IO) {
val memberHeader = request.extractMemberCodeHeader()
val searchDate = request.localDateQueryParam("searchDate")
val result = scheduleQueryService.getScheduleByDate(memberHeader.no, searchDate)
ServerResponse.ok().bodyValueAndAwait(result)
}

// 일정 상세 조회
suspend fun readScheduleDetail(request: ServerRequest): ServerResponse = withContext(Dispatchers.IO) {
val memberHeader = request.extractMemberCodeHeader()
val scheduleNo = request.pathVariable("scheduleNo").toIntOrNull()
?: throw IllegalArgumentException("Invalid or missing 'scheduleNo' path variable")
val result = scheduleQueryService.getScheduleByScheduleNo(memberHeader.no, scheduleNo)
ServerResponse.ok().bodyValueAndAwait(ScheduleDetailResponse.from(result))
}

// (커플) 특정 날짜의 일정 조회
suspend fun readCouplePartnerSchedules(request: ServerRequest): ServerResponse = withContext(Dispatchers.IO) {
val memberHeader = request.extractRawMemberCodeHeader()
val couplePartnerResponse = coupleService.getCouplePartnerInfo(memberHeader)
val partnerNo = couplePartnerResponse.no
val searchDate = request.localDateQueryParam("searchDate")

val result = scheduleQueryService.getScheduleByDate(partnerNo, searchDate)

ServerResponse.ok().bodyValueAndAwait(result)
}

// 일정 삭제
suspend fun deleteSchedule(request: ServerRequest): ServerResponse = withContext(Dispatchers.IO) {
val scheduleNo = request.intPathVariable("scheduleNo")

val isScheduleExists = scheduleQueryService.existsByScheduleNo(scheduleNo)
if (!isScheduleExists) {
return@withContext ServerResponse.status(404).bodyValueAndAwait("삭제하려는 일정이 존재하지 않습니다.")
}

scheduleCommandService.deleteSchedule(scheduleNo)
ServerResponse.ok().bodyValueAndAwait("일정이 성공적으로 삭제되었습니다.")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.yedongsoon.example_project.presentation.handler.model

import com.yedongsoon.example_project.application.schedule.model.ScheduleCreateCommand
import java.time.LocalDate
import java.time.LocalDateTime

data class ScheduleCreateRequest(
val scheduleName: String,
val scheduleLocation: String,
val scheduleWith: String,
val groupGenderType: String,
val scheduleStartAt: LocalDateTime,
val scheduleEndAt: LocalDateTime,
val scheduleAt: LocalDate,
) {
// 입력값 -> command
fun toCommand(no: Int) = ScheduleCreateCommand(
accountNo = no,
scheduleName = scheduleName,
scheduleLocation = scheduleLocation,
scheduleWith = scheduleWith,
groupGenderType = groupGenderType,
scheduleStartAt = scheduleStartAt,
scheduleEndAt = scheduleEndAt,
scheduleAt = scheduleAt
)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.yedongsoon.example_project.presentation.handler.model

import com.yedongsoon.example_project.domain.schedule.Schedule
import java.time.LocalDateTime

data class ScheduleDetailResponse(
val scheduleName: String,
val scheduleStartAt: LocalDateTime,
val scheduleEndAt: LocalDateTime
) {
companion object {
// 특정 일정 조회 (엔티티 -> response)
fun from(schedule: Schedule) = ScheduleDetailResponse(
scheduleName = schedule.scheduleName,
scheduleStartAt = schedule.scheduleStartAt,
scheduleEndAt = schedule.scheduleEndAt
)
}
}
Loading

0 comments on commit 4cdbb7c

Please sign in to comment.