From 8ee8085de22e687f6336148e52578a39b4260688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EB=8F=99=ED=98=84?= Date: Tue, 14 Nov 2023 22:18:57 +0900 Subject: [PATCH] =?UTF-8?q?[Feat]=20=EB=B3=B4=EA=B4=80=ED=95=A8=20crud=20a?= =?UTF-8?q?pi=20=EC=83=9D=EC=84=B1=20#363=20(#371)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 모든 보관함 조회 api 추가 * feat:보관함 생성 api * feat: 보관함 수정 api 완성 * feat: 보관함 삭제 api * docs: 보관함 crud api 문서화 * fix: signature를 대문자로 비교하기 위해 코드 수정 --- src/docs/asciidoc/api/store/store.adoc | 24 +++ .../java/upbrella/be/rent/entity/Locker.java | 5 + .../be/rent/service/LockerService.java | 61 ++++++- .../be/store/controller/LockerController.java | 73 +++++++++ .../controller/LockerExceptionHandler.java | 21 +++ .../dto/request/CreateLockerRequest.java | 16 ++ .../dto/request/UpdateLockerRequest.java | 16 ++ .../store/dto/response/AllLockerResponse.java | 17 ++ .../dto/response/SingleLockerResponse.java | 27 ++++ .../controller/LockerControllerTest.java | 153 ++++++++++++++++++ 10 files changed, 411 insertions(+), 2 deletions(-) create mode 100644 src/main/java/upbrella/be/store/controller/LockerController.java create mode 100644 src/main/java/upbrella/be/store/controller/LockerExceptionHandler.java create mode 100644 src/main/java/upbrella/be/store/dto/request/CreateLockerRequest.java create mode 100644 src/main/java/upbrella/be/store/dto/request/UpdateLockerRequest.java create mode 100644 src/main/java/upbrella/be/store/dto/response/AllLockerResponse.java create mode 100644 src/main/java/upbrella/be/store/dto/response/SingleLockerResponse.java create mode 100644 src/test/java/upbrella/be/store/controller/LockerControllerTest.java diff --git a/src/docs/asciidoc/api/store/store.adoc b/src/docs/asciidoc/api/store/store.adoc index 7a582854..4970f656 100644 --- a/src/docs/asciidoc/api/store/store.adoc +++ b/src/docs/asciidoc/api/store/store.adoc @@ -43,3 +43,27 @@ include::{snippets}/store-find-store-introduction-doc/http-request.adoc[] ==== HTTP Response include::{snippets}/store-find-store-introduction-doc/http-response.adoc[] include::{snippets}/store-find-store-introduction-doc/response-fields-data.adoc[] + +=== 보관함 전체 조회 +==== HTTP Request +include::{snippets}/find-all-lockers/http-request.adoc[] + +==== HTTP Response +include::{snippets}/find-all-lockers/http-response.adoc[] +include::{snippets}/find-all-lockers/response-fields-data.adoc[] + +=== 보관함 생성 +==== HTTP Request +include::{snippets}/create-locker/http-request.adoc[] +include::{snippets}/create-locker/request-fields.adoc[] + +=== 보관함 수정 +==== HTTP Request +include::{snippets}/update-locker/http-request.adoc[] +include::{snippets}/update-locker/path-parameters.adoc[] +include::{snippets}/update-locker/request-fields.adoc[] + +=== 보관함 삭제 +==== HTTP Request +include::{snippets}/delete-locker/http-request.adoc[] +include::{snippets}/delete-locker/path-parameters.adoc[] \ No newline at end of file diff --git a/src/main/java/upbrella/be/rent/entity/Locker.java b/src/main/java/upbrella/be/rent/entity/Locker.java index 1cc0a609..559fbbee 100644 --- a/src/main/java/upbrella/be/rent/entity/Locker.java +++ b/src/main/java/upbrella/be/rent/entity/Locker.java @@ -31,4 +31,9 @@ public void updateCount() { public void updateLastAccess(LocalDateTime now) { this.lastAccess = now; } + + public void updateLocker(StoreMeta storeMeta, String secretKey) { + this.storeMeta = storeMeta; + this.secretKey = secretKey; + } } diff --git a/src/main/java/upbrella/be/rent/service/LockerService.java b/src/main/java/upbrella/be/rent/service/LockerService.java index 28dd5e43..4cd85835 100644 --- a/src/main/java/upbrella/be/rent/service/LockerService.java +++ b/src/main/java/upbrella/be/rent/service/LockerService.java @@ -10,19 +10,69 @@ import upbrella.be.rent.exception.LockerSignatureErrorException; import upbrella.be.rent.exception.NoSignatureException; import upbrella.be.rent.repository.LockerRepository; +import upbrella.be.store.dto.request.CreateLockerRequest; +import upbrella.be.store.dto.request.UpdateLockerRequest; +import upbrella.be.store.dto.response.AllLockerResponse; +import upbrella.be.store.dto.response.SingleLockerResponse; +import upbrella.be.store.entity.StoreMeta; +import upbrella.be.store.service.StoreMetaService; import upbrella.be.util.HotpGenerator; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class LockerService { private final LockerRepository lockerRepository; + private final StoreMetaService storeMetaService; + + public AllLockerResponse findAll() { + List all = lockerRepository.findAll(); + + return new AllLockerResponse(all.stream() + .map(SingleLockerResponse::fromLocker) + .collect(Collectors.toList())); + + } + + @Transactional + public void createLocker(CreateLockerRequest request) { + + isMultipleLockers(request.getStoreId()); + + StoreMeta storeMeta = storeMetaService.findStoreMetaById(request.getStoreId()); + Locker locker = Locker.builder() + .storeMeta(storeMeta) + .secretKey(request.getSecretKey()) + .build(); + + lockerRepository.save(locker); + } + + @Transactional + public void updateLocker(Long lockerId, UpdateLockerRequest request) { + isMultipleLockers(request.getStoreId()); + + StoreMeta storeMeta = storeMetaService.findStoreMetaById(request.getStoreId()); + + lockerRepository.findById(lockerId) + .ifPresent(locker -> locker.updateLocker(storeMeta, request.getSecretKey())); + } + + public void deleteLocker(Long lockerId) { + + if(!lockerRepository.existsById(lockerId)) { + throw new IllegalArgumentException("해당 보관함이 존재하지 않습니다."); + } + lockerRepository.deleteById(lockerId); + } @Transactional public LockerPasswordResponse findLockerPassword(RentUmbrellaByUserRequest rentUmbrellaByUserRequest) { @@ -82,7 +132,7 @@ private String encodeHash(String lockerSecretKey, String salt) { throw new RuntimeException(e); } - byte[] encodedhash = digest.digest((lockerSecretKey + "." + salt).getBytes(StandardCharsets.UTF_8)); + byte[] encodedhash = digest.digest((lockerSecretKey.toUpperCase() + "." + salt.toUpperCase()).getBytes(StandardCharsets.UTF_8)); // 바이트 배열을 16진수 문자열로 변환 StringBuilder hexString = new StringBuilder(2 * encodedhash.length); @@ -94,7 +144,14 @@ private String encodeHash(String lockerSecretKey, String salt) { } hexString.append(hex); } - return hexString.toString(); + return hexString.toString().toUpperCase(); + } + + private void isMultipleLockers(Long storeId) { + Optional byStoreMetaId = lockerRepository.findByStoreMetaId(storeId); + if(byStoreMetaId.isPresent()) { + throw new IllegalArgumentException("이미 보관함이 존재합니다."); + } } } diff --git a/src/main/java/upbrella/be/store/controller/LockerController.java b/src/main/java/upbrella/be/store/controller/LockerController.java new file mode 100644 index 00000000..997c444f --- /dev/null +++ b/src/main/java/upbrella/be/store/controller/LockerController.java @@ -0,0 +1,73 @@ +package upbrella.be.store.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import upbrella.be.rent.service.LockerService; +import upbrella.be.store.dto.request.CreateLockerRequest; +import upbrella.be.store.dto.request.UpdateLockerRequest; +import upbrella.be.store.dto.response.AllLockerResponse; +import upbrella.be.util.CustomResponse; + +@RestController +@RequiredArgsConstructor +public class LockerController { + + private final LockerService lockerService; + + @GetMapping("/admin/lockers") + public ResponseEntity> getLockers() { + + AllLockerResponse lockers = lockerService.findAll(); + + return ResponseEntity + .ok() + .body(new CustomResponse<>( + "success", + 200, + "보관함 조회 성공", + lockers)); + } + + @PostMapping("/admin/lockers") + public ResponseEntity> createLocker(@RequestBody CreateLockerRequest request) { + + lockerService.createLocker(request); + + return ResponseEntity + .ok() + .body(new CustomResponse<>( + "success", + 200, + "보관함 생성 성공", + null)); + } + + @PatchMapping("/admin/lockers/{lockerId}") + public ResponseEntity> updateLocker(@PathVariable Long lockerId, @RequestBody UpdateLockerRequest request) { + + lockerService.updateLocker(lockerId, request); + + return ResponseEntity + .ok() + .body(new CustomResponse<>( + "success", + 200, + "보관함 업데이트 성공", + null)); + } + + @DeleteMapping("/admin/lockers/{lockerId}") + public ResponseEntity> deleteLocker(@PathVariable Long lockerId) { + + lockerService.deleteLocker(lockerId); + + return ResponseEntity + .ok() + .body(new CustomResponse<>( + "success", + 200, + "보관함 삭제 성공", + null)); + } +} diff --git a/src/main/java/upbrella/be/store/controller/LockerExceptionHandler.java b/src/main/java/upbrella/be/store/controller/LockerExceptionHandler.java new file mode 100644 index 00000000..584dd241 --- /dev/null +++ b/src/main/java/upbrella/be/store/controller/LockerExceptionHandler.java @@ -0,0 +1,21 @@ +package upbrella.be.store.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import upbrella.be.util.CustomErrorResponse; + +@RestControllerAdvice +public class LockerExceptionHandler { + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity nonExistingStoreDetail(IllegalArgumentException ex) { + + return ResponseEntity + .badRequest() + .body(new CustomErrorResponse( + "bad request", + 400, + ex.getMessage())); + } +} diff --git a/src/main/java/upbrella/be/store/dto/request/CreateLockerRequest.java b/src/main/java/upbrella/be/store/dto/request/CreateLockerRequest.java new file mode 100644 index 00000000..6b20884f --- /dev/null +++ b/src/main/java/upbrella/be/store/dto/request/CreateLockerRequest.java @@ -0,0 +1,16 @@ +package upbrella.be.store.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CreateLockerRequest { + + private long storeId; + private String secretKey; +} diff --git a/src/main/java/upbrella/be/store/dto/request/UpdateLockerRequest.java b/src/main/java/upbrella/be/store/dto/request/UpdateLockerRequest.java new file mode 100644 index 00000000..d090b4f2 --- /dev/null +++ b/src/main/java/upbrella/be/store/dto/request/UpdateLockerRequest.java @@ -0,0 +1,16 @@ +package upbrella.be.store.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UpdateLockerRequest { + + private long storeId; + private String secretKey; +} diff --git a/src/main/java/upbrella/be/store/dto/response/AllLockerResponse.java b/src/main/java/upbrella/be/store/dto/response/AllLockerResponse.java new file mode 100644 index 00000000..8ffc0f8d --- /dev/null +++ b/src/main/java/upbrella/be/store/dto/response/AllLockerResponse.java @@ -0,0 +1,17 @@ +package upbrella.be.store.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AllLockerResponse { + + private List lockers; +} diff --git a/src/main/java/upbrella/be/store/dto/response/SingleLockerResponse.java b/src/main/java/upbrella/be/store/dto/response/SingleLockerResponse.java new file mode 100644 index 00000000..2edb415f --- /dev/null +++ b/src/main/java/upbrella/be/store/dto/response/SingleLockerResponse.java @@ -0,0 +1,27 @@ +package upbrella.be.store.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import upbrella.be.rent.entity.Locker; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SingleLockerResponse { + + private long id; + private long storeMetaId; + private String secretKey; + + public static SingleLockerResponse fromLocker(Locker locker) { + + return SingleLockerResponse.builder() + .id(locker.getId()) + .storeMetaId(locker.getStoreMeta().getId()) + .secretKey(locker.getSecretKey()) + .build(); + } +} diff --git a/src/test/java/upbrella/be/store/controller/LockerControllerTest.java b/src/test/java/upbrella/be/store/controller/LockerControllerTest.java new file mode 100644 index 00000000..5ab96ffc --- /dev/null +++ b/src/test/java/upbrella/be/store/controller/LockerControllerTest.java @@ -0,0 +1,153 @@ +package upbrella.be.store.controller; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.restdocs.payload.JsonFieldType; +import upbrella.be.docs.utils.RestDocsSupport; +import upbrella.be.rent.service.LockerService; +import upbrella.be.store.dto.request.CreateLockerRequest; +import upbrella.be.store.dto.request.UpdateLockerRequest; +import upbrella.be.store.dto.response.AllLockerResponse; +import upbrella.be.store.dto.response.SingleLockerResponse; + +import java.util.List; + +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static upbrella.be.docs.utils.ApiDocumentUtils.getDocumentRequest; +import static upbrella.be.docs.utils.ApiDocumentUtils.getDocumentResponse; + +@ExtendWith(MockitoExtension.class) +class LockerControllerTest extends RestDocsSupport { + + @Mock + private LockerService lockerService; + + @Test + @DisplayName("모든 보관함을 조회할 수 있다.") + void findAllLockerTest() throws Exception { + // given + SingleLockerResponse locker = SingleLockerResponse.builder() + .id(1L) + .storeMetaId(1L) + .secretKey("secretKey") + .build(); + + AllLockerResponse response = AllLockerResponse.builder() + .lockers(List.of(locker)) + .build(); + + given(lockerService.findAll()).willReturn(response); + + // when & then + mockMvc.perform( + get("/admin/lockers") + ).andDo(print()) + .andExpect(status().isOk()) + .andDo(document("find-all-lockers", + getDocumentRequest(), + getDocumentResponse(), + responseFields( + beneathPath("data").withSubsectionId("data"), + fieldWithPath("lockers").type(JsonFieldType.ARRAY) + .description("보관함 목록"), + fieldWithPath("lockers[].id").type(JsonFieldType.NUMBER) + .description("보관함 ID"), + fieldWithPath("lockers[].storeMetaId").type(JsonFieldType.NUMBER) + .description("보관함이 속한 매장 ID"), + fieldWithPath("lockers[].secretKey").type(JsonFieldType.STRING) + .description("보관함 비밀키") + ))); + } + + @Test + @DisplayName("새로운 보관함을 생성할 수 있다.") + void createLockerTest() throws Exception { + // given + CreateLockerRequest request = CreateLockerRequest.builder() + .storeId(1L) + .secretKey("secretKey") + .build(); + + // then + mockMvc.perform(post("/admin/lockers") + .content(objectMapper.writeValueAsString(request)) + .contentType("application/json")) + .andDo(print()) + .andExpect(status().isOk()) + .andDo(document("create-locker", + getDocumentRequest(), + getDocumentResponse(), + requestFields( + fieldWithPath("storeId").type(JsonFieldType.NUMBER) + .description("보관함이 속할 매장 ID"), + fieldWithPath("secretKey").type(JsonFieldType.STRING) + .description("보관함 비밀키") + ))); + } + + @Test + @DisplayName("보관함의 정보를 수정할 수 있다.") + void updateLockerTest() throws Exception { + // given + UpdateLockerRequest request = UpdateLockerRequest.builder() + .storeId(1L) + .secretKey("secretKey") + .build(); + Long lockerId = 1L; + + // when + + // then + mockMvc.perform(patch("/admin/lockers/{lockerId}", lockerId) + .content(objectMapper.writeValueAsString(request)) + .contentType("application/json")) + .andDo(print()) + .andExpect(status().isOk()) + .andDo(document("update-locker", + getDocumentRequest(), + getDocumentResponse(), + pathParameters( + parameterWithName("lockerId").description("보관함 ID") + ), + requestFields( + fieldWithPath("storeId").type(JsonFieldType.NUMBER) + .description("보관함이 속할 매장 ID"), + fieldWithPath("secretKey").type(JsonFieldType.STRING) + .description("보관함 비밀키") + ))); + } + + @Test + @DisplayName("보관함을 삭제할 수 있다.") + void deleteLockerTest() throws Exception { + // given + Long lockerId = 1L; + + // then + mockMvc.perform(delete("/admin/lockers/{lockerId}", lockerId)) + .andDo(print()) + .andExpect(status().isOk()) + .andDo(document("delete-locker", + getDocumentRequest(), + getDocumentResponse(), + pathParameters( + parameterWithName("lockerId").description("보관함 ID") + ))); + } + + @Override + protected Object initController() { + return new LockerController(lockerService); + } +}