Skip to content

Commit

Permalink
[🌎 Feature] Goal 정보 수정하기 API (#170)
Browse files Browse the repository at this point in the history
* feat : Goal 정보 Update API 구현 (#169)

* test : Goal 정보 Update API test 코드 잓성 (#169)

* chore : 비구조화 할당 대신 with 문법 사용 (#169)

* refactor : Sticker와 Tag의 Select가 다른 객체의 생성을 위해 쓰일 때, getById가 아닌 getReferenceById로 가져오도록 변경 (#169)

* refactor : getReferenceById로 대체 가능한 부분 변경 (#169)

* feat : DB Entity Not Found Error들에 대한 handler 추가  (#169)

* refactor : Goal Update 메서드가 Goal을 반환하도록 변경 (#169)

* ktlint formatting
  • Loading branch information
binary-ho authored Feb 11, 2024
1 parent d8a4a2f commit 2acb163
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.raemian.api.log.LogService
import io.raemian.api.support.error.CoreApiException
import io.raemian.api.support.error.ErrorInfo
import io.raemian.api.support.response.ApiResponse
import jakarta.persistence.EntityNotFoundException
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.http.ResponseEntity
Expand All @@ -22,6 +23,15 @@ class GlobalExceptionHandler(
return ResponseEntity(ApiResponse.error(e.errorInfo), e.errorInfo.status)
}

@ExceptionHandler(NoSuchElementException::class, EntityNotFoundException::class)
fun handleResourceNotFoundException(e: CoreApiException): ResponseEntity<ApiResponse<Any>> {
log.error("Exception : {}", e.message, e)
return ResponseEntity(
ApiResponse.error(ErrorInfo.RESOURCE_NOT_FOUND),
ErrorInfo.RESOURCE_NOT_FOUND.status,
)
}

@ExceptionHandler(Exception::class)
fun handleException(e: Exception): ResponseEntity<ApiResponse<Any>> {
log.error("Exception : {}", e.message, e)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.raemian.api.goal

import io.raemian.api.goal.controller.request.CreateGoalRequest
import io.raemian.api.goal.controller.request.UpdateGoalRequest
import io.raemian.api.goal.controller.response.CreateGoalResponse
import io.raemian.api.goal.controller.response.GoalResponse
import io.raemian.api.sticker.StickerService
Expand Down Expand Up @@ -44,6 +45,25 @@ class GoalService(
return CreateGoalResponse(goal)
}

@Transactional
fun update(userId: Long, goalId: Long, updateGoalRequest: UpdateGoalRequest): GoalResponse {
val goal = goalRepository.getById(goalId)
validateGoalIsUsers(userId, goal)

with(updateGoalRequest) {
val updateTagGoal = goal.takeIf { it.tag.id == tagId }
?: updateTag(goal, tagId)

val updatedStickerGoal = updateTagGoal.takeIf { it.sticker.id == stickerId }
?: updateSticker(updateTagGoal, stickerId)

val deadline = RaemianLocalDate.of(yearOfDeadline, monthOfDeadline)
val updatedGoal = updatedStickerGoal.update(title, deadline, description)
goalRepository.save(updatedGoal)
return GoalResponse(updatedGoal)
}
}

@Transactional
fun delete(userId: Long, goalId: Long) {
val goal = goalRepository.getById(goalId)
Expand All @@ -52,16 +72,21 @@ class GoalService(
}

private fun createFirstLifeMap(userId: Long): LifeMap {
val user = userRepository.getById(userId)
return LifeMap(user, true, goals = ArrayList())
val user = userRepository.getReferenceById(userId)
return LifeMap(
user = user,
isPublic = true,
goals = ArrayList(),
)
}

private fun createGoal(createGoalRequest: CreateGoalRequest, lifeMap: LifeMap): Goal {
val (title, yearOfDeadline, monthOfDeadLine, stickerId, tagId, description) = createGoalRequest
val deadline = RaemianLocalDate.of(yearOfDeadline, monthOfDeadLine)
val sticker = stickerService.getById(stickerId)
val tag = tagService.getById(tagId)
return Goal(lifeMap, title, deadline, sticker, tag, description!!)
with(createGoalRequest) {
val deadline = RaemianLocalDate.of(yearOfDeadline, monthOfDeadline)
val sticker = stickerService.getReferenceById(stickerId)
val tag = tagService.getReferenceById(tagId)
return Goal(lifeMap, title, deadline, sticker, tag, description!!)
}
}

private fun validateAnotherUserLifeMapPublic(userId: Long, lifeMap: LifeMap) {
Expand All @@ -83,4 +108,14 @@ class GoalService(
throw MaxGoalCountExceededException()
}
}

private fun updateTag(goal: Goal, tagId: Long): Goal {
val newTag = tagService.getReferenceById(tagId)
return goal.updateTag(newTag)
}

private fun updateSticker(goal: Goal, stickerId: Long): Goal {
val newSticker = stickerService.getReferenceById(stickerId)
return goal.updateSticker(newSticker)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.raemian.api.goal.controller
import io.raemian.api.auth.domain.CurrentUser
import io.raemian.api.goal.GoalService
import io.raemian.api.goal.controller.request.CreateGoalRequest
import io.raemian.api.goal.controller.request.UpdateGoalRequest
import io.raemian.api.goal.controller.response.CreateGoalResponse
import io.raemian.api.goal.controller.response.GoalResponse
import io.raemian.api.support.response.ApiResponse
Expand All @@ -11,6 +12,7 @@ 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.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
Expand Down Expand Up @@ -48,6 +50,18 @@ class GoalController(
.body(ApiResponse.success(response))
}

@Operation(summary = "목표 수정 API")
@PatchMapping("/{goalId}")
fun update(
@AuthenticationPrincipal currentUser: CurrentUser,
@PathVariable("goalId") goalId: Long,
@RequestBody updateGoalRequest: UpdateGoalRequest,
): ResponseEntity<ApiResponse<GoalResponse>> {
val goalResponse = goalService.update(currentUser.id, goalId, updateGoalRequest)
return ResponseEntity
.ok(ApiResponse.success(goalResponse))
}

@Operation(summary = "목표 삭제 API")
@DeleteMapping("/{goalId}")
fun delete(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.raemian.api.goal.controller.request

data class UpdateGoalRequest(
val title: String,
val yearOfDeadline: String,
val monthOfDeadline: String,
val stickerId: Long,
val tagId: Long,
val description: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class StickerService(
}

@Transactional(readOnly = true)
fun getById(id: Long): Sticker {
return stickerRepository.getById(id)
fun getReferenceById(id: Long): Sticker {
return stickerRepository.getReferenceById(id)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ enum class ErrorInfo(
LogLevel.ERROR,
),

RESOURCE_NOT_FOUND(
HttpStatus.NOT_FOUND,
HttpStatus.NOT_FOUND.value(),
"Resource Not Found",
LogLevel.ERROR,
),

PRIVATE_LIFE_MAP_EXCEPTION(
HttpStatus.FORBIDDEN,
1001,
Expand All @@ -36,6 +43,7 @@ enum class ErrorInfo(
"세부 목표의 최대 갯수를 초과했습니다.",
LogLevel.INFO,
),

TOO_MANY_CHEERING(
HttpStatus.TOO_MANY_REQUESTS,
1004,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class TagService(
}

@Transactional(readOnly = true)
fun getById(id: Long): Tag {
return tagRepository.getById(id)
fun getReferenceById(id: Long): Tag {
return tagRepository.getReferenceById(id)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package io.raemian.api.integration.goal

import io.raemian.api.goal.GoalService
import io.raemian.api.goal.controller.request.CreateGoalRequest
import io.raemian.api.goal.controller.request.UpdateGoalRequest
import io.raemian.api.support.RaemianLocalDate
import io.raemian.api.support.error.MaxGoalCountExceededException
import io.raemian.api.support.error.PrivateLifeMapException
import io.raemian.storage.db.core.goal.Goal
Expand Down Expand Up @@ -201,6 +203,63 @@ class GoalServiceTest {
}.isInstanceOf(MaxGoalCountExceededException::class.java)
}

@Test
@DisplayName("Goal의 정보들을 수정할 수 있다.")
@Transactional
fun updateGoalTest() {
// given
val title = "Title"
val description = "Description"
val year = "2000"
val month = "05"
val deadline = RaemianLocalDate.of(year, month)

val goal = Goal(
lifeMap = LIFE_MAP_FIXTURE,
title = title,
description = description,
deadline = deadline,
sticker = STICKER_FIXTURE,
tag = TAG_FIXTURE,
)
goalRepository.save(goal)

// when
val newTitle = "New" + title
val newDescription = "New" + description

val newSticker = Sticker(
"New" + STICKER_FIXTURE.name,
"New" + STICKER_FIXTURE.url,
)
val newTag = Tag("New" + TAG_FIXTURE.content)
entityManager.merge(newSticker)
entityManager.merge(newTag)

val newDeadline = deadline.plusDays(77)
val newYear = newDeadline.year.toString()
val newMonth = newDeadline.monthValue.toString()

val updateGoalRequest = UpdateGoalRequest(
title = newTitle,
yearOfDeadline = newYear,
monthOfDeadline = newMonth,
stickerId = newSticker.id!!,
tagId = newTag.id!!,
description = newDescription,
)
goalService.update(USER_FIXTURE.id!!, goal.id!!, updateGoalRequest)

// then
val updatedGoal = goalRepository.getById(goal.id!!)
assertThat(updatedGoal.title).isEqualTo(newTitle)
assertThat(updatedGoal.description).isEqualTo(newDescription)
assertThat(updatedGoal.deadline.year).isEqualTo(newDeadline.year)
assertThat(updatedGoal.deadline.month).isEqualTo(newDeadline.month)
assertThat(updatedGoal.sticker.name).isEqualTo(newSticker.name)
assertThat(updatedGoal.tag.content).isEqualTo(newTag.content)
}

@Test
@DisplayName("Goal을 삭제할 수 있다.")
@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,21 @@ class Goal(

@Column(nullable = false)
@Nationalized
val title: String,
var title: String,

@Column(nullable = false)
val deadline: LocalDate,
var deadline: LocalDate,

@ManyToOne
@JoinColumn(name = "sticker_id", nullable = false)
val sticker: Sticker,
var sticker: Sticker,

@ManyToOne
@JoinColumn(name = "tag_id", nullable = false)
val tag: Tag,
var tag: Tag,

@Nationalized
val description: String = "",
var description: String = "",

@OneToMany(
mappedBy = "goal",
Expand All @@ -65,6 +65,18 @@ class Goal(
tasks.add(task)
}

fun update(
title: String,
deadline: LocalDate,
description: String,
): Goal = Goal(lifeMap, title, deadline, sticker, tag, description, tasks, id)

fun updateSticker(sticker: Sticker): Goal =
Goal(lifeMap, title, deadline, sticker, tag, description, tasks, id)

fun updateTag(tag: Tag): Goal =
Goal(lifeMap, title, deadline, sticker, tag, description, tasks, id)

private fun validateMaxTaskCount() =
require(tasks.size < MAX_TASK_COUNT)
}

0 comments on commit 2acb163

Please sign in to comment.