From fb5aa5d4b995ba9d13a32e2e617e50a4d43b1630 Mon Sep 17 00:00:00 2001 From: eedo_y Date: Thu, 18 Sep 2025 17:14:51 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20feat/71=20:=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20DTO=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/bookmark/BookmarkRequest.java | 4 +-- .../request/bookmark/BookmarkSyncRequest.java | 8 ++++- .../KeyboardRecommendationResponse.java | 5 +++- .../product/ProductDetailResponse.java | 4 +++ .../product/RecommendedItemResponse.java | 29 ------------------- .../response/search/SearchListResponse.java | 5 ++++ 6 files changed, 22 insertions(+), 33 deletions(-) delete mode 100644 src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/RecommendedItemResponse.java diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkRequest.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkRequest.java index 98f39b2..0b9fd24 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkRequest.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkRequest.java @@ -1,11 +1,10 @@ package site.kikihi.custom.platform.adapter.in.web.dto.request.bookmark; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Data; -import java.util.UUID; - @Data @Builder @Schema( @@ -15,6 +14,7 @@ public class BookmarkRequest { @Schema(description = "북마크할 상품 ID", example = "6896ed7d5198cf586e933d6e") + @NotNull private String productId; } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkSyncRequest.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkSyncRequest.java index b444240..2808cba 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkSyncRequest.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkSyncRequest.java @@ -1,14 +1,20 @@ package site.kikihi.custom.platform.adapter.in.web.dto.request.bookmark; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; import lombok.Data; import java.util.List; @Data +@Schema( + name = "[요청][북마크] 북마크 싱크 Request", + description = "상품을 하번에 북마크할 때 사용하는 요청 DTO입니다." +) public class BookmarkSyncRequest { @Schema(description = "북마크할 상품들", example = "6896ed7d5198cf586e933d6e") - private List productIds; + private List<@Valid BookmarkRequest> productIds; + } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/KeyboardRecommendationResponse.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/KeyboardRecommendationResponse.java index 898e254..8f73135 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/KeyboardRecommendationResponse.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/KeyboardRecommendationResponse.java @@ -18,7 +18,10 @@ */ @Builder -@Schema(name = "KeyboardRecommendationListResponse", description = "튜토리얼 키보드 추천 리스트 응답") +@Schema( + name = "[응답][튜토리얼] 추천 상품 응답 Response", + description = "튜토리얼 키보드 추천 리스트 응답" +) public record KeyboardRecommendationResponse( @Schema(description = "상품 아이디", example = "101") diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/ProductDetailResponse.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/ProductDetailResponse.java index 064abe3..cad979f 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/ProductDetailResponse.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/ProductDetailResponse.java @@ -98,6 +98,10 @@ public static ProductDetailResponse from(Product product, boolean likedByMe) { /// 내부에서만 사용되는 옵션 @Builder + @Schema( + name = "[응답][상품] 상품 옵션 조회 Response", + description = "상품의 옵션 정보를 반환하는 응답 DTO입니다." + ) private record ProductOptions( String optionName, Double price, diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/RecommendedItemResponse.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/RecommendedItemResponse.java deleted file mode 100644 index 4a74311..0000000 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/RecommendedItemResponse.java +++ /dev/null @@ -1,29 +0,0 @@ -package site.kikihi.custom.platform.adapter.in.web.dto.response.product; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Builder; - -/** - * 어울리는 상품 응답 DTO - * - * @param id 상품 ID - * @param name 상품명 - * @param thumbnail 썸네일 이미지 URL - */ - -@Builder -@Schema( - name = "[응답][상품] 어울리는 상품 응답 Response", - description = "타 상품과 어울리는 추천 상품 조회 응답 DTO입니다." -) -record RecommendedItemResponse( - @Schema(description = "상품 아이디", example = "202") - String id, - - @Schema(description = "상품명", example = "키캡 악세사리") - String name, - - @Schema(description = "상품 썸네일 이미지 URL", example = "https://example.com/recommended/202.jpg") - String thumbnail -) { -} diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/search/SearchListResponse.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/search/SearchListResponse.java index 07a96c4..78bc040 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/search/SearchListResponse.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/search/SearchListResponse.java @@ -1,10 +1,15 @@ package site.kikihi.custom.platform.adapter.in.web.dto.response.search; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import site.kikihi.custom.platform.domain.search.Search; import java.util.List; @Builder +@Schema( + name = "[응답][검색] 최신 검색어 조회 Response", + description = "사용자의 검색어 정보를 반환하는 응답 DTO입니다." +) public record SearchListResponse( Long searchId, String keyword From f677c60e1fafeb16122f93eb7f7c53830e50d082 Mon Sep 17 00:00:00 2001 From: eedo_y Date: Thu, 18 Sep 2025 17:14:51 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=A8=20feat/KIKI-71=20:=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20DTO=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/bookmark/BookmarkRequest.java | 4 +-- .../request/bookmark/BookmarkSyncRequest.java | 8 ++++- .../KeyboardRecommendationResponse.java | 5 +++- .../product/ProductDetailResponse.java | 4 +++ .../product/RecommendedItemResponse.java | 29 ------------------- .../response/search/SearchListResponse.java | 5 ++++ 6 files changed, 22 insertions(+), 33 deletions(-) delete mode 100644 src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/RecommendedItemResponse.java diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkRequest.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkRequest.java index 98f39b2..0b9fd24 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkRequest.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkRequest.java @@ -1,11 +1,10 @@ package site.kikihi.custom.platform.adapter.in.web.dto.request.bookmark; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Data; -import java.util.UUID; - @Data @Builder @Schema( @@ -15,6 +14,7 @@ public class BookmarkRequest { @Schema(description = "북마크할 상품 ID", example = "6896ed7d5198cf586e933d6e") + @NotNull private String productId; } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkSyncRequest.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkSyncRequest.java index b444240..2808cba 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkSyncRequest.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/bookmark/BookmarkSyncRequest.java @@ -1,14 +1,20 @@ package site.kikihi.custom.platform.adapter.in.web.dto.request.bookmark; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; import lombok.Data; import java.util.List; @Data +@Schema( + name = "[요청][북마크] 북마크 싱크 Request", + description = "상품을 하번에 북마크할 때 사용하는 요청 DTO입니다." +) public class BookmarkSyncRequest { @Schema(description = "북마크할 상품들", example = "6896ed7d5198cf586e933d6e") - private List productIds; + private List<@Valid BookmarkRequest> productIds; + } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/KeyboardRecommendationResponse.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/KeyboardRecommendationResponse.java index 898e254..8f73135 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/KeyboardRecommendationResponse.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/KeyboardRecommendationResponse.java @@ -18,7 +18,10 @@ */ @Builder -@Schema(name = "KeyboardRecommendationListResponse", description = "튜토리얼 키보드 추천 리스트 응답") +@Schema( + name = "[응답][튜토리얼] 추천 상품 응답 Response", + description = "튜토리얼 키보드 추천 리스트 응답" +) public record KeyboardRecommendationResponse( @Schema(description = "상품 아이디", example = "101") diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/ProductDetailResponse.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/ProductDetailResponse.java index 064abe3..cad979f 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/ProductDetailResponse.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/ProductDetailResponse.java @@ -98,6 +98,10 @@ public static ProductDetailResponse from(Product product, boolean likedByMe) { /// 내부에서만 사용되는 옵션 @Builder + @Schema( + name = "[응답][상품] 상품 옵션 조회 Response", + description = "상품의 옵션 정보를 반환하는 응답 DTO입니다." + ) private record ProductOptions( String optionName, Double price, diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/RecommendedItemResponse.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/RecommendedItemResponse.java deleted file mode 100644 index 4a74311..0000000 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/RecommendedItemResponse.java +++ /dev/null @@ -1,29 +0,0 @@ -package site.kikihi.custom.platform.adapter.in.web.dto.response.product; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Builder; - -/** - * 어울리는 상품 응답 DTO - * - * @param id 상품 ID - * @param name 상품명 - * @param thumbnail 썸네일 이미지 URL - */ - -@Builder -@Schema( - name = "[응답][상품] 어울리는 상품 응답 Response", - description = "타 상품과 어울리는 추천 상품 조회 응답 DTO입니다." -) -record RecommendedItemResponse( - @Schema(description = "상품 아이디", example = "202") - String id, - - @Schema(description = "상품명", example = "키캡 악세사리") - String name, - - @Schema(description = "상품 썸네일 이미지 URL", example = "https://example.com/recommended/202.jpg") - String thumbnail -) { -} diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/search/SearchListResponse.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/search/SearchListResponse.java index 07a96c4..78bc040 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/search/SearchListResponse.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/search/SearchListResponse.java @@ -1,10 +1,15 @@ package site.kikihi.custom.platform.adapter.in.web.dto.response.search; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import site.kikihi.custom.platform.domain.search.Search; import java.util.List; @Builder +@Schema( + name = "[응답][검색] 최신 검색어 조회 Response", + description = "사용자의 검색어 정보를 반환하는 응답 DTO입니다." +) public record SearchListResponse( Long searchId, String keyword From 231ab7d338b57dea4a0c2f3a1b87f0c0dfa1568c Mon Sep 17 00:00:00 2001 From: eedo_y Date: Fri, 19 Sep 2025 00:18:13 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=A8=20feat/KIKI-71=20:=20=EB=B6=80?= =?UTF-8?q?=ED=92=88=20=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/CustomKeyboardController.java | 38 ++++++-- .../custom/CustomKeyboardUpdateRequest.java | 9 ++ .../custom/CustomKeyboardDetailResponse.java | 8 ++ .../swagger/CustomKeyboardControllerSpec.java | 71 ++++++++++++++- .../adapter/out/CustomKeyboardAdapter.java | 47 +++++++++- .../jpa/custom/CustomKeyboardJpaEntity.java | 14 +++ .../out/custom/CustomKeyboardPort.java | 2 + .../service/CustomKeyboardService.java | 17 +++- .../domain/custom/CustomKeyboard.java | 91 +++++++++++++++++++ .../domain/custom/CustomKeyboardWithName.java | 4 + 10 files changed, 283 insertions(+), 18 deletions(-) diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/CustomKeyboardController.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/CustomKeyboardController.java index 7076aeb..f08f36a 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/CustomKeyboardController.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/CustomKeyboardController.java @@ -24,7 +24,7 @@ import java.util.UUID; @RestController -@RequestMapping("/api/v1/custom") +@RequestMapping("/api/v1/customs") @RequiredArgsConstructor public class CustomKeyboardController implements CustomKeyboardControllerSpec { @@ -50,14 +50,14 @@ public ApiResponse createCustomKeyboard( /** * 커스텀 키보드 상세 조회 * - * @param customKeyboardId 키보드 상세 조회 ID + * @param id 키보드 상세 조회 ID */ - @GetMapping("/{customKeyboardId}") + @GetMapping("/{id}") public ApiResponse getCustomKeyboard( - @PathVariable Long customKeyboardId + @PathVariable Long id ) { /// 서비스 - CustomKeyboardWithName keyBoard = service.getCustomKeyboard(customKeyboardId); + CustomKeyboardWithName keyBoard = service.getCustomKeyboard(id); /// DTO 변경 var response = CustomKeyboardDetailResponse.from(keyBoard); @@ -71,7 +71,7 @@ public ApiResponse getCustomKeyboard( * * @param principalDetails 유저 */ - @GetMapping("/myCustoms") + @GetMapping() public ApiResponse> getMyCustoms( @AuthenticationPrincipal PrincipalDetails principalDetails ) { @@ -104,7 +104,7 @@ public ApiResponse> getCustomKeyboardProducts @RequestParam CustomKeyboardLayout layout, @RequestParam(required = false) Integer minPrice, @RequestParam(required = false) Integer maxPrice, - @RequestParam(required = true, defaultValue = "false") boolean bookmark, + @RequestParam(defaultValue = "false") boolean bookmark, PageRequest pageRequest ) { @@ -169,6 +169,7 @@ public ApiResponse updateCustomKeyboard( /// 서비스 service.insertProductInCustomKeyboard(request.getId(), request.getCategory().getValue(), request.getProductId(), principalDetails.getId()); + /// 수정 완료 응답 제공 return ApiResponse.updated(); } @@ -188,7 +189,6 @@ public ApiResponse deleteCustomKeyboard( /// 응답 return ApiResponse.deleted(); - } /** @@ -198,7 +198,7 @@ public ApiResponse deleteCustomKeyboard( * @param productId 상품 ID * @param principalDetails 유저 */ - @DeleteMapping() + @DeleteMapping("/products") public ApiResponse deleteProductInsideCustom( @RequestParam Long id, @RequestParam CustomCategoryType category, @@ -209,6 +209,26 @@ public ApiResponse deleteProductInsideCustom( /// 서비스 호출 service.deleteCustomInside(id, category.getValue(), productId, principalDetails.getId()); + /// 삭제 완료 응답 제공 return ApiResponse.deleted(); } + + /** + * 커스텀 키보드 내부 상품 추가 API + * + * @param request 요청 + * @param principalDetails 유저 + */ + @Override + @PatchMapping("/products") + public ApiResponse addProductInsideCustom( + @RequestBody @Valid CustomKeyboardUpdateRequest request, + @AuthenticationPrincipal PrincipalDetails principalDetails) { + + /// 서비스 호출 + service.insertProductInCustomKeyboard(request.getId(), request.getCategory().getValue(), request.getProductId(), principalDetails.getId()); + + /// 수정 완료 응답 제공 + return ApiResponse.updated(); + } } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/custom/CustomKeyboardUpdateRequest.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/custom/CustomKeyboardUpdateRequest.java index 38a565f..28b68f4 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/custom/CustomKeyboardUpdateRequest.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/request/custom/CustomKeyboardUpdateRequest.java @@ -1,14 +1,23 @@ package site.kikihi.custom.platform.adapter.in.web.dto.request.custom; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data +@Schema( + name = "[요청][커스텀] 커스텀 키보드 수정 Request", + description = "사용자가 커스텀 키보드의 부품을 수정할 때 사용하는 요청 DTO입니다." +) public class CustomKeyboardUpdateRequest { + @Parameter(example = "1") private Long id; + @Parameter(example = "housing") private CustomCategoryType category; + @Parameter(example = "68b3f4fedc26d32d8881fe0e") private String productId; } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/custom/CustomKeyboardDetailResponse.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/custom/CustomKeyboardDetailResponse.java index 2ec157f..85237e1 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/custom/CustomKeyboardDetailResponse.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/custom/CustomKeyboardDetailResponse.java @@ -33,12 +33,18 @@ public record CustomKeyboardDetailResponse( @Schema(description = "하우징 상품 ID", example = "68b3f4fedc26d32d8881fe12") String housingId, + @Schema(description = "하우징 매핑 이미지 URL", example = "https://example.com/product/101.jpg") + String housingImageUrl, + @Schema(description = "하우징(프레임) 이름", example = "TX-65") String housingName, @Schema(description = "키캡 ID", example = "GMK Red Samurai") String keyCapId, + @Schema(description = "키캡 매핑 이미지 URL", example = "https://example.com/product/101.jpg") + String keyCapImageUrl, + @Schema(description = "키캡 이름", example = "GMK Red Samurai") String keyCapName, @@ -68,8 +74,10 @@ public static CustomKeyboardDetailResponse from(CustomKeyboardWithName entity){ .customName(entity.name()) .customKeyboardType(entity.layout()) .housingId(entity.frameId()) + .housingImageUrl(entity.frameUrl()) .housingName(entity.frameName()) .keyCapId(entity.keyCapId()) + .keyCapImageUrl(entity.keyCapUrl()) .keyCapName(entity.keyCapName()) .switchId(entity.switchId()) .switchName(entity.switchName()) diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/CustomKeyboardControllerSpec.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/CustomKeyboardControllerSpec.java index 2f300a4..719e016 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/CustomKeyboardControllerSpec.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/CustomKeyboardControllerSpec.java @@ -6,7 +6,7 @@ import site.kikihi.custom.global.response.page.SliceResponse; import site.kikihi.custom.platform.adapter.in.web.dto.request.custom.CustomCategoryType; import site.kikihi.custom.platform.adapter.in.web.dto.request.custom.CustomKeyboardRequest; -import site.kikihi.custom.platform.adapter.in.web.dto.response.custom.CustomKeyboardLayoutResponse; +import site.kikihi.custom.platform.adapter.in.web.dto.request.custom.CustomKeyboardUpdateRequest; import site.kikihi.custom.platform.adapter.in.web.dto.response.custom.CustomKeyboardDetailResponse; import site.kikihi.custom.platform.adapter.in.web.dto.response.custom.CustomKeyboardListResponse; import site.kikihi.custom.platform.adapter.in.web.dto.response.product.ProductListResponse; @@ -23,8 +23,6 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; -import java.util.List; - @Tag(name = "커스텀 키보드 API", description = "커스텀 키보드 관련 API입니다.") public interface CustomKeyboardControllerSpec { @@ -77,9 +75,16 @@ ApiResponse> getMyCustoms( @AuthenticationPrincipal PrincipalDetails principalDetails); /** + * 키보드 배열에 따른 목록 조회 * + * @param principalDetails 유저 + * @param category 카테고리 + * @param layout 레이아웃 + * @param minPrice 최소가격 + * @param maxPrice 최대가격 + * @param bookmark 북마크 여부 + * @param pageRequest 페이징 */ - @Operation( summary = "키보드 배열에 따른 가능한 부품 조회 API", description = "키보드 배열에 따라서 가능한 상품 목록을 조회합니다. 유저의 정보가 들어온다면 북마크 여부 또한 제공합니다." @@ -90,7 +95,7 @@ ApiResponse> getCustomKeyboardProductsByLayou @RequestParam CustomKeyboardLayout layout, @RequestParam(required = false) Integer minPrice, @RequestParam(required = false) Integer maxPrice, - @RequestParam(required = true) boolean bookmark, + @RequestParam(defaultValue = "false") boolean bookmark, PageRequest pageRequest ); @@ -100,10 +105,58 @@ ApiResponse> getCustomKeyboardProductsByLayou description = "JWT를 기반으로 커스텀 키보드를 삭제합니다." ) ApiResponse deleteCustomKeyboard( + @Parameter(example = "1") @PathVariable Long id, + + @AuthenticationPrincipal PrincipalDetails principalDetails + ); + + + @Operation( + summary = "커스텀 키보드 내부 부품 삭제 API", + description = "커스텀 키보드 내부 부품을 삭제합니다." + ) + ApiResponse deleteProductInsideCustom( + + @Parameter(example = "1") + @RequestParam Long id, + + @RequestParam CustomCategoryType category, + + @Parameter(example = "68b3f4fedc26d32d8881fe12") + @RequestParam String productId, + + @AuthenticationPrincipal PrincipalDetails principalDetails + ); + + + + /** + * 내부 부품을 추가 + * @param request 요청 + * @param principalDetails 유저 + */ + @Operation( + summary = "커스텀 키보드 내부 부품 추가 API", + description = "커스텀 키보드 내부 부품을 추가합니다.", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + examples = { + @ExampleObject(name = "수정 예시", value = UPDATE_REQUEST), + } + ) + ) + ) + ApiResponse addProductInsideCustom( + @RequestBody @Valid CustomKeyboardUpdateRequest request, @AuthenticationPrincipal PrincipalDetails principalDetails ); + + + + String REQUEST = """ { "layout" : "PERCENT_60", @@ -115,4 +168,12 @@ ApiResponse deleteCustomKeyboard( } """; + String UPDATE_REQUEST = """ + { + "id": "1", + "category": "housing", + "productId": "68b3f4fedc26d32d8881fe0e" + } + """; + } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/out/CustomKeyboardAdapter.java b/src/main/java/site/kikihi/custom/platform/adapter/out/CustomKeyboardAdapter.java index cc4e61f..eb52c92 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/out/CustomKeyboardAdapter.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/out/CustomKeyboardAdapter.java @@ -1,5 +1,7 @@ package site.kikihi.custom.platform.adapter.out; +import jakarta.transaction.Transactional; +import site.kikihi.custom.global.response.ErrorCode; import site.kikihi.custom.platform.adapter.out.jpa.custom.CustomKeyboardJpaEntity; import site.kikihi.custom.platform.adapter.out.jpa.custom.CustomKeyboardJpaRepository; import site.kikihi.custom.platform.application.out.custom.CustomKeyboardPort; @@ -8,6 +10,7 @@ import org.springframework.data.domain.Slice; import org.springframework.stereotype.Component; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.UUID; @@ -129,17 +132,55 @@ public void deleteCustomKeyboard(Long id) { } /** - * 삭제할 커스텀 키보드 + * 커스텀 키보드 내부 상품 삭제 * @param id 아이디 * @param categoryId 카테고리 * @param productId 상품 ID */ @Override + @Transactional public void deleteProductInsideCustomKeyboard(Long id, String categoryId, String productId) { - /// 삭제가 아닌 수정으로 진행 - /// 더티 체킹으로서 수행 + /// 실질적으로는 삭제가 아닌 수정으로 진행 + /// JPA이기에 영속성을 살려서, 더티 체킹으로서 수행 + + /// 조회를 통해, 영속성 컨테이너에 넣기 + CustomKeyboardJpaEntity entity = repository.findById(id) + .orElseThrow(() -> new NoSuchElementException(ErrorCode.PRODUCT_NOT_FOUND.getMessage())); + + /// 찾은 값을 도메인으로 변환 및 로직 수행 + CustomKeyboard domain = entity.toDomain(); + /// 도메인 로직 수행 + domain.removeProduct(categoryId, productId); + + /// 더티체킹 수행(영속성 컨테이너 있는 값 수정) + entity.update(domain); + + } + + /** + * 커스텀 키보드 내부 상품 추기 + * @param id 아이디 + * @param categoryId 카테고리 + * @param productId 상품 ID + */ + @Override + public void addProductInsideCustomKeyboard(Long id, String categoryId, String productId) { + + // 실질적으로는 삭제가 아닌 수정으로 진행 + /// JPA이기에 영속성을 살려서, 더티 체킹으로서 수행 + + /// 조회를 통해, 영속성 컨테이너에 넣기 + CustomKeyboardJpaEntity entity = repository.findById(id) + .orElseThrow(() -> new NoSuchElementException(ErrorCode.PRODUCT_NOT_FOUND.getMessage())); + + /// 찾은 값을 도메인으로 변환 및 로직 수행 + CustomKeyboard domain = entity.toDomain(); + /// 도메인 로직 수행 + domain.addProduct(categoryId, productId); + /// 더티체킹 수행(영속성 컨테이너 있는 값 수정) + entity.update(domain); } } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/out/jpa/custom/CustomKeyboardJpaEntity.java b/src/main/java/site/kikihi/custom/platform/adapter/out/jpa/custom/CustomKeyboardJpaEntity.java index dfe468f..7395d99 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/out/jpa/custom/CustomKeyboardJpaEntity.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/out/jpa/custom/CustomKeyboardJpaEntity.java @@ -69,4 +69,18 @@ public CustomKeyboard toDomain(){ .build(); } + + /// 수정하는 함수 + public void update(CustomKeyboard domain) { + this.id = domain.getId(); + this.userId = domain.getUserId(); + this.frameId = domain.getFrameId(); + this.switchId = domain.getSwitchId(); + this.keyCapId = domain.getKeyCapId(); + this.accessoryId = domain.getAccessoryId(); + this.name = domain.getName(); + this.imageUrl = domain.getImageUrl(); + this.layout = domain.getLayout(); + } + } diff --git a/src/main/java/site/kikihi/custom/platform/application/out/custom/CustomKeyboardPort.java b/src/main/java/site/kikihi/custom/platform/application/out/custom/CustomKeyboardPort.java index 16dbbc8..b50209b 100644 --- a/src/main/java/site/kikihi/custom/platform/application/out/custom/CustomKeyboardPort.java +++ b/src/main/java/site/kikihi/custom/platform/application/out/custom/CustomKeyboardPort.java @@ -35,4 +35,6 @@ public interface CustomKeyboardPort { void deleteProductInsideCustomKeyboard(Long id, String categoryId, String productId); + void addProductInsideCustomKeyboard(Long id, String categoryId, String productId); + } diff --git a/src/main/java/site/kikihi/custom/platform/application/service/CustomKeyboardService.java b/src/main/java/site/kikihi/custom/platform/application/service/CustomKeyboardService.java index 2eabaab..3da93db 100644 --- a/src/main/java/site/kikihi/custom/platform/application/service/CustomKeyboardService.java +++ b/src/main/java/site/kikihi/custom/platform/application/service/CustomKeyboardService.java @@ -85,6 +85,20 @@ public CustomKeyboard saveCustomKeyboard(CustomKeyboardRequest request, UUID use @Override public void insertProductInCustomKeyboard(Long customKeyboardId, String categoryId, String productId, UUID userId) { + /// 해당 유저의 커스텀 키보드인지 체크 + boolean checked = port.existCustomKeyboardByUserIdAndId(userId, customKeyboardId); + + /// 키보드가 요청자의 것이 아니라면 에러 발생 + if (!checked) { + throw new IllegalStateException(ErrorCode.UNAUTHORIZED_DELETE_CUSTOM.getMessage()); + } + + /// 추가하고자 하는 상품이 존재하는지 체크 + Product product = getProduct(productId); + + /// 해당 유저의 상품이며, 커스텀 내부에 상품이 존재하기에 삭제 가능하다. + port.addProductInsideCustomKeyboard(customKeyboardId, categoryId, product.getId()); + } // ================= @@ -336,12 +350,13 @@ public void deleteCustomInside(Long customKeyboardId, String categoryId, String /// 커스텀 내부에 해당 상품의 ID가 있는지 여부 확인 boolean existed = port.existProductInsideCustomKeyboard(customKeyboardId, categoryId, productId); + /// 키보드에 상품이 없었다면 삭제 if (!existed) { throw new IllegalStateException(ErrorCode.BAD_PRODUCT_DELETE_CUSTOM.getMessage()); } /// 해당 유저의 상품이며, 커스텀 내부에 상품이 존재하기에 삭제 가능하다. - port.deleteCustomKeyboard(customKeyboardId); + port.deleteProductInsideCustomKeyboard(customKeyboardId, categoryId, productId); } diff --git a/src/main/java/site/kikihi/custom/platform/domain/custom/CustomKeyboard.java b/src/main/java/site/kikihi/custom/platform/domain/custom/CustomKeyboard.java index 3a03b7f..f8c0f9f 100644 --- a/src/main/java/site/kikihi/custom/platform/domain/custom/CustomKeyboard.java +++ b/src/main/java/site/kikihi/custom/platform/domain/custom/CustomKeyboard.java @@ -1,5 +1,6 @@ package site.kikihi.custom.platform.domain.custom; +import site.kikihi.custom.global.response.ErrorCode; import site.kikihi.custom.platform.domain.BaseDomain; import lombok.AllArgsConstructor; import lombok.Builder; @@ -47,4 +48,94 @@ public static CustomKeyboard of(UUID userId, CustomKeyboardLayout layout, String .build(); } + /// 내부에 존재하하는 것, 삭제하는 함수 + public void removeProduct(String category, String productId) { + switch (category) { + case "housing": + if (this.frameId.equals(productId)) { + /// 도메인 내부에 있었다면, null로 바꾸기 + this.frameId = null; + } else { + throw new IllegalStateException(ErrorCode.BAD_PRODUCT_DELETE_CUSTOM.getMessage()); + } + + break; + case "switch": + if (this.switchId.equals(productId)) { + /// 도메인 내부에 있었다면, null로 바꾸기 + this.switchId = null; + } else { + throw new IllegalStateException(ErrorCode.BAD_PRODUCT_DELETE_CUSTOM.getMessage()); + } + break; + + case "keycap": + if (this.keyCapId.equals(productId)) { + /// 도메인 내부에 있었다면, null로 바꾸기 + this.keyCapId = null; + } else { + throw new IllegalStateException(ErrorCode.BAD_PRODUCT_DELETE_CUSTOM.getMessage()); + } + break; + + case "accessory": + if (this.accessoryId.equals(productId)) { + /// 도메인 내부에 있었다면, null로 바꾸기 + this.accessoryId = null; + } else { + throw new IllegalStateException(ErrorCode.BAD_PRODUCT_DELETE_CUSTOM.getMessage()); + } + break; + + default: + throw new IllegalStateException(ErrorCode.BAD_PRODUCT_DELETE_CUSTOM.getMessage()); + } + + } + + /// 내부에 존재하하는 것, 삭제하는 함수 + public void addProduct(String category, String productId) { + + switch (category) { + case "housing": + if (this.frameId == null) { + /// 도메인 내부에 없었다면, 새로 추가하기 + this.frameId = productId; + } else { + throw new IllegalStateException(ErrorCode.BAD_PRODUCT_DELETE_CUSTOM.getMessage()); + } + break; + case "switch": + if (this.switchId == null) { + /// 도메인 내부에 없었다면, 새로 추가하기 + this.switchId = productId; + } else { + throw new IllegalStateException(ErrorCode.BAD_PRODUCT_DELETE_CUSTOM.getMessage()); + } + break; + + case "keycap": + if (this.keyCapId.equals(productId)) { + /// 도메인 내부에 있었다면, null로 바꾸기 + this.keyCapId = null; + } else { + throw new IllegalStateException(ErrorCode.BAD_PRODUCT_DELETE_CUSTOM.getMessage()); + } + break; + + case "accessory": + if (this.accessoryId == null) { + /// 도메인 내부에 있었다면, null로 바꾸기 + this.accessoryId = productId; + } else { + throw new IllegalStateException(ErrorCode.BAD_PRODUCT_DELETE_CUSTOM.getMessage()); + } + break; + + default: + throw new IllegalStateException(ErrorCode.BAD_PRODUCT_DELETE_CUSTOM.getMessage()); + } + + } + } diff --git a/src/main/java/site/kikihi/custom/platform/domain/custom/CustomKeyboardWithName.java b/src/main/java/site/kikihi/custom/platform/domain/custom/CustomKeyboardWithName.java index f951e28..c6832fd 100644 --- a/src/main/java/site/kikihi/custom/platform/domain/custom/CustomKeyboardWithName.java +++ b/src/main/java/site/kikihi/custom/platform/domain/custom/CustomKeyboardWithName.java @@ -30,10 +30,12 @@ public record CustomKeyboardWithName( UUID userId, String layout, String frameId, + String frameUrl, String frameName, String switchId, String switchName, String keyCapId, + String keyCapUrl, String keyCapName, String accessoryId, String accessoryName, @@ -60,10 +62,12 @@ public static CustomKeyboardWithName of( .userId(keyboard.getUserId()) .layout(keyboard.getLayout().getLayoutName()) .frameId(housingProduct.getId()) + .frameUrl(housingProduct.getMapping()) .frameName(housingProduct.getName()) .switchId(switchProduct.getId()) .switchName(switchProduct.getName()) .keyCapId(keyCapProduct.getId()) + .keyCapUrl(keyCapProduct.getMapping()) .keyCapName(keyCapProduct.getName()) .accessoryId(accessoryProduct != null ? accessoryProduct.getId() : null) .accessoryName(accessoryProduct != null ? accessoryProduct.getName() : null) From 075b7c8e6e5cc840a4480c924a4828f78c01e107 Mon Sep 17 00:00:00 2001 From: eedo_y Date: Fri, 19 Sep 2025 00:31:30 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9C=A8=20feat/KIKI-71=20:=20=EB=B6=80?= =?UTF-8?q?=ED=92=88=20=EC=82=AD=EC=A0=9C=20=EC=B6=94=EA=B0=80=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/CustomKeyboardListResponse.java | 4 +++ .../service/CustomKeyboardService.java | 22 +++++++++--- .../domain/custom/CustomKeyboardWithName.java | 35 +++++++++++-------- 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/custom/CustomKeyboardListResponse.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/custom/CustomKeyboardListResponse.java index 481d4ba..35a06ef 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/custom/CustomKeyboardListResponse.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/custom/CustomKeyboardListResponse.java @@ -40,6 +40,9 @@ public record CustomKeyboardListResponse( @Schema(description = "스위치 이름", example = "Gateron Ink Black v2") String switchName, + @Schema(description = "악세사리 이름", example = "손목 보호대") + String accessoryName, + @Schema(description = "썸네일 이미지 URL", example = "https://example.com/custom/101.jpg") String thumbnail, @@ -56,6 +59,7 @@ public static CustomKeyboardListResponse from(CustomKeyboardWithName entity){ .housingName(entity.frameName()) .keyCapName(entity.keyCapName()) .switchName(entity.switchName()) + .accessoryName(entity.accessoryName()) .thumbnail(entity.imageUrl()) .price(entity.totalPrice()) .build(); diff --git a/src/main/java/site/kikihi/custom/platform/application/service/CustomKeyboardService.java b/src/main/java/site/kikihi/custom/platform/application/service/CustomKeyboardService.java index 3da93db..982d303 100644 --- a/src/main/java/site/kikihi/custom/platform/application/service/CustomKeyboardService.java +++ b/src/main/java/site/kikihi/custom/platform/application/service/CustomKeyboardService.java @@ -68,7 +68,13 @@ public CustomKeyboard saveCustomKeyboard(CustomKeyboardRequest request, UUID use } /// 객체 생성 - var customKeyboard = CustomKeyboard.of(userId, request.getLayout(), frameProduct.getId(), switchProduct.getId(), keyCapProduct.getId(), accessoryId, request.getName(), "thumbnail"); + var customKeyboard = CustomKeyboard.of(userId, + request.getLayout(), + frameProduct.getId(), + switchProduct.getId(), + keyCapProduct.getId(), + accessoryId, + request.getName(), ""); /// 저장 후 리턴 return port.saveCustomKeyboard(customKeyboard); @@ -168,9 +174,9 @@ public CustomKeyboardWithName getCustomKeyboard(Long customKeyboardId) { /// 개별 상품 조회 // TODO! 한번에 조회하도록 쿼리문 수정 - var frameProduct = getProduct(keyBoard.getFrameId()); - var switchProduct = getProduct(keyBoard.getSwitchId()); - var keyCapProduct = getProduct(keyBoard.getKeyCapId()); + var frameProduct = findProductOrNull(keyBoard.getFrameId()); + var switchProduct = findProductOrNull(keyBoard.getSwitchId()); + var keyCapProduct = findProductOrNull(keyBoard.getKeyCapId()); String accessoryId = null; if (keyBoard.getAccessoryId() != null && !keyBoard.getAccessoryId().isBlank()) { @@ -374,6 +380,14 @@ private Product getProduct(String productId) { .orElseThrow(() -> new NoSuchElementException(ErrorCode.PRODUCT_NOT_FOUND.getMessage())); } + /** + * 못 찾으면 null 반환(선택 필드에 사용) + */ + private Product findProductOrNull(String productId) { + if (productId == null || productId.isBlank()) return null; + return productPort.getProduct(productId).orElse(null); + } + /** * 상품 여러개 조회 함수 * @param productIds 상품 IDs diff --git a/src/main/java/site/kikihi/custom/platform/domain/custom/CustomKeyboardWithName.java b/src/main/java/site/kikihi/custom/platform/domain/custom/CustomKeyboardWithName.java index c6832fd..f8af57d 100644 --- a/src/main/java/site/kikihi/custom/platform/domain/custom/CustomKeyboardWithName.java +++ b/src/main/java/site/kikihi/custom/platform/domain/custom/CustomKeyboardWithName.java @@ -51,30 +51,35 @@ public static CustomKeyboardWithName of( Product keyCapProduct, Product accessoryProduct) { - // accessoryProduct가 null이면 가격에 더하지 않음 - double totalPrice = housingProduct.getPrice() + switchProduct.getPrice() + keyCapProduct.getPrice(); - if (accessoryProduct != null) { - totalPrice += accessoryProduct.getPrice(); - } + // 총 가격 계산 (null 안전) + double totalPrice = 0.0; + if (housingProduct != null) totalPrice += housingProduct.getPrice(); + if (switchProduct != null) totalPrice += switchProduct.getPrice(); + if (keyCapProduct != null) totalPrice += keyCapProduct.getPrice(); + if (accessoryProduct != null) totalPrice += accessoryProduct.getPrice(); return CustomKeyboardWithName.builder() .id(keyboard.getId()) .userId(keyboard.getUserId()) - .layout(keyboard.getLayout().getLayoutName()) - .frameId(housingProduct.getId()) - .frameUrl(housingProduct.getMapping()) - .frameName(housingProduct.getName()) - .switchId(switchProduct.getId()) - .switchName(switchProduct.getName()) - .keyCapId(keyCapProduct.getId()) - .keyCapUrl(keyCapProduct.getMapping()) - .keyCapName(keyCapProduct.getName()) + .layout(keyboard.getLayout() != null ? keyboard.getLayout().getLayoutName() : null) + + .frameId(housingProduct != null ? housingProduct.getId() : null) + .frameUrl(housingProduct != null ? housingProduct.getMapping() : null) + .frameName(housingProduct != null ? housingProduct.getName() : null) + + .switchId(switchProduct != null ? switchProduct.getId() : null) + .switchName(switchProduct != null ? switchProduct.getName() : null) + + .keyCapId(keyCapProduct != null ? keyCapProduct.getId() : null) + .keyCapUrl(keyCapProduct != null ? keyCapProduct.getMapping() : null) + .keyCapName(keyCapProduct != null ? keyCapProduct.getName() : null) + .accessoryId(accessoryProduct != null ? accessoryProduct.getId() : null) .accessoryName(accessoryProduct != null ? accessoryProduct.getName() : null) + .name(keyboard.getName()) .totalPrice(totalPrice) .imageUrl(keyboard.getImageUrl()) .build(); } - }