Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BLOOM-101] 내 식물 전체 조회 api 수정 #102

Merged
merged 9 commits into from
Sep 8, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,6 @@ interface MyPlantApi {
@Operation(summary = "내 전체 식물들을 조회하는 API 입니다.")
@ApiResponse(responseCode = "200", description = "내 식물 전체 조회 성공")
fun findAllMyPlant(
@Parameter(description = "위치 ID", required = false)
locationId: Long?,
@Parameter(description = "정렬", required = false)
sort: String,
@Schema(hidden = true)
user: User,
): List<MyPlantResponse>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import dnd11th.blooming.api.dto.myplant.AlarmModifyRequest
import dnd11th.blooming.api.dto.myplant.HealthCheckResponse
import dnd11th.blooming.api.dto.myplant.MyPlantDetailResponse
import dnd11th.blooming.api.dto.myplant.MyPlantModifyRequest
import dnd11th.blooming.api.dto.myplant.MyPlantQueryCreteria
import dnd11th.blooming.api.dto.myplant.MyPlantResponse
import dnd11th.blooming.api.dto.myplant.MyPlantSaveRequest
import dnd11th.blooming.api.dto.myplant.MyPlantSaveResponse
Expand All @@ -20,7 +19,6 @@ import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import java.time.LocalDate

Expand All @@ -39,10 +37,8 @@ class MyPlantController(
@Secured
@GetMapping
override fun findAllMyPlant(
@RequestParam locationId: Long?,
@RequestParam(defaultValue = "created_desc") sort: String,
@LoginUser user: User,
): List<MyPlantResponse> = myPlantService.findAllMyPlant(LocalDate.now(), locationId, MyPlantQueryCreteria.from(sort), user)
): List<MyPlantResponse> = myPlantService.findAllMyPlant(LocalDate.now(), user)

@Secured
@GetMapping("/{myPlantId}")
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dnd11th.blooming.api.dto.myplant
import dnd11th.blooming.domain.entity.MyPlant
import io.swagger.v3.oas.annotations.media.Schema
import java.time.LocalDate
import java.time.LocalDateTime

@Schema(
name = "MyPlant Response",
Expand All @@ -13,8 +14,10 @@ data class MyPlantResponse(
val myPlantId: Long?,
@field:Schema(description = "내 식물 별명", example = "쫑쫑이")
val nickname: String,
@field:Schema(description = "위치 존재 여부", example = "1")
val haveLocation: Boolean,
@field:Schema(description = "위치 ID", example = "1")
val locationId: Long?,
@field:Schema(description = "등록 일자", example = "2024-09-03T14:30:45")
val registeredDateTime: LocalDateTime,
@field:Schema(description = "이미지 URL", example = "image.com/7")
val imageUrl: String,
@field:Schema(description = "내 식물 학명", example = "몬스테라 델리오사")
Expand All @@ -35,12 +38,18 @@ data class MyPlantResponse(
MyPlantResponse(
myPlantId = myPlant.id,
nickname = myPlant.nickname,
haveLocation = myPlant.location != null,
locationId = myPlant.location?.id,
registeredDateTime = myPlant.createdDate,
imageUrl = imageUrl ?: myPlant.plantImageUrl,
scientificName = myPlant.scientificName,
dateSinceLastWater = myPlant.getDateSinceLastWater(now),
dateSinceLastFertilizer = myPlant.getDateSinceLastFertilizer(now),
dateSinceLastHealthCheck = myPlant.getDateSinceLastHealthCheck(now),
)

fun fromMyPlantWithImageUrlList(
myPlantWithImageUrlList: List<MyPlantWithImageUrl>,
now: LocalDate,
): List<MyPlantResponse> = myPlantWithImageUrlList.map { dto -> of(dto.myPlant, dto.imageUrl, now) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import dnd11th.blooming.api.dto.myplant.HealthCheckResponse
import dnd11th.blooming.api.dto.myplant.MyPlantCreateDto
import dnd11th.blooming.api.dto.myplant.MyPlantDetailResponse
import dnd11th.blooming.api.dto.myplant.MyPlantModifyRequest
import dnd11th.blooming.api.dto.myplant.MyPlantQueryCreteria
import dnd11th.blooming.api.dto.myplant.MyPlantResponse
import dnd11th.blooming.api.dto.myplant.MyPlantSaveResponse
import dnd11th.blooming.api.dto.myplant.MyPlantWithImageUrl
Expand Down Expand Up @@ -51,19 +50,11 @@ class MyPlantService(
@Transactional(readOnly = true)
fun findAllMyPlant(
now: LocalDate,
locationId: Long? = null,
sort: MyPlantQueryCreteria = MyPlantQueryCreteria.CreatedDesc,
user: User,
): List<MyPlantResponse> {
val myPlantsWithImageUrl = findSortedMyPlantsWithImage(locationId, user, sort)

return myPlantsWithImageUrl.stream().map { myPlantAndImageUrl ->
MyPlantResponse.of(
myPlantAndImageUrl.myPlant,
myPlantAndImageUrl.imageUrl,
now,
)
}.toList()
val myPlantsWithImageUrl: List<MyPlantWithImageUrl> = imageRepository.findMyPlantAndMostRecentFavoriteImageByUser(user)

return MyPlantResponse.fromMyPlantWithImageUrlList(myPlantsWithImageUrl, now)
}

@Transactional(readOnly = true)
Expand Down Expand Up @@ -174,23 +165,4 @@ class MyPlantService(

return HealthCheckResponse(myPlantMessageFactory.createHealthCheckMessage())
}

private fun findSortedMyPlantsWithImage(
locationId: Long?,
user: User,
sort: MyPlantQueryCreteria,
): List<MyPlantWithImageUrl> {
val location = locationId?.let { locationRepository.findByIdAndUser(locationId, user) }

// 정렬된 MyPlant 리스트를 생성
val sortedMyPlants: List<MyPlant> = myPlantRepository.findAllByLocationAndUserOrderBy(location, user, sort)

// MyPlantId -> imageUrl 맵을 생성
val urlMap: Map<Long, String> =
imageRepository.findFavoriteImagesForMyPlants(sortedMyPlants)
.associate { it.myPlantId to it.imageUrl }

// 정렬된 MyPlant 리스트 기준으로 Map에서 imageUrl를 찾고, MyPlant-ImageUrl 객체를 생성
return sortedMyPlants.map { myPlant -> MyPlantWithImageUrl(myPlant, urlMap[myPlant.id]) }
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package dnd11th.blooming.domain.repository

import dnd11th.blooming.api.dto.myplant.MyPlantIdWithImageUrl
import dnd11th.blooming.api.dto.myplant.MyPlantWithImageUrl
import dnd11th.blooming.domain.entity.Image
import dnd11th.blooming.domain.entity.MyPlant
import dnd11th.blooming.domain.entity.user.User
Expand All @@ -14,20 +14,23 @@ interface ImageRepository : JpaRepository<Image, Long> {

@Query(
"""
SELECT new dnd11th.blooming.api.dto.myplant.MyPlantIdWithImageUrl(i.url, mp.id)
SELECT new dnd11th.blooming.api.dto.myplant.MyPlantWithImageUrl(mp, i.url)
FROM Image i
JOIN i.myPlant mp
WHERE mp IN :myPlants
JOIN FETCH mp.location l
WHERE mp.user = :user
AND i.favorite = true
AND i.id = (
SELECT MIN(i2.id)
AND i.updatedDate = (
SELECT MAX(i2.updatedDate)
FROM Image i2
WHERE i2.myPlant = mp
AND i2.favorite = true
)
""",
)
fun findFavoriteImagesForMyPlants(myPlants: List<MyPlant>): List<MyPlantIdWithImageUrl>
fun findMyPlantAndMostRecentFavoriteImageByUser(
@Param("user") user: User,
): List<MyPlantWithImageUrl>

@Modifying
@Query(
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param

interface MyPlantRepository : JpaRepository<MyPlant, Long>, MyPlantQueryDslRepository {
interface MyPlantRepository : JpaRepository<MyPlant, Long> {
fun findByIdAndUser(
id: Long,
user: User,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.patch
import org.springframework.test.web.servlet.post
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime

class MyPlantControllerTest : WebMvcDescribeSpec() {
init {
Expand Down Expand Up @@ -65,12 +67,13 @@ class MyPlantControllerTest : WebMvcDescribeSpec() {
}

describe("내 식물 전체 조회") {
every { myPlantService.findAllMyPlant(any(), any(), any(), any()) } returns
every { myPlantService.findAllMyPlant(any(), any()) } returns
listOf(
MyPlantResponse(
myPlantId = MYPLANT_ID,
nickname = NICKNAME,
haveLocation = true,
locationId = LOCATION_ID,
registeredDateTime = REGISTERED_DATE,
imageUrl = IMAGE_URL,
scientificName = SCIENTIFIC_NAME,
dateSinceLastWater = DATE_SINCE_LAST_WATER,
Expand All @@ -80,7 +83,8 @@ class MyPlantControllerTest : WebMvcDescribeSpec() {
MyPlantResponse(
myPlantId = MYPLANT_ID2,
nickname = NICKNAME2,
haveLocation = true,
locationId = LOCATION_ID,
registeredDateTime = REGISTERED_DATE,
imageUrl = IMAGE_URL,
scientificName = SCIENTIFIC_NAME2,
dateSinceLastWater = DATE_SINCE_LAST_WATER,
Expand All @@ -96,29 +100,30 @@ class MyPlantControllerTest : WebMvcDescribeSpec() {
jsonPath("$.size()", equalTo(2))
jsonPath("$[0].myPlantId", equalTo(MYPLANT_ID.toInt()))
jsonPath("$[0].nickname", equalTo(NICKNAME))
jsonPath("$[0].haveLocation", equalTo(true))
jsonPath("$[0].locationId", equalTo(LOCATION_ID.toInt()))
jsonPath("$[0].imageUrl", equalTo(IMAGE_URL))
jsonPath("$[0].scientificName", equalTo(SCIENTIFIC_NAME))
jsonPath("$[0].dateSinceLastWater", equalTo(DATE_SINCE_LAST_WATER))
jsonPath("$[0].dateSinceLastFertilizer", equalTo(DATE_SINCE_LAST_FERTILIZER))
jsonPath("$[0].dateSinceLastHealthCheck", equalTo(DATE_SINCE_LAST_HEALTHCHECK))
jsonPath("$[1].myPlantId", equalTo(MYPLANT_ID2.toInt()))
jsonPath("$[1].nickname", equalTo(NICKNAME2))
jsonPath("$[0].haveLocation", equalTo(true))
jsonPath("$[0].imageUrl", equalTo(IMAGE_URL))
jsonPath("$[1].locationId", equalTo(LOCATION_ID.toInt()))
jsonPath("$[1].imageUrl", equalTo(IMAGE_URL))
jsonPath("$[1].scientificName", equalTo(SCIENTIFIC_NAME2))
jsonPath("$[1].dateSinceLastWater", equalTo(DATE_SINCE_LAST_WATER))
jsonPath("$[1].dateSinceLastFertilizer", equalTo(DATE_SINCE_LAST_FERTILIZER))
jsonPath("$[1].dateSinceLastHealthCheck", equalTo(DATE_SINCE_LAST_HEALTHCHECK))
}.andDo { print() }
}
}
every { myPlantService.findAllMyPlant(any(), LOCATION_ID, any(), any()) } returns
every { myPlantService.findAllMyPlant(any(), any()) } returns
listOf(
MyPlantResponse(
myPlantId = MYPLANT_ID,
nickname = NICKNAME,
haveLocation = true,
locationId = LOCATION_ID,
registeredDateTime = REGISTERED_DATE,
imageUrl = IMAGE_URL,
scientificName = SCIENTIFIC_NAME,
dateSinceLastWater = DATE_SINCE_LAST_WATER,
Expand Down Expand Up @@ -443,12 +448,14 @@ class MyPlantControllerTest : WebMvcDescribeSpec() {
const val LOCATION_ID = 100L
const val LOCATION_NAME = "거실"
val START_DATE: LocalDate = LocalDate.of(2024, 4, 19)
val REGISTERED_DATE: LocalDateTime = LocalDateTime.of(START_DATE, LocalTime.NOON)
const val WITH_DAYS = 234
const val LAST_WATERED_DATE_INT = 1
val LAST_WATERED_DATE: LocalDate = CURRENT_DAY.minusDays(LAST_WATERED_DATE_INT.toLong())
const val LAST_FERTILIZER_DATE_INT = 2
val LAST_FERTILIZER_DATE: LocalDate = CURRENT_DAY.minusDays(LAST_FERTILIZER_DATE_INT.toLong())
val LAST_HEALTHCHECK_DATE: LocalDate = LocalDate.of(2024, 6, 15)

// val LAST_HEALTHCHECK_DATE: LocalDate = LocalDate.of(2024, 6, 15)
const val DATE_SINCE_LAST_WATER = 3
const val DATE_SINCE_LAST_FERTILIZER = 3
const val DATE_SINCE_LAST_HEALTHCHECK = 3
Expand All @@ -463,9 +470,9 @@ class MyPlantControllerTest : WebMvcDescribeSpec() {
const val FERTILIZER_PERIOD = 30
const val HEALTHCHECK_ALARM = true

val LAST_FERTILIZER_TITLE = "비료주기"
val LAST_FERTILIZER_INFO = "이번 달"
val LAST_WATERED_TITLE = "마지막으로 물 준 날"
val LAST_WATERED_INFO = "${LAST_WATERED_DATE_INT}\n1일전"
const val LAST_FERTILIZER_TITLE = "비료주기"
const val LAST_FERTILIZER_INFO = "이번 달"
const val LAST_WATERED_TITLE = "마지막으로 물 준 날"
const val LAST_WATERED_INFO = "${LAST_WATERED_DATE_INT}\n1일전"
}
}
Loading
Loading