diff --git a/api-docs.yml b/api-docs.yml index 375a8db..2c248f4 100644 --- a/api-docs.yml +++ b/api-docs.yml @@ -125,6 +125,39 @@ paths: title: 5kg 다이어트 streak: 0 status: CLOSED + /v1/goals/{goalId}: + put: + tags: + - goals + summary: 목표 수정(달성 상태 변경 포함) + description: " " + operationId: modifyGoal + parameters: + - name: goalId + in: path + required: true + schema: + type: string + description: 목표 ID + responses: + "200": + description: OK + delete: + tags: + - goals + summary: 목표 삭제 + description: " " + operationId: deleteGoal + parameters: + - name: goalId + in: path + required: true + schema: + type: string + description: 목표 ID + responses: + "200": + description: OK # journals /v1/goals/{goalId}/journals: diff --git a/src/main/kotlin/com/likelionhgu/stepper/goal/Goal.kt b/src/main/kotlin/com/likelionhgu/stepper/goal/Goal.kt index 65e0165..cf2768b 100644 --- a/src/main/kotlin/com/likelionhgu/stepper/goal/Goal.kt +++ b/src/main/kotlin/com/likelionhgu/stepper/goal/Goal.kt @@ -19,6 +19,10 @@ class Goal( @Column var title: String, + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + val member: Member? = null, + @Column var startDate: LocalDate? = null, @@ -28,18 +32,21 @@ class Goal( @Column(length = 512) var thumbnail: String? = null, - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id") - val member: Member + @Column + var status: GoalStatus = GoalStatus.OPEN ) : BaseTime() { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val goalId = 0L - - @Column - var status = GoalStatus.OPEN + val goalId: Long = 0L @Column - var streak = 0 + var streak: Int = 0 + + fun update(targetGoal: Goal) { + title = targetGoal.title + startDate = targetGoal.startDate + endDate = targetGoal.endDate + thumbnail = targetGoal.thumbnail + status = targetGoal.status + } } \ No newline at end of file diff --git a/src/main/kotlin/com/likelionhgu/stepper/goal/GoalController.kt b/src/main/kotlin/com/likelionhgu/stepper/goal/GoalController.kt index 15eebbc..ac80a77 100644 --- a/src/main/kotlin/com/likelionhgu/stepper/goal/GoalController.kt +++ b/src/main/kotlin/com/likelionhgu/stepper/goal/GoalController.kt @@ -2,14 +2,18 @@ package com.likelionhgu.stepper.goal import com.likelionhgu.stepper.goal.enums.GoalSortType import com.likelionhgu.stepper.goal.request.GoalRequest +import com.likelionhgu.stepper.goal.request.GoalUpdateRequest import com.likelionhgu.stepper.goal.response.GoalResponseWrapper import com.likelionhgu.stepper.security.oauth2.CommonOAuth2Attribute import jakarta.validation.Valid import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @@ -38,4 +42,19 @@ class GoalController( val responseBody = GoalResponseWrapper.of(goals) return ResponseEntity.ok(responseBody) } + + @PutMapping("/v1/goals/{goalId}") + fun updateGoal( + @PathVariable goalId: Long, + @RequestBody goalRequest: GoalUpdateRequest, + ) { + goalService.updateGoal(goalId, goalRequest) + } + + @DeleteMapping("/v1/goals/{goalId}") + fun deleteGoal( + @PathVariable goalId: Long + ) { + goalService.deleteGoal(goalId) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/likelionhgu/stepper/goal/GoalService.kt b/src/main/kotlin/com/likelionhgu/stepper/goal/GoalService.kt index a33df0e..bc48117 100644 --- a/src/main/kotlin/com/likelionhgu/stepper/goal/GoalService.kt +++ b/src/main/kotlin/com/likelionhgu/stepper/goal/GoalService.kt @@ -4,6 +4,7 @@ import com.likelionhgu.stepper.exception.GoalNotFoundException import com.likelionhgu.stepper.exception.MemberNotFoundException import com.likelionhgu.stepper.goal.enums.GoalSortType import com.likelionhgu.stepper.goal.request.GoalRequest +import com.likelionhgu.stepper.goal.request.GoalUpdateRequest import com.likelionhgu.stepper.member.MemberRepository import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -21,15 +22,16 @@ class GoalService( val goal = Goal( goalRequest.title!!, + member, goalRequest.startDate, goalRequest.endDate, - goalRequest.thumbnail, - member + goalRequest.thumbnail ).let(goalRepository::save) return goal.goalId } + @Transactional(readOnly = true) fun memberGoals(oauth2UserId: String, sortType: GoalSortType): List { val member = memberRepository.findByOauth2Id(oauth2UserId) ?: throw MemberNotFoundException("The member with the oauth2 sub \"$oauth2UserId\" does not exist") @@ -44,8 +46,20 @@ class GoalService( * @return Goal The `Goal` entity corresponding to the provided ID. * @throws GoalNotFoundException if no goal is found with the provided ID. */ + @Transactional(readOnly = true) fun goalInfo(goalId: Long): Goal { return goalRepository.findById(goalId).getOrNull() ?: throw GoalNotFoundException("The goal with the id \"$goalId\" does not exist") } + + fun updateGoal(goalId: Long, goalRequest: GoalUpdateRequest) { + val sourceGoal = goalRepository.findById(goalId).getOrNull() + ?: throw GoalNotFoundException("The goal with the id \"$goalId\" does not exist") + + goalRequest.toEntity().also(sourceGoal::update) + } + + fun deleteGoal(goalId: Long) { + goalRepository.deleteById(goalId) + } } diff --git a/src/main/kotlin/com/likelionhgu/stepper/goal/request/GoalUpdateRequest.kt b/src/main/kotlin/com/likelionhgu/stepper/goal/request/GoalUpdateRequest.kt new file mode 100644 index 0000000..d2865bb --- /dev/null +++ b/src/main/kotlin/com/likelionhgu/stepper/goal/request/GoalUpdateRequest.kt @@ -0,0 +1,33 @@ +package com.likelionhgu.stepper.goal.request + +import com.likelionhgu.stepper.goal.Goal +import com.likelionhgu.stepper.goal.enums.GoalStatus +import jakarta.validation.constraints.NotNull +import org.springframework.format.annotation.DateTimeFormat +import java.time.LocalDate + +data class GoalUpdateRequest( + + @field:NotNull(message = "The title must not be null") + val title: String?, + + @field:NotNull(message = "The status must not be null") + val status: GoalStatus?, + + @field:DateTimeFormat(pattern = "yyyy-MM-dd") + val startDate: LocalDate? = null, + + @field:DateTimeFormat(pattern = "yyyy-MM-dd") + val endDate: LocalDate? = null, + + val thumbnail: String? = null, + + ) { + fun toEntity() = Goal( + title = title!!, + startDate = startDate, + endDate = endDate, + thumbnail = thumbnail, + status = status!! + ) +} diff --git a/src/test/kotlin/com/likelionhgu/stepper/goal/GoalServiceTest.kt b/src/test/kotlin/com/likelionhgu/stepper/goal/GoalServiceTest.kt index 03ddf1d..51ad73a 100644 --- a/src/test/kotlin/com/likelionhgu/stepper/goal/GoalServiceTest.kt +++ b/src/test/kotlin/com/likelionhgu/stepper/goal/GoalServiceTest.kt @@ -1,7 +1,9 @@ package com.likelionhgu.stepper.goal import com.likelionhgu.stepper.goal.enums.GoalSortType +import com.likelionhgu.stepper.goal.enums.GoalStatus import com.likelionhgu.stepper.goal.request.GoalRequest +import com.likelionhgu.stepper.goal.request.GoalUpdateRequest import com.likelionhgu.stepper.member.Member import com.likelionhgu.stepper.member.MemberRepository import io.kotest.core.spec.style.BehaviorSpec @@ -67,7 +69,6 @@ class GoalServiceTest( } `when`("the goals are retrieved with an order of descending") { - then("the goals should be sorted by ascending") { val goals = goalService.memberGoals(oauth2UserId, GoalSortType.DESC) @@ -75,6 +76,29 @@ class GoalServiceTest( goals[1].goalId shouldBe goalId1 } } + + `when`("the goal is updated") { + then("the goal should be updated") { + val request = GoalUpdateRequest( + title = goalTitle, + status = GoalStatus.CLOSED + ) + goalService.updateGoal(goalId1, request) + + val goal = goalService.goalInfo(goalId1) + goal shouldNotBe null + goal.status shouldBe GoalStatus.CLOSED + } + } + + `when`("the goal is deleted") { + then("the goal should be deleted") { + goalService.deleteGoal(goalId1) + + val goal = goalRepository.findById(goalId1).getOrNull() + goal shouldBe null + } + } } }) { override fun extensions() = listOf(SpringExtension)