Skip to content

Commit 71d754e

Browse files
authored
Merge pull request #92 from Leets-Official/fix/reading-goal
[Fix] 독서 목표 API 생성/수정/삭제 엔드포인트 분리
2 parents 0995851 + 28bab35 commit 71d754e

4 files changed

Lines changed: 104 additions & 40 deletions

File tree

src/main/kotlin/com/stepbookstep/server/domain/reading/presentation/ReadingController.kt

Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import com.stepbookstep.server.domain.book.domain.BookRepository
44
import com.stepbookstep.server.domain.reading.application.ReadingGoalService
55
import com.stepbookstep.server.domain.reading.application.ReadingLogService
66
import com.stepbookstep.server.domain.reading.presentation.dto.BookReadingDetailResponse
7+
import com.stepbookstep.server.domain.reading.presentation.dto.CreateReadingGoalRequest
78
import com.stepbookstep.server.domain.reading.presentation.dto.CreateReadingLogRequest
89
import com.stepbookstep.server.domain.reading.presentation.dto.CreateReadingLogResponse
910
import com.stepbookstep.server.domain.reading.presentation.dto.ReadingGoalResponse
1011
import com.stepbookstep.server.domain.reading.presentation.dto.RoutineItem
1112
import 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
1314
import com.stepbookstep.server.global.response.ApiResponse
1415
import com.stepbookstep.server.global.response.CustomException
1516
import com.stepbookstep.server.global.response.ErrorCode
@@ -20,6 +21,7 @@ import io.swagger.v3.oas.annotations.tags.Tag
2021
import jakarta.validation.Valid
2122
import org.springframework.http.HttpStatus
2223
import org.springframework.http.ResponseEntity
24+
import org.springframework.web.bind.annotation.DeleteMapping
2325
import org.springframework.web.bind.annotation.GetMapping
2426
import org.springframework.web.bind.annotation.PatchMapping
2527
import 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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.stepbookstep.server.domain.reading.presentation.dto
2+
3+
import com.stepbookstep.server.domain.reading.domain.GoalMetric
4+
import com.stepbookstep.server.domain.reading.domain.GoalPeriod
5+
import jakarta.validation.constraints.NotNull
6+
import jakarta.validation.constraints.Positive
7+
8+
/**
9+
* 독서 목표 생성 요청
10+
*/
11+
data class CreateReadingGoalRequest(
12+
@field:NotNull(message = "period는 필수입니다")
13+
val period: GoalPeriod,
14+
15+
@field:NotNull(message = "metric은 필수입니다")
16+
val metric: GoalMetric,
17+
18+
@field:NotNull(message = "targetAmount는 필수입니다")
19+
@field:Positive(message = "targetAmount는 1 이상이어야 합니다")
20+
val targetAmount: Int
21+
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.stepbookstep.server.domain.reading.presentation.dto
2+
3+
import com.stepbookstep.server.domain.reading.domain.GoalMetric
4+
import com.stepbookstep.server.domain.reading.domain.GoalPeriod
5+
import jakarta.validation.constraints.Positive
6+
7+
/**
8+
* 독서 목표 수정 요청
9+
* - 변경할 필드만 전달
10+
* - 전달하지 않은 필드는 기존 값 유지
11+
*/
12+
data class UpdateReadingGoalRequest(
13+
val period: GoalPeriod? = null,
14+
val metric: GoalMetric? = null,
15+
16+
@field:Positive(message = "targetAmount는 1 이상이어야 합니다")
17+
val targetAmount: Int? = null
18+
)

src/main/kotlin/com/stepbookstep/server/domain/reading/presentation/dto/UpsertReadingGoalRequest.kt

Lines changed: 0 additions & 17 deletions
This file was deleted.

0 commit comments

Comments
 (0)