From bc7beeb46667b68c929437bef2217f52a63e36b9 Mon Sep 17 00:00:00 2001 From: SUH SAECHAN Date: Fri, 27 Feb 2026 15:27:33 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=5F?= =?UTF-8?q?=EC=B5=9C=EA=B7=BC=5F=EB=AA=A9=EB=A1=9D=5F=ED=81=B4=EB=A6=AD=5F?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=5F=EC=B6=94=EA=B0=80=20:=20feat=20:=20?= =?UTF-8?q?=EC=B5=9C=EA=B7=BC=EB=AA=A9=EB=A1=9D=20=ED=81=B4=EB=A6=AD=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20https://github.com/TE?= =?UTF-8?q?AM-ROMROM/RomRom-BE/issues/552?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 59 ++++ .../com/romrom/common/dto/AdminResponse.java | 9 + .../com/romrom/item/service/ItemService.java | 16 + .../romrom/member/service/MemberService.java | 16 + .../controller/api/AdminApiController.java | 25 +- .../api/AdminApiControllerDocs.java | 294 ++++++++++++++++++ 6 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 RomRom-Web/src/main/java/com/romrom/web/controller/api/AdminApiControllerDocs.java diff --git a/CLAUDE.md b/CLAUDE.md index 58e26cce..9ce411a5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,11 +8,70 @@ ## 전체 API 컨벤션 +### Request/Response 구조 원칙 +- **하나의 Controller에 하나의 Request, 하나의 Response**로 관리 +- 어쩔 수 없는 예외 상황을 제외하면 Controller마다 전용 Request/Response 클래스 사용 +- 예: `AdminApiController` → `AdminRequest`, `AdminResponse` + +### POST API 패턴 (전체 공통) +- **모든 API는 POST + `consumes = MediaType.MULTIPART_FORM_DATA_VALUE` + `@ModelAttribute` 패턴 사용** +- 예시: + ```java + @PostMapping(value = "/sign-in", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @LogMonitor + public ResponseEntity signIn(@ModelAttribute AuthRequest request) { + return ResponseEntity.ok(authService.signIn(request)); + } + ``` + ### Response 원칙 - 프로젝트 전체적으로 Entity 객체를 DTO로 변환하지 않고 DB 값 그대로 Response에 담아서 전송 - 별도의 data class 변환 없이 JPA Entity를 직접 응답에 포함하는 것이 기본 원칙 - 일부 예외 케이스를 제외하면 Entity → DTO 매핑 없이 직접 반환 +## API 문서화 컨벤션 (Swagger / @ApiChangeLog) + +### 구조 원칙 +- **각 Controller마다 전용 `*ControllerDocs` 인터페이스를 생성**하고, Controller는 이 인터페이스를 `implements` +- Swagger 어노테이션(`@Operation`, `@Tag`, `@ApiChangeLogs`)은 **인터페이스에만** 작성, 구현체(Controller)는 깔끔하게 유지 +- 파일 위치: Controller와 **동일한 패키지**에 `{ControllerName}Docs.java`로 생성 + +### @ApiChangeLog 작성 규칙 +- 라이브러리: `me.suhsaechan.suhapilog.annotation.ApiChangeLog` / `ApiChangeLogs` +- **날짜 형식**: `"YYYY.MM.DD"` (예: `"2026.02.27"`) +- **author**: `Author.SUHSAECHAN` 등 `com.romrom.common.dto.Author` 상수 사용 +- **issueNumber**: GitHub Issue 번호 (int) +- **description**: 변경 내용 한 줄 설명 +- 예시: + ```java + @ApiChangeLogs({ + @ApiChangeLog(date = "2026.02.27", author = Author.SUHSAECHAN, issueNumber = 552, description = "관리자용 물품 단건 조회 API 추가"), + }) + ``` + +### @Operation 작성 규칙 +- **summary**: API 한 줄 요약 +- **description**: 아래 마크다운 포맷 준수 + ``` + ## 인증(JWT): **필요/불필요** + + ## 요청 파라미터 (DTO명) + - **`fieldName`**: 설명 + + ## 반환값 (DTO명) + - **`fieldName`**: 설명 + + ## 에러코드 + - **`ERROR_CODE`**: 설명 + ``` + +### Author 상수 목록 (com.romrom.common.dto.Author) +- `Author.SUHSAECHAN` = "서새찬" +- `Author.BAEKJIHOON` = "백지훈" +- `Author.WISEUNGJAE` = "위승재" +- `Author.KIMNAYOUNG` = "김나영" +- `Author.KIMKYUSEOP` = "김규섭" + ## Admin API 컨벤션 ### DTO 네이밍 diff --git a/RomRom-Common/src/main/java/com/romrom/common/dto/AdminResponse.java b/RomRom-Common/src/main/java/com/romrom/common/dto/AdminResponse.java index b661bc4f..e4dc5c7a 100644 --- a/RomRom-Common/src/main/java/com/romrom/common/dto/AdminResponse.java +++ b/RomRom-Common/src/main/java/com/romrom/common/dto/AdminResponse.java @@ -1,5 +1,7 @@ package com.romrom.common.dto; +import com.romrom.item.entity.postgres.Item; +import com.romrom.member.entity.Member; import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDateTime; import java.util.UUID; @@ -22,6 +24,13 @@ public class AdminResponse { @Schema(description = "전체 카운트") private Long totalCount; + // 단건 조회 응답 데이터 + @Schema(description = "단건 물품 (상세 조회용)") + private Item item; + + @Schema(description = "단건 회원 (상세 조회용)") + private Member member; + // 물품 관련 응답 데이터 @Schema(description = "페이지네이션된 물품 목록") private Page items; diff --git a/RomRom-Domain-Item/src/main/java/com/romrom/item/service/ItemService.java b/RomRom-Domain-Item/src/main/java/com/romrom/item/service/ItemService.java index 1eac014a..36b752c4 100644 --- a/RomRom-Domain-Item/src/main/java/com/romrom/item/service/ItemService.java +++ b/RomRom-Domain-Item/src/main/java/com/romrom/item/service/ItemService.java @@ -708,6 +708,22 @@ public AdminResponse getRecentItemsForAdmin(int limit) { .build(); } + /** + * 관리자용 물품 단건 조회 + */ + @Transactional(readOnly = true) + public AdminResponse getItemDetailForAdmin(AdminRequest request) { + Item item = itemRepository.findById(request.getItemId()) + .orElseThrow(() -> { + log.error("관리자 물품 단건 조회 실패 - 존재하지 않는 itemId: {}", request.getItemId()); + return new CustomException(ErrorCode.ITEM_NOT_FOUND); + }); + + return AdminResponse.builder() + .item(item) + .build(); + } + /** * 관리자용 물품 삭제 */ diff --git a/RomRom-Domain-Member/src/main/java/com/romrom/member/service/MemberService.java b/RomRom-Domain-Member/src/main/java/com/romrom/member/service/MemberService.java index 8c562da3..831a05f4 100644 --- a/RomRom-Domain-Member/src/main/java/com/romrom/member/service/MemberService.java +++ b/RomRom-Domain-Member/src/main/java/com/romrom/member/service/MemberService.java @@ -308,6 +308,22 @@ public AdminResponse getRecentMembersForAdmin(int limit) { .build(); } + /** + * 관리자용 회원 단건 조회 + */ + @Transactional(readOnly = true) + public AdminResponse getMemberDetailForAdmin(AdminRequest request) { + Member member = memberRepository.findById(request.getMemberId()) + .orElseThrow(() -> { + log.error("관리자 회원 단건 조회 실패 - 존재하지 않는 memberId: {}", request.getMemberId()); + return new CustomException(ErrorCode.MEMBER_NOT_FOUND); + }); + + return AdminResponse.builder() + .member(member) + .build(); + } + /** * PK 기반 회원조회 */ diff --git a/RomRom-Web/src/main/java/com/romrom/web/controller/api/AdminApiController.java b/RomRom-Web/src/main/java/com/romrom/web/controller/api/AdminApiController.java index f44d48e0..09001836 100644 --- a/RomRom-Web/src/main/java/com/romrom/web/controller/api/AdminApiController.java +++ b/RomRom-Web/src/main/java/com/romrom/web/controller/api/AdminApiController.java @@ -22,7 +22,7 @@ @RequiredArgsConstructor @RequestMapping("/api/admin") @Slf4j -public class AdminApiController { +public class AdminApiController implements AdminApiControllerDocs { private final AdminAuthService adminAuthService; private final ItemService itemService; @@ -30,6 +30,7 @@ public class AdminApiController { private final AdminReportService adminReportService; + @Override @PostMapping(value = "/login", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity login(@ModelAttribute AdminRequest request, HttpServletResponse response) { @@ -71,6 +72,7 @@ public ResponseEntity login(@ModelAttribute AdminRequest request, } } + @Override @PostMapping(value = "/logout", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity logout(@CookieValue(value = "refreshToken", required = false) String refreshTokenFromCookie, HttpServletResponse response) { @@ -104,6 +106,7 @@ public ResponseEntity logout(@CookieValue(value = "refreshToken", required // ==================== Dashboard ==================== + @Override @GetMapping("/dashboard/stats") @LogMonitor public ResponseEntity getDashboardStats() { @@ -115,12 +118,14 @@ public ResponseEntity getDashboardStats() { .build()); } + @Override @GetMapping("/dashboard/recent-members") @LogMonitor public ResponseEntity getRecentMembers() { return ResponseEntity.ok(memberService.getRecentMembersForAdmin(8)); } + @Override @GetMapping("/dashboard/recent-items") @LogMonitor public ResponseEntity getRecentItems() { @@ -129,12 +134,21 @@ public ResponseEntity getRecentItems() { // ==================== Items ==================== + @Override + @PostMapping(value = "/items/detail", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @LogMonitor + public ResponseEntity getItemDetail(@ModelAttribute AdminRequest request) { + return ResponseEntity.ok(itemService.getItemDetailForAdmin(request)); + } + + @Override @GetMapping("/items") @LogMonitor public ResponseEntity getItems(@ModelAttribute AdminRequest request) { return ResponseEntity.ok(itemService.getItemsForAdmin(request)); } + @Override @DeleteMapping("/items/{itemId}") @LogMonitor public ResponseEntity deleteItem(@PathVariable UUID itemId) { @@ -144,6 +158,14 @@ public ResponseEntity deleteItem(@PathVariable UUID itemId) { // ==================== Members ==================== + @Override + @PostMapping(value = "/members/detail", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @LogMonitor + public ResponseEntity getMemberDetail(@ModelAttribute AdminRequest request) { + return ResponseEntity.ok(memberService.getMemberDetailForAdmin(request)); + } + + @Override @GetMapping("/members") @LogMonitor public ResponseEntity getMembers(@ModelAttribute AdminRequest request) { @@ -152,6 +174,7 @@ public ResponseEntity getMembers(@ModelAttribute AdminRequest req // ==================== Reports ==================== + @Override @PostMapping("/reports") @LogMonitor public ResponseEntity handleReports(@RequestBody AdminReportRequest request) { diff --git a/RomRom-Web/src/main/java/com/romrom/web/controller/api/AdminApiControllerDocs.java b/RomRom-Web/src/main/java/com/romrom/web/controller/api/AdminApiControllerDocs.java new file mode 100644 index 00000000..575f2871 --- /dev/null +++ b/RomRom-Web/src/main/java/com/romrom/web/controller/api/AdminApiControllerDocs.java @@ -0,0 +1,294 @@ +package com.romrom.web.controller.api; + +import com.romrom.common.dto.AdminRequest; +import com.romrom.common.dto.AdminResponse; +import com.romrom.common.dto.Author; +import com.romrom.report.dto.AdminReportRequest; +import com.romrom.report.dto.AdminReportResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import me.suhsaechan.suhapilog.annotation.ApiChangeLog; +import me.suhsaechan.suhapilog.annotation.ApiChangeLogs; +import org.springframework.http.ResponseEntity; + +import java.util.UUID; + +@Tag(name = "Admin API", description = "관리자 전용 API") +public interface AdminApiControllerDocs { + + // ==================== Auth ==================== + + @ApiChangeLogs({ + @ApiChangeLog(date = "2026.02.27", author = Author.SUHSAECHAN, issueNumber = 0, description = "관리자 로그인 API 구현"), + }) + @Operation( + summary = "관리자 로그인", + description = """ + ## 인증(JWT): **불필요** + + ## 요청 파라미터 (AdminRequest) + - **`username`**: 관리자 아이디 + - **`password`**: 관리자 비밀번호 + + ## 반환값 (AdminResponse) + - **`accessToken`**: 발급된 AccessToken + - **`refreshToken`**: 발급된 RefreshToken + - **`username`**: 관리자 아이디 + - **`role`**: 관리자 권한 + + ## 에러코드 + - **`INVALID_CREDENTIALS`**: 아이디 또는 비밀번호가 올바르지 않습니다. + """ + ) + ResponseEntity login(AdminRequest request, HttpServletResponse response); + + @ApiChangeLogs({ + @ApiChangeLog(date = "2026.02.27", author = Author.SUHSAECHAN, issueNumber = 0, description = "관리자 로그아웃 API 구현"), + }) + @Operation( + summary = "관리자 로그아웃", + description = """ + ## 인증(JWT): **필요** + + ## 동작 설명 + - refreshToken 쿠키 무효화 처리 + - accessToken, refreshToken, authStatus 쿠키 삭제 + + ## 반환값 + - 성공 시 상태코드 200 (OK)와 빈 응답 본문 + """ + ) + ResponseEntity logout(String refreshTokenFromCookie, HttpServletResponse response); + + // ==================== Dashboard ==================== + + @ApiChangeLogs({ + @ApiChangeLog(date = "2026.02.27", author = Author.SUHSAECHAN, issueNumber = 0, description = "관리자 대시보드 통계 API 구현"), + }) + @Operation( + summary = "대시보드 통계 조회", + description = """ + ## 인증(JWT): **필요** (관리자) + + ## 반환값 (AdminResponse) + - **`dashboardStats.totalMembers`**: 전체 회원 수 + - **`dashboardStats.totalItems`**: 전체 물품 수 + """ + ) + ResponseEntity getDashboardStats(); + + @ApiChangeLogs({ + @ApiChangeLog(date = "2026.02.27", author = Author.SUHSAECHAN, issueNumber = 0, description = "대시보드 최근 가입 회원 목록 API 구현"), + }) + @Operation( + summary = "대시보드 최근 가입 회원 목록 조회", + description = """ + ## 인증(JWT): **필요** (관리자) + + ## 반환값 (AdminResponse) + - **`members`**: 최근 가입 회원 8명 목록 (Page) + - **`memberId`**: 회원 ID (UUID) + - **`nickname`**: 닉네임 + - **`profileUrl`**: 프로필 이미지 URL + - **`email`**: 이메일 + - **`isActive`**: 활성 상태 + - **`createdDate`**: 가입일 + - **`lastLoginDate`**: 최종 로그인일 + - **`totalCount`**: 반환된 회원 수 + """ + ) + ResponseEntity getRecentMembers(); + + @ApiChangeLogs({ + @ApiChangeLog(date = "2026.02.27", author = Author.SUHSAECHAN, issueNumber = 0, description = "대시보드 최근 등록 물품 목록 API 구현"), + }) + @Operation( + summary = "대시보드 최근 등록 물품 목록 조회", + description = """ + ## 인증(JWT): **필요** (관리자) + + ## 반환값 (AdminResponse) + - **`items`**: 최근 등록 물품 8개 목록 (Page) + - **`itemId`**: 물품 ID (UUID) + - **`itemName`**: 물품명 + - **`itemDescription`**: 물품 설명 + - **`itemCategory`**: 카테고리 + - **`itemCondition`**: 물품 상태 + - **`itemStatus`**: 거래 상태 + - **`price`**: 가격 + - **`likeCount`**: 좋아요 수 + - **`mainImageUrl`**: 대표 이미지 URL + - **`sellerNickname`**: 판매자 닉네임 + - **`sellerId`**: 판매자 ID (UUID) + - **`createdDate`**: 등록일 + - **`totalCount`**: 반환된 물품 수 + """ + ) + ResponseEntity getRecentItems(); + + // ==================== Items ==================== + + @ApiChangeLogs({ + @ApiChangeLog(date = "2026.02.27", author = Author.SUHSAECHAN, issueNumber = 552, description = "관리자용 물품 단건 조회 API 추가"), + }) + @Operation( + summary = "관리자용 물품 단건 조회", + description = """ + ## 인증(JWT): **필요** (관리자) + + ## 요청 파라미터 (AdminRequest) + - **`itemId`**: 조회할 물품 ID (UUID, 필수) + + ## 반환값 (AdminResponse) + - **`item`**: 물품 Entity 전체 정보 + - **`itemId`**: 물품 ID + - **`itemName`**: 물품명 + - **`itemDescription`**: 물품 설명 + - **`itemCategory`**: 카테고리 + - **`itemCondition`**: 물품 상태 + - **`itemStatus`**: 거래 상태 + - **`price`**: 가격 + - **`likeCount`**: 좋아요 수 + - **`itemImages`**: 이미지 목록 + - **`member`**: 판매자 정보 + - **`createdDate`**: 등록일 + + ## 에러코드 + - **`ITEM_NOT_FOUND`**: 해당 물품을 찾을 수 없습니다. + """ + ) + ResponseEntity getItemDetail(AdminRequest request); + + @ApiChangeLogs({ + @ApiChangeLog(date = "2026.02.27", author = Author.SUHSAECHAN, issueNumber = 0, description = "관리자용 물품 목록 조회 API 구현"), + }) + @Operation( + summary = "관리자용 물품 목록 조회", + description = """ + ## 인증(JWT): **필요** (관리자) + + ## 요청 파라미터 (AdminRequest) + - **`pageNumber`**: 페이지 번호 (기본값: 0) + - **`pageSize`**: 페이지 크기 (기본값: 20) + - **`sortBy`**: 정렬 필드 (기본값: createdDate) + - **`sortDirection`**: 정렬 방향 (기본값: DESC) + - **`searchKeyword`**: 검색 키워드 (물품명, 설명, 판매자 닉네임) + - **`itemCategory`**: 카테고리 필터 + - **`itemCondition`**: 물품 상태 필터 + - **`itemStatus`**: 거래 상태 필터 + - **`minPrice`**: 최소 가격 + - **`maxPrice`**: 최대 가격 + - **`startDate`**: 등록일 시작 (yyyy-MM-dd) + - **`endDate`**: 등록일 종료 (yyyy-MM-dd) + + ## 반환값 (AdminResponse) + - **`items`**: 물품 목록 (Page) + - **`totalCount`**: 전체 물품 수 + """ + ) + ResponseEntity getItems(AdminRequest request); + + @ApiChangeLogs({ + @ApiChangeLog(date = "2026.02.27", author = Author.SUHSAECHAN, issueNumber = 0, description = "관리자용 물품 삭제 API 구현"), + }) + @Operation( + summary = "관리자용 물품 삭제", + description = """ + ## 인증(JWT): **필요** (관리자) + + ## Path Variable + - **`itemId`**: 삭제할 물품 ID (UUID) + + ## 동작 설명 + - 물품 Soft Delete 처리 + - 관련 이미지, 임베딩, 좋아요 기록 등 연관 데이터 삭제 + + ## 반환값 + - 성공 시 상태코드 200 (OK)와 빈 응답 본문 + + ## 에러코드 + - **`ITEM_NOT_FOUND`**: 해당 물품을 찾을 수 없습니다. + """ + ) + ResponseEntity deleteItem(UUID itemId); + + // ==================== Members ==================== + + @ApiChangeLogs({ + @ApiChangeLog(date = "2026.02.27", author = Author.SUHSAECHAN, issueNumber = 552, description = "관리자용 회원 단건 조회 API 추가"), + }) + @Operation( + summary = "관리자용 회원 단건 조회", + description = """ + ## 인증(JWT): **필요** (관리자) + + ## 요청 파라미터 (AdminRequest) + - **`memberId`**: 조회할 회원 ID (UUID, 필수) + + ## 반환값 (AdminResponse) + - **`member`**: 회원 Entity 전체 정보 + - **`memberId`**: 회원 ID + - **`email`**: 이메일 + - **`nickname`**: 닉네임 + - **`profileUrl`**: 프로필 이미지 URL + - **`accountStatus`**: 계정 상태 + - **`socialPlatform`**: 소셜 로그인 플랫폼 + - **`role`**: 권한 + - **`totalLikeCount`**: 받은 좋아요 수 + - **`createdDate`**: 가입일 + - **`lastActiveAt`**: 마지막 활동 시간 + + ## 에러코드 + - **`MEMBER_NOT_FOUND`**: 해당 회원을 찾을 수 없습니다. + """ + ) + ResponseEntity getMemberDetail(AdminRequest request); + + @ApiChangeLogs({ + @ApiChangeLog(date = "2026.02.27", author = Author.SUHSAECHAN, issueNumber = 0, description = "관리자용 회원 목록 조회 API 구현"), + }) + @Operation( + summary = "관리자용 회원 목록 조회", + description = """ + ## 인증(JWT): **필요** (관리자) + + ## 요청 파라미터 (AdminRequest) + - **`pageNumber`**: 페이지 번호 (기본값: 0) + - **`pageSize`**: 페이지 크기 (기본값: 20) + - **`sortBy`**: 정렬 필드 (기본값: createdDate) + - **`sortDirection`**: 정렬 방향 (기본값: DESC) + - **`searchKeyword`**: 검색 키워드 (닉네임, 이메일) + + ## 반환값 (AdminResponse) + - **`members`**: 회원 목록 (Page) + - **`totalCount`**: 전체 회원 수 + """ + ) + ResponseEntity getMembers(AdminRequest request); + + // ==================== Reports ==================== + + @ApiChangeLogs({ + @ApiChangeLog(date = "2026.02.27", author = Author.SUHSAECHAN, issueNumber = 0, description = "관리자 신고 관리 API 구현"), + }) + @Operation( + summary = "관리자 신고 관리 (Action 기반)", + description = """ + ## 인증(JWT): **필요** (관리자) + + ## 요청 파라미터 (AdminReportRequest) + - **`action`**: 수행할 동작 + - `item-list`: 물품 신고 목록 조회 + - `member-list`: 회원 신고 목록 조회 + - `item-detail`: 물품 신고 상세 조회 + - `member-detail`: 회원 신고 상세 조회 + - `update-status`: 신고 상태 변경 + - `stats`: 신고 통계 조회 + + ## 반환값 (AdminReportResponse) + - action에 따라 반환값 상이 + """ + ) + ResponseEntity handleReports(AdminReportRequest request); +} From ec23bd024d84018ac5cfb5536b35c06658165e8d Mon Sep 17 00:00:00 2001 From: SUH SAECHAN Date: Fri, 27 Feb 2026 15:39:38 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=5F?= =?UTF-8?q?=EC=B5=9C=EA=B7=BC=5F=EB=AA=A9=EB=A1=9D=5F=ED=81=B4=EB=A6=AD=5F?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=5F=EC=B6=94=EA=B0=80=20:=20fix=20:=20n+1?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95,=20enum=20=EB=A7=A4?= =?UTF-8?q?=ED=95=91=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95=20https://g?= =?UTF-8?q?ithub.com/TEAM-ROMROM/RomRom-BE/issues/552?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/romrom/common/dto/AdminResponse.java | 13 ++++++------- .../item/repository/postgres/ItemRepository.java | 4 ++++ .../java/com/romrom/item/service/ItemService.java | 4 ++-- .../com/romrom/member/service/MemberService.java | 5 +++-- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/RomRom-Common/src/main/java/com/romrom/common/dto/AdminResponse.java b/RomRom-Common/src/main/java/com/romrom/common/dto/AdminResponse.java index e4dc5c7a..4cb5bc4f 100644 --- a/RomRom-Common/src/main/java/com/romrom/common/dto/AdminResponse.java +++ b/RomRom-Common/src/main/java/com/romrom/common/dto/AdminResponse.java @@ -1,5 +1,6 @@ package com.romrom.common.dto; +import com.romrom.common.constant.AccountStatus; import com.romrom.item.entity.postgres.Item; import com.romrom.member.entity.Member; import io.swagger.v3.oas.annotations.media.Schema; @@ -98,11 +99,6 @@ public static class AdminItemDto { @Schema(description = "수정일") private LocalDateTime updatedDate; - public static AdminItemDto from(Object item, Object itemImages) { - // 이 메서드는 ItemService에서 구체적인 타입으로 구현되어야 합니다 - // 현재는 타입 안전성을 위해 기본 구현만 제공 - return AdminItemDto.builder().build(); - } } @ToString @@ -125,13 +121,16 @@ public static class AdminMemberDto { @Schema(description = "이메일") private String email; - @Schema(description = "활성 상태") + @Schema(description = "활성 상태 (isDeleted 반전)") private Boolean isActive; + @Schema(description = "계정 상태 (ACTIVE, SUSPENDED 등)") + private AccountStatus accountStatus; + @Schema(description = "가입일") private LocalDateTime createdDate; - @Schema(description = "최종 로그인일") + @Schema(description = "최종 활동일") private LocalDateTime lastLoginDate; } diff --git a/RomRom-Domain-Item/src/main/java/com/romrom/item/repository/postgres/ItemRepository.java b/RomRom-Domain-Item/src/main/java/com/romrom/item/repository/postgres/ItemRepository.java index e4bb8664..a05cc62c 100644 --- a/RomRom-Domain-Item/src/main/java/com/romrom/item/repository/postgres/ItemRepository.java +++ b/RomRom-Domain-Item/src/main/java/com/romrom/item/repository/postgres/ItemRepository.java @@ -3,6 +3,7 @@ import com.romrom.item.entity.postgres.Item; import com.romrom.member.entity.Member; import java.util.List; +import java.util.Optional; import java.util.UUID; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -54,4 +55,7 @@ public interface ItemRepository extends JpaRepository, ItemRepositor @Query("SELECT i.itemId FROM Item i WHERE i.member.memberId = :memberId AND i.isDeleted = false") List findAllIdsByMemberId(@Param("memberId") UUID memberId); + + @Query("SELECT i FROM Item i LEFT JOIN FETCH i.member LEFT JOIN FETCH i.itemImages WHERE i.itemId = :itemId") + Optional findByItemIdWithDetails(@Param("itemId") UUID itemId); } diff --git a/RomRom-Domain-Item/src/main/java/com/romrom/item/service/ItemService.java b/RomRom-Domain-Item/src/main/java/com/romrom/item/service/ItemService.java index 36b752c4..1ba2557d 100644 --- a/RomRom-Domain-Item/src/main/java/com/romrom/item/service/ItemService.java +++ b/RomRom-Domain-Item/src/main/java/com/romrom/item/service/ItemService.java @@ -704,7 +704,7 @@ public AdminResponse getRecentItemsForAdmin(int limit) { return AdminResponse.builder() .items(adminItemDtoPage) - .totalCount((long) adminItemDtoPage.getContent().size()) + .totalCount(adminItemDtoPage.getTotalElements()) .build(); } @@ -713,7 +713,7 @@ public AdminResponse getRecentItemsForAdmin(int limit) { */ @Transactional(readOnly = true) public AdminResponse getItemDetailForAdmin(AdminRequest request) { - Item item = itemRepository.findById(request.getItemId()) + Item item = itemRepository.findByItemIdWithDetails(request.getItemId()) .orElseThrow(() -> { log.error("관리자 물품 단건 조회 실패 - 존재하지 않는 itemId: {}", request.getItemId()); return new CustomException(ErrorCode.ITEM_NOT_FOUND); diff --git a/RomRom-Domain-Member/src/main/java/com/romrom/member/service/MemberService.java b/RomRom-Domain-Member/src/main/java/com/romrom/member/service/MemberService.java index 831a05f4..f89e484f 100644 --- a/RomRom-Domain-Member/src/main/java/com/romrom/member/service/MemberService.java +++ b/RomRom-Domain-Member/src/main/java/com/romrom/member/service/MemberService.java @@ -297,14 +297,15 @@ public AdminResponse getRecentMembersForAdmin(int limit) { .profileUrl(member.getProfileUrl()) .email(member.getEmail()) .isActive(!member.getIsDeleted()) + .accountStatus(member.getAccountStatus()) .createdDate(member.getCreatedDate()) - .lastLoginDate(member.getUpdatedDate()) // 임시로 updatedDate 사용 + .lastLoginDate(member.getLastActiveAt()) .build() ); return AdminResponse.builder() .members(adminMemberDtoPage) - .totalCount((long) adminMemberDtoPage.getContent().size()) + .totalCount(adminMemberDtoPage.getTotalElements()) .build(); }