Main & Develop 코드 버전 동기화#360
Conversation
feat: 예외 로직 분리
…mail fix: Apple 재로그인 시 email 없는 경우 providerId로 기존 유저 조회
menuLike/menuId 누락 시 FAILED_VALIDATION으로 실패시키고, isLike/imageUrls/menuLikes null도 안전하게 처리해 NPE를 방지합니다.
CreateMenuReviewRequestV2의 rating/menuLike와 MenuLikeRequest의 menuId에 검증을 추가해 잘못된 요청을 조기에 400으로 처리합니다.
Removed placeholder for resolved issue number in PR template.
fix: 리뷰v2 - menu 리뷰 작성시 menuLike가 null일때 발생하는 NPE을 방지해요
…iod-type feat: 제휴 기간 타입 구분을 위한 periodType 추가
* feat: User Entity내 language컬럼 추가 및 flyway 쿼리문 추가 * feat: 유저 언어 설정 API 추가 및 UserService 중복 코드 제거 * feat: College Entity내 i8n컬럼 추가 및 flyway 쿼리문 추가 * feat: Department Entity내 i8n컬럼 추가 및 flyway 쿼리문 추가 * feat: PartnershipRestaurant Entity내 i8n컬럼 추가 및 flyway 쿼리문 추가 * feat: 제휴정보 조회 시 유저의 언어 설정에 따라서 store_name 분기 처리 추가 * feat: 단과대/학과 정보 조회 시 유저의 언어 설정에 따른 분기 처리 추가 * feat: 유저 프로필 조회에 언어 값 추가 및 유저의 언어 설정 조회 API 추가 * fix: 리뷰사항 반영 * fix: UserService @component 중복 선언 제거 및 @transactional import 경로 수정 * fix: 리뷰 반영
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이 PR은 다국어 지원(i18n) 기능을 도입하여 글로벌 사용자 경험을 개선하고, 파트너십 정보에 기간 타입을 추가하여 운영의 유연성을 확보했습니다. 또한, Apple 로그인 시 발생할 수 있는 이메일 누락 케이스를 처리하고 리뷰 관련 입력값 검증을 강화하여 시스템의 전반적인 안정성을 향상시켰습니다. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. 다국어 지원으로 넓어지는 세상, 코드마다 언어의 꽃이 피어나네. 사용자 설정에 맞춘 정보들, 더 나은 서비스로 나아가는 길. Footnotes
|
There was a problem hiding this comment.
Code Review
이번 풀 리퀘스트는 다국어 지원(KO, EN, JA, VI) 기능 추가 및 사용자 언어 설정 API 구현, 그리고 제휴(Partnership) 정보에 기간 타입(PeriodType)을 도입하는 변경 사항을 포함하고 있습니다. 리뷰어는 성능 향상을 위해 ReviewServiceV2 내 루프 및 스트림 수집기에서 findById를 반복 호출하는 대신 findAllById를 사용하여 배치 조회할 것을 제안했습니다. 또한, 다국어 지원의 일관성을 위해 PartnershipInfo.fromEntity에 Language 파라미터를 추가하여 단과대 및 학과명을 처리하도록 권장하였으며, SystemAppleAuthenticator에서 JWT 검증 시 발생할 수 있는 다양한 예외를 포괄적으로 처리하도록 예외 캐치 범위를 넓힐 것을 제안했습니다. 마지막으로, JPA EnumType.STRING 매핑 시 향후 확장성을 고려하여 DB 마이그레이션 파일에서 ENUM 타입 대신 VARCHAR 타입을 사용할 것을 제안했습니다.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| List<MenuLikeRequest> menuLikes = Optional.ofNullable(request.getMenuLikes()).orElse(Collections.emptyList()); | ||
| for (MenuLikeRequest menuLike : menuLikes) { | ||
| if (menuLike == null || menuLike.getMenuId() == null) { | ||
| throw new BaseException(FAILED_VALIDATION); | ||
| } | ||
| Menu menu = menuRepository.findById(menuLike.getMenuId()) | ||
| .orElseThrow(() -> new BaseException(NOT_FOUND_MENU)); | ||
| review.addReviewMenuLike(menu, menuLike.getIsLike()); | ||
| boolean isLike = Boolean.TRUE.equals(menuLike.getIsLike()); | ||
| review.addReviewMenuLike(menu, isLike); | ||
| } |
There was a problem hiding this comment.
[Reviewer, 2024-01-01] 루프 내에서 menuRepository.findById를 매번 호출하는 대신, findAllById를 사용하여 한 번에 조회하면 데이터베이스 쿼리 횟수를 줄여 성능을 향상시킬 수 있습니다.
List<MenuLikeRequest> menuLikes = Optional.ofNullable(request.getMenuLikes()).orElse(Collections.emptyList());
if (menuLikes.stream().anyMatch(m -> m == null || m.getMenuId() == null)) {
throw new BaseException(FAILED_VALIDATION);
}
List<Long> menuIds = menuLikes.stream().map(MenuLikeRequest::getMenuId).distinct().toList();
List<Menu> menus = menuRepository.findAllById(menuIds);
if (menus.size() != menuIds.size()) {
throw new BaseException(NOT_FOUND_MENU);
}
Map<Long, Menu> menuMap = menus.stream().collect(Collectors.toMap(Menu::getId, m -> m));
for (MenuLikeRequest menuLike : menuLikes) {
Menu menu = menuMap.get(menuLike.getMenuId());
boolean isLike = Boolean.TRUE.equals(menuLike.getIsLike());
review.addReviewMenuLike(menu, isLike);
}| List<MenuLikeRequest> menuLikeRequests = Optional.ofNullable(request.getMenuLikes()).orElse(Collections.emptyList()); | ||
|
|
||
| Map<Menu, Boolean> menuLikes = menuLikeRequests.stream() | ||
| .filter(Objects::nonNull) | ||
| .collect(Collectors.toMap( | ||
| menuLike -> { | ||
| if (menuLike.getMenuId() == null) { | ||
| throw new BaseException(FAILED_VALIDATION); | ||
| } | ||
| return menuRepository.findById(menuLike.getMenuId()) | ||
| .orElseThrow(() -> new BaseException( | ||
| NOT_FOUND_MENU)); | ||
| }, | ||
| menuLike -> Boolean.TRUE.equals(menuLike.getIsLike()))); |
There was a problem hiding this comment.
[Reviewer, 2024-01-01] Collectors.toMap 내부에서 menuRepository.findById를 호출하면 매 요소마다 쿼리가 실행되므로, findAllById를 사용하여 일괄 조회한 후 Map으로 변환하여 처리하는 것이 효율적입니다.
| List<MenuLikeRequest> menuLikeRequests = Optional.ofNullable(request.getMenuLikes()).orElse(Collections.emptyList()); | |
| Map<Menu, Boolean> menuLikes = menuLikeRequests.stream() | |
| .filter(Objects::nonNull) | |
| .collect(Collectors.toMap( | |
| menuLike -> { | |
| if (menuLike.getMenuId() == null) { | |
| throw new BaseException(FAILED_VALIDATION); | |
| } | |
| return menuRepository.findById(menuLike.getMenuId()) | |
| .orElseThrow(() -> new BaseException( | |
| NOT_FOUND_MENU)); | |
| }, | |
| menuLike -> Boolean.TRUE.equals(menuLike.getIsLike()))); | |
| List<MenuLikeRequest> menuLikeRequests = Optional.ofNullable(request.getMenuLikes()).orElse(Collections.emptyList()); | |
| if (menuLikeRequests.stream().anyMatch(m -> m == null || m.getMenuId() == null)) { | |
| throw new BaseException(FAILED_VALIDATION); | |
| } | |
| List<Long> menuIds = menuLikeRequests.stream().map(MenuLikeRequest::getMenuId).distinct().toList(); | |
| List<Menu> menus = menuRepository.findAllById(menuIds); | |
| if (menus.size() != menuIds.size()) { | |
| throw new BaseException(NOT_FOUND_MENU); | |
| } | |
| Map<Long, Menu> menuMap = menus.stream().collect(Collectors.toMap(Menu::getId, m -> m)); | |
| Map<Menu, Boolean> menuLikes = menuLikeRequests.stream() | |
| .collect(Collectors.toMap( | |
| menuLike -> menuMap.get(menuLike.getMenuId()), | |
| menuLike -> Boolean.TRUE.equals(menuLike.getIsLike()), | |
| (existing, replacement) -> replacement)); |
| .description(partnership.getDescription()) | ||
| .startDate(partnership.getStartDate()) | ||
| .endDate(partnership.getEndDate()) | ||
| .periodType(partnership.getPeriodType()) |
| try { | ||
| claims = Jwts.parserBuilder() | ||
| .setSigningKey(publicKey) | ||
| .build() | ||
| .parseClaimsJws(identityToken) | ||
| .getBody(); | ||
| } catch (ExpiredJwtException exception) { | ||
| throw new BaseException(INVALID_IDENTITY_TOKEN); | ||
| } |
There was a problem hiding this comment.
[Reviewer, 2024-01-01] ExpiredJwtException뿐만 아니라 SignatureException, MalformedJwtException 등 다른 JWT 관련 예외도 처리할 수 있도록 JwtException 및 IllegalArgumentException을 캐치하는 것이 안전합니다.
| try { | |
| claims = Jwts.parserBuilder() | |
| .setSigningKey(publicKey) | |
| .build() | |
| .parseClaimsJws(identityToken) | |
| .getBody(); | |
| } catch (ExpiredJwtException exception) { | |
| throw new BaseException(INVALID_IDENTITY_TOKEN); | |
| } | |
| try { | |
| claims = Jwts.parserBuilder() | |
| .setSigningKey(publicKey) | |
| .build() | |
| .parseClaimsJws(identityToken) | |
| .getBody(); | |
| } catch (JwtException | IllegalArgumentException exception) { | |
| throw new BaseException(INVALID_IDENTITY_TOKEN); | |
| } |
| -- ========================= | ||
|
|
||
| ALTER TABLE partnership | ||
| ADD COLUMN period_type ENUM ('NORMAL', 'FESTIVAL') NOT NULL DEFAULT 'NORMAL'; |
| @@ -0,0 +1,2 @@ | |||
| ALTER TABLE user | |||
| ADD COLUMN language ENUM ('KO', 'EN', 'JA', 'VI') NOT NULL DEFAULT 'KO'; | |||
#️⃣ Issue Number
📝 요약(Summary)
💬 공유사항 to 리뷰어
✅ PR Checklist
PR이 다음 요구 사항을 충족하는지 확인하세요.