From ea3252c6dd4e85862c9128fde86ece775de4b51e Mon Sep 17 00:00:00 2001 From: WhitePiano Date: Thu, 25 Jan 2024 11:20:36 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20Swagger=EB=A5=BC=20=ED=86=B5?= =?UTF-8?q?=ED=95=9C=20=EB=AC=B8=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 3 +++ .../plantory/common/config/SwaggerConfig.kt | 20 +++++++++++++++++++ .../common/support/AccessDeviceToken.kt | 4 ++++ .../common/support/DeviceHeaderExtractor.kt | 2 +- 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/gdsc/plantory/common/config/SwaggerConfig.kt diff --git a/build.gradle.kts b/build.gradle.kts index 75ca3ad..5ce7d53 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -60,6 +60,9 @@ dependencies { // firebase implementation("com.google.firebase:firebase-admin:9.2.0") + + // swagger + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0") } tasks.withType { diff --git a/src/main/kotlin/gdsc/plantory/common/config/SwaggerConfig.kt b/src/main/kotlin/gdsc/plantory/common/config/SwaggerConfig.kt new file mode 100644 index 0000000..1cb9c29 --- /dev/null +++ b/src/main/kotlin/gdsc/plantory/common/config/SwaggerConfig.kt @@ -0,0 +1,20 @@ +package gdsc.plantory.common.config + +import io.swagger.v3.oas.models.parameters.Parameter +import org.springdoc.core.customizers.ParameterCustomizer +import org.springframework.core.MethodParameter +import org.springframework.stereotype.Component +import org.springframework.web.bind.annotation.RequestBody + +@Component +class SwaggerConfig : ParameterCustomizer { + + override fun customize(parameterModel: Parameter?, methodParameter: MethodParameter?): Parameter? { + if (methodParameter?.getParameterAnnotation(RequestBody::class.java) != null) { + parameterModel + ?.`in`("request body") + } + + return parameterModel; + } +} diff --git a/src/main/kotlin/gdsc/plantory/common/support/AccessDeviceToken.kt b/src/main/kotlin/gdsc/plantory/common/support/AccessDeviceToken.kt index 723b604..3426ca2 100644 --- a/src/main/kotlin/gdsc/plantory/common/support/AccessDeviceToken.kt +++ b/src/main/kotlin/gdsc/plantory/common/support/AccessDeviceToken.kt @@ -1,5 +1,9 @@ package gdsc.plantory.common.support +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.enums.ParameterIn + @Target(AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.RUNTIME) +@Parameter(`in` = ParameterIn.HEADER, name = DEVICE_ID_HEADER) annotation class AccessDeviceToken diff --git a/src/main/kotlin/gdsc/plantory/common/support/DeviceHeaderExtractor.kt b/src/main/kotlin/gdsc/plantory/common/support/DeviceHeaderExtractor.kt index d4eb24d..9d05913 100644 --- a/src/main/kotlin/gdsc/plantory/common/support/DeviceHeaderExtractor.kt +++ b/src/main/kotlin/gdsc/plantory/common/support/DeviceHeaderExtractor.kt @@ -2,7 +2,7 @@ package gdsc.plantory.common.support import org.springframework.web.context.request.NativeWebRequest -private const val DEVICE_ID_HEADER = "Device-Token" +const val DEVICE_ID_HEADER = "Device-Token" class DeviceHeaderExtractor { From 9d27dfb392578768bee548538a506a5dec60d80b Mon Sep 17 00:00:00 2001 From: WhitePiano Date: Thu, 25 Jan 2024 12:43:48 +0900 Subject: [PATCH 2/8] =?UTF-8?q?refactor:=20openAPI=20v3=20spec=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EA=B2=8C=20GET=EC=9D=B4=20req=20body=EB=A5=BC=20?= =?UTF-8?q?=EA=B0=80=EC=A7=80=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plantory/common/config/SwaggerConfig.kt | 20 ------------------- .../plant/presentation/PlantQueryApi.kt | 18 +++++++++++------ .../plantory/acceptance/CompanionPlantStep.kt | 10 ++++------ 3 files changed, 16 insertions(+), 32 deletions(-) delete mode 100644 src/main/kotlin/gdsc/plantory/common/config/SwaggerConfig.kt diff --git a/src/main/kotlin/gdsc/plantory/common/config/SwaggerConfig.kt b/src/main/kotlin/gdsc/plantory/common/config/SwaggerConfig.kt deleted file mode 100644 index 1cb9c29..0000000 --- a/src/main/kotlin/gdsc/plantory/common/config/SwaggerConfig.kt +++ /dev/null @@ -1,20 +0,0 @@ -package gdsc.plantory.common.config - -import io.swagger.v3.oas.models.parameters.Parameter -import org.springdoc.core.customizers.ParameterCustomizer -import org.springframework.core.MethodParameter -import org.springframework.stereotype.Component -import org.springframework.web.bind.annotation.RequestBody - -@Component -class SwaggerConfig : ParameterCustomizer { - - override fun customize(parameterModel: Parameter?, methodParameter: MethodParameter?): Parameter? { - if (methodParameter?.getParameterAnnotation(RequestBody::class.java) != null) { - parameterModel - ?.`in`("request body") - } - - return parameterModel; - } -} diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt index d6abe38..4f27765 100644 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt +++ b/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt @@ -7,12 +7,14 @@ import gdsc.plantory.plant.presentation.dto.PlantHistoriesLookupResponse import gdsc.plantory.plant.presentation.dto.PlantRecordLookupRequest import gdsc.plantory.plant.presentation.dto.PlantRecordLookupResponse import gdsc.plantory.plant.service.PlantService -import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.PathVariable 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 +import java.time.YearMonth @RestController @RequestMapping("/api/v1/plants") @@ -28,20 +30,24 @@ class PlantQueryApi( return ResponseEntity.ok().body(companionPlants) } - @GetMapping("/histories", consumes = [MediaType.APPLICATION_JSON_VALUE]) + @GetMapping("/{companionPlantId}/histories") fun lookupAllPlantHistoriesOfMonth( - @RequestBody request: PlantHistoriesLookupRequest, + @PathVariable companionPlantId: Long, + @RequestParam targetMonth: YearMonth, @AccessDeviceToken deviceToken: String ): ResponseEntity { + val request = PlantHistoriesLookupRequest(companionPlantId, targetMonth) val histories = plantService.lookupAllPlantHistoriesOfMonth(request, deviceToken) return ResponseEntity.ok().body(histories) } - @GetMapping("/records", consumes = [MediaType.APPLICATION_JSON_VALUE]) + @GetMapping("/{companionPlantId}/records") fun lookupPlantRecordOfDate( - @RequestBody request: PlantRecordLookupRequest, + @PathVariable companionPlantId: Long, + @RequestParam recordDate: LocalDate, @AccessDeviceToken deviceToken: String ): ResponseEntity { + val request = PlantRecordLookupRequest(companionPlantId, recordDate) val plantRecord = plantService.lookupPlantRecordOfDate(request, deviceToken) return ResponseEntity.ok().body(plantRecord) } diff --git a/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantStep.kt b/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantStep.kt index 28eddc7..1b55e88 100644 --- a/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantStep.kt +++ b/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantStep.kt @@ -126,12 +126,11 @@ class CompanionPlantStep { ): ExtractableResponse { return RestAssured .given() - .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Device-Token", deviceToken) .log().all() - .body(request) + .queryParam("recordDate", request.recordDate.toString()) .`when`() - .get("/api/v1/plants/records") + .get("/api/v1/plants/{companionPlantId}/records", request.companionPlantId) .then() .log().all() .extract() @@ -152,12 +151,11 @@ class CompanionPlantStep { ): ExtractableResponse { return RestAssured .given() - .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Device-Token", deviceToken) .log().all() - .body(request) + .queryParam("targetMonth", request.targetMonth.toString()) .`when`() - .get("/api/v1/plants/histories") + .get("/api/v1/plants/{companionPlantId}/histories", request.companionPlantId) .then() .log().all() .extract() From 0a77822f0ce6357a30e41e2de06cb44cfd5c757b Mon Sep 17 00:00:00 2001 From: WhitePiano Date: Thu, 25 Jan 2024 13:47:44 +0900 Subject: [PATCH 3/8] =?UTF-8?q?refactor:=20API=20description=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/support/AccessDeviceToken.kt | 2 +- .../member/presentation/MemberCommandApi.kt | 7 +++++- .../plant/presentation/PlantCommandApi.kt | 23 ++++++++++++++----- .../plant/presentation/PlantQueryApi.kt | 17 ++++++++++---- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/gdsc/plantory/common/support/AccessDeviceToken.kt b/src/main/kotlin/gdsc/plantory/common/support/AccessDeviceToken.kt index 3426ca2..c7c0af4 100644 --- a/src/main/kotlin/gdsc/plantory/common/support/AccessDeviceToken.kt +++ b/src/main/kotlin/gdsc/plantory/common/support/AccessDeviceToken.kt @@ -5,5 +5,5 @@ import io.swagger.v3.oas.annotations.enums.ParameterIn @Target(AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.RUNTIME) -@Parameter(`in` = ParameterIn.HEADER, name = DEVICE_ID_HEADER) +@Parameter(`in` = ParameterIn.HEADER, name = DEVICE_ID_HEADER, description = "사용자 디바이스/인증 토큰") annotation class AccessDeviceToken diff --git a/src/main/kotlin/gdsc/plantory/member/presentation/MemberCommandApi.kt b/src/main/kotlin/gdsc/plantory/member/presentation/MemberCommandApi.kt index 93d831f..4d55981 100644 --- a/src/main/kotlin/gdsc/plantory/member/presentation/MemberCommandApi.kt +++ b/src/main/kotlin/gdsc/plantory/member/presentation/MemberCommandApi.kt @@ -2,6 +2,9 @@ package gdsc.plantory.member.presentation import gdsc.plantory.member.presentation.dto.MemberCreateRequest import gdsc.plantory.member.service.MemberService +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.responses.ApiResponse import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -14,9 +17,11 @@ class MemberCommandApi( private val memberService: MemberService, ) { + @Operation(summary = "사용자 등록", description = "사용자를 등록/추가합니다.") + @ApiResponse(responseCode = "200", description = "등록 성공") @PostMapping fun signUp( - @RequestBody request: MemberCreateRequest, + @Parameter(description = "사용자 정보") @RequestBody request: MemberCreateRequest, ): ResponseEntity { memberService.signUp(request.deviceToken) return ResponseEntity.ok().build() diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/PlantCommandApi.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/PlantCommandApi.kt index faee114..a2809d0 100644 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/PlantCommandApi.kt +++ b/src/main/kotlin/gdsc/plantory/plant/presentation/PlantCommandApi.kt @@ -8,6 +8,9 @@ import gdsc.plantory.plant.presentation.dto.CompanionPlantDeleteRequest import gdsc.plantory.plant.presentation.dto.PlantRecordCreateRequest import gdsc.plantory.plant.presentation.dto.PlantHistoryRequest import gdsc.plantory.plant.service.PlantService +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.responses.ApiResponse import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.DeleteMapping @@ -24,28 +27,34 @@ class PlantCommandApi( private val plantService: PlantService, ) { + @Operation(summary = "반려식물 등록", description = "사용자의 반려식물을 등록/추가합니다.") + @ApiResponse(responseCode = "200", description = "등록 성공") @PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE]) fun create( - @RequestPart(name = "request") request: CompanionPlantCreateRequest, - @RequestPart(name = "image", required = false) image: MultipartFile?, + @Parameter(description = "반려식물 정보") @RequestPart(name = "request") request: CompanionPlantCreateRequest, + @Parameter(description = "반려식물 사진") @RequestPart(name = "image", required = false) image: MultipartFile?, @AccessDeviceToken deviceToken: String, ): ResponseEntity { plantService.create(request, image, deviceToken) return ResponseEntity.ok().build() } + @Operation(summary = "반려식물 삭제", description = "사용자의 반려식물을 삭제합니다.") + @ApiResponse(responseCode = "204", description = "삭제 성공") @DeleteMapping(consumes = [MediaType.APPLICATION_JSON_VALUE]) fun remove( - @RequestBody request: CompanionPlantDeleteRequest, + @Parameter(description = "삭제할 반려식물 ID") @RequestBody request: CompanionPlantDeleteRequest, @AccessDeviceToken deviceToken: String, ): ResponseEntity { plantService.remove(request, deviceToken) return ResponseEntity.noContent().build() } + @Operation(summary = "반려식물 히스토리 등록", description = "반려식물 히스토리(데일리 기록, 물줌, 분갈이)를 등록/추가합니다.") + @ApiResponse(responseCode = "200", description = "등록 성공") @PostMapping("/histories", consumes = [MediaType.APPLICATION_JSON_VALUE]) fun createPlantHistory( - @RequestBody request: PlantHistoryRequest, + @Parameter(description = "히스토리 정보") @RequestBody request: PlantHistoryRequest, @AccessDeviceToken deviceToken: String, ): ResponseEntity { val historyType = HistoryType.byNameIgnoreCaseOrNull(request.historyType) @@ -55,10 +64,12 @@ class PlantCommandApi( return ResponseEntity.ok().build() } + @Operation(summary = "반려식물 데일리 기록 등록", description = "반려식물 데일리 기록을 등록/추가합니다.") + @ApiResponse(responseCode = "200", description = "등록 성공") @PostMapping("/records", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE]) fun createPlantRecord( - @RequestPart(name = "request") request: PlantRecordCreateRequest, - @RequestPart(name = "image", required = false) image: MultipartFile?, + @Parameter(description = "데일리 기록 정보") @RequestPart(name = "request") request: PlantRecordCreateRequest, + @Parameter(description = "데일리 기록 사진") @RequestPart(name = "image", required = false) image: MultipartFile?, @AccessDeviceToken deviceToken: String, ): ResponseEntity { plantService.createRecord(request, image, deviceToken) diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt index 4f27765..7e191e5 100644 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt +++ b/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt @@ -7,6 +7,9 @@ import gdsc.plantory.plant.presentation.dto.PlantHistoriesLookupResponse import gdsc.plantory.plant.presentation.dto.PlantRecordLookupRequest import gdsc.plantory.plant.presentation.dto.PlantRecordLookupResponse import gdsc.plantory.plant.service.PlantService +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.responses.ApiResponse import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -22,6 +25,8 @@ class PlantQueryApi( private val plantService: PlantService ) { + @Operation(summary = "반려식물 조회", description = "사용자의 반려식물 목록을 조회합니다.") + @ApiResponse(responseCode = "200", description = "조회 성공") @GetMapping fun lookupAllCompanionPlantsOfMember( @AccessDeviceToken deviceToken: String @@ -30,10 +35,12 @@ class PlantQueryApi( return ResponseEntity.ok().body(companionPlants) } + @Operation(summary = "반려식물 히스토리(데일리 기록, 물줌, 분갈이) 조회", description = "해당 달의 반려식물 히스토리를 조회합니다.") + @ApiResponse(responseCode = "200", description = "조회 성공") @GetMapping("/{companionPlantId}/histories") fun lookupAllPlantHistoriesOfMonth( - @PathVariable companionPlantId: Long, - @RequestParam targetMonth: YearMonth, + @Parameter(description = "조회할 반려식물 ID") @PathVariable companionPlantId: Long, + @Parameter(description = "조회 기간(달)") @RequestParam targetMonth: YearMonth, @AccessDeviceToken deviceToken: String ): ResponseEntity { val request = PlantHistoriesLookupRequest(companionPlantId, targetMonth) @@ -41,10 +48,12 @@ class PlantQueryApi( return ResponseEntity.ok().body(histories) } + @Operation(summary = "반려식물 데일리 기록 조회", description = "데일리 기록(이미지, 일지 등)을 조회합니다.") + @ApiResponse(responseCode = "200", description = "조회 성공") @GetMapping("/{companionPlantId}/records") fun lookupPlantRecordOfDate( - @PathVariable companionPlantId: Long, - @RequestParam recordDate: LocalDate, + @Parameter(description = "데일리 기록의 반려식물") @PathVariable companionPlantId: Long, + @Parameter(description = "데일리 기록의 날짜") @RequestParam recordDate: LocalDate, @AccessDeviceToken deviceToken: String ): ResponseEntity { val request = PlantRecordLookupRequest(companionPlantId, recordDate) From e61108d8daf2364b55b7d972e57309d64b5f553e Mon Sep 17 00:00:00 2001 From: WhitePiano Date: Thu, 25 Jan 2024 14:10:24 +0900 Subject: [PATCH 4/8] =?UTF-8?q?refactor:=20query=EC=99=80=20command=20URL?= =?UTF-8?q?=20=ED=98=95=EC=8B=9D=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plant/presentation/PlantCommandApi.kt | 27 ++++++++------- .../plant/presentation/PlantQueryApi.kt | 8 ++--- .../dto/CompanionPlantDeleteRequest.kt | 5 --- .../presentation/dto/CompanionPlantDto.kt | 24 -------------- .../dto/PlantHistoriesLookupRequest.kt | 8 ----- .../presentation/dto/PlantHistoryRequest.kt | 1 - .../dto/PlantRecordCreateRequest.kt | 1 - .../dto/PlantRecordLookupResponse.kt | 2 -- .../plantory/plant/service/PlantService.kt | 33 +++++++++++-------- .../CompanionPlantAcceptanceTest.kt | 32 ++++++------------ .../plantory/acceptance/CompanionPlantStep.kt | 31 ++++++++--------- .../plantory/fixture/CompanionPlantFixture.kt | 8 ----- .../plant/service/PlantServiceTest.kt | 4 +-- 13 files changed, 65 insertions(+), 119 deletions(-) delete mode 100644 src/main/kotlin/gdsc/plantory/plant/presentation/dto/CompanionPlantDeleteRequest.kt delete mode 100644 src/main/kotlin/gdsc/plantory/plant/presentation/dto/CompanionPlantDto.kt delete mode 100644 src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantHistoriesLookupRequest.kt diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/PlantCommandApi.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/PlantCommandApi.kt index a2809d0..512fc00 100644 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/PlantCommandApi.kt +++ b/src/main/kotlin/gdsc/plantory/plant/presentation/PlantCommandApi.kt @@ -4,9 +4,8 @@ import BadRequestException import gdsc.plantory.common.support.AccessDeviceToken import gdsc.plantory.plant.domain.HistoryType import gdsc.plantory.plant.presentation.dto.CompanionPlantCreateRequest -import gdsc.plantory.plant.presentation.dto.CompanionPlantDeleteRequest -import gdsc.plantory.plant.presentation.dto.PlantRecordCreateRequest import gdsc.plantory.plant.presentation.dto.PlantHistoryRequest +import gdsc.plantory.plant.presentation.dto.PlantRecordCreateRequest import gdsc.plantory.plant.service.PlantService import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter @@ -14,6 +13,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.DeleteMapping +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 @@ -41,38 +41,43 @@ class PlantCommandApi( @Operation(summary = "반려식물 삭제", description = "사용자의 반려식물을 삭제합니다.") @ApiResponse(responseCode = "204", description = "삭제 성공") - @DeleteMapping(consumes = [MediaType.APPLICATION_JSON_VALUE]) + @DeleteMapping("/{companionPlantId}") fun remove( - @Parameter(description = "삭제할 반려식물 ID") @RequestBody request: CompanionPlantDeleteRequest, + @Parameter(description = "삭제할 반려식물 ID") @PathVariable companionPlantId: Long, @AccessDeviceToken deviceToken: String, ): ResponseEntity { - plantService.remove(request, deviceToken) + plantService.remove(companionPlantId, deviceToken) return ResponseEntity.noContent().build() } @Operation(summary = "반려식물 히스토리 등록", description = "반려식물 히스토리(데일리 기록, 물줌, 분갈이)를 등록/추가합니다.") @ApiResponse(responseCode = "200", description = "등록 성공") - @PostMapping("/histories", consumes = [MediaType.APPLICATION_JSON_VALUE]) + @PostMapping("/{companionPlantId}/histories", consumes = [MediaType.APPLICATION_JSON_VALUE]) fun createPlantHistory( - @Parameter(description = "히스토리 정보") @RequestBody request: PlantHistoryRequest, + @Parameter(description = "히스토리를 등록할 반려식물 ID") @PathVariable companionPlantId: Long, + @Parameter(description = "히스토리 유형/종류") @RequestBody request: PlantHistoryRequest, @AccessDeviceToken deviceToken: String, ): ResponseEntity { val historyType = HistoryType.byNameIgnoreCaseOrNull(request.historyType) ?: throw BadRequestException("잘못된 히스토리 타입입니다.") - plantService.createPlantHistory(request.companionPlantId, deviceToken, historyType) + plantService.createPlantHistory(companionPlantId, deviceToken, historyType) return ResponseEntity.ok().build() } @Operation(summary = "반려식물 데일리 기록 등록", description = "반려식물 데일리 기록을 등록/추가합니다.") @ApiResponse(responseCode = "200", description = "등록 성공") - @PostMapping("/records", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE]) + @PostMapping( + "/{companionPlantId}/records", + consumes = [MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE] + ) fun createPlantRecord( - @Parameter(description = "데일리 기록 정보") @RequestPart(name = "request") request: PlantRecordCreateRequest, + @Parameter(description = "데일리 기록을 등록할 반려식물 ID") @PathVariable companionPlantId: Long, + @Parameter(description = "데일리 기록 내용") @RequestPart request: PlantRecordCreateRequest, @Parameter(description = "데일리 기록 사진") @RequestPart(name = "image", required = false) image: MultipartFile?, @AccessDeviceToken deviceToken: String, ): ResponseEntity { - plantService.createRecord(request, image, deviceToken) + plantService.createRecord(companionPlantId, request, image, deviceToken) return ResponseEntity.ok().build() } } diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt index 7e191e5..183260e 100644 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt +++ b/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt @@ -2,9 +2,7 @@ package gdsc.plantory.plant.presentation import gdsc.plantory.common.support.AccessDeviceToken import gdsc.plantory.plant.presentation.dto.CompanionPlantsLookupResponse -import gdsc.plantory.plant.presentation.dto.PlantHistoriesLookupRequest import gdsc.plantory.plant.presentation.dto.PlantHistoriesLookupResponse -import gdsc.plantory.plant.presentation.dto.PlantRecordLookupRequest import gdsc.plantory.plant.presentation.dto.PlantRecordLookupResponse import gdsc.plantory.plant.service.PlantService import io.swagger.v3.oas.annotations.Operation @@ -43,8 +41,7 @@ class PlantQueryApi( @Parameter(description = "조회 기간(달)") @RequestParam targetMonth: YearMonth, @AccessDeviceToken deviceToken: String ): ResponseEntity { - val request = PlantHistoriesLookupRequest(companionPlantId, targetMonth) - val histories = plantService.lookupAllPlantHistoriesOfMonth(request, deviceToken) + val histories = plantService.lookupAllPlantHistoriesOfMonth(companionPlantId, targetMonth, deviceToken) return ResponseEntity.ok().body(histories) } @@ -56,8 +53,7 @@ class PlantQueryApi( @Parameter(description = "데일리 기록의 날짜") @RequestParam recordDate: LocalDate, @AccessDeviceToken deviceToken: String ): ResponseEntity { - val request = PlantRecordLookupRequest(companionPlantId, recordDate) - val plantRecord = plantService.lookupPlantRecordOfDate(request, deviceToken) + val plantRecord = plantService.lookupPlantRecordOfDate(companionPlantId, recordDate, deviceToken) return ResponseEntity.ok().body(plantRecord) } } diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/CompanionPlantDeleteRequest.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/dto/CompanionPlantDeleteRequest.kt deleted file mode 100644 index 4f37761..0000000 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/CompanionPlantDeleteRequest.kt +++ /dev/null @@ -1,5 +0,0 @@ -package gdsc.plantory.plant.presentation.dto - -data class CompanionPlantDeleteRequest( - val companionPlantId: Long -) diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/CompanionPlantDto.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/dto/CompanionPlantDto.kt deleted file mode 100644 index 7c86960..0000000 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/CompanionPlantDto.kt +++ /dev/null @@ -1,24 +0,0 @@ -package gdsc.plantory.plant.presentation.dto - -import gdsc.plantory.plant.domain.CompanionPlant -import java.time.LocalDate - -data class CompanionPlantDto( - val id: Long, - val imageUrl: String, - val nickname: String, - val shortDescription: String, - val birthDate: LocalDate, - val name: String, -) { - companion object { - fun from(plant: CompanionPlant): CompanionPlantDto = CompanionPlantDto( - id = plant.getId, - imageUrl = plant.getImageUrl, - nickname = plant.getNickName, - shortDescription = plant.getSortDescription, - birthDate = plant.getBirthDate, - name = plant.getName, - ) - } -} diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantHistoriesLookupRequest.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantHistoriesLookupRequest.kt deleted file mode 100644 index fa67bc4..0000000 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantHistoriesLookupRequest.kt +++ /dev/null @@ -1,8 +0,0 @@ -package gdsc.plantory.plant.presentation.dto - -import java.time.YearMonth - -data class PlantHistoriesLookupRequest( - val companionPlantId: Long, - val targetMonth: YearMonth -) diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantHistoryRequest.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantHistoryRequest.kt index 5eed80b..df64014 100644 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantHistoryRequest.kt +++ b/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantHistoryRequest.kt @@ -3,6 +3,5 @@ package gdsc.plantory.plant.presentation.dto import jakarta.validation.constraints.NotBlank data class PlantHistoryRequest( - @NotBlank val companionPlantId: Long, @NotBlank val historyType: String, ) diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordCreateRequest.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordCreateRequest.kt index 5a181f2..c88f420 100644 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordCreateRequest.kt +++ b/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordCreateRequest.kt @@ -1,6 +1,5 @@ package gdsc.plantory.plant.presentation.dto data class PlantRecordCreateRequest( - val companionPlantId: Long, val comment: String, ) \ No newline at end of file diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordLookupResponse.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordLookupResponse.kt index da0830a..5f0645f 100644 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordLookupResponse.kt +++ b/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordLookupResponse.kt @@ -1,7 +1,6 @@ package gdsc.plantory.plant.presentation.dto class PlantRecordLookupResponse( - val plantRecordId: Long, val imageUrl: String, val comment: String, val nickname: String, @@ -10,7 +9,6 @@ class PlantRecordLookupResponse( companion object { fun of(plantRecord: PlantRecordDto, hasWater: Boolean): PlantRecordLookupResponse { return PlantRecordLookupResponse( - plantRecord.plantRecordId, plantRecord.imageUrl, plantRecord.comment, plantRecord.nickname, diff --git a/src/main/kotlin/gdsc/plantory/plant/service/PlantService.kt b/src/main/kotlin/gdsc/plantory/plant/service/PlantService.kt index f6211dc..63361eb 100644 --- a/src/main/kotlin/gdsc/plantory/plant/service/PlantService.kt +++ b/src/main/kotlin/gdsc/plantory/plant/service/PlantService.kt @@ -7,18 +7,17 @@ import gdsc.plantory.plant.domain.CompanionPlantRepository import gdsc.plantory.plant.domain.HistoryType import gdsc.plantory.plant.domain.findByIdAndMemberIdOrThrow import gdsc.plantory.plant.presentation.dto.CompanionPlantCreateRequest -import gdsc.plantory.plant.presentation.dto.CompanionPlantDeleteRequest import gdsc.plantory.plant.presentation.dto.CompanionPlantsLookupResponse -import gdsc.plantory.plant.presentation.dto.PlantHistoriesLookupRequest import gdsc.plantory.plant.presentation.dto.PlantHistoriesLookupResponse import gdsc.plantory.plant.presentation.dto.PlantRecordCreateRequest -import gdsc.plantory.plant.presentation.dto.PlantRecordLookupRequest import gdsc.plantory.plant.presentation.dto.PlantRecordLookupResponse import gdsc.plantory.plantInformation.domain.PlantInformationRepository import gdsc.plantory.plantInformation.domain.findByIdOrThrow import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile +import java.time.LocalDate +import java.time.YearMonth @Service @Transactional @@ -39,9 +38,9 @@ class PlantService( companionPlantRepository.save(companionPlant) } - fun remove(request: CompanionPlantDeleteRequest, deviceToken: String) { + fun remove(companionPlantId: Long, deviceToken: String) { val findMember = memberRepository.findByDeviceTokenOrThrow(deviceToken) - companionPlantRepository.removeByIdAndMemberId(request.companionPlantId, findMember.getId) + companionPlantRepository.removeByIdAndMemberId(companionPlantId, findMember.getId) } @Transactional(readOnly = true) @@ -61,28 +60,30 @@ class PlantService( @Transactional(readOnly = true) fun lookupAllPlantHistoriesOfMonth( - request: PlantHistoriesLookupRequest, + companionPlantId: Long, + targetMonth: YearMonth, deviceToken: String ): PlantHistoriesLookupResponse { val findMember = memberRepository.findByDeviceTokenOrThrow(deviceToken) val findPlantHistories = companionPlantRepository.findAllHistoriesByMonth( - request.companionPlantId, + companionPlantId, findMember.getId, - request.targetMonth.year, - request.targetMonth.monthValue + targetMonth.year, + targetMonth.monthValue ) return PlantHistoriesLookupResponse.from(findPlantHistories) } fun createRecord( + companionPlantId: Long, request: PlantRecordCreateRequest, image: MultipartFile?, deviceToken: String, ) { val findMember = memberRepository.findByDeviceTokenOrThrow(deviceToken) val findCompanionPlant = - companionPlantRepository.findByIdAndMemberIdOrThrow(request.companionPlantId, findMember.getId) + companionPlantRepository.findByIdAndMemberIdOrThrow(companionPlantId, findMember.getId) val imagePath: String = saveImageAndGetPath(image, findCompanionPlant.getImageUrl) findCompanionPlant.saveRecord(request.comment, imagePath) @@ -90,16 +91,20 @@ class PlantService( } @Transactional(readOnly = true) - fun lookupPlantRecordOfDate(request: PlantRecordLookupRequest, deviceToken: String): PlantRecordLookupResponse { + fun lookupPlantRecordOfDate( + companionPlantId: Long, + recordDate: LocalDate, + deviceToken: String + ): PlantRecordLookupResponse { val findMember = memberRepository.findByDeviceTokenOrThrow(deviceToken) val findPlantRecord = companionPlantRepository.findRecordByDate( - request.companionPlantId, + companionPlantId, findMember.getId, - request.recordDate + recordDate ) val historyType = - companionPlantRepository.findAllHistoryTypeByDate(request.companionPlantId, request.recordDate) + companionPlantRepository.findAllHistoryTypeByDate(companionPlantId, recordDate) return PlantRecordLookupResponse.of(findPlantRecord, historyType.contains(HistoryType.WATER_CHANGE)) } diff --git a/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantAcceptanceTest.kt b/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantAcceptanceTest.kt index e47071a..82b080a 100644 --- a/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantAcceptanceTest.kt +++ b/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantAcceptanceTest.kt @@ -16,11 +16,8 @@ import gdsc.plantory.fixture.기록있는_테스트식물_ID import gdsc.plantory.fixture.테스터_디바이스_토큰 import gdsc.plantory.fixture.테스트_식물정보_ID import gdsc.plantory.fixture.CompanionPlantFixture.generateCompanionPlantCreateRequest -import gdsc.plantory.fixture.CompanionPlantFixture.generatePlantRecordCreateRequest -import gdsc.plantory.plant.presentation.dto.CompanionPlantDeleteRequest import gdsc.plantory.plant.presentation.dto.PlantHistoryRequest -import gdsc.plantory.plant.presentation.dto.PlantHistoriesLookupRequest -import gdsc.plantory.plant.presentation.dto.PlantRecordLookupRequest +import gdsc.plantory.plant.presentation.dto.PlantRecordCreateRequest import gdsc.plantory.util.AcceptanceTest import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test @@ -45,11 +42,8 @@ class CompanionPlantAcceptanceTest : AcceptanceTest() { @Test fun `반려식물 삭제`() { - // given - val 반려_식물_정보 = CompanionPlantDeleteRequest(기록있는_테스트식물_ID) - // when - val 식물_삭제_요청_응답 = 반려_식물_삭제_요청(반려_식물_정보, 테스터_디바이스_토큰) + val 식물_삭제_요청_응답 = 반려_식물_삭제_요청(기록있는_테스트식물_ID, 테스터_디바이스_토큰) // then 응답_확인(식물_삭제_요청_응답, HttpStatus.NO_CONTENT) @@ -58,10 +52,10 @@ class CompanionPlantAcceptanceTest : AcceptanceTest() { @Test fun `반려식물 물주기 히스토리 등록`() { // given - val 물줌_기록 = PlantHistoryRequest(기록없는_테스트식물_ID, "WATER_CHANGE") + val 물줌_기록 = PlantHistoryRequest("WATER_CHANGE") // when - val 식물_히스토리_생성_응답 = 식물_히스토리_생성_요청(물줌_기록, 테스터_디바이스_토큰) + val 식물_히스토리_생성_응답 = 식물_히스토리_생성_요청(기록없는_테스트식물_ID, 물줌_기록, 테스터_디바이스_토큰) // then 응답_확인(식물_히스토리_생성_응답, HttpStatus.OK) @@ -79,10 +73,10 @@ class CompanionPlantAcceptanceTest : AcceptanceTest() { @Test fun `반려식물 데일리 기록 등록`() { // given - val 데일리_기록_정보 = generatePlantRecordCreateRequest(기록없는_테스트식물_ID) + val 데일리_기록_정보 = PlantRecordCreateRequest("오늘도 즐거운 하루~!") // when - val 데일리_기록_등록_요청_응답 = 데일리_기록_등록_요청(데일리_기록_정보, 테스터_디바이스_토큰) + val 데일리_기록_등록_요청_응답 = 데일리_기록_등록_요청(기록없는_테스트식물_ID, 데일리_기록_정보, 테스터_디바이스_토큰) // then 응답_확인(데일리_기록_등록_요청_응답, HttpStatus.OK) @@ -97,13 +91,13 @@ class CompanionPlantAcceptanceTest : AcceptanceTest() { fun `반려식물 데일리 기록 중복 등록`() { // given 데일리_기록_등록_요청( - generatePlantRecordCreateRequest(기록없는_테스트식물_ID), 테스터_디바이스_토큰 + 기록없는_테스트식물_ID, PlantRecordCreateRequest("오늘도 즐거운 하루~!"), 테스터_디바이스_토큰 ) // when val 데일리_기록_등록_요청_응답 = 데일리_기록_등록_요청( - generatePlantRecordCreateRequest(기록없는_테스트식물_ID), 테스터_디바이스_토큰 + 기록없는_테스트식물_ID, PlantRecordCreateRequest("오늘도 즐거운 하루~!"), 테스터_디바이스_토큰 ) // then @@ -112,11 +106,8 @@ class CompanionPlantAcceptanceTest : AcceptanceTest() { @Test fun `반려식물 데일리 기록 조회`() { - // given - val 데일리_기록_조회_정보 = PlantRecordLookupRequest(기록있는_테스트식물_ID, LocalDate.now()) - // when - val 데일리_기록_조회_요청_응답 = 데일리_기록_조회_요청(데일리_기록_조회_정보, 테스터_디바이스_토큰) + val 데일리_기록_조회_요청_응답 = 데일리_기록_조회_요청(기록있는_테스트식물_ID, LocalDate.now(), 테스터_디바이스_토큰) // then 데일리_기록_조회_응답_확인(데일리_기록_조회_요청_응답) @@ -124,11 +115,8 @@ class CompanionPlantAcceptanceTest : AcceptanceTest() { @Test fun `반려식물 히스토리 조회`() { - // given - val 히스토리_조회_정보 = PlantHistoriesLookupRequest(기록있는_테스트식물_ID, YearMonth.parse("2024-01")) - // when - val 히스토리_조회_요청_응답 = 히스토리_조회_요청(히스토리_조회_정보, 테스터_디바이스_토큰) + val 히스토리_조회_요청_응답 = 히스토리_조회_요청(기록있는_테스트식물_ID, YearMonth.parse("2024-01"), 테스터_디바이스_토큰) // then 히스토리_조회_응답_확인(히스토리_조회_요청_응답) diff --git a/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantStep.kt b/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantStep.kt index 1b55e88..d702b4b 100644 --- a/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantStep.kt +++ b/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantStep.kt @@ -1,11 +1,8 @@ package gdsc.plantory.acceptance import gdsc.plantory.plant.presentation.dto.CompanionPlantCreateRequest -import gdsc.plantory.plant.presentation.dto.CompanionPlantDeleteRequest -import gdsc.plantory.plant.presentation.dto.PlantHistoriesLookupRequest import gdsc.plantory.plant.presentation.dto.PlantHistoryRequest import gdsc.plantory.plant.presentation.dto.PlantRecordCreateRequest -import gdsc.plantory.plant.presentation.dto.PlantRecordLookupRequest import io.restassured.RestAssured import io.restassured.builder.MultiPartSpecBuilder import io.restassured.mapper.ObjectMapperType @@ -16,6 +13,8 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.assertAll import org.springframework.http.HttpStatus import org.springframework.http.MediaType +import java.time.LocalDate +import java.time.YearMonth class CompanionPlantStep { @@ -41,17 +40,15 @@ class CompanionPlantStep { } fun 반려_식물_삭제_요청( - request: CompanionPlantDeleteRequest, + companionPlantId: Long, deviceToken: String, ): ExtractableResponse { return RestAssured .given() - .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Device-Token", deviceToken) .log().all() - .body(request) .`when`() - .delete("/api/v1/plants") + .delete("/api/v1/plants/{companionPlantId}", companionPlantId) .then() .log().all() .statusCode(HttpStatus.NO_CONTENT.value()) @@ -59,6 +56,7 @@ class CompanionPlantStep { } fun 식물_히스토리_생성_요청( + companionPlantId: Long, request: PlantHistoryRequest, deviceToken: String, ): ExtractableResponse = @@ -68,7 +66,7 @@ class CompanionPlantStep { .header("Device-Token", deviceToken) .log().all() .body(request) - .`when`().post("/api/v1/plants/histories") + .`when`().post("/api/v1/plants/{companionPlantId}/histories", companionPlantId) .then() .log().all() .extract() @@ -102,6 +100,7 @@ class CompanionPlantStep { } fun 데일리_기록_등록_요청( + companionPlantId: Long, request: PlantRecordCreateRequest, deviceToken: String, ): ExtractableResponse { @@ -114,23 +113,24 @@ class CompanionPlantStep { .header("Device-Token", deviceToken) .log().all() .`when`() - .post("/api/v1/plants/records") + .post("/api/v1/plants/{companionPlantId}/records", companionPlantId) .then() .log().all() .extract() } fun 데일리_기록_조회_요청( - request: PlantRecordLookupRequest, + companionPlantId: Long, + recordDate: LocalDate, deviceToken: String ): ExtractableResponse { return RestAssured .given() .header("Device-Token", deviceToken) .log().all() - .queryParam("recordDate", request.recordDate.toString()) + .queryParam("recordDate", recordDate.toString()) .`when`() - .get("/api/v1/plants/{companionPlantId}/records", request.companionPlantId) + .get("/api/v1/plants/{companionPlantId}/records", companionPlantId) .then() .log().all() .extract() @@ -146,16 +146,17 @@ class CompanionPlantStep { } fun 히스토리_조회_요청( - request: PlantHistoriesLookupRequest, + companionPlantId: Long, + targetMonth: YearMonth, deviceToken: String ): ExtractableResponse { return RestAssured .given() .header("Device-Token", deviceToken) .log().all() - .queryParam("targetMonth", request.targetMonth.toString()) + .queryParam("targetMonth", targetMonth.toString()) .`when`() - .get("/api/v1/plants/{companionPlantId}/histories", request.companionPlantId) + .get("/api/v1/plants/{companionPlantId}/histories", companionPlantId) .then() .log().all() .extract() diff --git a/src/test/kotlin/gdsc/plantory/fixture/CompanionPlantFixture.kt b/src/test/kotlin/gdsc/plantory/fixture/CompanionPlantFixture.kt index 105d65c..fbfc034 100644 --- a/src/test/kotlin/gdsc/plantory/fixture/CompanionPlantFixture.kt +++ b/src/test/kotlin/gdsc/plantory/fixture/CompanionPlantFixture.kt @@ -2,7 +2,6 @@ package gdsc.plantory.fixture import gdsc.plantory.plant.domain.CompanionPlant import gdsc.plantory.plant.presentation.dto.CompanionPlantCreateRequest -import gdsc.plantory.plant.presentation.dto.PlantRecordCreateRequest import java.time.LocalDate private var _기록없는_테스트식물_ID = 0L @@ -71,11 +70,4 @@ object CompanionPlantFixture { lastWaterDate = LocalDate.of(2024, 3, 5), ) } - - fun generatePlantRecordCreateRequest(companionPlantId: Long): PlantRecordCreateRequest { - return PlantRecordCreateRequest( - companionPlantId = companionPlantId, - comment = "오늘도 즐거운 하루~!" - ) - } } diff --git a/src/test/kotlin/gdsc/plantory/plant/service/PlantServiceTest.kt b/src/test/kotlin/gdsc/plantory/plant/service/PlantServiceTest.kt index 68fcdc0..5bb0dfe 100644 --- a/src/test/kotlin/gdsc/plantory/plant/service/PlantServiceTest.kt +++ b/src/test/kotlin/gdsc/plantory/plant/service/PlantServiceTest.kt @@ -70,7 +70,7 @@ class PlantServiceTest( // when val result = plantService.lookupPlantRecordOfDate( - PlantRecordLookupRequest(savedPlant.getId, today), "device-token" + savedPlant.getId, today, "device-token" ) // then @@ -126,7 +126,7 @@ class PlantServiceTest( // when val result = plantService.lookupPlantRecordOfDate( - PlantRecordLookupRequest(savedPlant.getId, today), "device-token" + savedPlant.getId, today, "device-token" ) // then From c3fa3b6b046c91ca0b1e1649c0f77e6988089253 Mon Sep 17 00:00:00 2001 From: WhitePiano Date: Thu, 25 Jan 2024 14:57:54 +0900 Subject: [PATCH 5/8] =?UTF-8?q?refactor:=20query=EC=99=80=20command=20URL?= =?UTF-8?q?=20=ED=98=95=EC=8B=9D=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plant/presentation/dto/PlantRecordLookupRequest.kt | 8 -------- .../plant/presentation/dto/PlantRecordLookupResponse.kt | 2 ++ .../kotlin/gdsc/plantory/acceptance/CompanionPlantStep.kt | 2 ++ .../gdsc/plantory/plant/service/PlantServiceTest.kt | 1 - 4 files changed, 4 insertions(+), 9 deletions(-) delete mode 100644 src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordLookupRequest.kt diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordLookupRequest.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordLookupRequest.kt deleted file mode 100644 index 0e63d87..0000000 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordLookupRequest.kt +++ /dev/null @@ -1,8 +0,0 @@ -package gdsc.plantory.plant.presentation.dto - -import java.time.LocalDate - -data class PlantRecordLookupRequest( - val companionPlantId: Long, - val recordDate: LocalDate -) \ No newline at end of file diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordLookupResponse.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordLookupResponse.kt index 5f0645f..da0830a 100644 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordLookupResponse.kt +++ b/src/main/kotlin/gdsc/plantory/plant/presentation/dto/PlantRecordLookupResponse.kt @@ -1,6 +1,7 @@ package gdsc.plantory.plant.presentation.dto class PlantRecordLookupResponse( + val plantRecordId: Long, val imageUrl: String, val comment: String, val nickname: String, @@ -9,6 +10,7 @@ class PlantRecordLookupResponse( companion object { fun of(plantRecord: PlantRecordDto, hasWater: Boolean): PlantRecordLookupResponse { return PlantRecordLookupResponse( + plantRecord.plantRecordId, plantRecord.imageUrl, plantRecord.comment, plantRecord.nickname, diff --git a/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantStep.kt b/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantStep.kt index d702b4b..1736209 100644 --- a/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantStep.kt +++ b/src/test/kotlin/gdsc/plantory/acceptance/CompanionPlantStep.kt @@ -142,6 +142,8 @@ class CompanionPlantStep { { assertThat(response.jsonPath().getString("plantRecordId")).isNotBlank() }, { assertThat(response.jsonPath().getString("imageUrl")).isNotBlank() }, { assertThat(response.jsonPath().getString("comment")).isNotBlank() }, + { assertThat(response.jsonPath().getString("nickname")).isNotBlank() }, + { assertThat(response.jsonPath().getString("water")).isNotBlank() }, ) } diff --git a/src/test/kotlin/gdsc/plantory/plant/service/PlantServiceTest.kt b/src/test/kotlin/gdsc/plantory/plant/service/PlantServiceTest.kt index 5bb0dfe..3656415 100644 --- a/src/test/kotlin/gdsc/plantory/plant/service/PlantServiceTest.kt +++ b/src/test/kotlin/gdsc/plantory/plant/service/PlantServiceTest.kt @@ -3,7 +3,6 @@ package gdsc.plantory.plant.service import gdsc.plantory.plant.domain.CompanionPlant import gdsc.plantory.plant.domain.CompanionPlantRepository import gdsc.plantory.plant.domain.HistoryType -import gdsc.plantory.plant.presentation.dto.PlantRecordLookupRequest import gdsc.plantory.plantInformation.domain.PlantInformation import gdsc.plantory.plantInformation.domain.PlantInformationRepository import gdsc.plantory.util.AcceptanceTest From 5930771c008497b21d4fb4c6807f396e96966d20 Mon Sep 17 00:00:00 2001 From: WhitePiano Date: Thu, 25 Jan 2024 15:49:55 +0900 Subject: [PATCH 6/8] =?UTF-8?q?fix:=20=EB=B0=98=EB=A0=A4=EC=8B=9D=EB=AC=BC?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EC=8B=9C,=20`PlantInformation`?= =?UTF-8?q?=EC=9D=B4=20=EC=A0=9C=EB=8C=80=EB=A1=9C=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plant/presentation/dto/CompanionPlantCreateRequest.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/CompanionPlantCreateRequest.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/dto/CompanionPlantCreateRequest.kt index ecb29cc..bfff546 100644 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/dto/CompanionPlantCreateRequest.kt +++ b/src/main/kotlin/gdsc/plantory/plant/presentation/dto/CompanionPlantCreateRequest.kt @@ -17,9 +17,9 @@ data class CompanionPlantCreateRequest( @PastOrPresent(message = "마지막 물주기 날짜는 과거 또는 현재의 날짜여야 합니다. lastWaterDate: \${validatedValue}") @DateTimeFormat(pattern = "yyyy-MM-dd") val lastWaterDate: LocalDate, ) { - fun toEntity(imagePath: String, memberId: Long, waterCycle: Int): CompanionPlant { + fun toEntity(imagePath: String, memberId: Long, waterCycle: Int, plantInformationId: Long): CompanionPlant { // TODO : Cloud 환경으로 이전 후 제거, 로컬 사진 저장 테스트 용도 - val baseUrl: String = "https://nongsaro.go.kr/" + val baseUrl = "https://nongsaro.go.kr/" return CompanionPlant( _imageUrl = baseUrl + imagePath, _shortDescription = this.shortDescription, @@ -29,6 +29,7 @@ data class CompanionPlantCreateRequest( waterCycle = waterCycle, birthDate = this.birthDate, memberId = memberId, + plantInformationId = plantInformationId ) } } From 2930b9afc886aa01a9ac23ecb0e883f1cf46ee2a Mon Sep 17 00:00:00 2001 From: WhitePiano Date: Thu, 25 Jan 2024 15:51:01 +0900 Subject: [PATCH 7/8] =?UTF-8?q?fix:=20=EC=9E=84=EC=8B=9C=EC=A1=B0=EC=B9=98?= =?UTF-8?q?,=20=EB=8D=B0=EC=9D=BC=EB=A6=AC=20=EA=B8=B0=EB=A1=9D=EC=97=90?= =?UTF-8?q?=20=EC=82=AC=EC=A7=84=EC=97=90=20=EC=A0=80=EC=9E=A5=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gdsc/plantory/plant/service/PlantService.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/gdsc/plantory/plant/service/PlantService.kt b/src/main/kotlin/gdsc/plantory/plant/service/PlantService.kt index 63361eb..168440c 100644 --- a/src/main/kotlin/gdsc/plantory/plant/service/PlantService.kt +++ b/src/main/kotlin/gdsc/plantory/plant/service/PlantService.kt @@ -33,7 +33,12 @@ class PlantService( val findPlantInformation = plantInformationRepository.findByIdOrThrow(request.plantInformationId) val imagePath: String = saveImageAndGetPath(image, findPlantInformation.getImageUrl) - val companionPlant = request.toEntity(imagePath, findMember.getId, findPlantInformation.getWaterCycle) + val companionPlant = request.toEntity( + imagePath, + findMember.getId, + findPlantInformation.getWaterCycle, + request.plantInformationId + ) companionPlantRepository.save(companionPlant) } @@ -86,7 +91,9 @@ class PlantService( companionPlantRepository.findByIdAndMemberIdOrThrow(companionPlantId, findMember.getId) val imagePath: String = saveImageAndGetPath(image, findCompanionPlant.getImageUrl) - findCompanionPlant.saveRecord(request.comment, imagePath) + // TODO : Cloud 환경으로 이전 후 제거, 로컬 사진 저장 테스트 용도 + val baseUrl = "https://nongsaro.go.kr/" + findCompanionPlant.saveRecord(request.comment, baseUrl + imagePath) findCompanionPlant.saveHistory(HistoryType.RECORDING) } From da27fe2546b838a249cc36d91b8ec390d440eab1 Mon Sep 17 00:00:00 2001 From: WhitePiano Date: Thu, 25 Jan 2024 16:32:27 +0900 Subject: [PATCH 8/8] =?UTF-8?q?doc:=20=EB=AC=B8=EC=84=9C=20=EB=B3=B4?= =?UTF-8?q?=EA=B0=95(tag,=20securityRequirement)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plantory/common/config/SwaggerConfig.kt | 20 +++++++++++++++++++ .../member/presentation/MemberCommandApi.kt | 2 ++ .../plant/presentation/PlantCommandApi.kt | 2 ++ .../plant/presentation/PlantQueryApi.kt | 2 ++ 4 files changed, 26 insertions(+) create mode 100644 src/main/kotlin/gdsc/plantory/common/config/SwaggerConfig.kt diff --git a/src/main/kotlin/gdsc/plantory/common/config/SwaggerConfig.kt b/src/main/kotlin/gdsc/plantory/common/config/SwaggerConfig.kt new file mode 100644 index 0000000..88aac86 --- /dev/null +++ b/src/main/kotlin/gdsc/plantory/common/config/SwaggerConfig.kt @@ -0,0 +1,20 @@ +package gdsc.plantory.common.config + +import gdsc.plantory.common.support.AccessDeviceToken +import gdsc.plantory.common.support.DEVICE_ID_HEADER +import io.swagger.v3.oas.models.Operation +import io.swagger.v3.oas.models.security.SecurityRequirement +import org.springdoc.core.customizers.OperationCustomizer +import org.springframework.stereotype.Component +import org.springframework.web.method.HandlerMethod + +@Component +class Operation : OperationCustomizer { + override fun customize(operation: Operation?, handlerMethod: HandlerMethod?): Operation? { + if (handlerMethod?.methodParameters?.find { it.hasParameterAnnotation(AccessDeviceToken::class.java) } != null) { + operation?.addSecurityItem(SecurityRequirement().addList(DEVICE_ID_HEADER)) + } + + return operation + } +} \ No newline at end of file diff --git a/src/main/kotlin/gdsc/plantory/member/presentation/MemberCommandApi.kt b/src/main/kotlin/gdsc/plantory/member/presentation/MemberCommandApi.kt index 4d55981..9cc9331 100644 --- a/src/main/kotlin/gdsc/plantory/member/presentation/MemberCommandApi.kt +++ b/src/main/kotlin/gdsc/plantory/member/presentation/MemberCommandApi.kt @@ -5,12 +5,14 @@ import gdsc.plantory.member.service.MemberService import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity 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.RestController +@Tag(name = "Member Command", description = "사용자 정보 수정") @RestController @RequestMapping("/api/v1/members") class MemberCommandApi( diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/PlantCommandApi.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/PlantCommandApi.kt index 512fc00..45e174c 100644 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/PlantCommandApi.kt +++ b/src/main/kotlin/gdsc/plantory/plant/presentation/PlantCommandApi.kt @@ -10,6 +10,7 @@ import gdsc.plantory.plant.service.PlantService import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.DeleteMapping @@ -21,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestPart import org.springframework.web.bind.annotation.RestController import org.springframework.web.multipart.MultipartFile +@Tag(name = "Plant Query", description = "반려식물 정보 조회") @RestController @RequestMapping("/api/v1/plants") class PlantCommandApi( diff --git a/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt b/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt index 183260e..a9dcf9a 100644 --- a/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt +++ b/src/main/kotlin/gdsc/plantory/plant/presentation/PlantQueryApi.kt @@ -8,6 +8,7 @@ import gdsc.plantory.plant.service.PlantService import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.RestController import java.time.LocalDate import java.time.YearMonth +@Tag(name = "Plant Command", description = "반려식물 정보 수정") @RestController @RequestMapping("/api/v1/plants") class PlantQueryApi(