Skip to content

Commit c1728f0

Browse files
authored
feat: create schedule v2 (#68)
2 parents 35e975d + d83c554 commit c1728f0

File tree

5 files changed

+382
-1
lines changed

5 files changed

+382
-1
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package noweekend.core.api.controller.v2
2+
3+
import noweekend.core.api.controller.v1.response.ScheduleResponse
4+
import noweekend.core.api.controller.v2.docs.ScheduleControllerDocsV2
5+
import noweekend.core.api.controller.v2.request.ScheduleCreateRequestV2
6+
import noweekend.core.api.controller.v2.request.ScheduleUpdateRequestV2
7+
import noweekend.core.api.security.annotations.CurrentUserId
8+
import noweekend.core.api.service.schedule.ScheduleApplicationService
9+
import noweekend.core.support.response.ApiResponse
10+
import org.springframework.validation.annotation.Validated
11+
import org.springframework.web.bind.annotation.PathVariable
12+
import org.springframework.web.bind.annotation.PostMapping
13+
import org.springframework.web.bind.annotation.PutMapping
14+
import org.springframework.web.bind.annotation.RequestBody
15+
import org.springframework.web.bind.annotation.RequestMapping
16+
import org.springframework.web.bind.annotation.RestController
17+
18+
@RestController
19+
@RequestMapping("/api/v2/schedule")
20+
class ScheduleControllerV2(
21+
private val scheduleApplicationService: ScheduleApplicationService,
22+
) : ScheduleControllerDocsV2 {
23+
24+
@PostMapping
25+
override fun createSchedule(
26+
@CurrentUserId userId: String,
27+
@Validated @RequestBody request: ScheduleCreateRequestV2,
28+
): ApiResponse<ScheduleResponse> {
29+
return ApiResponse.success(scheduleApplicationService.createScheduleV2(userId, request))
30+
}
31+
32+
@PutMapping("/{id}")
33+
override fun updateSchedule(
34+
@CurrentUserId userId: String,
35+
@PathVariable id: String,
36+
@Validated @RequestBody request: ScheduleUpdateRequestV2,
37+
): ApiResponse<ScheduleResponse> {
38+
return ApiResponse.success(scheduleApplicationService.updateScheduleV2(userId, id, request))
39+
}
40+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package noweekend.core.api.controller.v2.docs
2+
3+
import io.swagger.v3.oas.annotations.Operation
4+
import io.swagger.v3.oas.annotations.media.Content
5+
import io.swagger.v3.oas.annotations.media.ExampleObject
6+
import io.swagger.v3.oas.annotations.media.Schema
7+
import io.swagger.v3.oas.annotations.parameters.RequestBody
8+
import noweekend.core.api.controller.v1.response.ScheduleResponse
9+
import noweekend.core.api.controller.v2.request.ScheduleCreateRequestV2
10+
import noweekend.core.api.controller.v2.request.ScheduleUpdateRequestV2
11+
import noweekend.core.support.response.ApiResponse
12+
import io.swagger.v3.oas.annotations.responses.ApiResponse as SwaggerApiResponse
13+
14+
interface ScheduleControllerDocsV2 {
15+
16+
@Operation(
17+
summary = "캘린더: 일정 생성",
18+
description = "일정을 생성합니다.",
19+
requestBody = RequestBody(
20+
required = true,
21+
content = [
22+
Content(
23+
mediaType = "application/json",
24+
schema = Schema(implementation = ScheduleCreateRequestV2::class),
25+
examples = [
26+
ExampleObject(
27+
name = "예시 요청",
28+
value = """
29+
{
30+
"title": "회의",
31+
"startDateTime": "2024-05-27 10:00:00",
32+
"endDateTime": "2024-05-27 11:00:00",
33+
"category": "COMPANY",
34+
"temperature": 3,
35+
"alarmOption": "FIFTEEN_MINUTES_BEFORE"
36+
}
37+
""",
38+
),
39+
],
40+
),
41+
],
42+
),
43+
responses = [
44+
SwaggerApiResponse(
45+
responseCode = "200",
46+
description = "일정 생성 성공",
47+
content = [
48+
Content(
49+
mediaType = "application/json",
50+
schema = Schema(implementation = ApiResponse::class),
51+
examples = [
52+
ExampleObject(
53+
name = "예시 응답",
54+
value = """
55+
{
56+
"result": "SUCCESS",
57+
"data": {
58+
"id": "abc123",
59+
"title": "회의",
60+
"startDateTime": "2025-05-01T10:00:00",
61+
"endDateTime": "2025-05-01T11:00:00",
62+
"category": "COMPANY",
63+
"temperature": 3,
64+
"alarmOption": "FIFTEEN_MINUTES_BEFORE",
65+
"completed": false
66+
},
67+
"error": null
68+
}
69+
""",
70+
),
71+
],
72+
),
73+
],
74+
),
75+
SwaggerApiResponse(
76+
responseCode = "400",
77+
description = "잘못된 요청",
78+
content = [
79+
Content(
80+
mediaType = "application/json",
81+
schema = Schema(implementation = ApiResponse::class),
82+
examples = [
83+
ExampleObject(
84+
name = "예시 응답",
85+
value = """
86+
{
87+
"result": "ERROR",
88+
"data": null,
89+
"error": {
90+
"code": "INVALID_PARAMETER",
91+
"message": "올바르지 않은 요청입니다.",
92+
"data": {}
93+
}
94+
}
95+
""",
96+
),
97+
],
98+
),
99+
],
100+
),
101+
],
102+
)
103+
fun createSchedule(
104+
@Schema(hidden = true) userId: String,
105+
request: ScheduleCreateRequestV2,
106+
): ApiResponse<ScheduleResponse>
107+
108+
@Operation(
109+
summary = "캘린더: 일정 수정",
110+
description = "일정의 시작/종료 시간, 카테고리, 온도, 알람 옵션을 수정합니다.",
111+
requestBody = RequestBody(
112+
required = true,
113+
content = [
114+
Content(
115+
mediaType = "application/json",
116+
schema = Schema(implementation = ScheduleUpdateRequestV2::class),
117+
),
118+
],
119+
),
120+
responses = [
121+
SwaggerApiResponse(
122+
responseCode = "200",
123+
description = "일정 수정 성공",
124+
content = [
125+
Content(
126+
mediaType = "application/json",
127+
schema = Schema(implementation = ApiResponse::class),
128+
examples = [
129+
ExampleObject(
130+
name = "예시 응답",
131+
value = """
132+
{
133+
"result": "SUCCESS",
134+
"data": {
135+
"id": "abc123",
136+
"title": "회의",
137+
"startDateTime": "2025-05-01T10:00:00",
138+
"endDateTime": "2025-05-01T11:00:00",
139+
"category": "COMPANY",
140+
"temperature": 3,
141+
"alarmOption": "FIFTEEN_MINUTES_BEFORE",
142+
"completed": false
143+
},
144+
"error": null
145+
}
146+
""",
147+
),
148+
],
149+
),
150+
],
151+
),
152+
SwaggerApiResponse(
153+
responseCode = "400",
154+
description = "잘못된 요청",
155+
content = [
156+
Content(
157+
mediaType = "application/json",
158+
schema = Schema(implementation = ApiResponse::class),
159+
examples = [
160+
ExampleObject(
161+
name = "예시 응답",
162+
value = """
163+
{
164+
"result": "ERROR",
165+
"data": null,
166+
"error": {
167+
"code": "INVALID_PARAMETER",
168+
"message": "올바르지 않은 요청입니다.",
169+
"data": {}
170+
}
171+
}
172+
""",
173+
),
174+
],
175+
),
176+
],
177+
),
178+
],
179+
)
180+
fun updateSchedule(
181+
@Schema(hidden = true) userId: String,
182+
id: String,
183+
request: ScheduleUpdateRequestV2,
184+
): ApiResponse<ScheduleResponse>
185+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package noweekend.core.api.controller.v2.request
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
import jakarta.validation.constraints.Max
5+
import jakarta.validation.constraints.NotBlank
6+
import jakarta.validation.constraints.NotNull
7+
import jakarta.validation.constraints.Positive
8+
import noweekend.core.domain.enumerate.AlarmOption
9+
import noweekend.core.domain.enumerate.ScheduleCategory
10+
import java.time.LocalDateTime
11+
12+
@Schema(description = "일정 생성 요청")
13+
data class ScheduleCreateRequestV2(
14+
@field:NotBlank(message = "제목은 필수입니다.")
15+
@Schema(description = "제목")
16+
val title: String,
17+
18+
@field:NotNull(message = "시작 시간은 필수입니다.")
19+
@Schema(description = "시작 시간")
20+
val startDateTime: LocalDateTime,
21+
22+
@field:NotNull(message = "종료 시간은 필수입니다.")
23+
@Schema(description = "종료 시간")
24+
val endDateTime: LocalDateTime,
25+
26+
@field:NotNull(message = "카테고리는 필수입니다.")
27+
@Schema(description = "카테고리")
28+
val category: ScheduleCategory,
29+
30+
@field:NotNull(message = "온도는 필수입니다.")
31+
@field:Positive(message = "온도는 양수여야 합니다.")
32+
@field:Max(value = 100, message = "온도는 100 이하여야 합니다.")
33+
@Schema(description = "온도 (감정 등 표현)")
34+
val temperature: Int,
35+
36+
@field:NotNull(message = "알람 설정은 필수입니다.")
37+
@Schema(description = "알람 설정")
38+
val alarmOption: AlarmOption,
39+
)
40+
41+
@Schema(description = "일정 수정 요청")
42+
data class ScheduleUpdateRequestV2(
43+
@field:NotBlank(message = "제목은 필수입니다.")
44+
@Schema(description = "제목")
45+
val title: String,
46+
47+
@field:NotNull(message = "시작 시간은 필수입니다.")
48+
@Schema(description = "시작 시간")
49+
val startDateTime: LocalDateTime,
50+
51+
@field:NotNull(message = "종료 시간은 필수입니다.")
52+
@Schema(description = "종료 시간")
53+
val endDateTime: LocalDateTime,
54+
55+
@field:NotNull(message = "카테고리는 필수입니다.")
56+
@Schema(description = "카테고리")
57+
val category: ScheduleCategory,
58+
59+
@field:NotNull(message = "온도는 필수입니다.")
60+
@field:Positive(message = "온도는 양수여야 합니다.")
61+
@field:Max(value = 100, message = "온도는 100 이하여야 합니다.")
62+
@Schema(description = "온도 (감정 등 표현)")
63+
val temperature: Int,
64+
65+
@field:NotNull(message = "알람 설정은 필수입니다.")
66+
@Schema(description = "알람 설정")
67+
val alarmOption: AlarmOption,
68+
)

noweekend-core/core-api/src/main/kotlin/noweekend/core/api/service/schedule/ScheduleApplicationService.kt

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import noweekend.core.api.controller.v1.request.ScheduleCreateRequest
44
import noweekend.core.api.controller.v1.request.ScheduleUpdateRequest
55
import noweekend.core.api.controller.v1.response.DailyScheduleResponse
66
import noweekend.core.api.controller.v1.response.ScheduleResponse
7+
import noweekend.core.api.controller.v2.request.ScheduleCreateRequestV2
8+
import noweekend.core.api.controller.v2.request.ScheduleUpdateRequestV2
79
import noweekend.core.domain.schedule.Schedule
810
import noweekend.core.domain.schedule.ScheduleReader
911
import noweekend.core.domain.schedule.ScheduleWriter
@@ -25,12 +27,23 @@ interface ScheduleApplicationService {
2527
request: ScheduleCreateRequest,
2628
): ScheduleResponse
2729

30+
fun createScheduleV2(
31+
userId: String,
32+
request: ScheduleCreateRequestV2,
33+
): ScheduleResponse
34+
2835
fun updateSchedule(
2936
userId: String,
3037
scheduleId: String,
3138
request: ScheduleUpdateRequest,
3239
): ScheduleResponse
3340

41+
fun updateScheduleV2(
42+
userId: String,
43+
scheduleId: String,
44+
request: ScheduleUpdateRequestV2,
45+
): ScheduleResponse
46+
3447
fun updateScheduleState(
3548
userId: String,
3649
scheduleId: String,
@@ -108,6 +121,28 @@ class ScheduleApplicationServiceImpl(
108121
return savedSchedule.toResponse()
109122
}
110123

124+
override fun createScheduleV2(
125+
userId: String,
126+
request: ScheduleCreateRequestV2,
127+
): ScheduleResponse {
128+
if (request.startDateTime > request.endDateTime) {
129+
throw CoreException(ErrorType.INVALID_PARAMETER, "startDateTime must greater than endDateTime")
130+
}
131+
132+
val schedule = Schedule.newScheduleV2(
133+
userId = userId,
134+
title = request.title,
135+
startTime = request.startDateTime,
136+
endTime = request.startDateTime,
137+
category = request.category,
138+
temperature = request.temperature,
139+
alarmOption = request.alarmOption,
140+
)
141+
142+
val savedSchedule = scheduleWriter.save(schedule)
143+
return savedSchedule.toResponse()
144+
}
145+
111146
override fun updateSchedule(
112147
userId: String,
113148
scheduleId: String,
@@ -147,6 +182,35 @@ class ScheduleApplicationServiceImpl(
147182
return savedSchedule.toResponse()
148183
}
149184

185+
override fun updateScheduleV2(
186+
userId: String,
187+
scheduleId: String,
188+
request: ScheduleUpdateRequestV2,
189+
): ScheduleResponse {
190+
val existingSchedule = scheduleReader.findScheduleById(scheduleId)
191+
?: throw CoreException(ErrorType.NOT_FOUND_ERROR, "Schedule not found: $scheduleId")
192+
193+
if (existingSchedule.userId != userId) {
194+
throw CoreException(ErrorType.FORBIDDEN_ERROR, "You don't have permission to update this schedule")
195+
}
196+
197+
if (request.startDateTime > request.endDateTime) {
198+
throw CoreException(ErrorType.INVALID_PARAMETER, "startDateTime must greater than endDateTime")
199+
}
200+
201+
val updatedSchedule = existingSchedule.copy(
202+
title = request.title,
203+
startTime = request.startDateTime,
204+
endTime = request.endDateTime,
205+
category = request.category,
206+
temperature = request.temperature,
207+
alarmOption = request.alarmOption,
208+
)
209+
210+
val savedSchedule = scheduleWriter.update(updatedSchedule)
211+
return savedSchedule.toResponse()
212+
}
213+
150214
override fun updateScheduleState(
151215
userId: String,
152216
scheduleId: String,

0 commit comments

Comments
 (0)