Skip to content

Conversation

@dev-ant
Copy link
Contributor

@dev-ant dev-ant commented Nov 26, 2025

📋 상세 설명

  • 아이템 조회 API에 topCategory가 필수값으로 추가되고 페이지네이션/필터링 엔드포인트 도입
  • 경매 기록과의 동기화를 구현하면서 중복 감지 및 DB 인덱싱으로 조회 성능 개선
  • 게이트웨이 인증 인프라가 구축되었지만 API는 여전히 공개 접근 가능하게 설정

📊 체크리스트

  • PR 제목이 형식에 맞나요 e.g. feat: PR을 등록한다
  • 코드가 테스트 되었나요
  • 문서는 업데이트 되었나요
  • 불필요한 코드를 제거했나요
  • 이슈와 라벨이 등록되었나요

📆 마감일

Close #78, #77

@dev-ant dev-ant requested a review from Copilot November 26, 2025 16:33
@dev-ant dev-ant self-assigned this Nov 26, 2025
@dev-ant dev-ant added the ✨feature 새로운 기능 추가 label Nov 26, 2025
@github-actions
Copy link

github-actions bot commented Nov 26, 2025

✅ 테스트 결과 for PR

Build: success

🧪 테스트 실행 with Gradle
📈 Coverage: -0.00%

📁 테스트 결과
📁 커버리지 보고서 (HTML)

@codecov
Copy link

codecov bot commented Nov 26, 2025

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a required topCategory parameter to item metadata retrieval APIs and introduces new endpoints for paginated/filtered item queries and synchronization with auction history data. The changes enhance query performance through database indexing and implement authentication infrastructure via a gateway filter, though the API endpoints themselves remain publicly accessible.

Key Changes

  • Added topCategory as a required parameter for item search APIs with validation
  • Introduced new /detail and /summary endpoints with pagination and filtering capabilities
  • Implemented item synchronization from auction history with duplicate detection
  • Created database indexes for improved query performance on item_info table
  • Added gateway authentication filter infrastructure (but APIs remain public)

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
ItemInfoService.java Added search methods with topCategory validation and auction history sync logic
ItemInfoController.java Replaced category-based search endpoints with new detail/summary/sync endpoints
ItemInfoSearchRequest.java New DTO with required topCategory field validation
ItemInfoQueryDslRepository.java QueryDSL implementation for dynamic search with pagination
V12__create_item_info_indexes.sql Database indexes for top_category, sub_category, and name combinations
ItemInfoServiceTest.java Comprehensive tests for new search and sync functionality
SecurityConfig.java Enhanced security configuration with gateway auth filter integration
GatewayAuthFilter.java New filter to process authentication headers from gateway
AuctionHistoryJpaRepository.java Added query to fetch distinct item information

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 103 to 112
.description(null)
.inventoryWidth(null)
.inventoryHeight(null)
.inventoryMaxBundleCount(null)
.history(null)
.acquisitionMethod(null)
.storeSalesPrice(null)
.weaponType(null)
.repair(null)
.maxAlterationCount(null)
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Code maintainability issue: Explicitly setting all fields to null in the builder is verbose and adds no value since unset builder fields default to null anyway. This makes the code harder to maintain when the entity structure changes.

Consider simplifying to:

ItemInfo itemInfo = ItemInfo.builder()
        .id(itemInfoId)
        .build();

This achieves the same result with less code and is easier to maintain.

Suggested change
.description(null)
.inventoryWidth(null)
.inventoryHeight(null)
.inventoryMaxBundleCount(null)
.history(null)
.acquisitionMethod(null)
.storeSalesPrice(null)
.weaponType(null)
.repair(null)
.maxAlterationCount(null)

Copilot uses AI. Check for mistakes.
Comment on lines 93 to 94
String topCategory = (String) item[1];
String subCategory = (String) item[2];
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order of array destructuring does not match the query result order. The query returns a.itemName, a.itemTopCategory, a.itemSubCategory (line 42 of AuctionHistoryJpaRepository), but the code expects {name, topCategory, subCategory}. This should be {name, subCategory, topCategory} to match ItemInfoId constructor at line 96.

Lines 92-94 should be:

String itemName = (String) item[0];
String subCategory = (String) item[1];
String topCategory = (String) item[2];
Suggested change
String topCategory = (String) item[1];
String subCategory = (String) item[2];
String subCategory = (String) item[1];
String topCategory = (String) item[2];

Copilot uses AI. Check for mistakes.
Comment on lines 91 to 119
for (Object[] item : distinctItems) {
String itemName = (String) item[0];
String topCategory = (String) item[1];
String subCategory = (String) item[2];

ItemInfoId itemInfoId = new ItemInfoId(itemName, subCategory, topCategory);

// 이미 존재하는 아이템인지 확인
if (!itemInfoRepository.existsById(itemInfoId)) {
ItemInfo itemInfo =
ItemInfo.builder()
.id(itemInfoId)
.description(null)
.inventoryWidth(null)
.inventoryHeight(null)
.inventoryMaxBundleCount(null)
.history(null)
.acquisitionMethod(null)
.storeSalesPrice(null)
.weaponType(null)
.repair(null)
.maxAlterationCount(null)
.build();

newItemInfos.add(itemInfo);
syncedItemNames.add(itemName);
log.debug("Adding new item to sync: {}", itemName);
}
}
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential N+1 query problem. The code checks existsById() for each item in a loop (line 99), which generates one database query per item. For large datasets, this could result in hundreds or thousands of individual queries.

Consider using a batch approach:

  1. Fetch all existing ItemInfoIds in a single query
  2. Filter in-memory to identify new items
  3. Bulk insert new items

Example:

Set<ItemInfoId> existingIds = new HashSet<>(itemInfoRepository.findAllIds());
for (Object[] item : distinctItems) {
    ItemInfoId itemInfoId = new ItemInfoId(itemName, subCategory, topCategory);
    if (!existingIds.contains(itemInfoId)) {
        // add to new items
    }
}

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +31 to +33
.where(whereClause)
.orderBy(itemInfo.id.name.asc());

Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The searchWithPagination method hardcodes sorting to name ascending (line 32) and ignores the Pageable's sort configuration. This is inconsistent with the search method (lines 51-66) which properly applies the Pageable's sort information. Consider applying the Pageable's sort here as well for consistency, or document why this endpoint always sorts by name ascending.

Suggested change
.where(whereClause)
.orderBy(itemInfo.id.name.asc());
.where(whereClause);
// Apply Pageable's sort information, mirroring the logic from search()
if (pageable.getSort().isSorted()) {
pageable.getSort()
.forEach(
order -> {
if (order.getProperty().equals("name")
|| order.getProperty().equals("id.name")) {
query.orderBy(
order.isAscending()
? itemInfo.id.name.asc()
: itemInfo.id.name.desc());
}
});
} else {
// Default sort: name ascending
query.orderBy(itemInfo.id.name.asc());
}

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +69
org.springframework.data.domain.Sort.Direction direction) {
validateTopCategory(searchRequest);
// direction을 Pageable로 변환
Pageable pageable =
org.springframework.data.domain.PageRequest.of(
0,
Integer.MAX_VALUE,
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The findAllSummary method uses Integer.MAX_VALUE as the page size to fetch all records. This could cause memory and performance issues if the number of items grows large. Consider adding a maximum limit or implementing proper pagination for this endpoint as well.

Suggested change
org.springframework.data.domain.Sort.Direction direction) {
validateTopCategory(searchRequest);
// direction을 Pageable로 변환
Pageable pageable =
org.springframework.data.domain.PageRequest.of(
0,
Integer.MAX_VALUE,
org.springframework.data.domain.Sort.Direction direction,
int page,
int size) {
validateTopCategory(searchRequest);
// Enforce a maximum page size to prevent memory issues
final int MAX_PAGE_SIZE = 1000;
int safeSize = Math.min(size, MAX_PAGE_SIZE);
Pageable pageable =
org.springframework.data.domain.PageRequest.of(
page,
safeSize,

Copilot uses AI. Check for mistakes.
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@ToString
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default toString(): ItemInfoId inherits toString() from Object, and so is not suitable for printing.

Suggested change
@ToString
@ToString(exclude = "id")

Copilot uses AI. Check for mistakes.
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@ToString
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default toString(): ItemInfoId inherits toString() from Object, and so is not suitable for printing.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 23 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +75 to +79
private void validateTopCategory(ItemInfoSearchRequest searchRequest) {
if (searchRequest.topCategory() == null || searchRequest.topCategory().isBlank()) {
throw new CustomException(ItemInfoExceptionCode.TOP_CATEGORY_REQUIRED);
}
}
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The validateTopCategory method performs manual validation that duplicates the @NotBlank validation already present on the topCategory field in ItemInfoSearchRequest. Since the controller uses @Valid on the request parameter, Spring's validation framework will already enforce the @NotBlank constraint before the service method is called.

Consider removing this manual validation method and relying solely on the @Valid annotation at the controller level to avoid redundancy. If you want to keep defensive validation in the service layer, consider adding a comment explaining why (e.g., "defensive check for direct service calls").

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +89
// Roles가 없으면 익명 사용자로 처리
if (rolesHeader == null || rolesHeader.isEmpty()) {
log.debug("No roles found, creating anonymous authentication");
return new UsernamePasswordAuthenticationToken(userId, null);
}
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When rolesHeader is null or empty, the method creates an authentication with no authorities (line 88). This creates an authenticated user without any roles, which might not be the intended behavior.

Consider whether this should actually be treated as an anonymous/unauthenticated request instead, or if you want to require roles for all authenticated requests. If the current behavior is intentional, add a comment explaining that authenticated users without roles are valid in this system.

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +106
String role = rolesHeader.trim();
if (!role.startsWith("ROLE_")) {
role = "ROLE_" + role;
}
authorities.add(new SimpleGrantedAuthority(role));

log.debug(
"Created authentication for userId: {}, username: {}, role: {}",
userId,
usernameHeader,
role);
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The role parsing logic only handles a single role (line 96), but the header name X-Auth-Roles (plural) and the variable name rolesHeader suggest it should support multiple roles. If multiple roles are passed (e.g., "ROLE_USER,ROLE_ADMIN"), only one SimpleGrantedAuthority will be created for the entire string "ROLE_USER,ROLE_ADMIN".

If multiple roles are supported, the logic should split the header by a delimiter (e.g., comma) and create a SimpleGrantedAuthority for each role:

String[] roles = rolesHeader.split(",");
for (String role : roles) {
    role = role.trim();
    if (!role.startsWith("ROLE_")) {
        role = "ROLE_" + role;
    }
    authorities.add(new SimpleGrantedAuthority(role));
}

If only single role is supported, consider renaming the header to X-Auth-Role (singular) for clarity.

Suggested change
String role = rolesHeader.trim();
if (!role.startsWith("ROLE_")) {
role = "ROLE_" + role;
}
authorities.add(new SimpleGrantedAuthority(role));
log.debug(
"Created authentication for userId: {}, username: {}, role: {}",
userId,
usernameHeader,
role);
String[] roles = rolesHeader.split(",");
List<String> parsedRoles = new ArrayList<>();
for (String r : roles) {
String role = r.trim();
if (!role.startsWith("ROLE_")) {
role = "ROLE_" + role;
}
authorities.add(new SimpleGrantedAuthority(role));
parsedRoles.add(role);
}
log.debug(
"Created authentication for userId: {}, username: {}, roles: {}",
userId,
usernameHeader,
parsedRoles);

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +26
* <p>Gateway에서 전달하는 헤더: - X-Auth-User-Id: 사용자 ID (Long) - X-Auth-Username: 사용자 이메일/username
* (String) - X-Auth-Roles: 사용자 역할 (예: ROLE_USER, ROLE_ADMIN)
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The Javadoc comment states "Gateway에서 전달한 인증 헤더(X-Auth-*)를 기반으로..." but the actual description format has a line break issue. The description text appears on line 25 without proper spacing from the <p> tag on line 25.

Consider reformatting for better readability:

/**
 * Gateway에서 전달한 인증 헤더(X-Auth-*)를 기반으로 Spring Security의 Authentication을 생성하는 필터
 *
 * <p>Gateway에서 전달하는 헤더:
 * <ul>
 * <li>X-Auth-User-Id: 사용자 ID (Long)</li>
 * <li>X-Auth-Username: 사용자 이메일/username (String)</li>
 * <li>X-Auth-Roles: 사용자 역할 (예: ROLE_USER, ROLE_ADMIN)</li>
 * </ul>
 */
Suggested change
* <p>Gateway에서 전달하는 헤더: - X-Auth-User-Id: 사용자 ID (Long) - X-Auth-Username: 사용자 이메일/username
* (String) - X-Auth-Roles: 사용자 역할 (: ROLE_USER, ROLE_ADMIN)
* <p>Gateway에서 전달하는 헤더:
* <ul>
* <li>X-Auth-User-Id: 사용자 ID (Long)</li>
* <li>X-Auth-Username: 사용자 이메일/username (String)</li>
* <li>X-Auth-Roles: 사용자 역할 (: ROLE_USER, ROLE_ADMIN)</li>
* </ul>

Copilot uses AI. Check for mistakes.
@dev-ant dev-ant merged commit 67ad8aa into dev Dec 5, 2025
1 of 2 checks passed
@dev-ant dev-ant deleted the feat/reduce-item-info-api-return-columm branch December 5, 2025 10:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨feature 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

아이템 메타 데이터 조회 API에 item_top_category 파라미터 추가

2 participants