@@ -4,12 +4,13 @@ import com.stepbookstep.server.domain.book.domain.BookRepository
44import com.stepbookstep.server.domain.reading.application.ReadingGoalService
55import com.stepbookstep.server.domain.reading.application.ReadingLogService
66import com.stepbookstep.server.domain.reading.presentation.dto.BookReadingDetailResponse
7+ import com.stepbookstep.server.domain.reading.presentation.dto.CreateReadingGoalRequest
78import com.stepbookstep.server.domain.reading.presentation.dto.CreateReadingLogRequest
89import com.stepbookstep.server.domain.reading.presentation.dto.CreateReadingLogResponse
910import com.stepbookstep.server.domain.reading.presentation.dto.ReadingGoalResponse
1011import com.stepbookstep.server.domain.reading.presentation.dto.RoutineItem
1112import com.stepbookstep.server.domain.reading.presentation.dto.RoutineListResponse
12- import com.stepbookstep.server.domain.reading.presentation.dto.UpsertReadingGoalRequest
13+ import com.stepbookstep.server.domain.reading.presentation.dto.UpdateReadingGoalRequest
1314import com.stepbookstep.server.global.response.ApiResponse
1415import com.stepbookstep.server.global.response.CustomException
1516import com.stepbookstep.server.global.response.ErrorCode
@@ -20,6 +21,7 @@ import io.swagger.v3.oas.annotations.tags.Tag
2021import jakarta.validation.Valid
2122import org.springframework.http.HttpStatus
2223import org.springframework.http.ResponseEntity
24+ import org.springframework.web.bind.annotation.DeleteMapping
2325import org.springframework.web.bind.annotation.GetMapping
2426import org.springframework.web.bind.annotation.PatchMapping
2527import org.springframework.web.bind.annotation.PathVariable
@@ -65,31 +67,19 @@ class ReadingController(
6567 }
6668
6769 @Operation(
68- summary = " 독서 목표 생성/수정/삭제 " ,
70+ summary = " 독서 목표 생성" ,
6971 description = """
70- 독서 목표를 생성, 수정, 삭제합니다 .
71- - 생성/수정: period, metric, targetAmount를 모두 포함
72- - 삭제: delete=true 명시
72+ 새로운 독서 목표를 생성합니다 .
73+ - period, metric, targetAmount 모두 필수
74+ - 이미 활성 목표가 있으면 해당 목표를 수정합니다.
7375 """
7476 )
75- @PatchMapping (" /books/{bookId}/goals" )
76- fun upsertOrDeleteGoal (
77+ @PostMapping (" /books/{bookId}/goals" )
78+ fun createGoal (
7779 @Parameter(description = " 도서 ID" ) @PathVariable bookId : Long ,
7880 @Parameter(hidden = true ) @LoginUserId userId : Long ,
79- @Valid @RequestBody request : UpsertReadingGoalRequest
80- ): ResponseEntity <ApiResponse <ReadingGoalResponse ?>> {
81- // 삭제 요청인 경우
82- if (request.delete == true ) {
83- readingGoalService.deleteGoal(userId, bookId)
84- return ResponseEntity .ok(ApiResponse .ok(null ))
85- }
86-
87- // 생성/수정 요청 - 필수 필드 검증
88- if (request.period == null || request.metric == null || request.targetAmount == null ) {
89- throw CustomException (ErrorCode .INVALID_INPUT )
90- }
91-
92- // 생성/수정 요청인 경우
81+ @Valid @RequestBody request : CreateReadingGoalRequest
82+ ): ResponseEntity <ApiResponse <ReadingGoalResponse >> {
9383 val goal = readingGoalService.upsertGoal(
9484 userId = userId,
9585 bookId = bookId,
@@ -104,12 +94,64 @@ class ReadingController(
10494 val response = ReadingGoalResponse .from(
10595 goal = goalWithProgress.goal,
10696 currentProgress = goalWithProgress.currentProgress,
107- achievedAmount = goalWithProgress.achievedAmount // 추가!
97+ achievedAmount = goalWithProgress.achievedAmount
98+ )
99+
100+ return ResponseEntity .status(HttpStatus .CREATED )
101+ .body(ApiResponse .created(response))
102+ }
103+
104+ @Operation(
105+ summary = " 독서 목표 수정" ,
106+ description = """
107+ 기존 독서 목표를 수정합니다.
108+ - period, metric, targetAmount 중 변경할 필드만 전달 가능
109+ - 활성 목표가 없으면 404
110+ """
111+ )
112+ @PatchMapping(" /books/{bookId}/goals" )
113+ fun updateGoal (
114+ @Parameter(description = " 도서 ID" ) @PathVariable bookId : Long ,
115+ @Parameter(hidden = true ) @LoginUserId userId : Long ,
116+ @Valid @RequestBody request : UpdateReadingGoalRequest
117+ ): ResponseEntity <ApiResponse <ReadingGoalResponse >> {
118+ // 기존 활성 목표가 있어야 수정 가능
119+ val existing = readingGoalService.getActiveGoalWithProgress(userId, bookId)
120+ ? : throw CustomException (ErrorCode .GOAL_NOT_FOUND )
121+
122+ val goal = readingGoalService.upsertGoal(
123+ userId = userId,
124+ bookId = bookId,
125+ period = request.period ? : existing.goal.period,
126+ metric = request.metric ? : existing.goal.metric,
127+ targetAmount = request.targetAmount ? : existing.goal.targetAmount
128+ )
129+
130+ val goalWithProgress = readingGoalService.getActiveGoalWithProgress(userId, bookId)
131+ ? : throw CustomException (ErrorCode .GOAL_NOT_FOUND )
132+
133+ val response = ReadingGoalResponse .from(
134+ goal = goalWithProgress.goal,
135+ currentProgress = goalWithProgress.currentProgress,
136+ achievedAmount = goalWithProgress.achievedAmount
108137 )
109138
110139 return ResponseEntity .ok(ApiResponse .ok(response))
111140 }
112141
142+ @Operation(
143+ summary = " 독서 목표 삭제" ,
144+ description = " 활성 독서 목표를 삭제(비활성화)합니다."
145+ )
146+ @DeleteMapping(" /books/{bookId}/goals" )
147+ fun deleteGoal (
148+ @Parameter(description = " 도서 ID" ) @PathVariable bookId : Long ,
149+ @Parameter(hidden = true ) @LoginUserId userId : Long
150+ ): ResponseEntity <ApiResponse <Nothing ?>> {
151+ readingGoalService.deleteGoal(userId, bookId)
152+ return ResponseEntity .ok(ApiResponse .ok(null ))
153+ }
154+
113155 @Operation(summary = " 책 목표 조회" , description = " 특정 책의 독서 목표를 조회합니다. 완독/중지 상태에서도 비활성화된 목표를 표시합니다." )
114156 @GetMapping(" /books/{bookId}/goals" )
115157 fun getGoal (
@@ -122,7 +164,7 @@ class ReadingController(
122164 ReadingGoalResponse .from(
123165 goal = it.goal,
124166 currentProgress = it.currentProgress,
125- achievedAmount = it.achievedAmount // 추가!
167+ achievedAmount = it.achievedAmount
126168 )
127169 }
128170
0 commit comments